1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 // Search for: FIXME: leaks if multithreaded gc
4 
5 // https://freedesktop.org/wiki/Specifications/XDND/
6 
7 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
8 
9 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
10 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
11 
12 
13 // on Mac with X11: -L-L/usr/X11/lib
14 
15 /+
16 
17 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
18 
19 	Progress bar in taskbar
20 		- i can probably just set a property on the window...
21 		  it sets that prop to an integer 0 .. 100. Taskbar
22 		  deletes it or window deletes it when it is handled.
23 		- prolly display it as a nice little line at the bottom.
24 
25 
26 from gtk:
27 
28 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
29 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
30 
31 >+  if (cardinal > 0)
32 >+  {
33 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
34 >+                     xid,
35 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
36 >+                     XA_CARDINAL, 32,
37 >+                     PropModeReplace,
38 >+                     (guchar *) &cardinal, 1);
39 >+  }
40 >+  else
41 >+  {
42 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
45 >+  }
46 
47 from Windows:
48 
49 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
50 
51 interface
52 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
53 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
54 listen for msg, return TRUE
55 interface->SetProgressState(hwnd, TBPF_NORMAL);
56 interface->SetProgressValue(hwnd, 40, 100);
57 
58 
59 	My new notification system.
60 		- use a unix socket? or a x property? or a udp port?
61 		- could of course also get on the dbus train but ugh.
62 		- it could also reply with the info as a string for easy remote examination.
63 
64 +/
65 
66 /*
67 	Event Loop would be nices:
68 
69 	* add on idle - runs when nothing else happens
70 		* which can specify how long to yield for
71 	* send messages without a recipient window
72 	* setTimeout
73 	* setInterval
74 */
75 
76 /*
77 	Classic games I want to add:
78 		* my tetris clone
79 		* pac man
80 */
81 
82 /*
83 	Text layout needs a lot of work. Plain drawText is useful but too
84 	limited. It will need some kind of text context thing which it will
85 	update and you can pass it on and get more details out of it.
86 
87 	It will need a bounding box, a current cursor location that is updated
88 	as drawing continues, and various changable facts (which can also be
89 	changed on the painter i guess) like font, color, size, background,
90 	etc.
91 
92 	We can also fetch the caret location from it somehow.
93 
94 	Should prolly be an overload of drawText
95 
96 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
97 
98 		WS_EX_NOACTIVATE
99 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
100 		full screen windows. Can just set the atom on X. Windows will be harder.
101 
102 		moving windows. resizing windows.
103 
104 		hide cursor, capture cursor, change cursor.
105 
106 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
107 	sure the pieces are there to do its job easily and make other jobs possible.
108 */
109 
110 /++
111 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
112 	including creating windows, drawing on them, working with the clipboard,
113 	timers, OpenGL, and more. However, it does NOT provide high level GUI
114 	widgets. See my minigui.d, an extension to this module, for that
115 	functionality.
116 
117 	simpledisplay provides cross-platform wrapping for Windows and Linux
118 	(and perhaps other OSes that use X11), but also does not prevent you
119 	from using the underlying facilities if you need them. It has a goal
120 	of working efficiently over a remote X link (at least as far as Xlib
121 	reasonably allows.)
122 
123 	simpledisplay depends on [arsd.color|color.d], which should be available from the
124 	same place where you got this file. Other than that, however, it has
125 	very few dependencies and ones that don't come with the OS and/or the
126 	compiler are all opt-in.
127 
128 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
129 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
130 
131 	simpledisplay is basically stable. I plan to refactor the internals,
132 	and may add new features and fix bugs, but It do not expect to
133 	significantly change the API. It has been stable a few years already now.
134 
135 	Installation_instructions:
136 
137 	`simpledisplay.d` does not have any dependencies outside the
138 	operating system and `color.d`, so it should just work most the
139 	time, but there are a few caveats on some systems:
140 
141 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
142 	console to be automatically allocated.
143 
144 	Please note when compiling on Win64, you need to explicitly list
145 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
146 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
147 
148 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
149 	note the "w".
150 
151 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
152 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
153 	See [EnableWindowsSubsystem] for more information.
154 
155 	$(PITFALL
156 		With the Windows subsystem, there is no console, so standard writeln will throw!
157 		You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
158 		create a console as needed.
159 	)
160 
161 	On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command.
162 
163 	On Ubuntu, you might need to install X11 development libraries to
164 	successfully link.
165 
166 	$(CONSOLE
167 		$ sudo apt-get install libglc-dev
168 		$ sudo apt-get install libx11-dev
169 	)
170 
171 
172 	Jump_list:
173 
174 	Don't worry, you don't have to read this whole documentation file!
175 
176 	Check out the [#event-example] and [#Pong-example] to get started quickly.
177 
178 	The main classes you may want to create are [SimpleWindow], [Timer],
179 	[Image], and [Sprite].
180 
181 	The main functions you'll want are [setClipboardText] and [getClipboardText].
182 
183 	There are also platform-specific functions available such as [XDisplayConnection]
184 	and [GetAtom] for X11, among others.
185 
186 	See the examples and topics list below to learn more.
187 
188 	$(WARNING
189 		There should only be one GUI thread per application,
190 		and all windows should be created in it and your
191 		event loop should run there.
192 
193 		To do otherwise is undefined behavior and has no
194 		cross platform guarantees.
195 	)
196 
197 	$(H2 About this documentation)
198 
199 	The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow.
200 
201 	Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples!
202 
203 	All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them.
204 
205 	To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example.
206 
207 	If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file.
208 
209 	At points, I will talk about implementation details in the documentation. These are sometimes
210 	subject to change, but nevertheless useful to understand what is really going on. You can learn
211 	more about some of the referenced things by searching the web for info about using them from C.
212 	You can always look at the source of simpledisplay.d too for the most authoritative source on
213 	its specific implementation. If you disagree with how I did something, please contact me so we
214 	can discuss it!
215 
216 	$(H2 Using with fibers)
217 
218 	simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor).
219 
220 	$(H2 Topics)
221 
222 	$(H3 $(ID topic-windows) Windows)
223 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
224 		window on the user's screen.
225 
226 		You may create multiple windows, if the underlying platform supports it. You may check
227 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
228 		SimpleWindow's constructor at runtime to handle those cases.
229 
230 		A single running event loop will handle as many windows as needed.
231 
232 	$(H3 $(ID topic-event-loops) Event loops)
233 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
234 
235 		The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there:
236 
237 		---
238 		// dmd example.d simpledisplay.d color.d
239 		import arsd.simpledisplay;
240 		void main() {
241 			auto window = new SimpleWindow(200, 200);
242 			window.eventLoop(0,
243 			  delegate (dchar) { /* got a character key press */ }
244 			);
245 		}
246 		---
247 
248 		$(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.)
249 
250 		On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run.
251 
252 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
253 
254 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
255 
256 		You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway.
257 
258 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
259 		Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like.
260 
261 		See the [NotificationAreaIcon] class.
262 
263 	$(H3 $(ID topic-input-handling) Input handling)
264 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
265 
266 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
267 
268 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
269 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
270 
271 		Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example:
272 
273 		---
274 		// dmd example.d simpledisplay.d color.d
275 		import arsd.simpledisplay;
276 		void main() {
277 			auto window = new SimpleWindow(200, 200);
278 			{ // introduce sub-scope
279 				auto painter = window.draw(); // begin drawing
280 				/* draw here */
281 				painter.outlineColor = Color.red;
282 				painter.fillColor = Color.black;
283 				painter.drawRectangle(Point(0, 0), 200, 200);
284 			} // end scope, calling `painter`'s destructor, drawing to the screen.
285 			window.eventLoop(0); // handle events
286 		}
287 		---
288 
289 		Painting is done based on two color properties, a pen and a brush.
290 
291 		At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead.
292 
293 		FIXME Add example of 2d opengl drawing here.
294 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
295 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
296 
297 		Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it.
298 
299 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
300 
301 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
302 
303 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon].
304 
305 		simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program.
306 
307 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
308 
309 		---
310 		import arsd.simpledisplay;
311 
312 		void main() {
313 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
314 
315 			float otherColor = 0.0;
316 			float colorDelta = 0.05;
317 
318 			window.redrawOpenGlScene = delegate() {
319 				glLoadIdentity();
320 				glBegin(GL_QUADS);
321 
322 				glColor3f(1.0, otherColor, 0);
323 				glVertex3f(-0.8, -0.8, 0);
324 
325 				glColor3f(1.0, otherColor, 1.0);
326 				glVertex3f(0.8, -0.8, 0);
327 
328 				glColor3f(0, 1.0, otherColor);
329 				glVertex3f(0.8, 0.8, 0);
330 
331 				glColor3f(otherColor, 0, 1.0);
332 				glVertex3f(-0.8, 0.8, 0);
333 
334 				glEnd();
335 			};
336 
337 			window.eventLoop(50, () {
338 				otherColor += colorDelta;
339 				if(otherColor > 1.0) {
340 					otherColor = 1.0;
341 					colorDelta = -0.05;
342 				}
343 				if(otherColor < 0) {
344 					otherColor = 0;
345 					colorDelta = 0.05;
346 				}
347 				// at the end of the timer, we have to request a redraw
348 				// or we won't see the changes.
349 				window.redrawOpenGlSceneSoon();
350 			});
351 		}
352 		---
353 
354 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
355 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
356 		simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too.
357 
358 		This example program shows how you can set up a shader to draw a rectangle:
359 
360 		---
361 		module opengl3test;
362 		import arsd.simpledisplay;
363 
364 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
365 
366 		void main() {
367 			// First thing we do, before creating the window, is declare what version we want.
368 			setOpenGLContextVersion(3, 3);
369 			// turning off legacy compat is required to use version 3.3 and newer
370 			openGLContextCompatible = false;
371 
372 			uint VAO;
373 			OpenGlShader shader;
374 
375 			// then we can create the window.
376 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
377 
378 			// additional setup needs to be done when it is visible, simpledisplay offers a property
379 			// for exactly that:
380 			window.visibleForTheFirstTime = delegate() {
381 				// now with the window loaded, we can start loading the modern opengl functions.
382 
383 				// you MUST set the context first.
384 				window.setAsCurrentOpenGlContext;
385 				// then load the remainder of the library
386 				gl3.loadDynamicLibrary();
387 
388 				// now you can create the shaders, etc.
389 				shader = new OpenGlShader(
390 					OpenGlShader.Source(GL_VERTEX_SHADER, `
391 						#version 330 core
392 						layout (location = 0) in vec3 aPos;
393 						void main() {
394 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
395 						}
396 					`),
397 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
398 						#version 330 core
399 						out vec4 FragColor;
400 						uniform vec4 mycolor;
401 						void main() {
402 							FragColor = mycolor;
403 						}
404 					`),
405 				);
406 
407 				// and do whatever other setup you want.
408 
409 				float[] vertices = [
410 					0.5f,  0.5f, 0.0f,  // top right
411 					0.5f, -0.5f, 0.0f,  // bottom right
412 					-0.5f, -0.5f, 0.0f,  // bottom left
413 					-0.5f,  0.5f, 0.0f   // top left
414 				];
415 				uint[] indices = [  // note that we start from 0!
416 					0, 1, 3,  // first Triangle
417 					1, 2, 3   // second Triangle
418 				];
419 				uint VBO, EBO;
420 				glGenVertexArrays(1, &VAO);
421 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
422 				glBindVertexArray(VAO);
423 
424 				glGenBuffers(1, &VBO);
425 				glGenBuffers(1, &EBO);
426 
427 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
428 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
429 
430 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
431 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
432 
433 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
434 				glEnableVertexAttribArray(0);
435 
436 				// the library will set the initial viewport and trigger our first draw,
437 				// so these next two lines are NOT needed. they are just here as comments
438 				// to show what would happen next.
439 
440 				// glViewport(0, 0, window.width, window.height);
441 				// window.redrawOpenGlSceneNow();
442 			};
443 
444 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
445 			// it is our render method.
446 			window.redrawOpenGlScene = delegate() {
447 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
448 				glClear(GL_COLOR_BUFFER_BIT);
449 
450 				glUseProgram(shader.shaderProgram);
451 
452 				// the shader helper class has methods to set uniforms too
453 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
454 
455 				glBindVertexArray(VAO);
456 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
457 			};
458 
459 			window.eventLoop(0);
460 		}
461 		---
462 
463 	This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread.
464 
465 
466 	$(H3 $(ID topic-images) Displaying images)
467 		You can also load PNG images using [arsd.png].
468 
469 		---
470 		// dmd example.d simpledisplay.d color.d png.d
471 		import arsd.simpledisplay;
472 		import arsd.png;
473 
474 		void main() {
475 			auto image = Image.fromMemoryImage(readPng("image.png"));
476 			displayImage(image);
477 		}
478 		---
479 
480 		Compile with `dmd example.d simpledisplay.d png.d`.
481 
482 		If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel.
483 
484 	$(H3 $(ID topic-sprites) Sprites)
485 		The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link.
486 
487 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
488 
489 	$(H3 $(ID topic-clipboard) Clipboard)
490 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
491 
492 		It also has helpers for handling X-specific events.
493 
494 	$(H3 $(ID topic-dnd) Drag and Drop)
495 		See [enableDragAndDrop] and [draggable].
496 
497 	$(H3 $(ID topic-timers) Timers)
498 		There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer].
499 
500 		The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse.
501 
502 		---
503 			import arsd.simpledisplay;
504 
505 			void main() {
506 				auto window = new SimpleWindow(400, 400);
507 				// every 100 ms, it will draw a random line
508 				// on the window.
509 				window.eventLoop(100, {
510 					auto painter = window.draw();
511 
512 					import std.random;
513 					// random color
514 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
515 					// random line
516 					painter.drawLine(
517 						Point(uniform(0, window.width), uniform(0, window.height)),
518 						Point(uniform(0, window.width), uniform(0, window.height)));
519 
520 				});
521 			}
522 		---
523 
524 		The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish.
525 
526 		The pulse timer and instances of the [Timer] class may be combined at will.
527 
528 		---
529 			import arsd.simpledisplay;
530 
531 			void main() {
532 				auto window = new SimpleWindow(400, 400);
533 				auto timer = new Timer(1000, delegate {
534 					auto painter = window.draw();
535 					painter.clear();
536 				});
537 
538 				window.eventLoop(0);
539 			}
540 		---
541 
542 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
543 
544 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
545 		simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself.
546 
547 		See also: `xwindows.d` from my github.
548 
549 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
550 		`handleNativeEvent` and `handleNativeGlobalEvent`.
551 
552 	$(H3 $(ID topic-integration) Integration with other libraries)
553 		Integration with a third-party event loop is possible.
554 
555 		On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d.
556 
557 	$(H3 $(ID topic-guis) GUI widgets)
558 		simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you!
559 
560 		Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes.
561 
562 		Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.)
563 
564 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
565 
566 	$(H2 Platform-specific tips and tricks)
567 
568 	X_tips:
569 
570 	On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release.
571 
572 	Windows_tips:
573 
574 	You can add icons or manifest files to your exe using a resource file.
575 
576 	To create a Windows .ico file, use the gimp or something. I'll write a helper
577 	program later.
578 
579 	Create `yourapp.rc`:
580 
581 	```rc
582 		1 ICON filename.ico
583 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
584 	```
585 
586 	And `yourapp.exe.manifest`:
587 
588 	```xml
589 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
590 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
591 		<assemblyIdentity
592 		    version="1.0.0.0"
593 		    processorArchitecture="*"
594 		    name="CompanyName.ProductName.YourApplication"
595 		    type="win32"
596 		/>
597 		<description>Your application description here.</description>
598 		<dependency>
599 		    <dependentAssembly>
600 			<assemblyIdentity
601 			    type="win32"
602 			    name="Microsoft.Windows.Common-Controls"
603 			    version="6.0.0.0"
604 			    processorArchitecture="*"
605 			    publicKeyToken="6595b64144ccf1df"
606 			    language="*"
607 			/>
608 		    </dependentAssembly>
609 		</dependency>
610 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
611 			<windowsSettings>
612 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
613 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
614 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
615 				<!-- to render crisply in DPI-unaware contexts -->
616 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
617 			</windowsSettings>
618 		</application>
619 		</assembly>
620 	```
621 
622 	You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`.
623 
624 	Doing this lets you opt into various new things since Windows XP.
625 
626 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
627 
628 	$(H2 Tips)
629 
630 	$(H3 Name conflicts)
631 
632 	simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import:
633 
634 	---
635 	static import sdpy = arsd.simpledisplay;
636 	import arsd.simpledisplay : SimpleWindow;
637 
638 	void main() {
639 		auto window = new SimpleWindow();
640 		sdpy.EventLoop.get.run();
641 	}
642 	---
643 
644 	$(H2 $(ID developer-notes) Developer notes)
645 
646 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
647 	implementation though.
648 
649 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
650 	suck. If I was rewriting it, I wouldn't do it that way again.
651 
652 	This file must not have any more required dependencies. If you need bindings, add
653 	them right to this file. Once it gets into druntime and is there for a while, remove
654 	bindings from here to avoid conflicts (or put them in an appropriate version block
655 	so it continues to just work on old dmd), but wait a couple releases before making the
656 	transition so this module remains usable with older versions of dmd.
657 
658 	You may have optional dependencies if needed by putting them in version blocks or
659 	template functions. You may also extend the module with other modules with UFCS without
660 	actually editing this - that is nice to do if you can.
661 
662 	Try to make functions work the same way across operating systems. I typically make
663 	it thinly wrap Windows, then emulate that on Linux.
664 
665 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
666 	Phobos! So try to avoid it.
667 
668 	See more comments throughout the source.
669 
670 	I realize this file is fairly large, but over half that is just bindings at the bottom
671 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
672 	to understand. I suggest you jump around the source by looking for a particular
673 	declaration you're interested in, like `class SimpleWindow` using your editor's search
674 	function, then look at one piece at a time.
675 
676 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
677 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
678 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
679 
680 	I live in the eastern United States, so I will most likely not be around at night in
681 	that US east timezone.
682 
683 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
684 
685 	Building documentation: use my adrdox generator, `dub run adrdox`.
686 
687 	Examples:
688 
689 	$(DIV $(ID Event-example))
690 	$(H3 $(ID event-example) Event example)
691 	This program creates a window and draws events inside them as they
692 	happen, scrolling the text in the window as needed. Run this program
693 	and experiment to get a feel for where basic input events take place
694 	in the library.
695 
696 	---
697 	// dmd example.d simpledisplay.d color.d
698 	import arsd.simpledisplay;
699 	import std.conv;
700 
701 	void main() {
702 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
703 
704 		int y = 0;
705 
706 		void addLine(string text) {
707 			auto painter = window.draw();
708 
709 			if(y + painter.fontHeight >= window.height) {
710 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
711 				y -= painter.fontHeight;
712 			}
713 
714 			painter.outlineColor = Color.red;
715 			painter.fillColor = Color.black;
716 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
717 
718 			painter.outlineColor = Color.white;
719 
720 			painter.drawText(Point(10, y), text);
721 
722 			y += painter.fontHeight;
723 		}
724 
725 		window.eventLoop(1000,
726 		  () {
727 			addLine("Timer went off!");
728 		  },
729 		  (KeyEvent event) {
730 			addLine(to!string(event));
731 		  },
732 		  (MouseEvent event) {
733 			addLine(to!string(event));
734 		  },
735 		  (dchar ch) {
736 			addLine(to!string(ch));
737 		  }
738 		);
739 	}
740 	---
741 
742 	If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too.
743 
744 	$(COMMENT
745 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
746 
747 	---
748 
749 	---
750 	)
751 
752 	History:
753 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
754 
755 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
756 +/
757 module arsd.simpledisplay;
758 
759 import arsd.core;
760 
761 // FIXME: tetris demo
762 // FIXME: space invaders demo
763 // FIXME: asteroids demo
764 
765 /++ $(ID Pong-example)
766 	$(H3 Pong)
767 
768 	This program creates a little Pong-like game. Player one is controlled
769 	with the keyboard.  Player two is controlled with the mouse. It demos
770 	the pulse timer, event handling, and some basic drawing.
771 +/
772 version(demos)
773 unittest {
774 	// dmd example.d simpledisplay.d color.d
775 	import arsd.simpledisplay;
776 
777 	enum paddleMovementSpeed = 8;
778 	enum paddleHeight = 48;
779 
780 	void main() {
781 		auto window = new SimpleWindow(600, 400, "Pong game!");
782 
783 		int playerOnePosition, playerTwoPosition;
784 		int playerOneMovement, playerTwoMovement;
785 		int playerOneScore, playerTwoScore;
786 
787 		int ballX, ballY;
788 		int ballDx, ballDy;
789 
790 		void serve() {
791 			import std.random;
792 
793 			ballX = window.width / 2;
794 			ballY = window.height / 2;
795 			ballDx = uniform(-4, 4) * 3;
796 			ballDy = uniform(-4, 4) * 3;
797 			if(ballDx == 0)
798 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
799 		}
800 
801 		serve();
802 
803 		window.eventLoop(50, // set a 50 ms timer pulls
804 			// This runs once per timer pulse
805 			delegate () {
806 				auto painter = window.draw();
807 
808 				painter.clear();
809 
810 				// Update everyone's motion
811 				playerOnePosition += playerOneMovement;
812 				playerTwoPosition += playerTwoMovement;
813 
814 				ballX += ballDx;
815 				ballY += ballDy;
816 
817 				// Bounce off the top and bottom edges of the window
818 				if(ballY + 7 >= window.height)
819 					ballDy = -ballDy;
820 				if(ballY - 8 <= 0)
821 					ballDy = -ballDy;
822 
823 				// Bounce off the paddle, if it is in position
824 				if(ballX - 8 <= 16) {
825 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
826 						ballDx = -ballDx + 1; // add some speed to keep it interesting
827 						ballDy += playerOneMovement; // and y movement based on your controls too
828 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
829 					} else {
830 						// Missed it
831 						playerTwoScore ++;
832 						serve();
833 					}
834 				}
835 
836 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
837 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
838 						ballDx = -ballDx - 1;
839 						ballDy += playerTwoMovement;
840 						ballX = window.width - 24;
841 					} else {
842 						// Missed it
843 						playerOneScore ++;
844 						serve();
845 					}
846 				}
847 
848 				// Draw the paddles
849 				painter.outlineColor = Color.black;
850 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
851 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
852 
853 				// Draw the ball
854 				painter.fillColor = Color.red;
855 				painter.outlineColor = Color.yellow;
856 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
857 
858 				// Draw the score
859 				painter.outlineColor = Color.blue;
860 				import std.conv;
861 				painter.drawText(Point(64, 4), to!string(playerOneScore));
862 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
863 
864 			},
865 			delegate (KeyEvent event) {
866 				// Player 1's controls are the arrow keys on the keyboard
867 				if(event.key == Key.Down)
868 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
869 				if(event.key == Key.Up)
870 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
871 
872 			},
873 			delegate (MouseEvent event) {
874 				// Player 2's controls are mouse movement while the left button is held down
875 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
876 					if(event.dy > 0)
877 						playerTwoMovement = paddleMovementSpeed;
878 					else if(event.dy < 0)
879 						playerTwoMovement = -paddleMovementSpeed;
880 				} else {
881 					playerTwoMovement = 0;
882 				}
883 			}
884 		);
885 	}
886 }
887 
888 /++ $(H3 $(ID example-minesweeper) Minesweeper)
889 
890 	This minesweeper demo shows how we can implement another classic
891 	game with simpledisplay and shows some mouse input and basic output
892 	code.
893 +/
894 version(demos)
895 unittest {
896 	import arsd.simpledisplay;
897 
898 	enum GameSquare {
899 		mine = 0,
900 		clear,
901 		m1, m2, m3, m4, m5, m6, m7, m8
902 	}
903 
904 	enum UserSquare {
905 		unknown,
906 		revealed,
907 		flagged,
908 		questioned
909 	}
910 
911 	enum GameState {
912 		inProgress,
913 		lose,
914 		win
915 	}
916 
917 	GameSquare[] board;
918 	UserSquare[] userState;
919 	GameState gameState;
920 	int boardWidth;
921 	int boardHeight;
922 
923 	bool isMine(int x, int y) {
924 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
925 			return false;
926 		return board[y * boardWidth + x] == GameSquare.mine;
927 	}
928 
929 	GameState reveal(int x, int y) {
930 		if(board[y * boardWidth + x] == GameSquare.clear) {
931 			floodFill(userState, boardWidth, boardHeight,
932 				UserSquare.unknown, UserSquare.revealed,
933 				x, y,
934 				(x, y) {
935 					if(board[y * boardWidth + x] == GameSquare.clear)
936 						return true;
937 					else {
938 						userState[y * boardWidth + x] = UserSquare.revealed;
939 						return false;
940 					}
941 				});
942 		} else {
943 			userState[y * boardWidth + x] = UserSquare.revealed;
944 			if(isMine(x, y))
945 				return GameState.lose;
946 		}
947 
948 		foreach(state; userState) {
949 			if(state == UserSquare.unknown || state == UserSquare.questioned)
950 				return GameState.inProgress;
951 		}
952 
953 		return GameState.win;
954 	}
955 
956 	void initializeBoard(int width, int height, int numberOfMines) {
957 		boardWidth = width;
958 		boardHeight = height;
959 		board.length = width * height;
960 
961 		userState.length = width * height;
962 		userState[] = UserSquare.unknown;
963 
964 		import std.algorithm, std.random, std.range;
965 
966 		board[] = GameSquare.clear;
967 
968 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
969 			board[minePosition] = GameSquare.mine;
970 
971 		int x;
972 		int y;
973 		foreach(idx, ref square; board) {
974 			if(square == GameSquare.clear) {
975 				int danger = 0;
976 				danger += isMine(x-1, y-1)?1:0;
977 				danger += isMine(x-1, y)?1:0;
978 				danger += isMine(x-1, y+1)?1:0;
979 				danger += isMine(x, y-1)?1:0;
980 				danger += isMine(x, y+1)?1:0;
981 				danger += isMine(x+1, y-1)?1:0;
982 				danger += isMine(x+1, y)?1:0;
983 				danger += isMine(x+1, y+1)?1:0;
984 
985 				square = cast(GameSquare) (danger + 1);
986 			}
987 
988 			x++;
989 			if(x == width) {
990 				x = 0;
991 				y++;
992 			}
993 		}
994 	}
995 
996 	void redraw(SimpleWindow window) {
997 		import std.conv;
998 
999 		auto painter = window.draw();
1000 
1001 		painter.clear();
1002 
1003 		final switch(gameState) with(GameState) {
1004 			case inProgress:
1005 				break;
1006 			case win:
1007 				painter.fillColor = Color.green;
1008 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1009 				return;
1010 			case lose:
1011 				painter.fillColor = Color.red;
1012 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1013 				return;
1014 		}
1015 
1016 		int x = 0;
1017 		int y = 0;
1018 
1019 		foreach(idx, square; board) {
1020 			auto state = userState[idx];
1021 
1022 			final switch(state) with(UserSquare) {
1023 				case unknown:
1024 					painter.outlineColor = Color.black;
1025 					painter.fillColor = Color(128,128,128);
1026 
1027 					painter.drawRectangle(
1028 						Point(x * 20, y * 20),
1029 						20, 20
1030 					);
1031 				break;
1032 				case revealed:
1033 					if(square == GameSquare.clear) {
1034 						painter.outlineColor = Color.white;
1035 						painter.fillColor = Color.white;
1036 
1037 						painter.drawRectangle(
1038 							Point(x * 20, y * 20),
1039 							20, 20
1040 						);
1041 					} else {
1042 						painter.outlineColor = Color.black;
1043 						painter.fillColor = Color.white;
1044 
1045 						painter.drawText(
1046 							Point(x * 20, y * 20),
1047 							to!string(square)[1..2],
1048 							Point(x * 20 + 20, y * 20 + 20),
1049 							TextAlignment.Center | TextAlignment.VerticalCenter);
1050 					}
1051 				break;
1052 				case flagged:
1053 					painter.outlineColor = Color.black;
1054 					painter.fillColor = Color.red;
1055 					painter.drawRectangle(
1056 						Point(x * 20, y * 20),
1057 						20, 20
1058 					);
1059 				break;
1060 				case questioned:
1061 					painter.outlineColor = Color.black;
1062 					painter.fillColor = Color.yellow;
1063 					painter.drawRectangle(
1064 						Point(x * 20, y * 20),
1065 						20, 20
1066 					);
1067 				break;
1068 			}
1069 
1070 			x++;
1071 			if(x == boardWidth) {
1072 				x = 0;
1073 				y++;
1074 			}
1075 		}
1076 
1077 	}
1078 
1079 	void main() {
1080 		auto window = new SimpleWindow(200, 200);
1081 
1082 		initializeBoard(10, 10, 10);
1083 
1084 		redraw(window);
1085 		window.eventLoop(0,
1086 			delegate (MouseEvent me) {
1087 				if(me.type != MouseEventType.buttonPressed)
1088 					return;
1089 				auto x = me.x / 20;
1090 				auto y = me.y / 20;
1091 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1092 					if(me.button == MouseButton.left) {
1093 						gameState = reveal(x, y);
1094 					} else {
1095 						userState[y*boardWidth+x] = UserSquare.flagged;
1096 					}
1097 					redraw(window);
1098 				}
1099 			}
1100 		);
1101 	}
1102 }
1103 
1104 /*
1105 version(OSX) {
1106 	version=without_opengl;
1107 	version=allow_unimplemented_features;
1108 	version=OSXCocoa;
1109 	pragma(linkerDirective, "-framework Cocoa");
1110 }
1111 */
1112 
1113 version(without_opengl) {
1114 	enum SdpyIsUsingIVGLBinds = false;
1115 } else /*version(Posix)*/ {
1116 	static if (__traits(compiles, (){import iv.glbinds;})) {
1117 		enum SdpyIsUsingIVGLBinds = true;
1118 		public import iv.glbinds;
1119 		//pragma(msg, "SDPY: using iv.glbinds");
1120 	} else {
1121 		enum SdpyIsUsingIVGLBinds = false;
1122 	}
1123 //} else {
1124 //	enum SdpyIsUsingIVGLBinds = false;
1125 }
1126 
1127 
1128 version(Windows) {
1129 	//import core.sys.windows.windows;
1130 	import core.sys.windows.winnls;
1131 	import core.sys.windows.windef;
1132 	import core.sys.windows.basetyps;
1133 	import core.sys.windows.winbase;
1134 	import core.sys.windows.winuser;
1135 	import core.sys.windows.shellapi;
1136 	import core.sys.windows.wingdi;
1137 	static import gdi = core.sys.windows.wingdi; // so i
1138 
1139 	pragma(lib, "gdi32");
1140 	pragma(lib, "user32");
1141 
1142 	// for AlphaBlend... a breaking change....
1143 	version(CRuntime_DigitalMars) { } else
1144 		pragma(lib, "msimg32");
1145 } else version (linux) {
1146 	//k8: this is hack for rdmd. sorry.
1147 	static import core.sys.linux.epoll;
1148 	static import core.sys.linux.timerfd;
1149 }
1150 
1151 
1152 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1153 
1154 // http://wiki.dlang.org/Simpledisplay.d
1155 
1156 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1157 
1158 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1159 // but can i control the scroll lock led
1160 
1161 
1162 // Note: if you are using Image on X, you might want to do:
1163 /*
1164 	static if(UsingSimpledisplayX11) {
1165 		if(!Image.impl.xshmAvailable) {
1166 			// the images will use the slower XPutImage, you might
1167 			// want to consider an alternative method to get better speed
1168 		}
1169 	}
1170 
1171 	If the shared memory extension is available though, simpledisplay uses it
1172 	for a significant speed boost whenever you draw large Images.
1173 */
1174 
1175 // 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.
1176 
1177 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1178 
1179 /*
1180 	Biggest FIXME:
1181 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1182 
1183 		clean up opengl contexts when their windows close
1184 
1185 		fix resizing the bitmaps/pixmaps
1186 */
1187 
1188 // BTW on Windows:
1189 // -L/SUBSYSTEM:WINDOWS:5.0
1190 // to dmd will make a nice windows binary w/o a console if you want that.
1191 
1192 /*
1193 	Stuff to add:
1194 
1195 	use multibyte functions everywhere we can
1196 
1197 	OpenGL windows
1198 	more event stuff
1199 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1200 
1201 
1202 	resizeEvent
1203 		and make the windows non-resizable by default,
1204 		or perhaps stretched (if I can find something in X like StretchBlt)
1205 
1206 	take a screenshot function!
1207 
1208 	Pens and brushes?
1209 	Maybe a global event loop?
1210 
1211 	Mouse deltas
1212 	Key items
1213 */
1214 
1215 /*
1216 From MSDN:
1217 
1218 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1219 
1220 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.
1221 
1222 */
1223 
1224 version(linux) {
1225 	version = X11;
1226 	version(without_libnotify) {
1227 		// we cool
1228 	}
1229 	else
1230 		version = libnotify;
1231 }
1232 
1233 version(libnotify) {
1234 	pragma(lib, "dl");
1235 	import core.sys.posix.dlfcn;
1236 
1237 	void delegate()[int] libnotify_action_delegates;
1238 	int libnotify_action_delegates_count;
1239 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1240 		auto idx = cast(int) user_data;
1241 		if(auto dgptr = idx in libnotify_action_delegates) {
1242 			(*dgptr)();
1243 			libnotify_action_delegates.remove(idx);
1244 		}
1245 	}
1246 
1247 	struct C_DynamicLibrary {
1248 		void* handle;
1249 		this(string name) {
1250 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1251 			if(handle is null)
1252 				throw new Exception("dlopen");
1253 		}
1254 
1255 		void close() {
1256 			dlclose(handle);
1257 		}
1258 
1259 		~this() {
1260 			// close
1261 		}
1262 
1263 		// FIXME: this looks up by name every time....
1264 		template call(string func, Ret, Args...) {
1265 			extern(C) Ret function(Args) fptr;
1266 			typeof(fptr) call() {
1267 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1268 				return fptr;
1269 			}
1270 		}
1271 	}
1272 
1273 	C_DynamicLibrary* libnotify;
1274 }
1275 
1276 version(OSX) {
1277 	version(OSXCocoa) {}
1278 	else { version = X11; }
1279 }
1280 	//version = OSXCocoa; // this was written by KennyTM
1281 version(FreeBSD)
1282 	version = X11;
1283 version(Solaris)
1284 	version = X11;
1285 
1286 version(X11) {
1287 	version(without_xft) {}
1288 	else version=with_xft;
1289 }
1290 
1291 void featureNotImplemented()() {
1292 	version(allow_unimplemented_features)
1293 		throw new NotYetImplementedException();
1294 	else
1295 		static assert(0);
1296 }
1297 
1298 // these are so the static asserts don't trigger unless you want to
1299 // add support to it for an OS
1300 version(Windows)
1301 	version = with_timer;
1302 version(linux)
1303 	version = with_timer;
1304 
1305 version(with_timer)
1306 	enum bool SimpledisplayTimerAvailable = true;
1307 else
1308 	enum bool SimpledisplayTimerAvailable = false;
1309 
1310 /// 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.
1311 version(Windows)
1312 	enum bool UsingSimpledisplayWindows = true;
1313 else
1314 	enum bool UsingSimpledisplayWindows = false;
1315 
1316 /// 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.
1317 version(X11)
1318 	enum bool UsingSimpledisplayX11 = true;
1319 else
1320 	enum bool UsingSimpledisplayX11 = false;
1321 
1322 /// 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.
1323 version(OSXCocoa)
1324 	enum bool UsingSimpledisplayCocoa = true;
1325 else
1326 	enum bool UsingSimpledisplayCocoa = false;
1327 
1328 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1329 version(Windows)
1330 	enum multipleWindowsSupported = true;
1331 else version(X11)
1332 	enum multipleWindowsSupported = true;
1333 else version(OSXCocoa)
1334 	enum multipleWindowsSupported = true;
1335 else
1336 	static assert(0);
1337 
1338 version(without_opengl)
1339 	enum bool OpenGlEnabled = false;
1340 else
1341 	enum bool OpenGlEnabled = true;
1342 
1343 /++
1344 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1345 	If you mix this in above your `main` function, you no longer need to use the linker
1346 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1347 
1348 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1349 
1350 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1351 	stderr writeln. It will fail and throw an exception.
1352 
1353 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1354 
1355 	History:
1356 		Added November 24, 2021 (dub v10.4)
1357 +/
1358 mixin template EnableWindowsSubsystem() {
1359 	version(Windows)
1360 	version(CRuntime_Microsoft) {
1361 		pragma(linkerDirective, "/subsystem:windows");
1362 		version(LDC)
1363 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1364 		else
1365 			pragma(linkerDirective, "/entry:mainCRTStartup");
1366 	}
1367 }
1368 
1369 
1370 /++
1371 	After selecting a type from [WindowTypes], you may further customize
1372 	its behavior by setting one or more of these flags.
1373 
1374 
1375 	The different window types have different meanings of `normal`. If the
1376 	window type already is a good match for what you want to do, you should
1377 	just use [WindowFlags.normal], the default, which will do the right thing
1378 	for your users.
1379 
1380 	The window flags will not always be honored by the operating system
1381 	and window managers; they are hints, not commands.
1382 +/
1383 enum WindowFlags : int {
1384 	normal = 0, ///
1385 	skipTaskbar = 1, ///
1386 	alwaysOnTop = 2, ///
1387 	alwaysOnBottom = 4, ///
1388 	cannotBeActivated = 8, ///
1389 	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.
1390 	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.
1391 	/++
1392 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1393 		it is still a top-level window. This should NOT be set separately for most window types.
1394 
1395 		A transient window will not keep the application open if its main window closes.
1396 
1397 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1398 
1399 
1400 		From the ICCM:
1401 
1402 		$(BLOCKQUOTE
1403 			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.
1404 
1405 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1406 		)
1407 
1408 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1409 
1410 		History:
1411 			Added February 23, 2021 but not yet stabilized.
1412 	+/
1413 	transient = 64,
1414 	/++
1415 		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.
1416 
1417 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1418 
1419 		History:
1420 			Added April 1, 2022
1421 	+/
1422 	managesChildWindowFocus = 128,
1423 
1424 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1425 }
1426 
1427 /++
1428 	When creating a window, you can pass a type to SimpleWindow's constructor,
1429 	then further customize the window by changing `WindowFlags`.
1430 
1431 
1432 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1433 	use. The others are there to build a foundation for a higher level GUI toolkit,
1434 	but are themselves not as high level as you might think from their names.
1435 
1436 	This list is based on the EMWH spec for X11.
1437 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1438 +/
1439 enum WindowTypes : int {
1440 	/// An ordinary application window.
1441 	normal,
1442 	/// 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.
1443 	undecorated,
1444 	/// 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.
1445 	eventOnly,
1446 	/// A drop down menu, such as from a menu bar
1447 	dropdownMenu,
1448 	/// A popup menu, such as from a right click
1449 	popupMenu,
1450 	/// A popup bubble notification
1451 	notification,
1452 	/*
1453 	menu, /// a tearable menu bar
1454 	splashScreen, /// a loading splash screen for your application
1455 	tooltip, /// A tiny window showing temporary help text or something.
1456 	comboBoxDropdown,
1457 	dialog,
1458 	toolbar
1459 	*/
1460 	/// a child nested inside the parent. You must pass a parent window to the ctor
1461 	nestedChild,
1462 
1463 	/++
1464 		The type you get when you pass in an existing browser handle, which means most
1465 		of simpledisplay's fancy things will not be done since they were never set up.
1466 
1467 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1468 		failure; you should use the existing handle constructor.
1469 
1470 		History:
1471 			Added November 17, 2022 (previously it would have type `normal`)
1472 	+/
1473 	minimallyWrapped
1474 }
1475 
1476 
1477 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1478 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1479 private __gshared char* sdpyWindowClassStr = null;
1480 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1481 
1482 /**
1483 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1484 	You may want to change context version if you want to use advanced shaders or
1485 	other modern OpenGL techinques. This setting doesn't affect already created
1486 	windows. You may use version 2.1 as your default, which should be supported
1487 	by any box since 2006, so seems to be a reasonable choice.
1488 
1489 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1490 	old context creation code without any version specified. This is the safest
1491 	way to init OpenGL, but it may not give you access to advanced features.
1492 
1493 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1494 */
1495 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1496 
1497 /**
1498 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1499 	pipeline functions, and without "compatible" mode you won't be able to use
1500 	your old non-shader-based code with such contexts. By default SimpleDisplay
1501 	creates compatible context, so you can gradually upgrade your OpenGL code if
1502 	you want to (or leave it as is, as it should "just work").
1503 */
1504 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1505 
1506 /**
1507 	Set to `true` to allow creating OpenGL context with lower version than requested
1508 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1509 	`openGLContextFallbackActivated()` will return `true`.
1510 	*/
1511 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1512 
1513 /**
1514 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1515 	*/
1516 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1517 
1518 
1519 /**
1520 	Set window class name for all following `new SimpleWindow()` calls.
1521 
1522 	WARNING! For Windows, you should set your class name before creating any
1523 	window, and NEVER change it after that!
1524 */
1525 void sdpyWindowClass (const(char)[] v) {
1526 	import core.stdc.stdlib : realloc;
1527 	if (v.length == 0) v = "SimpleDisplayWindow";
1528 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1529 	if (sdpyWindowClassStr is null) return; // oops
1530 	sdpyWindowClassStr[0..v.length+1] = 0;
1531 	sdpyWindowClassStr[0..v.length] = v[];
1532 }
1533 
1534 /**
1535 	Get current window class name.
1536 */
1537 string sdpyWindowClass () {
1538 	if (sdpyWindowClassStr is null) return null;
1539 	foreach (immutable idx; 0..size_t.max-1) {
1540 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1541 	}
1542 	return null;
1543 }
1544 
1545 /++
1546 	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.
1547 
1548 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1549 +/
1550 float[2] getDpi() {
1551 	float[2] dpi;
1552 	version(Windows) {
1553 		HDC screen = GetDC(null);
1554 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1555 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1556 	} else version(X11) {
1557 		auto display = XDisplayConnection.get;
1558 		auto screen = DefaultScreen(display);
1559 
1560 		void fallback() {
1561 			/+
1562 			// 25.4 millimeters in an inch...
1563 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1564 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1565 			+/
1566 
1567 			// the physical size isn't actually as important as the logical size since this is
1568 			// all about scaling really
1569 			dpi[0] = 96;
1570 			dpi[1] = 96;
1571 		}
1572 
1573 		auto xft = getXftDpi();
1574 		if(xft is float.init)
1575 			fallback();
1576 		else {
1577 			dpi[0] = xft;
1578 			dpi[1] = xft;
1579 		}
1580 	}
1581 
1582 	return dpi;
1583 }
1584 
1585 version(X11)
1586 float getXftDpi() {
1587 	auto display = XDisplayConnection.get;
1588 
1589 	char* resourceString = XResourceManagerString(display);
1590 	XrmInitialize();
1591 
1592 	if (resourceString) {
1593 		auto db = XrmGetStringDatabase(resourceString);
1594 		XrmValue value;
1595 		char* type;
1596 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1597 			if (value.addr) {
1598 				import core.stdc.stdlib;
1599 				return atof(cast(char*) value.addr);
1600 			}
1601 		}
1602 	}
1603 
1604 	return float.init;
1605 }
1606 
1607 /++
1608 	Implementation used by [SimpleWindow.takeScreenshot].
1609 
1610 	Params:
1611 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1612 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1613 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1614 		x = the x-offset of the image to capture, from the left.
1615 		y = the y-offset of the image to capture, from the top.
1616 
1617 	History:
1618 		Added on March 14, 2021
1619 
1620 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1621 
1622 +/
1623 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1624 	TrueColorImage got;
1625 	version(X11) {
1626 		auto display = XDisplayConnection.get;
1627 		if(handle == 0)
1628 			handle = RootWindow(display, DefaultScreen(display));
1629 
1630 		if(width == 0 || height == 0) {
1631 			Window root;
1632 			int xpos, ypos;
1633 			uint widthret, heightret, borderret, depthret;
1634 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1635 
1636 			if(width == 0)
1637 				width = widthret;
1638 			if(height == 0)
1639 				height = heightret;
1640 		}
1641 
1642 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1643 
1644 		// https://github.com/adamdruppe/arsd/issues/98
1645 
1646 		auto i = new Image(image);
1647 		got = i.toTrueColorImage();
1648 
1649 		XDestroyImage(image);
1650 	} else version(Windows) {
1651 		auto hdc = GetDC(handle);
1652 		scope(exit) ReleaseDC(handle, hdc);
1653 
1654 		if(width == 0 || height == 0) {
1655 			BITMAP bmHeader;
1656 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1657 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1658 			if(width == 0)
1659 				width = bmHeader.bmWidth;
1660 			if(height == 0)
1661 				height = bmHeader.bmHeight;
1662 		}
1663 
1664 		auto i = new Image(width, height);
1665 		HDC hdcMem = CreateCompatibleDC(hdc);
1666 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1667 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1668 		SelectObject(hdcMem, hbmOld);
1669 		DeleteDC(hdcMem);
1670 
1671 		got = i.toTrueColorImage();
1672 	} else featureNotImplemented();
1673 
1674 	return got;
1675 }
1676 
1677 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1678 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1679 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1680 
1681 version(Windows)
1682 shared static this() {
1683 	auto lib = LoadLibrary("User32.dll");
1684 	if(lib is null)
1685 		return;
1686 	//scope(exit)
1687 		//FreeLibrary(lib);
1688 
1689 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1690 
1691 	if(SetProcessDpiAwarenessContext is null)
1692 		return;
1693 
1694 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1695 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1696 		//writeln(GetLastError());
1697 	}
1698 
1699 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1700 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1701 }
1702 
1703 /++
1704 	Blocking mode for event loop calls associated with a window instance.
1705 
1706 	History:
1707 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1708 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1709 		is, all would block until the application quit.
1710 
1711 		That behavior can still be achieved here with `untilApplicationQuits`,
1712 		or explicitly calling the top-level `EventLoop.get.run` function.
1713 +/
1714 enum BlockingMode {
1715 	/++
1716 		The event loop call will block until the whole application is ready
1717 		to quit if it is the only one running, but if it is nested inside
1718 		another one, it will only block until the window you're calling it on
1719 		closes.
1720 	+/
1721 	automatic             = 0x00,
1722 	/++
1723 		The event loop call will only return when the whole application
1724 		is ready to quit. This usually means all windows have been closed.
1725 
1726 		This is appropriate for your main application event loop.
1727 	+/
1728 	untilApplicationQuits = 0x01,
1729 	/++
1730 		The event loop will return when the window you're calling it on
1731 		closes. If there are other windows still open, they may be destroyed
1732 		unless you have another event loop running later.
1733 
1734 		This might be appropriate for a modal dialog box loop. Remember that
1735 		other windows are still processing input though, so you can end up
1736 		with a lengthy call stack if this happens in a loop, similar to a
1737 		recursive function (well, it literally is a recursive function, just
1738 		not an obvious looking one).
1739 	+/
1740 	untilWindowCloses     = 0x02,
1741 	/++
1742 		If an event loop is already running, this call will immediately
1743 		return, allowing the existing loop to handle it. If not, this call
1744 		will block until the condition you bitwise-or into the flag.
1745 
1746 		The default is to block until the application quits, same as with
1747 		the `automatic` setting (since if it were nested, which triggers until
1748 		window closes in automatic, this flag would instead not block at all),
1749 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1750 		it will only nest until the window closes. You might want that if you are
1751 		going to open two windows simultaneously and want closing just one of them
1752 		to trigger the event loop return.
1753 	+/
1754 	onlyIfNotNested       = 0x10,
1755 }
1756 
1757 /++
1758 	The flagship window class.
1759 
1760 
1761 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1762 	out of more advanced or complex features of the underlying windowing system.
1763 
1764 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1765 	and get a suitable window to work with.
1766 
1767 	From there, you can opt into additional features, like custom resizability and OpenGL support
1768 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1769 	and customization flags with the final two constructor arguments.
1770 
1771 	If none of that works for you, you can also create a window using native function calls, then
1772 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1773 	though, if you do this, managing the window is still your own responsibility! Notably, you
1774 	will need to destroy it yourself.
1775 +/
1776 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1777 
1778 	/++
1779 		Copies the window's current state into a [TrueColorImage].
1780 
1781 		Be warned: this can be a very slow operation
1782 
1783 		History:
1784 			Actually implemented on March 14, 2021
1785 	+/
1786 	TrueColorImage takeScreenshot() {
1787 		version(Windows)
1788 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1789 		else version(OSXCocoa)
1790 			throw new NotYetImplementedException();
1791 		else
1792 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
1793 	}
1794 
1795 	/++
1796 		Returns the actual logical DPI for the window on its current display monitor. If the window
1797 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1798 
1799 		Please note this function may return zero if it doesn't know the answer!
1800 
1801 
1802 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1803 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1804 
1805 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1806 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1807 		window primarily resides on by checking the center point of the window against the monitor map.
1808 
1809 		Returns:
1810 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1811 			assumes the X and Y dpi are the same.
1812 
1813 		History:
1814 			Added November 26, 2021 (dub v10.4)
1815 
1816 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1817 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1818 			that.
1819 
1820 		Bugs:
1821 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1822 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1823 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1824 			and 1.5 on the secondary monitor.
1825 
1826 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1827 			is a historical misnomer - the real thing of interest is the scale factor and due to
1828 			compatibility concerns the scale would modify dpi values to trick applications. But since
1829 			that's the terminology common out there, I used it too.
1830 
1831 		See_Also:
1832 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1833 			as this since the window many be on a different monitor, but it is a reasonable fallback
1834 			to use if `actualDpi` returns 0.
1835 
1836 			[onDpiChanged] is changed when `actualDpi` has changed.
1837 	+/
1838 	int actualDpi() {
1839 		if(!actualDpiLoadAttempted) {
1840 			// FIXME: do the actual monitor we are on
1841 			// and on X this is a good chance to load the monitor map.
1842 			version(Windows) {
1843 				if(GetDpiForWindow)
1844 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1845 			} else version(X11) {
1846 				if(!xRandrInfoLoadAttemped) {
1847 					xRandrInfoLoadAttemped = true;
1848 					if(!XRandrLibrary.attempted) {
1849 						XRandrLibrary.loadDynamicLibrary();
1850 					}
1851 
1852 					if(XRandrLibrary.loadSuccessful) {
1853 						auto display = XDisplayConnection.get;
1854 						int scratch;
1855 						int major, minor;
1856 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1857 							goto fallback;
1858 
1859 						XRRQueryVersion(display, &major, &minor);
1860 						if(major <= 1 && minor < 5)
1861 							goto fallback;
1862 
1863 						int count;
1864 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1865 						if(monitors is null)
1866 							goto fallback;
1867 						scope(exit) XRRFreeMonitors(monitors);
1868 
1869 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1870 						MonitorInfo.info.assumeSafeAppend();
1871 						foreach(idx, monitor; monitors[0 .. count]) {
1872 							MonitorInfo.info ~= MonitorInfo(
1873 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1874 								Size(monitor.mwidth, monitor.mheight),
1875 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1876 							);
1877 
1878 							/+
1879 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1880 							// unknown physical size, just guess 96 to avoid divide by zero
1881 							MonitorInfo.info ~= MonitorInfo(
1882 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1883 								Size(monitor.mwidth, monitor.mheight),
1884 								96
1885 							);
1886 							else
1887 							// and actual thing
1888 							MonitorInfo.info ~= MonitorInfo(
1889 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1890 								Size(monitor.mwidth, monitor.mheight),
1891 								minInternal(
1892 									// millimeter to int then rounding up.
1893 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1894 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1895 								)
1896 							);
1897 							+/
1898 						}
1899 					// import std.stdio; writeln("Here", MonitorInfo.info);
1900 					}
1901 				}
1902 
1903 				if(XRandrLibrary.loadSuccessful) {
1904 					updateActualDpi(true);
1905 					//import std.stdio; writeln("updated");
1906 
1907 					if(!requestedInput) {
1908 						// this is what requests live updates should the configuration change
1909 						// each time you select input, it sends an initial event, so very important
1910 						// to not get into a loop of selecting input, getting event, updating data,
1911 						// and reselecting input...
1912 						requestedInput = true;
1913 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1914 						//import std.stdio; writeln("requested input");
1915 					}
1916 				} else {
1917 					fallback:
1918 					// make sure we disable events that aren't coming
1919 					xrrEventBase = -1;
1920 					// best guess... respect the custom scaling user command to some extent at least though
1921 					actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1922 				}
1923 			}
1924 			actualDpiLoadAttempted = true;
1925 		}
1926 		return actualDpi_;
1927 	}
1928 
1929 	private int actualDpi_;
1930 	private bool actualDpiLoadAttempted;
1931 
1932 	version(X11) private {
1933 		bool requestedInput;
1934 		static bool xRandrInfoLoadAttemped;
1935 		struct MonitorInfo {
1936 			Rectangle position;
1937 			Size size;
1938 			int dpi;
1939 
1940 			static MonitorInfo[] info;
1941 		}
1942 		bool screenPositionKnown;
1943 		int screenPositionX;
1944 		int screenPositionY;
1945 		void updateActualDpi(bool loadingNow = false) {
1946 			if(!loadingNow && !actualDpiLoadAttempted)
1947 				actualDpi(); // just to make it do the load
1948 			foreach(idx, m; MonitorInfo.info) {
1949 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1950 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1951 					actualDpi_ = m.dpi;
1952 					//import std.stdio; writeln("monitor ", idx);
1953 					if(changed && onDpiChanged)
1954 						onDpiChanged();
1955 					break;
1956 				}
1957 			}
1958 		}
1959 	}
1960 
1961 	/++
1962 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
1963 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
1964 
1965 		History:
1966 			Added November 26, 2021 (dub v10.4)
1967 
1968 		See_Also:
1969 			[actualDpi]
1970 	+/
1971 	void delegate() onDpiChanged;
1972 
1973 	version(X11) {
1974 		void recreateAfterDisconnect() {
1975 			if(!stateDiscarded) return;
1976 
1977 			if(_parent !is null && _parent.stateDiscarded)
1978 				_parent.recreateAfterDisconnect();
1979 
1980 			bool wasHidden = hidden;
1981 
1982 			activeScreenPainter = null; // should already be done but just to confirm
1983 
1984 			actualDpi_ = 0;
1985 			actualDpiLoadAttempted = false;
1986 			xRandrInfoLoadAttemped = false;
1987 
1988 			impl.createWindow(_width, _height, _title, openglMode, _parent);
1989 
1990 			if(auto dh = dropHandler) {
1991 				dropHandler = null;
1992 				enableDragAndDrop(this, dh);
1993 			}
1994 
1995 			if(recreateAdditionalConnectionState)
1996 				recreateAdditionalConnectionState();
1997 
1998 			hidden = wasHidden;
1999 			stateDiscarded = false;
2000 		}
2001 
2002 		bool stateDiscarded;
2003 		void discardConnectionState() {
2004 			if(XDisplayConnection.display)
2005 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2006 			if(discardAdditionalConnectionState)
2007 				discardAdditionalConnectionState();
2008 			stateDiscarded = true;
2009 		}
2010 
2011 		void delegate() discardAdditionalConnectionState;
2012 		void delegate() recreateAdditionalConnectionState;
2013 
2014 	}
2015 
2016 	private DropHandler dropHandler;
2017 
2018 	SimpleWindow _parent;
2019 	bool beingOpenKeepsAppOpen = true;
2020 	/++
2021 		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.
2022 
2023 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2024 
2025 		Params:
2026 
2027 		width = the width of the window's client area, in pixels
2028 		height = the height of the window's client area, in pixels
2029 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2030 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2031 		resizable = [Resizability] has three options:
2032 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2033 			$(P `fixedSize` will not allow the user to resize the window.)
2034 			$(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.)
2035 		windowType = The type of window you want to make.
2036 		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.
2037 		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".
2038 	+/
2039 	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) {
2040 		claimGuiThread();
2041 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2042 		this._width = this._virtualWidth = width;
2043 		this._height = this._virtualHeight = height;
2044 		this.openglMode = opengl;
2045 		version(X11) {
2046 			// auto scale not implemented except with opengl and even there it is kinda weird
2047 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2048 				resizable = Resizability.fixedSize;
2049 		}
2050 		this.resizability = resizable;
2051 		this.windowType = windowType;
2052 		this.customizationFlags = customizationFlags;
2053 		this._title = (title is null ? "D Application" : title);
2054 		this._parent = parent;
2055 		impl.createWindow(width, height, this._title, opengl, parent);
2056 
2057 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2058 			beingOpenKeepsAppOpen = false;
2059 	}
2060 
2061 	/// ditto
2062 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2063 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2064 	}
2065 
2066 	/// Same as above, except using the `Size` struct instead of separate width and height.
2067 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2068 		this(size.width, size.height, title, opengl, resizable);
2069 	}
2070 
2071 	/// ditto
2072 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2073 		this(size, title, opengl, resizable);
2074 	}
2075 
2076 
2077 	/++
2078 		Creates a window based on the given [Image]. It's client area
2079 		width and height is equal to the image. (A window's client area
2080 		is the drawable space inside; it excludes the title bar, etc.)
2081 
2082 		Windows based on images will not be resizable and do not use OpenGL.
2083 
2084 		It will draw the image in upon creation, but this will be overwritten
2085 		upon any draws, including the initial window visible event.
2086 
2087 		You probably do not want to use this and it may be removed from
2088 		the library eventually, or I might change it to be a "permanent"
2089 		background image; one that is automatically drawn on it before any
2090 		other drawing event. idk.
2091 	+/
2092 	this(Image image, string title = null) {
2093 		this(image.width, image.height, title);
2094 		this.image = image;
2095 	}
2096 
2097 	/++
2098 		Wraps a native window handle with very little additional processing - notably no destruction
2099 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2100 		windows created through the low level API (so you can use platform-specific options and
2101 		other details SimpleWindow does not expose) available to the event loop wrappers.
2102 	+/
2103 	this(NativeWindowHandle nativeWindow) {
2104 		windowType = WindowTypes.minimallyWrapped;
2105 		version(Windows)
2106 			impl.hwnd = nativeWindow;
2107 		else version(X11) {
2108 			impl.window = nativeWindow;
2109 			if(nativeWindow)
2110 				display = XDisplayConnection.get(); // get initial display to not segfault
2111 		} else version(OSXCocoa)
2112 			throw new NotYetImplementedException();
2113 		else featureNotImplemented();
2114 		// FIXME: set the size correctly
2115 		_width = 1;
2116 		_height = 1;
2117 		if(nativeWindow)
2118 			nativeMapping[nativeWindow] = this;
2119 
2120 		beingOpenKeepsAppOpen = false;
2121 
2122 		if(nativeWindow)
2123 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2124 		_suppressDestruction = true; // so it doesn't try to close
2125 	}
2126 
2127 	/++
2128 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2129 		The delegate will be called when the window manager asks you to take focus.
2130 
2131 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2132 
2133 		History:
2134 			Added April 1, 2022 (dub v10.8)
2135 	+/
2136 	SimpleWindow delegate() setRequestedInputFocus;
2137 
2138 	/// Experimental, do not use yet
2139 	/++
2140 		Grabs exclusive input from the user until you release it with
2141 		[releaseInputGrab].
2142 
2143 
2144 		Note: it is extremely rude to do this without good reason.
2145 		Reasons may include doing some kind of mouse drag operation
2146 		or popping up a temporary menu that should get events and will
2147 		be dismissed at ease by the user clicking away.
2148 
2149 		Params:
2150 			keyboard = do you want to grab keyboard input?
2151 			mouse = grab mouse input?
2152 			confine = confine the mouse cursor to inside this window?
2153 
2154 		History:
2155 			Prior to March 11, 2021, grabbing the keyboard would always also
2156 			set the X input focus. Now, it only focuses if it is a non-transient
2157 			window and otherwise manages the input direction internally.
2158 
2159 			This means spurious focus/blur events will no longer be sent and the
2160 			application will not steal focus from other applications (which the
2161 			window manager may have rejected anyway).
2162 	+/
2163 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2164 		static if(UsingSimpledisplayX11) {
2165 			XSync(XDisplayConnection.get, 0);
2166 			if(keyboard) {
2167 				if(isTransient && _parent) {
2168 					/*
2169 					FIXME:
2170 						setting the keyboard focus is not actually that helpful, what I more likely want
2171 						is the events from the parent window to be sent over here if we're transient.
2172 					*/
2173 
2174 					_parent.inputProxy = this;
2175 				} else {
2176 
2177 					SimpleWindow setTo;
2178 					if(setRequestedInputFocus !is null)
2179 						setTo = setRequestedInputFocus();
2180 					if(setTo is null)
2181 						setTo = this;
2182 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2183 				}
2184 			}
2185 			if(mouse) {
2186 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2187 				EventMask.PointerMotionMask // FIXME: not efficient
2188 				| EventMask.ButtonPressMask
2189 				| EventMask.ButtonReleaseMask
2190 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2191 				)
2192 			{
2193 				XSync(XDisplayConnection.get, 0);
2194 				import core.stdc.stdio;
2195 				printf("Grab input failed %d\n", res);
2196 				//throw new Exception("Grab input failed");
2197 			} else {
2198 				// cool
2199 			}
2200 			}
2201 
2202 		} else version(Windows) {
2203 			// FIXME: keyboard?
2204 			SetCapture(impl.hwnd);
2205 			if(confine) {
2206 				RECT rcClip;
2207 				//RECT rcOldClip;
2208 				//GetClipCursor(&rcOldClip);
2209 				GetWindowRect(hwnd, &rcClip);
2210 				ClipCursor(&rcClip);
2211 			}
2212 		} else version(OSXCocoa) {
2213 			throw new NotYetImplementedException();
2214 		} else static assert(0);
2215 	}
2216 
2217 	private Point imePopupLocation = Point(0, 0);
2218 
2219 	/++
2220 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2221 
2222 		Bugs:
2223 			Not implemented outside X11.
2224 	+/
2225 	void setIMEPopupLocation(Point location) {
2226 		static if(UsingSimpledisplayX11) {
2227 			imePopupLocation = location;
2228 			updateIMEPopupLocation();
2229 		} else {
2230 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2231 			// throw new NotYetImplementedException();
2232 		}
2233 	}
2234 
2235 	/// ditto
2236 	void setIMEPopupLocation(int x, int y) {
2237 		return setIMEPopupLocation(Point(x, y));
2238 	}
2239 
2240 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2241 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2242 	// receives a ConfigureNotify event
2243 	private void updateIMEPopupLocation() {
2244 		static if(UsingSimpledisplayX11) {
2245 			if (xic is null) {
2246 				return;
2247 			}
2248 
2249 			XPoint nspot;
2250 			nspot.x = cast(short) imePopupLocation.x;
2251 			nspot.y = cast(short) imePopupLocation.y;
2252 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2253 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2254 			XFree(preeditAttr);
2255 		}
2256 	}
2257 
2258 	private bool imeFocused = true;
2259 
2260 	/++
2261 		Tells the IME whether or not an input field is currently focused in the window.
2262 
2263 		Bugs:
2264 			Not implemented outside X11.
2265 	+/
2266 	void setIMEFocused(bool value) {
2267 		imeFocused = value;
2268 		updateIMEFocused();
2269 	}
2270 
2271 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2272 	private void updateIMEFocused() {
2273 		static if(UsingSimpledisplayX11) {
2274 			if (xic is null) {
2275 				return;
2276 			}
2277 
2278 			if (focused && imeFocused) {
2279 				XSetICFocus(xic);
2280 			} else {
2281 				XUnsetICFocus(xic);
2282 			}
2283 		}
2284 	}
2285 
2286 	/++
2287 		Returns the native window.
2288 
2289 		History:
2290 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2291 			to access it through the `impl` member (which is semi-supported
2292 			but platform specific and here it is simple enough to offer an accessor).
2293 
2294 		Bugs:
2295 			Not implemented outside Windows or X11.
2296 	+/
2297 	NativeWindowHandle nativeWindowHandle() {
2298 		version(X11)
2299 			return impl.window;
2300 		else version(Windows)
2301 			return impl.hwnd;
2302 		else
2303 			throw new NotYetImplementedException();
2304 	}
2305 
2306 	private bool isTransient() {
2307 		with(WindowTypes)
2308 		final switch(windowType) {
2309 			case normal, undecorated, eventOnly:
2310 			case nestedChild, minimallyWrapped:
2311 				return (customizationFlags & WindowFlags.transient) ? true : false;
2312 			case dropdownMenu, popupMenu, notification:
2313 				return true;
2314 		}
2315 	}
2316 
2317 	private SimpleWindow inputProxy;
2318 
2319 	/++
2320 		Releases the grab acquired by [grabInput].
2321 	+/
2322 	void releaseInputGrab() {
2323 		static if(UsingSimpledisplayX11) {
2324 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2325 			if(_parent)
2326 				_parent.inputProxy = null;
2327 		} else version(Windows) {
2328 			ReleaseCapture();
2329 			ClipCursor(null);
2330 		} else version(OSXCocoa) {
2331 			throw new NotYetImplementedException();
2332 		} else static assert(0);
2333 	}
2334 
2335 	/++
2336 		Sets the input focus to this window.
2337 
2338 		You shouldn't call this very often - please let the user control the input focus.
2339 	+/
2340 	void focus() {
2341 		static if(UsingSimpledisplayX11) {
2342 			SimpleWindow setTo;
2343 			if(setRequestedInputFocus !is null)
2344 				setTo = setRequestedInputFocus();
2345 			if(setTo is null)
2346 				setTo = this;
2347 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2348 		} else version(Windows) {
2349 			SetFocus(this.impl.hwnd);
2350 		} else version(OSXCocoa) {
2351 			throw new NotYetImplementedException();
2352 		} else static assert(0);
2353 	}
2354 
2355 	/++
2356 		Requests attention from the user for this window.
2357 
2358 
2359 		The typical result of this function is to change the color
2360 		of the taskbar icon, though it may be tweaked on specific
2361 		platforms.
2362 
2363 		It is meant to unobtrusively tell the user that something
2364 		relevant to them happened in the background and they should
2365 		check the window when they get a chance. Upon receiving the
2366 		keyboard focus, the window will automatically return to its
2367 		natural state.
2368 
2369 		If the window already has the keyboard focus, this function
2370 		may do nothing, because the user is presumed to already be
2371 		giving the window attention.
2372 
2373 		Implementation_note:
2374 
2375 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2376 		atom on X11 and the FlashWindow function on Windows.
2377 	+/
2378 	void requestAttention() {
2379 		if(_focused)
2380 			return;
2381 
2382 		version(Windows) {
2383 			FLASHWINFO info;
2384 			info.cbSize = info.sizeof;
2385 			info.hwnd = impl.hwnd;
2386 			info.dwFlags = FLASHW_TRAY;
2387 			info.uCount = 1;
2388 
2389 			FlashWindowEx(&info);
2390 
2391 		} else version(X11) {
2392 			demandingAttention = true;
2393 			demandAttention(this, true);
2394 		} else version(OSXCocoa) {
2395 			throw new NotYetImplementedException();
2396 		} else static assert(0);
2397 	}
2398 
2399 	private bool _focused;
2400 
2401 	version(X11) private bool demandingAttention;
2402 
2403 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2404 	/// You'll have to call `close()` manually if you set this delegate.
2405 	void delegate () closeQuery;
2406 
2407 	/// This will be called when window visibility was changed.
2408 	void delegate (bool becomesVisible) visibilityChanged;
2409 
2410 	/// This will be called when window becomes visible for the first time.
2411 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2412 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2413 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2414 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2415 	private bool _visibleForTheFirstTimeCalled;
2416 	void delegate () visibleForTheFirstTime;
2417 
2418 	/// Returns true if the window has been closed.
2419 	final @property bool closed() { return _closed; }
2420 
2421 	private final @property bool notClosed() { return !_closed; }
2422 
2423 	/// Returns true if the window is focused.
2424 	final @property bool focused() { return _focused; }
2425 
2426 	private bool _visible;
2427 	/// Returns true if the window is visible (mapped).
2428 	final @property bool visible() { return _visible; }
2429 
2430 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2431 	void close() {
2432 		if (!_closed) {
2433 			runInGuiThread( {
2434 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2435 				if (onClosing !is null) onClosing();
2436 				impl.closeWindow();
2437 				_closed = true;
2438 			} );
2439 		}
2440 	}
2441 
2442 	/++
2443 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2444 
2445 		History:
2446 			Overload added on March 7, 2021.
2447 	+/
2448 	void close() shared {
2449 		(cast() this).close();
2450 	}
2451 
2452 	/++
2453 
2454 	+/
2455 	void maximize() {
2456 		version(Windows)
2457 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2458 		else version(X11) {
2459 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2460 
2461 			// also note _NET_WM_STATE_FULLSCREEN
2462 		}
2463 
2464 	}
2465 
2466 	private bool _fullscreen;
2467 	version(Windows)
2468 	private WINDOWPLACEMENT g_wpPrev;
2469 
2470 	/// not fully implemented but planned for a future release
2471 	void fullscreen(bool yes) {
2472 		version(Windows) {
2473 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2474 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2475 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2476 				MONITORINFO mi;
2477 				mi.cbSize = MONITORINFO.sizeof;
2478 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2479 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2480 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2481 					SetWindowLong(hwnd, GWL_STYLE,
2482 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2483 					SetWindowPos(hwnd, HWND_TOP,
2484 						     mi.rcMonitor.left, mi.rcMonitor.top,
2485 						     mi.rcMonitor.right - mi.rcMonitor.left,
2486 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2487 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2488 				}
2489 			} else {
2490 				SetWindowLong(hwnd, GWL_STYLE,
2491 					      dwStyle | WS_OVERLAPPEDWINDOW);
2492 				SetWindowPlacement(hwnd, &g_wpPrev);
2493 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2494 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2495 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2496 			}
2497 
2498 		} else version(X11) {
2499 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2500 		}
2501 
2502 		_fullscreen = yes;
2503 
2504 	}
2505 
2506 	bool fullscreen() {
2507 		return _fullscreen;
2508 	}
2509 
2510 	/++
2511 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2512 
2513 	+/
2514 	void minimize() {
2515 		version(Windows)
2516 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2517 		//else version(X11)
2518 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2519 	}
2520 
2521 	/// Alias for `hidden = false`
2522 	void show() {
2523 		hidden = false;
2524 	}
2525 
2526 	/// Alias for `hidden = true`
2527 	void hide() {
2528 		hidden = true;
2529 	}
2530 
2531 	/// Hide cursor when it enters the window.
2532 	void hideCursor() {
2533 		version(OSXCocoa) throw new NotYetImplementedException(); else
2534 		if (!_closed) impl.hideCursor();
2535 	}
2536 
2537 	/// Don't hide cursor when it enters the window.
2538 	void showCursor() {
2539 		version(OSXCocoa) throw new NotYetImplementedException(); else
2540 		if (!_closed) impl.showCursor();
2541 	}
2542 
2543 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2544 	 *
2545 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2546 	 * control. Try to think for other approaches before using this function.
2547 	 *
2548 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2549 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2550 	 *       receive "mouse moved here" event.
2551 	 */
2552 	bool warpMouse (int x, int y) {
2553 		version(X11) {
2554 			if (!_closed) { impl.warpMouse(x, y); return true; }
2555 		} else version(Windows) {
2556 			if (!_closed) {
2557 				POINT point;
2558 				point.x = x;
2559 				point.y = y;
2560 				if(ClientToScreen(impl.hwnd, &point)) {
2561 					SetCursorPos(point.x, point.y);
2562 					return true;
2563 				}
2564 			}
2565 		}
2566 		return false;
2567 	}
2568 
2569 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2570 	void sendDummyEvent () {
2571 		version(X11) {
2572 			if (!_closed) { impl.sendDummyEvent(); }
2573 		}
2574 	}
2575 
2576 	/// Set window minimal size.
2577 	void setMinSize (int minwidth, int minheight) {
2578 		version(OSXCocoa) throw new NotYetImplementedException(); else
2579 		if (!_closed) impl.setMinSize(minwidth, minheight);
2580 	}
2581 
2582 	/// Set window maximal size.
2583 	void setMaxSize (int maxwidth, int maxheight) {
2584 		version(OSXCocoa) throw new NotYetImplementedException(); else
2585 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2586 	}
2587 
2588 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2589 	/// Currently only supported on X11.
2590 	void setResizeGranularity (int granx, int grany) {
2591 		version(OSXCocoa) throw new NotYetImplementedException(); else
2592 		if (!_closed) impl.setResizeGranularity(granx, grany);
2593 	}
2594 
2595 	/// Move window.
2596 	void move(int x, int y) {
2597 		version(OSXCocoa) throw new NotYetImplementedException(); else
2598 		if (!_closed) impl.move(x, y);
2599 	}
2600 
2601 	/// ditto
2602 	void move(Point p) {
2603 		version(OSXCocoa) throw new NotYetImplementedException(); else
2604 		if (!_closed) impl.move(p.x, p.y);
2605 	}
2606 
2607 	/++
2608 		Resize window.
2609 
2610 		Note that the width and height of the window are NOT instantly
2611 		updated - it waits for the window manager to approve the resize
2612 		request, which means you must return to the event loop before the
2613 		width and height are actually changed.
2614 	+/
2615 	void resize(int w, int h) {
2616 		if(!_closed && _fullscreen) fullscreen = false;
2617 		version(OSXCocoa) throw new NotYetImplementedException(); else
2618 		if (!_closed) impl.resize(w, h);
2619 	}
2620 
2621 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2622 	void moveResize (int x, int y, int w, int h) {
2623 		if(!_closed && _fullscreen) fullscreen = false;
2624 		version(OSXCocoa) throw new NotYetImplementedException(); else
2625 		if (!_closed) impl.moveResize(x, y, w, h);
2626 	}
2627 
2628 	private bool _hidden;
2629 
2630 	/// Returns true if the window is hidden.
2631 	final @property bool hidden() {
2632 		return _hidden;
2633 	}
2634 
2635 	/// Shows or hides the window based on the bool argument.
2636 	final @property void hidden(bool b) {
2637 		_hidden = b;
2638 		version(Windows) {
2639 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2640 		} else version(X11) {
2641 			if(b)
2642 				//XUnmapWindow(impl.display, impl.window);
2643 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2644 			else
2645 				XMapWindow(impl.display, impl.window);
2646 		} else version(OSXCocoa) {
2647 			throw new NotYetImplementedException();
2648 		} else static assert(0);
2649 	}
2650 
2651 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2652 	void opacity(double opacity) @property
2653 	in {
2654 		assert(opacity >= 0 && opacity <= 1);
2655 	} do {
2656 		version (Windows) {
2657 			impl.setOpacity(cast(ubyte)(255 * opacity));
2658 		} else version (X11) {
2659 			impl.setOpacity(cast(uint)(uint.max * opacity));
2660 		} else throw new NotYetImplementedException();
2661 	}
2662 
2663 	/++
2664 		Sets your event handlers, without entering the event loop. Useful if you
2665 		have multiple windows - set the handlers on each window, then only do
2666 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2667 
2668 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2669 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2670 		delegate signatures.
2671 	+/
2672 	void setEventHandlers(T...)(T eventHandlers) {
2673 		// FIXME: add more events
2674 		foreach(handler; eventHandlers) {
2675 			static if(__traits(compiles, handleKeyEvent = handler)) {
2676 				handleKeyEvent = handler;
2677 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2678 				handleCharEvent = handler;
2679 			} else static if(__traits(compiles, handlePulse = handler)) {
2680 				handlePulse = handler;
2681 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2682 				handleMouseEvent = handler;
2683 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2684 		}
2685 	}
2686 
2687 	/++
2688 		The event loop automatically returns when the window is closed
2689 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2690 		pulse timer is created. The event loop will block until an event
2691 		arrives or the pulse timer goes off.
2692 
2693 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2694 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2695 		[handleMouseEvent], based on the signature of delegates you provide.
2696 
2697 		Give one with no parameters to set a timer pulse handler. Give one that
2698 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2699 		and one that takes `dchar` for a char event handler. You can use as many
2700 		or as few handlers as you need for your application.
2701 
2702 		Bugs:
2703 
2704 		$(PITFALL
2705 			You should always have one event loop live for your application.
2706 			If you make two windows in sequence, the second call to eventLoop
2707 			might fail:
2708 
2709 			---
2710 			// don't do this!
2711 			auto window = new SimpleWindow();
2712 			window.eventLoop(0);
2713 
2714 			auto window2 = new SimpleWindow();
2715 			window2.eventLoop(0); // problematic! might crash
2716 			---
2717 
2718 			simpledisplay's current implementation assumes that final cleanup is
2719 			done when the event loop refcount reaches zero. So after the first
2720 			eventLoop returns, when there isn't already another one active, it assumes
2721 			the program will exit soon and cleans up.
2722 
2723 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2724 			it eventually, but in the mean time, there's an easy solution:
2725 
2726 			---
2727 			// do this
2728 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2729 
2730 			auto window = new SimpleWindow();
2731 			window.eventLoop(0);
2732 
2733 			auto window2 = new SimpleWindow();
2734 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2735 			---
2736 
2737 			By adding a top-level reference to the event loop, it ensures the final cleanup
2738 			is not performed until it goes out of scope too, letting the individual window loops
2739 			work without trouble despite the bug.
2740 		)
2741 
2742 		History:
2743 			The overload without `pulseTimeout` was added on December 8, 2021.
2744 
2745 			On December 9, 2021, the default blocking mode (which is now configurable
2746 			because [eventLoopWithBlockingMode] was added) switched from
2747 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2748 			should almost never be noticeable to you since the typical simpledisplay
2749 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2750 
2751 		See_Also:
2752 			[eventLoopWithBlockingMode]
2753 	+/
2754 	final int eventLoop(T...)(
2755 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2756 		T eventHandlers) /// delegate list like std.concurrency.receive
2757 	{
2758 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2759 	}
2760 
2761 	/// ditto
2762 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2763 	{
2764 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2765 	}
2766 
2767 	/++
2768 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2769 
2770 		History:
2771 			Added December 8, 2021 (dub v10.5)
2772 
2773 			Previously, this implementation was right inside [eventLoop], but when I wanted
2774 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2775 			just renamed it instead of adding as an overload. Besides, the new name makes it
2776 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2777 
2778 		See_Also:
2779 			[SimpleWindow.eventLoop], [EventLoop]
2780 
2781 		Bugs:
2782 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2783 	+/
2784 	final int eventLoopWithBlockingMode(T...)(
2785 		BlockingMode blockingMode, /// when you want this function to block until
2786 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2787 		T eventHandlers) /// delegate list like std.concurrency.receive
2788 	{
2789 		setEventHandlers(eventHandlers);
2790 
2791 		version(with_eventloop) {
2792 			// delegates event loop to my other module
2793 			version(X11)
2794 				XFlush(display);
2795 
2796 			import arsd.eventloop;
2797 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2798 			scope(exit) clearInterval(handle);
2799 
2800 			loop();
2801 			return 0;
2802 		} else version(OSXCocoa) {
2803 			// FIXME
2804 			if (handlePulse !is null && pulseTimeout != 0) {
2805 				timer = scheduledTimer(pulseTimeout*1e-3,
2806 					view, sel_registerName("simpledisplay_pulse"),
2807 					null, true);
2808 			}
2809 
2810             		setNeedsDisplay(view, true);
2811             		run(NSApp);
2812             		return 0;
2813         	} else {
2814 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2815 
2816 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2817 				return 0;
2818 
2819 			return el.run(
2820 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2821 					null :
2822 					&this.notClosed
2823 			);
2824 		}
2825 	}
2826 
2827 	/++
2828 		This lets you draw on the window (or its backing buffer) using basic
2829 		2D primitives.
2830 
2831 		Be sure to call this in a limited scope because your changes will not
2832 		actually appear on the window until ScreenPainter's destructor runs.
2833 
2834 		Returns: an instance of [ScreenPainter], which has the drawing methods
2835 		on it to draw on this window.
2836 
2837 		Params:
2838 			manualInvalidations = if you set this to true, you will need to
2839 			set the invalid rectangle on the painter yourself. If false, it
2840 			assumes the whole window has been redrawn each time you draw.
2841 
2842 			Only invalidated rectangles are blitted back to the window when
2843 			the destructor runs. Doing this yourself can reduce flickering
2844 			of child windows.
2845 
2846 		History:
2847 			The `manualInvalidations` parameter overload was added on
2848 			December 30, 2021 (dub v10.5)
2849 	+/
2850 	ScreenPainter draw() {
2851 		return draw(false);
2852 	}
2853 	/// ditto
2854 	ScreenPainter draw(bool manualInvalidations) {
2855 		return impl.getPainter(manualInvalidations);
2856 	}
2857 
2858 	// This is here to implement the interface we use for various native handlers.
2859 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2860 
2861 	// maps native window handles to SimpleWindow instances, if there are any
2862 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2863 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2864 
2865 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
2866 	private int _virtualWidth;
2867 	private int _virtualHeight;
2868 
2869 	/// Width of the window's drawable client area, in pixels.
2870 	@scriptable
2871 	final @property int width() const pure nothrow @safe @nogc {
2872 		if(resizability == Resizability.automaticallyScaleIfPossible)
2873 			return _virtualWidth;
2874 		else
2875 			return _width;
2876 	}
2877 
2878 	/// Height of the window's drawable client area, in pixels.
2879 	@scriptable
2880 	final @property int height() const pure nothrow @safe @nogc {
2881 		if(resizability == Resizability.automaticallyScaleIfPossible)
2882 			return _virtualHeight;
2883 		else
2884 			return _height;
2885 	}
2886 
2887 	/++
2888 		Returns the actual size of the window, bypassing the logical
2889 		illusions of [Resizability.automaticallyScaleIfPossible].
2890 
2891 		History:
2892 			Added November 11, 2022 (dub v10.10)
2893 	+/
2894 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
2895 		return Size(_width, _height);
2896 	}
2897 
2898 
2899 	private int _width;
2900 	private int _height;
2901 
2902 	// HACK: making the best of some copy constructor woes with refcounting
2903 	private ScreenPainterImplementation* activeScreenPainter_;
2904 
2905 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2906 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2907 
2908 	private OpenGlOptions openglMode;
2909 	private Resizability resizability;
2910 	private WindowTypes windowType;
2911 	private int customizationFlags;
2912 
2913 	/// `true` if OpenGL was initialized for this window.
2914 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2915 		version(without_opengl)
2916 			return false;
2917 		else
2918 			return (openglMode == OpenGlOptions.yes);
2919 	}
2920 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2921 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2922 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2923 
2924 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2925 	/// to call this, as it's not recommended to share window between threads.
2926 	void mtLock () {
2927 		version(X11) {
2928 			XLockDisplay(this.display);
2929 		}
2930 	}
2931 
2932 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2933 	/// to call this, as it's not recommended to share window between threads.
2934 	void mtUnlock () {
2935 		version(X11) {
2936 			XUnlockDisplay(this.display);
2937 		}
2938 	}
2939 
2940 	/// Emit a beep to get user's attention.
2941 	void beep () {
2942 		version(X11) {
2943 			XBell(this.display, 100);
2944 		} else version(Windows) {
2945 			MessageBeep(0xFFFFFFFF);
2946 		}
2947 	}
2948 
2949 
2950 
2951 	version(without_opengl) {} else {
2952 
2953 		/// 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`.
2954 		void delegate() redrawOpenGlScene;
2955 
2956 		/// This will allow you to change OpenGL vsync state.
2957 		final @property void vsync (bool wait) {
2958 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2959 		  version(X11) {
2960 		    setAsCurrentOpenGlContext();
2961 		    glxSetVSync(display, impl.window, wait);
2962 		  } else version(Windows) {
2963 		    setAsCurrentOpenGlContext();
2964                     wglSetVSync(wait);
2965 		  }
2966 		}
2967 
2968 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
2969 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
2970 		/// enough without waiting 'em to finish their frame business.
2971 		bool useGLFinish = true;
2972 
2973 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
2974 		/// 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.
2975 		void redrawOpenGlSceneNow() {
2976 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
2977 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2978 			if(redrawOpenGlScene is null)
2979 				return;
2980 
2981 			this.mtLock();
2982 			scope(exit) this.mtUnlock();
2983 
2984 			this.setAsCurrentOpenGlContext();
2985 
2986 			redrawOpenGlScene();
2987 
2988 			this.swapOpenGlBuffers();
2989 			// 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.
2990 			if (useGLFinish) glFinish();
2991 		}
2992 
2993 		private bool redrawOpenGlSceneSoonSet = false;
2994 		private static class RedrawOpenGlSceneEvent {
2995 			SimpleWindow w;
2996 			this(SimpleWindow w) { this.w = w; }
2997 		}
2998 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
2999 		/++
3000 			Queues an opengl redraw as soon as the other pending events are cleared.
3001 		+/
3002 		void redrawOpenGlSceneSoon() {
3003 			if(!redrawOpenGlSceneSoonSet) {
3004 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3005 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3006 				redrawOpenGlSceneSoonSet = true;
3007 			}
3008 			this.postEvent(redrawOpenGlSceneEvent, true);
3009 		}
3010 
3011 
3012 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3013 		void setAsCurrentOpenGlContext() {
3014 			assert(openglMode == OpenGlOptions.yes);
3015 			version(X11) {
3016 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3017 					throw new Exception("glXMakeCurrent");
3018 			} else version(Windows) {
3019 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3020 				if (!wglMakeCurrent(ghDC, ghRC))
3021 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3022 			}
3023 		}
3024 
3025 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3026 		/// This doesn't throw, returning success flag instead.
3027 		bool setAsCurrentOpenGlContextNT() nothrow {
3028 			assert(openglMode == OpenGlOptions.yes);
3029 			version(X11) {
3030 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3031 			} else version(Windows) {
3032 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3033 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3034 			}
3035 		}
3036 
3037 		/// 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.
3038 		/// This doesn't throw, returning success flag instead.
3039 		bool releaseCurrentOpenGlContext() nothrow {
3040 			assert(openglMode == OpenGlOptions.yes);
3041 			version(X11) {
3042 				return (glXMakeCurrent(display, 0, null) != 0);
3043 			} else version(Windows) {
3044 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3045 				return wglMakeCurrent(ghDC, null) ? true : false;
3046 			}
3047 		}
3048 
3049 		/++
3050 			simpledisplay always uses double buffering, usually automatically. This
3051 			manually swaps the OpenGL buffers.
3052 
3053 
3054 			You should not need to call this yourself because simpledisplay will do it
3055 			for you after calling your `redrawOpenGlScene`.
3056 
3057 			Remember that this may throw an exception, which you can catch in a multithreaded
3058 			application to keep your thread from dying from an unhandled exception.
3059 		+/
3060 		void swapOpenGlBuffers() {
3061 			assert(openglMode == OpenGlOptions.yes);
3062 			version(X11) {
3063 				if (!this._visible) return; // no need to do this if window is invisible
3064 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3065 				glXSwapBuffers(display, impl.window);
3066 			} else version(Windows) {
3067 				SwapBuffers(ghDC);
3068 			}
3069 		}
3070 	}
3071 
3072 	/++
3073 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3074 
3075 
3076 		---
3077 			auto window = new SimpleWindow(100, 100, "First title");
3078 			window.title = "A new title";
3079 		---
3080 
3081 		You may call this function at any time.
3082 	+/
3083 	@property void title(string title) {
3084 		_title = title;
3085 		version(OSXCocoa) throw new NotYetImplementedException(); else
3086 		impl.setTitle(title);
3087 	}
3088 
3089 	private string _title;
3090 
3091 	/// Gets the title
3092 	@property string title() {
3093 		if(_title is null)
3094 			_title = getRealTitle();
3095 		return _title;
3096 	}
3097 
3098 	/++
3099 		Get the title as set by the window manager.
3100 		May not match what you attempted to set.
3101 	+/
3102 	string getRealTitle() {
3103 		static if(is(typeof(impl.getTitle())))
3104 			return impl.getTitle();
3105 		else
3106 			return null;
3107 	}
3108 
3109 	// don't use this generally it is not yet really released
3110 	version(X11)
3111 	@property Image secret_icon() {
3112 		return secret_icon_inner;
3113 	}
3114 	private Image secret_icon_inner;
3115 
3116 
3117 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3118 	@property void icon(MemoryImage icon) {
3119 		if(icon is null)
3120 			return;
3121 		auto tci = icon.getAsTrueColorImage();
3122 		version(Windows) {
3123 			winIcon = new WindowsIcon(icon);
3124 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3125 		} else version(X11) {
3126 			secret_icon_inner = Image.fromMemoryImage(icon);
3127 			// FIXME: ensure this is correct
3128 			auto display = XDisplayConnection.get;
3129 			arch_ulong[] buffer;
3130 			buffer ~= icon.width;
3131 			buffer ~= icon.height;
3132 			foreach(c; tci.imageData.colors) {
3133 				arch_ulong b;
3134 				b |= c.a << 24;
3135 				b |= c.r << 16;
3136 				b |= c.g << 8;
3137 				b |= c.b;
3138 				buffer ~= b;
3139 			}
3140 
3141 			XChangeProperty(
3142 				display,
3143 				impl.window,
3144 				GetAtom!("_NET_WM_ICON", true)(display),
3145 				GetAtom!"CARDINAL"(display),
3146 				32 /* bits */,
3147 				0 /*PropModeReplace*/,
3148 				buffer.ptr,
3149 				cast(int) buffer.length);
3150 		} else version(OSXCocoa) {
3151 			throw new NotYetImplementedException();
3152 		} else static assert(0);
3153 	}
3154 
3155 	version(Windows)
3156 		private WindowsIcon winIcon;
3157 
3158 	bool _suppressDestruction;
3159 
3160 	~this() {
3161 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3162 		if(_suppressDestruction)
3163 			return;
3164 		impl.dispose();
3165 	}
3166 
3167 	private bool _closed;
3168 
3169 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3170 	/*
3171 	ScreenPainter drawTransiently() {
3172 		return impl.getPainter();
3173 	}
3174 	*/
3175 
3176 	/// Draws an image on the window. This is meant to provide quick look
3177 	/// of a static image generated elsewhere.
3178 	@property void image(Image i) {
3179 	/+
3180 		version(Windows) {
3181 			BITMAP bm;
3182 			HDC hdc = GetDC(hwnd);
3183 			HDC hdcMem = CreateCompatibleDC(hdc);
3184 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3185 
3186 			GetObject(i.handle, bm.sizeof, &bm);
3187 
3188 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3189 
3190 			SelectObject(hdcMem, hbmOld);
3191 			DeleteDC(hdcMem);
3192 			ReleaseDC(hwnd, hdc);
3193 
3194 			/*
3195 			RECT r;
3196 			r.right = i.width;
3197 			r.bottom = i.height;
3198 			InvalidateRect(hwnd, &r, false);
3199 			*/
3200 		} else
3201 		version(X11) {
3202 			if(!destroyed) {
3203 				if(i.usingXshm)
3204 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3205 				else
3206 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3207 			}
3208 		} else
3209 		version(OSXCocoa) {
3210 			draw().drawImage(Point(0, 0), i);
3211 			setNeedsDisplay(view, true);
3212 		} else static assert(0);
3213 	+/
3214 		auto painter = this.draw;
3215 		painter.drawImage(Point(0, 0), i);
3216 	}
3217 
3218 	/++
3219 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3220 
3221 		---
3222 		window.cursor = GenericCursor.Help;
3223 		// now the window mouse cursor is set to a generic help
3224 		---
3225 
3226 	+/
3227 	@property void cursor(MouseCursor cursor) {
3228 		version(OSXCocoa)
3229 			featureNotImplemented();
3230 		else
3231 		if(this.impl.curHidden <= 0) {
3232 			static if(UsingSimpledisplayX11) {
3233 				auto ch = cursor.cursorHandle;
3234 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3235 			} else version(Windows) {
3236 				auto ch = cursor.cursorHandle;
3237 				impl.currentCursor = ch;
3238 				SetCursor(ch); // redraw without waiting for mouse movement to update
3239 			} else featureNotImplemented();
3240 		}
3241 
3242 	}
3243 
3244 	/// What follows are the event handlers. These are set automatically
3245 	/// by the eventLoop function, but are still public so you can change
3246 	/// them later. wasPressed == true means key down. false == key up.
3247 
3248 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3249 	void delegate(KeyEvent ke) handleKeyEvent;
3250 
3251 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3252 	void delegate(dchar c) handleCharEvent;
3253 
3254 	/// Handles a timer pulse. Settable through setEventHandlers.
3255 	void delegate() handlePulse;
3256 
3257 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3258 	void delegate(bool) onFocusChange;
3259 
3260 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3261 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3262 	void delegate() onClosing;
3263 
3264 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3265 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3266 	 * last minute cleanup. */
3267 	void delegate() onDestroyed;
3268 
3269 	static if (UsingSimpledisplayX11)
3270 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3271 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3272 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3273 	 *
3274 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3275 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3276 
3277 	//version(Windows)
3278 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3279 
3280 	private {
3281 		int lastMouseX = int.min;
3282 		int lastMouseY = int.min;
3283 		void mdx(ref MouseEvent ev) {
3284 			if(lastMouseX == int.min || lastMouseY == int.min) {
3285 				ev.dx = 0;
3286 				ev.dy = 0;
3287 			} else {
3288 				ev.dx = ev.x - lastMouseX;
3289 				ev.dy = ev.y - lastMouseY;
3290 			}
3291 
3292 			lastMouseX = ev.x;
3293 			lastMouseY = ev.y;
3294 		}
3295 	}
3296 
3297 	/// Mouse event handler. Settable through setEventHandlers.
3298 	void delegate(MouseEvent) handleMouseEvent;
3299 
3300 	/// use to redraw child widgets if you use system apis to add stuff
3301 	void delegate() paintingFinished;
3302 
3303 	void delegate() paintingFinishedDg() {
3304 		return paintingFinished;
3305 	}
3306 
3307 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3308 	/// for this to ever happen.
3309 	void delegate(int width, int height) windowResized;
3310 
3311 	/++
3312 		Platform specific - handle any native message this window gets.
3313 
3314 		Note: this is called *in addition to* other event handlers, unless you either:
3315 
3316 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3317 
3318 		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.
3319 
3320 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3321 
3322 		On X, it takes the form of `int delegate(XEvent)`.
3323 
3324 		History:
3325 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3326 
3327 			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.
3328 	+/
3329 	NativeEventHandler handleNativeEvent_;
3330 
3331 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3332 		return handleNativeEvent_;
3333 	}
3334 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3335 		handleNativeEvent_ = neh;
3336 	}
3337 
3338 	version(Windows)
3339 	// compatibility shim with the old deprecated way
3340 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3341 	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) {
3342 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3343 			auto ret = dg(h, m, w, l);
3344 			if(ret == 0)
3345 				r = 1;
3346 			return ret;
3347 		};
3348 	}
3349 
3350 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3351 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3352 	/// this instead and it will work the same way.
3353 	__gshared NativeEventHandler handleNativeGlobalEvent;
3354 
3355 //  private:
3356 	/// The native implementation is available, but you shouldn't use it unless you are
3357 	/// familiar with the underlying operating system, don't mind depending on it, and
3358 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3359 	/// do what you need to do with handleNativeEvent instead.
3360 	///
3361 	/// This is likely to eventually change to be just a struct holding platform-specific
3362 	/// handles instead of a template mixin at some point because I'm not happy with the
3363 	/// code duplication here (ironically).
3364 	mixin NativeSimpleWindowImplementation!() impl;
3365 
3366 	/**
3367 		This is in-process one-way (from anything to window) event sending mechanics.
3368 		It is thread-safe, so it can be used in multi-threaded applications to send,
3369 		for example, "wake up and repaint" events when thread completed some operation.
3370 		This will allow to avoid using timer pulse to check events with synchronization,
3371 		'cause event handler will be called in UI thread. You can stop guessing which
3372 		pulse frequency will be enough for your app.
3373 		Note that events handlers may be called in arbitrary order, i.e. last registered
3374 		handler can be called first, and vice versa.
3375 	*/
3376 public:
3377 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3378 	 * "spamming" window with events it can't cope with.
3379 	 * It is safe to call this from non-UI threads.
3380 	 */
3381 	@property bool eventQueueEmpty() () {
3382 		synchronized(this) {
3383 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3384 		}
3385 		return true;
3386 	}
3387 
3388 	/** Does our custom event queue contains at least one with the given type?
3389 	 * Can be used in simple cases to prevent "spamming" window with events
3390 	 * it can't cope with.
3391 	 * It is safe to call this from non-UI threads.
3392 	 */
3393 	@property bool eventQueued(ET:Object) () {
3394 		synchronized(this) {
3395 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3396 				if (!o.doProcess) {
3397 					if (cast(ET)(o.evt)) return true;
3398 				}
3399 			}
3400 		}
3401 		return false;
3402 	}
3403 
3404 	/++
3405 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3406 
3407 		History:
3408 			Added May 12, 2021
3409 	+/
3410 	void delegate(Exception e) nothrow eventUncaughtException;
3411 
3412 	/** Add listener for custom event. Can be used like this:
3413 	 *
3414 	 * ---------------------
3415 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3416 	 *   ...
3417 	 *   win.removeEventListener(eid);
3418 	 * ---------------------
3419 	 *
3420 	 * Returns: 0 on failure (should never happen, so ignore it)
3421 	 *
3422 	 * $(WARNING Don't use this method in object destructors!)
3423 	 *
3424 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3425 	 *           'cause if event handler id counter will overflow, you won't be able
3426 	 *           to register any more events.)
3427 	 */
3428 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3429 		if (dg is null) return 0; // ignore empty handlers
3430 		synchronized(this) {
3431 			//FIXME: abort on overflow?
3432 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3433 			EventHandlerEntry e;
3434 			e.dg = delegate (Object o) {
3435 				if (auto co = cast(ET)o) {
3436 					try {
3437 						dg(co);
3438 					} catch (Exception e) {
3439 						// sorry!
3440 						if(eventUncaughtException)
3441 							eventUncaughtException(e);
3442 					}
3443 					return true;
3444 				}
3445 				return false;
3446 			};
3447 			e.id = lastUsedHandlerId;
3448 			auto optr = eventHandlers.ptr;
3449 			eventHandlers ~= e;
3450 			if (eventHandlers.ptr !is optr) {
3451 				import core.memory : GC;
3452 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3453 			}
3454 			return lastUsedHandlerId;
3455 		}
3456 	}
3457 
3458 	/// Remove event listener. It is safe to pass invalid event id here.
3459 	/// $(WARNING Don't use this method in object destructors!)
3460 	void removeEventListener() (uint id) {
3461 		if (id == 0 || id > lastUsedHandlerId) return;
3462 		synchronized(this) {
3463 			foreach (immutable idx; 0..eventHandlers.length) {
3464 				if (eventHandlers[idx].id == id) {
3465 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3466 					eventHandlers[$-1].dg = null;
3467 					eventHandlers.length -= 1;
3468 					eventHandlers.assumeSafeAppend;
3469 					return;
3470 				}
3471 			}
3472 		}
3473 	}
3474 
3475 	/// Post event to queue. It is safe to call this from non-UI threads.
3476 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3477 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3478 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3479 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3480 		if (this.closed) return false; // closed windows can't handle events
3481 
3482 		// remove all events of type `ET`
3483 		void removeAllET () {
3484 			uint eidx = 0, ec = eventQueueUsed;
3485 			auto eptr = eventQueue.ptr;
3486 			while (eidx < ec) {
3487 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3488 				if (cast(ET)eptr.evt !is null) {
3489 					// i found her!
3490 					if (inCustomEventProcessor) {
3491 						// if we're in custom event processing loop, processor will clear it for us
3492 						eptr.evt = null;
3493 						++eidx;
3494 						++eptr;
3495 					} else {
3496 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3497 						ec = --eventQueueUsed;
3498 						// clear last event (it is already copied)
3499 						eventQueue.ptr[ec].evt = null;
3500 					}
3501 				} else {
3502 					++eidx;
3503 					++eptr;
3504 				}
3505 			}
3506 		}
3507 
3508 		if (evt is null) {
3509 			if (replace) { synchronized(this) removeAllET(); }
3510 			// ignore empty events, they can't be handled anyway
3511 			return false;
3512 		}
3513 
3514 		// add events even if no event FD/event object created yet
3515 		synchronized(this) {
3516 			if (replace) removeAllET();
3517 			if (eventQueueUsed == uint.max) return false; // just in case
3518 			if (eventQueueUsed < eventQueue.length) {
3519 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3520 			} else {
3521 				if (eventQueue.capacity == eventQueue.length) {
3522 					// need to reallocate; do a trick to ensure that old array is cleared
3523 					auto oarr = eventQueue;
3524 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3525 					// just in case, do yet another check
3526 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3527 					import core.memory : GC;
3528 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3529 				} else {
3530 					auto optr = eventQueue.ptr;
3531 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3532 					assert(eventQueue.ptr is optr);
3533 				}
3534 				++eventQueueUsed;
3535 				assert(eventQueueUsed == eventQueue.length);
3536 			}
3537 			if (!eventWakeUp()) {
3538 				// can't wake up event processor, so there is no reason to keep the event
3539 				assert(eventQueueUsed > 0);
3540 				eventQueue[--eventQueueUsed].evt = null;
3541 				return false;
3542 			}
3543 			return true;
3544 		}
3545 	}
3546 
3547 	/// Post event to queue. It is safe to call this from non-UI threads.
3548 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3549 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3550 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3551 		return postTimeout!ET(evt, 0, replace);
3552 	}
3553 
3554 private:
3555 	private import core.time : MonoTime;
3556 
3557 	version(Posix) {
3558 		__gshared int customEventFDRead = -1;
3559 		__gshared int customEventFDWrite = -1;
3560 		__gshared int customSignalFD = -1;
3561 	} else version(Windows) {
3562 		__gshared HANDLE customEventH = null;
3563 	}
3564 
3565 	// wake up event processor
3566 	static bool eventWakeUp () {
3567 		version(X11) {
3568 			import core.sys.posix.unistd : write;
3569 			ulong n = 1;
3570 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3571 			return true;
3572 		} else version(Windows) {
3573 			if (customEventH !is null) SetEvent(customEventH);
3574 			return true;
3575 		} else {
3576 			// not implemented for other OSes
3577 			return false;
3578 		}
3579 	}
3580 
3581 	static struct QueuedEvent {
3582 		Object evt;
3583 		bool timed = false;
3584 		MonoTime hittime = MonoTime.zero;
3585 		bool doProcess = false; // process event at the current iteration (internal flag)
3586 
3587 		this (Object aevt, uint toutmsecs) {
3588 			evt = aevt;
3589 			if (toutmsecs > 0) {
3590 				import core.time : msecs;
3591 				timed = true;
3592 				hittime = MonoTime.currTime+toutmsecs.msecs;
3593 			}
3594 		}
3595 	}
3596 
3597 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3598 	static struct EventHandlerEntry {
3599 		CustomEventHandler dg;
3600 		uint id;
3601 	}
3602 
3603 	uint lastUsedHandlerId;
3604 	EventHandlerEntry[] eventHandlers;
3605 	QueuedEvent[] eventQueue = null;
3606 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3607 	bool inCustomEventProcessor = false; // required to properly remove events
3608 
3609 	// process queued events and call custom event handlers
3610 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3611 	void processCustomEvents () {
3612 		bool hasSomethingToDo = false;
3613 		uint ecount;
3614 		bool ocep;
3615 		synchronized(this) {
3616 			ocep = inCustomEventProcessor;
3617 			inCustomEventProcessor = true;
3618 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3619 			auto ctt = MonoTime.currTime;
3620 			bool hasEmpty = false;
3621 			// mark events to process (this is required for `eventQueued()`)
3622 			foreach (ref qe; eventQueue[0..ecount]) {
3623 				if (qe.evt is null) { hasEmpty = true; continue; }
3624 				if (qe.timed) {
3625 					qe.doProcess = (qe.hittime <= ctt);
3626 				} else {
3627 					qe.doProcess = true;
3628 				}
3629 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3630 			}
3631 			if (!hasSomethingToDo) {
3632 				// remove empty events
3633 				if (hasEmpty) {
3634 					uint eidx = 0, ec = eventQueueUsed;
3635 					auto eptr = eventQueue.ptr;
3636 					while (eidx < ec) {
3637 						if (eptr.evt is null) {
3638 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3639 							ec = --eventQueueUsed;
3640 							eventQueue.ptr[ec].evt = null; // make GC life easier
3641 						} else {
3642 							++eidx;
3643 							++eptr;
3644 						}
3645 					}
3646 				}
3647 				inCustomEventProcessor = ocep;
3648 				return;
3649 			}
3650 		}
3651 		// process marked events
3652 		uint efree = 0; // non-processed events will be put at this index
3653 		EventHandlerEntry[] eh;
3654 		Object evt;
3655 		foreach (immutable eidx; 0..ecount) {
3656 			synchronized(this) {
3657 				if (!eventQueue[eidx].doProcess) {
3658 					// skip this event
3659 					assert(efree <= eidx);
3660 					if (efree != eidx) {
3661 						// copy this event to queue start
3662 						eventQueue[efree] = eventQueue[eidx];
3663 						eventQueue[eidx].evt = null; // just in case
3664 					}
3665 					++efree;
3666 					continue;
3667 				}
3668 				evt = eventQueue[eidx].evt;
3669 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3670 				if (evt is null) continue; // just in case
3671 				// try all handlers; this can be slow, but meh...
3672 				eh = eventHandlers;
3673 			}
3674 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3675 			evt = null;
3676 			eh = null;
3677 		}
3678 		synchronized(this) {
3679 			// move all unprocessed events to queue top; efree holds first "free index"
3680 			foreach (immutable eidx; ecount..eventQueueUsed) {
3681 				assert(efree <= eidx);
3682 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3683 				++efree;
3684 			}
3685 			eventQueueUsed = efree;
3686 			// wake up event processor on next event loop iteration if we have more queued events
3687 			// also, remove empty events
3688 			bool awaken = false;
3689 			uint eidx = 0, ec = eventQueueUsed;
3690 			auto eptr = eventQueue.ptr;
3691 			while (eidx < ec) {
3692 				if (eptr.evt is null) {
3693 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3694 					ec = --eventQueueUsed;
3695 					eventQueue.ptr[ec].evt = null; // make GC life easier
3696 				} else {
3697 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3698 					++eidx;
3699 					++eptr;
3700 				}
3701 			}
3702 			inCustomEventProcessor = ocep;
3703 		}
3704 	}
3705 
3706 	// for all windows in nativeMapping
3707 	package static void processAllCustomEvents () {
3708 
3709 		cleanupQueue.process();
3710 
3711 		justCommunication.processCustomEvents();
3712 
3713 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3714 			if (sw is null || sw.closed) continue;
3715 			sw.processCustomEvents();
3716 		}
3717 
3718 		runPendingRunInGuiThreadDelegates();
3719 	}
3720 
3721 	// 0: infinite (i.e. no scheduled events in queue)
3722 	uint eventQueueTimeoutMSecs () {
3723 		synchronized(this) {
3724 			if (eventQueueUsed == 0) return 0;
3725 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3726 			uint res = int.max;
3727 			auto ctt = MonoTime.currTime;
3728 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3729 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3730 				if (qe.doProcess) continue; // just in case
3731 				if (!qe.timed) return 1; // minimal
3732 				if (qe.hittime <= ctt) return 1; // minimal
3733 				auto tms = (qe.hittime-ctt).total!"msecs";
3734 				if (tms < 1) tms = 1; // safety net
3735 				if (tms >= int.max) tms = int.max-1; // and another safety net
3736 				if (res > tms) res = cast(uint)tms;
3737 			}
3738 			return (res >= int.max ? 0 : res);
3739 		}
3740 	}
3741 
3742 	// for all windows in nativeMapping
3743 	static uint eventAllQueueTimeoutMSecs () {
3744 		uint res = uint.max;
3745 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3746 			if (sw is null || sw.closed) continue;
3747 			uint to = sw.eventQueueTimeoutMSecs();
3748 			if (to && to < res) {
3749 				res = to;
3750 				if (to == 1) break; // can't have less than this
3751 			}
3752 		}
3753 		return (res >= int.max ? 0 : res);
3754 	}
3755 
3756 	version(X11) {
3757 		ResizeEvent pendingResizeEvent;
3758 	}
3759 
3760 	/++
3761 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3762 
3763 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3764 		worth so you can disable it by setting this to `true`.
3765 
3766 		History:
3767 			Added November 13, 2022.
3768 	+/
3769 	public bool suppressAutoOpenglViewport = false;
3770 	private void updateOpenglViewportIfNeeded(int width, int height) {
3771 		if(suppressAutoOpenglViewport) return;
3772 
3773 		version(without_opengl) {} else
3774 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
3775 		import std.stdio; writeln(width, " ", height);
3776 			setAsCurrentOpenGlContextNT();
3777 			glViewport(0, 0, width, height);
3778 		}
3779 	}
3780 }
3781 
3782 /++
3783 	Magic pseudo-window for just posting events to a global queue.
3784 
3785 	Not entirely supported, I might delete it at any time.
3786 
3787 	Added Nov 5, 2021.
3788 +/
3789 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init);
3790 
3791 /* Drag and drop support { */
3792 version(X11) {
3793 
3794 } else version(Windows) {
3795 	import core.sys.windows.uuid;
3796 	import core.sys.windows.ole2;
3797 	import core.sys.windows.oleidl;
3798 	import core.sys.windows.objidl;
3799 	import core.sys.windows.wtypes;
3800 
3801 	pragma(lib, "ole32");
3802 	void initDnd() {
3803 		auto err = OleInitialize(null);
3804 		if(err != S_OK && err != S_FALSE)
3805 			throw new Exception("init");//err);
3806 	}
3807 }
3808 /* } End drag and drop support */
3809 
3810 
3811 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3812 /// See [GenericCursor].
3813 class MouseCursor {
3814 	int osId;
3815 	bool isStockCursor;
3816 	private this(int osId) {
3817 		this.osId = osId;
3818 		this.isStockCursor = true;
3819 	}
3820 
3821 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3822 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3823 
3824 	version(Windows) {
3825 		HCURSOR cursor_;
3826 		HCURSOR cursorHandle() {
3827 			if(cursor_ is null)
3828 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3829 			return cursor_;
3830 		}
3831 
3832 	} else static if(UsingSimpledisplayX11) {
3833 		Cursor cursor_ = None;
3834 		int xDisplaySequence;
3835 
3836 		Cursor cursorHandle() {
3837 			if(this.osId == None)
3838 				return None;
3839 
3840 			// we need to reload if we on a new X connection
3841 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3842 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3843 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3844 			}
3845 			return cursor_;
3846 		}
3847 	}
3848 }
3849 
3850 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3851 // https://tronche.com/gui/x/xlib/appendix/b/
3852 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3853 /// 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.
3854 enum GenericCursorType {
3855 	Default, /// The default arrow pointer.
3856 	Wait, /// A cursor indicating something is loading and the user must wait.
3857 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3858 	Help, /// A cursor indicating the user can get help about the pointer location.
3859 	Cross, /// A crosshair.
3860 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3861 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3862 	UpArrow, /// An arrow pointing straight up.
3863 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3864 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3865 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3866 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3867 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3868 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3869 
3870 }
3871 
3872 /*
3873 	X_plus == css cell == Windows ?
3874 */
3875 
3876 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3877 static struct GenericCursor {
3878 	static:
3879 	///
3880 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3881 		static MouseCursor mc;
3882 
3883 		auto type = __traits(getMember, GenericCursorType, str);
3884 
3885 		if(mc is null) {
3886 
3887 			version(Windows) {
3888 				int osId;
3889 				final switch(type) {
3890 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3891 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3892 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3893 					case GenericCursorType.Help: osId = IDC_HELP; break;
3894 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3895 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3896 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3897 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3898 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3899 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3900 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3901 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3902 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3903 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3904 				}
3905 			} else static if(UsingSimpledisplayX11) {
3906 				int osId;
3907 				final switch(type) {
3908 					case GenericCursorType.Default: osId = None; break;
3909 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3910 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3911 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3912 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3913 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3914 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3915 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3916 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3917 
3918 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3919 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3920 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3921 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3922 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3923 				}
3924 
3925 			} else featureNotImplemented();
3926 
3927 			mc = new MouseCursor(osId);
3928 		}
3929 		return mc;
3930 	}
3931 }
3932 
3933 
3934 /++
3935 	If you want to get more control over the event loop, you can use this.
3936 
3937 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
3938 	to `EventLoop.get.run`.
3939 +/
3940 struct EventLoop {
3941 	@disable this();
3942 
3943 	/// Gets a reference to an existing event loop
3944 	static EventLoop get() {
3945 		return EventLoop(0, null);
3946 	}
3947 
3948 	static void quitApplication() {
3949 		EventLoop.get().exit();
3950 	}
3951 
3952 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
3953 
3954 	/// Construct an application-global event loop for yourself
3955 	/// See_Also: [SimpleWindow.setEventHandlers]
3956 	this(long pulseTimeout, void delegate() handlePulse) {
3957 		synchronized(monitor) {
3958 			if(impl is null) {
3959 				claimGuiThread();
3960 				version(sdpy_thread_checks) assert(thisIsGuiThread);
3961 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
3962 			} else {
3963 				if(pulseTimeout) {
3964 					impl.pulseTimeout = pulseTimeout;
3965 					impl.handlePulse = handlePulse;
3966 				}
3967 			}
3968 			impl.refcount++;
3969 		}
3970 	}
3971 
3972 	~this() {
3973 		if(impl is null)
3974 			return;
3975 		impl.refcount--;
3976 		if(impl.refcount == 0) {
3977 			impl.dispose();
3978 			if(thisIsGuiThread)
3979 				guiThreadFinalize();
3980 		}
3981 
3982 	}
3983 
3984 	this(this) {
3985 		if(impl is null)
3986 			return;
3987 		impl.refcount++;
3988 	}
3989 
3990 	/// Runs the event loop until the whileCondition, if present, returns false
3991 	int run(bool delegate() whileCondition = null) {
3992 		assert(impl !is null);
3993 		impl.notExited = true;
3994 		return impl.run(whileCondition);
3995 	}
3996 
3997 	/// Exits the event loop
3998 	void exit() {
3999 		assert(impl !is null);
4000 		impl.notExited = false;
4001 	}
4002 
4003 	version(linux)
4004 	ref void delegate(int) signalHandler() {
4005 		assert(impl !is null);
4006 		return impl.signalHandler;
4007 	}
4008 
4009 	__gshared static EventLoopImpl* impl;
4010 }
4011 
4012 version(linux)
4013 	void delegate(int, int) globalHupHandler;
4014 
4015 version(Posix)
4016 	void makeNonBlocking(int fd) {
4017 		import fcntl = core.sys.posix.fcntl;
4018 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4019 		if(flags == -1)
4020 			throw new Exception("fcntl get");
4021 		flags |= fcntl.O_NONBLOCK;
4022 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4023 		if(s == -1)
4024 			throw new Exception("fcntl set");
4025 	}
4026 
4027 struct EventLoopImpl {
4028 	int refcount;
4029 
4030 	bool notExited = true;
4031 
4032 	version(linux) {
4033 		static import ep = core.sys.linux.epoll;
4034 		static import unix = core.sys.posix.unistd;
4035 		static import err = core.stdc.errno;
4036 		import core.sys.linux.timerfd;
4037 
4038 		void delegate(int) signalHandler;
4039 	}
4040 
4041 	version(X11) {
4042 		int pulseFd = -1;
4043 		version(linux) ep.epoll_event[16] events = void;
4044 	} else version(Windows) {
4045 		Timer pulser;
4046 		HANDLE[] handles;
4047 	}
4048 
4049 
4050 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4051 	/// to call this, as it's not recommended to share window between threads.
4052 	void mtLock () {
4053 		version(X11) {
4054 			XLockDisplay(this.display);
4055 		}
4056 	}
4057 
4058 	version(X11)
4059 	auto display() { return XDisplayConnection.get; }
4060 
4061 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4062 	/// to call this, as it's not recommended to share window between threads.
4063 	void mtUnlock () {
4064 		version(X11) {
4065 			XUnlockDisplay(this.display);
4066 		}
4067 	}
4068 
4069 	version(with_eventloop)
4070 	void initialize(long pulseTimeout) {}
4071 	else
4072 	void initialize(long pulseTimeout) {
4073 		version(Windows) {
4074 			if(pulseTimeout && handlePulse !is null)
4075 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4076 
4077 			if (customEventH is null) {
4078 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4079 				if (customEventH !is null) {
4080 					handles ~= customEventH;
4081 				} else {
4082 					// this is something that should not be; better be safe than sorry
4083 					throw new Exception("can't create eventfd for custom event processing");
4084 				}
4085 			}
4086 
4087 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4088 		}
4089 
4090 		version(linux) {
4091 			prepareEventLoop();
4092 			{
4093 				auto display = XDisplayConnection.get;
4094 				// adding Xlib file
4095 				ep.epoll_event ev = void;
4096 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4097 				ev.events = ep.EPOLLIN;
4098 				ev.data.fd = display.fd;
4099 				//import std.conv;
4100 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4101 					throw new Exception("add x fd");// ~ to!string(epollFd));
4102 				displayFd = display.fd;
4103 			}
4104 
4105 			if(pulseTimeout && handlePulse !is null) {
4106 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4107 				if(pulseFd == -1)
4108 					throw new Exception("pulse timer create failed");
4109 
4110 				itimerspec value;
4111 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4112 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4113 
4114 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4115 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4116 
4117 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4118 					throw new Exception("couldn't make pulse timer");
4119 
4120 				ep.epoll_event ev = void;
4121 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4122 				ev.events = ep.EPOLLIN;
4123 				ev.data.fd = pulseFd;
4124 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4125 			}
4126 
4127 			// eventfd for custom events
4128 			if (customEventFDWrite == -1) {
4129 				customEventFDWrite = eventfd(0, 0);
4130 				customEventFDRead = customEventFDWrite;
4131 				if (customEventFDRead >= 0) {
4132 					ep.epoll_event ev = void;
4133 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4134 					ev.events = ep.EPOLLIN;
4135 					ev.data.fd = customEventFDRead;
4136 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4137 				} else {
4138 					// this is something that should not be; better be safe than sorry
4139 					throw new Exception("can't create eventfd for custom event processing");
4140 				}
4141 			}
4142 
4143 			if (customSignalFD == -1) {
4144 				import core.sys.linux.sys.signalfd;
4145 
4146 				sigset_t sigset;
4147 				auto err = sigemptyset(&sigset);
4148 				assert(!err);
4149 				err = sigaddset(&sigset, SIGINT);
4150 				assert(!err);
4151 				err = sigaddset(&sigset, SIGHUP);
4152 				assert(!err);
4153 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4154 				assert(!err);
4155 
4156 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4157 				assert(customSignalFD != -1);
4158 
4159 				ep.epoll_event ev = void;
4160 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4161 				ev.events = ep.EPOLLIN;
4162 				ev.data.fd = customSignalFD;
4163 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4164 			}
4165 		} else version(Posix) {
4166 			prepareEventLoop();
4167 			if (customEventFDRead == -1) {
4168 				int[2] bfr;
4169 				import core.sys.posix.unistd;
4170 				auto ret = pipe(bfr);
4171 				if(ret == -1) throw new Exception("pipe");
4172 				customEventFDRead = bfr[0];
4173 				customEventFDWrite = bfr[1];
4174 			}
4175 
4176 		}
4177 
4178 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4179 
4180 		version(linux) {
4181 			this.mtLock();
4182 			scope(exit) this.mtUnlock();
4183 			XPending(display); // no, really
4184 		}
4185 
4186 		disposed = false;
4187 	}
4188 
4189 	bool disposed = true;
4190 	version(X11)
4191 		int displayFd = -1;
4192 
4193 	version(with_eventloop)
4194 	void dispose() {}
4195 	else
4196 	void dispose() {
4197 		disposed = true;
4198 		version(X11) {
4199 			if(pulseFd != -1) {
4200 				import unix = core.sys.posix.unistd;
4201 				unix.close(pulseFd);
4202 				pulseFd = -1;
4203 			}
4204 
4205 				version(linux)
4206 				if(displayFd != -1) {
4207 					// 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
4208 					ep.epoll_event ev = void;
4209 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4210 					ev.events = ep.EPOLLIN;
4211 					ev.data.fd = displayFd;
4212 					//import std.conv;
4213 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4214 					displayFd = -1;
4215 				}
4216 
4217 		} else version(Windows) {
4218 			if(pulser !is null) {
4219 				pulser.destroy();
4220 				pulser = null;
4221 			}
4222 			if (customEventH !is null) {
4223 				CloseHandle(customEventH);
4224 				customEventH = null;
4225 			}
4226 		}
4227 	}
4228 
4229 	this(long pulseTimeout, void delegate() handlePulse) {
4230 		this.pulseTimeout = pulseTimeout;
4231 		this.handlePulse = handlePulse;
4232 		initialize(pulseTimeout);
4233 	}
4234 
4235 	private long pulseTimeout;
4236 	void delegate() handlePulse;
4237 
4238 	~this() {
4239 		dispose();
4240 	}
4241 
4242 	version(Posix)
4243 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4244 	version(Posix)
4245 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4246 	version(linux)
4247 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4248 	version(Windows)
4249 	ref auto customEventH() { return SimpleWindow.customEventH; }
4250 
4251 	version(with_eventloop) {
4252 		int loopHelper(bool delegate() whileCondition) {
4253 			// FIXME: whileCondition
4254 			import arsd.eventloop;
4255 			loop();
4256 			return 0;
4257 		}
4258 	} else
4259 	int loopHelper(bool delegate() whileCondition) {
4260 		version(X11) {
4261 			bool done = false;
4262 
4263 			XFlush(display);
4264 			insideXEventLoop = true;
4265 			scope(exit) insideXEventLoop = false;
4266 
4267 			version(linux) {
4268 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4269 					bool forceXPending = false;
4270 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4271 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4272 					{
4273 						this.mtLock();
4274 						scope(exit) this.mtUnlock();
4275 						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
4276 					}
4277 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4278 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4279 					if(nfds == -1) {
4280 						if(err.errno == err.EINTR) {
4281 							//if(forceXPending) goto xpending;
4282 							continue; // interrupted by signal, just try again
4283 						}
4284 						throw new Exception("epoll wait failure");
4285 					}
4286 
4287 					SimpleWindow.processAllCustomEvents(); // anyway
4288 					//version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4289 					foreach(idx; 0 .. nfds) {
4290 						if(done) break;
4291 						auto fd = events[idx].data.fd;
4292 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4293 						auto flags = events[idx].events;
4294 						if(flags & ep.EPOLLIN) {
4295 							if (fd == customSignalFD) {
4296 								version(linux) {
4297 									import core.sys.linux.sys.signalfd;
4298 									import core.sys.posix.unistd : read;
4299 									signalfd_siginfo info;
4300 									read(customSignalFD, &info, info.sizeof);
4301 
4302 									auto sig = info.ssi_signo;
4303 
4304 									if(EventLoop.get.signalHandler !is null) {
4305 										EventLoop.get.signalHandler()(sig);
4306 									} else {
4307 										EventLoop.get.exit();
4308 									}
4309 								}
4310 							} else if(fd == display.fd) {
4311 								version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
4312 								this.mtLock();
4313 								scope(exit) this.mtUnlock();
4314 								while(!done && XPending(display)) {
4315 									done = doXNextEvent(this.display);
4316 								}
4317 								forceXPending = false;
4318 							} else if(fd == pulseFd) {
4319 								long expirationCount;
4320 								// 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...
4321 
4322 								handlePulse();
4323 
4324 								// read just to clear the buffer so poll doesn't trigger again
4325 								// BTW I read AFTER the pulse because if the pulse handler takes
4326 								// a lot of time to execute, we don't want the app to get stuck
4327 								// in a loop of timer hits without a chance to do anything else
4328 								//
4329 								// IOW handlePulse happens at most once per pulse interval.
4330 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4331 								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
4332 							} else if (fd == customEventFDRead) {
4333 								// we have some custom events; process 'em
4334 								import core.sys.posix.unistd : read;
4335 								ulong n;
4336 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4337 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4338 								//SimpleWindow.processAllCustomEvents();
4339 							} else {
4340 								// some other timer
4341 								version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); }
4342 
4343 								if(Timer* t = fd in Timer.mapping)
4344 									(*t).trigger();
4345 
4346 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4347 									(*pfr).ready(flags);
4348 
4349 								// or i might add support for other FDs too
4350 								// but for now it is just timer
4351 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4352 							}
4353 						}
4354 						if(flags & ep.EPOLLHUP) {
4355 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4356 								(*pfr).hup(flags);
4357 							if(globalHupHandler)
4358 								globalHupHandler(fd, flags);
4359 						}
4360 						/+
4361 						} else {
4362 							// not interested in OUT, we are just reading here.
4363 							//
4364 							// error or hup might also be reported
4365 							// but it shouldn't here since we are only
4366 							// using a few types of FD and Xlib will report
4367 							// if it dies.
4368 							// so instead of thoughtfully handling it, I'll
4369 							// just throw. for now at least
4370 
4371 							throw new Exception("epoll did something else");
4372 						}
4373 						+/
4374 					}
4375 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4376 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4377 					xpending:
4378 					if (!done && forceXPending) {
4379 						this.mtLock();
4380 						scope(exit) this.mtUnlock();
4381 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4382 						while(!done && XPending(display)) {
4383 							done = doXNextEvent(this.display);
4384 						}
4385 					}
4386 				}
4387 			} else {
4388 				// Generic fallback: yes to simple pulse support,
4389 				// but NO timer support!
4390 
4391 				// FIXME: we could probably support the POSIX timer_create
4392 				// signal-based option, but I'm in no rush to write it since
4393 				// I prefer the fd-based functions.
4394 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4395 
4396 					import core.sys.posix.poll;
4397 
4398 					pollfd[] pfds;
4399 					pollfd[32] pfdsBuffer;
4400 					auto len = PosixFdReader.mapping.length + 2;
4401 					// FIXME: i should just reuse the buffer
4402 					if(len < pfdsBuffer.length)
4403 						pfds = pfdsBuffer[0 .. len];
4404 					else
4405 						pfds = new pollfd[](len);
4406 
4407 					pfds[0].fd = display.fd;
4408 					pfds[0].events = POLLIN;
4409 					pfds[0].revents = 0;
4410 
4411 					int slot = 1;
4412 
4413 					if(customEventFDRead != -1) {
4414 						pfds[slot].fd = customEventFDRead;
4415 						pfds[slot].events = POLLIN;
4416 						pfds[slot].revents = 0;
4417 
4418 						slot++;
4419 					}
4420 
4421 					foreach(fd, obj; PosixFdReader.mapping) {
4422 						if(!obj.enabled) continue;
4423 						pfds[slot].fd = fd;
4424 						pfds[slot].events = POLLIN;
4425 						pfds[slot].revents = 0;
4426 
4427 						slot++;
4428 					}
4429 
4430 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4431 					if(ret == -1) throw new Exception("poll");
4432 
4433 					if(ret == 0) {
4434 						// FIXME it may not necessarily time out if events keep coming
4435 						if(handlePulse !is null)
4436 							handlePulse();
4437 					} else {
4438 						foreach(s; 0 .. slot) {
4439 							if(pfds[s].revents == 0) continue;
4440 
4441 							if(pfds[s].fd == display.fd) {
4442 								while(!done && XPending(display)) {
4443 									this.mtLock();
4444 									scope(exit) this.mtUnlock();
4445 									done = doXNextEvent(this.display);
4446 								}
4447 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4448 
4449 								import core.sys.posix.unistd : read;
4450 								ulong n;
4451 								read(customEventFDRead, &n, n.sizeof);
4452 								SimpleWindow.processAllCustomEvents();
4453 							} else {
4454 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4455 								if(pfds[s].revents & POLLNVAL) {
4456 									obj.dispose();
4457 								} else {
4458 									obj.ready(pfds[s].revents);
4459 								}
4460 							}
4461 
4462 							ret--;
4463 							if(ret == 0) break;
4464 						}
4465 					}
4466 				}
4467 			}
4468 		}
4469 
4470 		version(Windows) {
4471 			int ret = -1;
4472 			MSG message;
4473 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4474 				eventLoopRound++;
4475 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4476 				auto waitResult = MsgWaitForMultipleObjectsEx(
4477 					cast(int) handles.length, handles.ptr,
4478 					(wto == 0 ? INFINITE : wto), /* timeout */
4479 					0x04FF, /* QS_ALLINPUT */
4480 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4481 
4482 				SimpleWindow.processAllCustomEvents(); // anyway
4483 				enum WAIT_OBJECT_0 = 0;
4484 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4485 					auto h = handles[waitResult - WAIT_OBJECT_0];
4486 					if(auto e = h in WindowsHandleReader.mapping) {
4487 						(*e).ready();
4488 					}
4489 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4490 					// message ready
4491 					int count;
4492 					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
4493 						ret = GetMessage(&message, null, 0, 0);
4494 						if(ret == -1)
4495 							throw new WindowsApiException("GetMessage", GetLastError());
4496 						TranslateMessage(&message);
4497 						DispatchMessage(&message);
4498 
4499 						count++;
4500 						if(count > 10)
4501 							break; // take the opportunity to catch up on other events
4502 
4503 						if(ret == 0) { // WM_QUIT
4504 							EventLoop.quitApplication();
4505 							break;
4506 						}
4507 					}
4508 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4509 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4510 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4511 					// timeout, should never happen since we aren't using it
4512 				} else if(waitResult == 0xFFFFFFFF) {
4513 						// failed
4514 						throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4515 				} else {
4516 					// idk....
4517 				}
4518 			}
4519 
4520 			// return message.wParam;
4521 			return 0;
4522 		} else {
4523 			return 0;
4524 		}
4525 	}
4526 
4527 	int run(bool delegate() whileCondition = null) {
4528 		if(disposed)
4529 			initialize(this.pulseTimeout);
4530 
4531 		version(X11) {
4532 			try {
4533 				return loopHelper(whileCondition);
4534 			} catch(XDisconnectException e) {
4535 				if(e.userRequested) {
4536 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4537 						item.discardConnectionState();
4538 					XCloseDisplay(XDisplayConnection.display);
4539 				}
4540 
4541 				XDisplayConnection.display = null;
4542 
4543 				this.dispose();
4544 
4545 				throw e;
4546 			}
4547 		} else {
4548 			return loopHelper(whileCondition);
4549 		}
4550 	}
4551 }
4552 
4553 
4554 /++
4555 	Provides an icon on the system notification area (also known as the system tray).
4556 
4557 
4558 	If a notification area is not available with the NotificationIcon object is created,
4559 	it will silently succeed and simply attempt to create one when an area becomes available.
4560 
4561 
4562 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
4563 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
4564 	use the older version.
4565 +/
4566 version(OSXCocoa) {} else // NotYetImplementedException
4567 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4568 
4569 	version(X11) {
4570 		void recreateAfterDisconnect() {
4571 			stateDiscarded = false;
4572 			clippixmap = None;
4573 			throw new Exception("NOT IMPLEMENTED");
4574 		}
4575 
4576 		bool stateDiscarded;
4577 		void discardConnectionState() {
4578 			stateDiscarded = true;
4579 		}
4580 	}
4581 
4582 
4583 	version(X11) {
4584 		Image img;
4585 
4586 		NativeEventHandler getNativeEventHandler() {
4587 			return delegate int(XEvent e) {
4588 				switch(e.type) {
4589 					case EventType.Expose:
4590 					//case EventType.VisibilityNotify:
4591 						redraw();
4592 					break;
4593 					case EventType.ClientMessage:
4594 						version(sddddd) {
4595 						import std.stdio;
4596 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4597 						writeln("\t", e.xclient.format);
4598 						writeln("\t", e.xclient.data.l);
4599 						}
4600 					break;
4601 					case EventType.ButtonPress:
4602 						auto event = e.xbutton;
4603 						if (onClick !is null || onClickEx !is null) {
4604 							MouseButton mb = cast(MouseButton)0;
4605 							switch (event.button) {
4606 								case 1: mb = MouseButton.left; break; // left
4607 								case 2: mb = MouseButton.middle; break; // middle
4608 								case 3: mb = MouseButton.right; break; // right
4609 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4610 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4611 								case 6: break; // scroll left...
4612 								case 7: break; // scroll right...
4613 								case 8: mb = MouseButton.backButton; break;
4614 								case 9: mb = MouseButton.forwardButton; break;
4615 								default:
4616 							}
4617 							if (mb) {
4618 								try { onClick()(mb); } catch (Exception) {}
4619 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4620 							}
4621 						}
4622 					break;
4623 					case EventType.EnterNotify:
4624 						if (onEnter !is null) {
4625 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4626 						}
4627 						break;
4628 					case EventType.LeaveNotify:
4629 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4630 						break;
4631 					case EventType.DestroyNotify:
4632 						active = false;
4633 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4634 					break;
4635 					case EventType.ConfigureNotify:
4636 						auto event = e.xconfigure;
4637 						this.width = event.width;
4638 						this.height = event.height;
4639 						//import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4640 						redraw();
4641 					break;
4642 					default: return 1;
4643 				}
4644 				return 1;
4645 			};
4646 		}
4647 
4648 		/* private */ void hideBalloon() {
4649 			balloon.close();
4650 			version(with_timer)
4651 				timer.destroy();
4652 			balloon = null;
4653 			version(with_timer)
4654 				timer = null;
4655 		}
4656 
4657 		void redraw() {
4658 			if (!active) return;
4659 
4660 			auto display = XDisplayConnection.get;
4661 			auto gc = DefaultGC(display, DefaultScreen(display));
4662 			XClearWindow(display, nativeHandle);
4663 
4664 			XSetClipMask(display, gc, clippixmap);
4665 
4666 			XSetForeground(display, gc,
4667 				cast(uint) 0 << 16 |
4668 				cast(uint) 0 << 8 |
4669 				cast(uint) 0);
4670 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4671 
4672 			if (img is null) {
4673 				XSetForeground(display, gc,
4674 					cast(uint) 0 << 16 |
4675 					cast(uint) 127 << 8 |
4676 					cast(uint) 0);
4677 				XFillArc(display, nativeHandle,
4678 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4679 			} else {
4680 				int dx = 0;
4681 				int dy = 0;
4682 				if(width > img.width)
4683 					dx = (width - img.width) / 2;
4684 				if(height > img.height)
4685 					dy = (height - img.height) / 2;
4686 				XSetClipOrigin(display, gc, dx, dy);
4687 
4688 				if (img.usingXshm)
4689 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
4690 				else
4691 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
4692 			}
4693 			XSetClipMask(display, gc, None);
4694 			flushGui();
4695 		}
4696 
4697 		static Window getTrayOwner() {
4698 			auto display = XDisplayConnection.get;
4699 			auto i = cast(int) DefaultScreen(display);
4700 			if(i < 10 && i >= 0) {
4701 				static Atom atom;
4702 				if(atom == None)
4703 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4704 				return XGetSelectionOwner(display, atom);
4705 			}
4706 			return None;
4707 		}
4708 
4709 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4710 			auto to = getTrayOwner();
4711 			auto display = XDisplayConnection.get;
4712 			XEvent ev;
4713 			ev.xclient.type = EventType.ClientMessage;
4714 			ev.xclient.window = to;
4715 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4716 			ev.xclient.format = 32;
4717 			ev.xclient.data.l[0] = CurrentTime;
4718 			ev.xclient.data.l[1] = message;
4719 			ev.xclient.data.l[2] = d1;
4720 			ev.xclient.data.l[3] = d2;
4721 			ev.xclient.data.l[4] = d3;
4722 
4723 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4724 		}
4725 
4726 		private static NotificationAreaIcon[] activeIcons;
4727 
4728 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4729 		private void newManager() {
4730 			close();
4731 			createXWin();
4732 
4733 			if(this.clippixmap)
4734 				XFreePixmap(XDisplayConnection.get, clippixmap);
4735 			if(this.originalMemoryImage)
4736 				this.icon = this.originalMemoryImage;
4737 			else if(this.img)
4738 				this.icon = this.img;
4739 		}
4740 
4741 		private void createXWin () {
4742 			// create window
4743 			auto display = XDisplayConnection.get;
4744 
4745 			// to check for MANAGER on root window to catch new/changed tray owners
4746 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4747 			// so if a thing does appear, we can handle it
4748 			foreach(ai; activeIcons)
4749 				if(ai is this)
4750 					goto alreadythere;
4751 			activeIcons ~= this;
4752 			alreadythere:
4753 
4754 			// and check for an existing tray
4755 			auto trayOwner = getTrayOwner();
4756 			if(trayOwner == None)
4757 				return;
4758 				//throw new Exception("No notification area found");
4759 
4760 			Visual* v = cast(Visual*) CopyFromParent;
4761 			/+
4762 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4763 			if(visualProp !is null) {
4764 				c_ulong[] info = cast(c_ulong[]) visualProp;
4765 				if(info.length == 1) {
4766 					auto vid = info[0];
4767 					int returned;
4768 					XVisualInfo t;
4769 					t.visualid = vid;
4770 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4771 					if(got !is null) {
4772 						if(returned == 1) {
4773 							v = got.visual;
4774 							import std.stdio;
4775 							writeln("using special visual ", *got);
4776 						}
4777 						XFree(got);
4778 					}
4779 				}
4780 			}
4781 			+/
4782 
4783 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
4784 			assert(nativeWindow);
4785 
4786 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4787 
4788 			nativeHandle = nativeWindow;
4789 
4790 			///+
4791 			arch_ulong[2] info;
4792 			info[0] = 0;
4793 			info[1] = 1;
4794 
4795 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4796 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4797 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4798 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4799 
4800 			XChangeProperty(
4801 				display,
4802 				nativeWindow,
4803 				GetAtom!("_XEMBED_INFO", true)(display),
4804 				GetAtom!("_XEMBED_INFO", true)(display),
4805 				32 /* bits */,
4806 				0 /*PropModeReplace*/,
4807 				info.ptr,
4808 				2);
4809 
4810 			import core.sys.posix.unistd;
4811 			arch_ulong pid = getpid();
4812 
4813 			XChangeProperty(
4814 				display,
4815 				nativeWindow,
4816 				GetAtom!("_NET_WM_PID", true)(display),
4817 				XA_CARDINAL,
4818 				32 /* bits */,
4819 				0 /*PropModeReplace*/,
4820 				&pid,
4821 				1);
4822 
4823 			updateNetWmIcon();
4824 
4825 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4826 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4827 				XClassHint klass;
4828 				XWMHints wh;
4829 				XSizeHints size;
4830 				klass.res_name = sdpyWindowClassStr;
4831 				klass.res_class = sdpyWindowClassStr;
4832 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4833 			}
4834 
4835 				// believe it or not, THIS is what xfce needed for the 9999 issue
4836 				XSizeHints sh;
4837 					c_long spr;
4838 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4839 					sh.flags |= PMaxSize | PMinSize;
4840 				// FIXME maybe nicer resizing
4841 				sh.min_width = 16;
4842 				sh.min_height = 16;
4843 				sh.max_width = 16;
4844 				sh.max_height = 16;
4845 				XSetWMNormalHints(display, nativeWindow, &sh);
4846 
4847 
4848 			//+/
4849 
4850 
4851 			XSelectInput(display, nativeWindow,
4852 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4853 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4854 
4855 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4856 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
4857 			active = true;
4858 		}
4859 
4860 		void updateNetWmIcon() {
4861 			if(img is null) return;
4862 			auto display = XDisplayConnection.get;
4863 			// FIXME: ensure this is correct
4864 			arch_ulong[] buffer;
4865 			auto imgMi = img.toTrueColorImage;
4866 			buffer ~= imgMi.width;
4867 			buffer ~= imgMi.height;
4868 			foreach(c; imgMi.imageData.colors) {
4869 				arch_ulong b;
4870 				b |= c.a << 24;
4871 				b |= c.r << 16;
4872 				b |= c.g << 8;
4873 				b |= c.b;
4874 				buffer ~= b;
4875 			}
4876 
4877 			XChangeProperty(
4878 				display,
4879 				nativeHandle,
4880 				GetAtom!"_NET_WM_ICON"(display),
4881 				GetAtom!"CARDINAL"(display),
4882 				32 /* bits */,
4883 				0 /*PropModeReplace*/,
4884 				buffer.ptr,
4885 				cast(int) buffer.length);
4886 		}
4887 
4888 
4889 
4890 		private SimpleWindow balloon;
4891 		version(with_timer)
4892 		private Timer timer;
4893 
4894 		private Window nativeHandle;
4895 		private Pixmap clippixmap = None;
4896 		private int width = 16;
4897 		private int height = 16;
4898 		private bool active = false;
4899 
4900 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
4901 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
4902 		void delegate () onLeave; /// X11 only.
4903 
4904 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
4905 
4906 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
4907 		void getWindowRect (out int x, out int y, out int width, out int height) {
4908 			if (!active) { width = 1; height = 1; return; } // 1: just in case
4909 			Window dummyw;
4910 			auto dpy = XDisplayConnection.get;
4911 			//XWindowAttributes xwa;
4912 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
4913 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
4914 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
4915 			width = this.width;
4916 			height = this.height;
4917 		}
4918 	}
4919 
4920 	/+
4921 		What I actually want from this:
4922 
4923 		* set / change: icon, tooltip
4924 		* handle: mouse click, right click
4925 		* show: notification bubble.
4926 	+/
4927 
4928 	version(Windows) {
4929 		WindowsIcon win32Icon;
4930 		HWND hwnd;
4931 
4932 		NOTIFYICONDATAW data;
4933 
4934 		NativeEventHandler getNativeEventHandler() {
4935 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
4936 				if(msg == WM_USER) {
4937 					auto event = LOWORD(lParam);
4938 					auto iconId = HIWORD(lParam);
4939 					//auto x = GET_X_LPARAM(wParam);
4940 					//auto y = GET_Y_LPARAM(wParam);
4941 					switch(event) {
4942 						case WM_LBUTTONDOWN:
4943 							onClick()(MouseButton.left);
4944 						break;
4945 						case WM_RBUTTONDOWN:
4946 							onClick()(MouseButton.right);
4947 						break;
4948 						case WM_MBUTTONDOWN:
4949 							onClick()(MouseButton.middle);
4950 						break;
4951 						case WM_MOUSEMOVE:
4952 							// sent, we could use it.
4953 						break;
4954 						case WM_MOUSEWHEEL:
4955 							// NOT SENT
4956 						break;
4957 						//case NIN_KEYSELECT:
4958 						//case NIN_SELECT:
4959 						//break;
4960 						default: {}
4961 					}
4962 				}
4963 				return 0;
4964 			};
4965 		}
4966 
4967 		enum NIF_SHOWTIP = 0x00000080;
4968 
4969 		private static struct NOTIFYICONDATAW {
4970 			DWORD cbSize;
4971 			HWND  hWnd;
4972 			UINT  uID;
4973 			UINT  uFlags;
4974 			UINT  uCallbackMessage;
4975 			HICON hIcon;
4976 			WCHAR[128] szTip;
4977 			DWORD dwState;
4978 			DWORD dwStateMask;
4979 			WCHAR[256] szInfo;
4980 			union {
4981 				UINT uTimeout;
4982 				UINT uVersion;
4983 			}
4984 			WCHAR[64] szInfoTitle;
4985 			DWORD dwInfoFlags;
4986 			GUID  guidItem;
4987 			HICON hBalloonIcon;
4988 		}
4989 
4990 	}
4991 
4992 	/++
4993 		Note that on Windows, only left, right, and middle buttons are sent.
4994 		Mouse wheel buttons are NOT set, so don't rely on those events if your
4995 		program is meant to be used on Windows too.
4996 	+/
4997 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
4998 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
4999 		// but on X, we need an Image, so its canonical ctor is there. They should
5000 		// forward to each other though.
5001 		version(X11) {
5002 			this.name = name;
5003 			this.onClick = onClick;
5004 			createXWin();
5005 			this.icon = icon;
5006 		} else version(Windows) {
5007 			this.onClick = onClick;
5008 			this.win32Icon = new WindowsIcon(icon);
5009 
5010 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5011 
5012 			static bool registered = false;
5013 			if(!registered) {
5014 				WNDCLASSEX wc;
5015 				wc.cbSize = wc.sizeof;
5016 				wc.hInstance = hInstance;
5017 				wc.lpfnWndProc = &WndProc;
5018 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5019 				if(!RegisterClassExW(&wc))
5020 					throw new WindowsApiException("RegisterClass", GetLastError());
5021 				registered = true;
5022 			}
5023 
5024 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5025 			if(hwnd is null)
5026 				throw new WindowsApiException("CreateWindow", GetLastError());
5027 
5028 			data.cbSize = data.sizeof;
5029 			data.hWnd = hwnd;
5030 			data.uID = cast(uint) cast(void*) this;
5031 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5032 				// NIF_INFO means show balloon
5033 			data.uCallbackMessage = WM_USER;
5034 			data.hIcon = this.win32Icon.hIcon;
5035 			data.szTip = ""; // FIXME
5036 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5037 			data.dwStateMask = NIS_HIDDEN; // windows vista
5038 
5039 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5040 
5041 
5042 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5043 
5044 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5045 		} else version(OSXCocoa) {
5046 			throw new NotYetImplementedException();
5047 		} else static assert(0);
5048 	}
5049 
5050 	/// ditto
5051 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5052 		version(X11) {
5053 			this.onClick = onClick;
5054 			this.name = name;
5055 			createXWin();
5056 			this.icon = icon;
5057 		} else version(Windows) {
5058 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5059 		} else version(OSXCocoa) {
5060 			throw new NotYetImplementedException();
5061 		} else static assert(0);
5062 	}
5063 
5064 	version(X11) {
5065 		/++
5066 			X-specific extension (for now at least)
5067 		+/
5068 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5069 			this.onClickEx = onClickEx;
5070 			createXWin();
5071 			if (icon !is null) this.icon = icon;
5072 		}
5073 
5074 		/// ditto
5075 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5076 			this.onClickEx = onClickEx;
5077 			createXWin();
5078 			this.icon = icon;
5079 		}
5080 	}
5081 
5082 	private void delegate (MouseButton button) onClick_;
5083 
5084 	///
5085 	@property final void delegate(MouseButton) onClick() {
5086 		if(onClick_ is null)
5087 			onClick_ = delegate void(MouseButton) {};
5088 		return onClick_;
5089 	}
5090 
5091 	/// ditto
5092 	@property final void onClick(void delegate(MouseButton) handler) {
5093 		// I made this a property setter so we can wrap smaller arg
5094 		// delegates and just forward all to onClickEx or something.
5095 		onClick_ = handler;
5096 	}
5097 
5098 
5099 	string name_;
5100 	@property void name(string n) {
5101 		name_ = n;
5102 	}
5103 
5104 	@property string name() {
5105 		return name_;
5106 	}
5107 
5108 	private MemoryImage originalMemoryImage;
5109 
5110 	///
5111 	@property void icon(MemoryImage i) {
5112 		version(X11) {
5113 			this.originalMemoryImage = i;
5114 			if (!active) return;
5115 			if (i !is null) {
5116 				this.img = Image.fromMemoryImage(i);
5117 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5118 				//import std.stdio; writeln("using pixmap ", clippixmap);
5119 				updateNetWmIcon();
5120 				redraw();
5121 			} else {
5122 				if (this.img !is null) {
5123 					this.img = null;
5124 					redraw();
5125 				}
5126 			}
5127 		} else version(Windows) {
5128 			this.win32Icon = new WindowsIcon(i);
5129 
5130 			data.uFlags = NIF_ICON;
5131 			data.hIcon = this.win32Icon.hIcon;
5132 
5133 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5134 		} else version(OSXCocoa) {
5135 			throw new NotYetImplementedException();
5136 		} else static assert(0);
5137 	}
5138 
5139 	/// ditto
5140 	@property void icon (Image i) {
5141 		version(X11) {
5142 			if (!active) return;
5143 			if (i !is img) {
5144 				originalMemoryImage = null;
5145 				img = i;
5146 				redraw();
5147 			}
5148 		} else version(Windows) {
5149 			this.icon(i is null ? null : i.toTrueColorImage());
5150 		} else version(OSXCocoa) {
5151 			throw new NotYetImplementedException();
5152 		} else static assert(0);
5153 	}
5154 
5155 	/++
5156 		Shows a balloon notification. You can only show one balloon at a time, if you call
5157 		it twice while one is already up, the first balloon will be replaced.
5158 
5159 
5160 		The user is free to block notifications and they will automatically disappear after
5161 		a timeout period.
5162 
5163 		Params:
5164 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5165 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5166 			icon = the icon to display with the notification. If null, it uses your existing icon.
5167 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5168 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5169 	+/
5170 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5171 		bool useCustom = true;
5172 		version(libnotify) {
5173 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5174 			try {
5175 				if(!active) return;
5176 
5177 				if(libnotify is null) {
5178 					libnotify = new C_DynamicLibrary("libnotify.so");
5179 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5180 				}
5181 
5182 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5183 
5184 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5185 
5186 				if(onclick) {
5187 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5188 					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);
5189 					libnotify_action_delegates_count++;
5190 				}
5191 
5192 				// FIXME icon
5193 
5194 				// set hint image-data
5195 				// set default action for onclick
5196 
5197 				void* error;
5198 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5199 
5200 				useCustom = false;
5201 			} catch(Exception e) {
5202 
5203 			}
5204 		}
5205 
5206 		version(X11) {
5207 		if(useCustom) {
5208 			if(!active) return;
5209 			if(balloon) {
5210 				hideBalloon();
5211 			}
5212 			// I know there are two specs for this, but one is never
5213 			// implemented by any window manager I have ever seen, and
5214 			// the other is a bloated mess and too complicated for simpledisplay...
5215 			// so doing my own little window instead.
5216 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5217 
5218 			int x, y, width, height;
5219 			getWindowRect(x, y, width, height);
5220 
5221 			int bx = x - balloon.width;
5222 			int by = y - balloon.height;
5223 			if(bx < 0)
5224 				bx = x + width + balloon.width;
5225 			if(by < 0)
5226 				by = y + height;
5227 
5228 			// just in case, make sure it is actually on scren
5229 			if(bx < 0)
5230 				bx = 0;
5231 			if(by < 0)
5232 				by = 0;
5233 
5234 			balloon.move(bx, by);
5235 			auto painter = balloon.draw();
5236 			painter.fillColor = Color(220, 220, 220);
5237 			painter.outlineColor = Color.black;
5238 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5239 			auto iconWidth = icon is null ? 0 : icon.width;
5240 			if(icon)
5241 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5242 			iconWidth += 6; // margin around the icon
5243 
5244 			// draw a close button
5245 			painter.outlineColor = Color(44, 44, 44);
5246 			painter.fillColor = Color(255, 255, 255);
5247 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5248 			painter.pen = Pen(Color.black, 3);
5249 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5250 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5251 			painter.pen = Pen(Color.black, 1);
5252 			painter.fillColor = Color(220, 220, 220);
5253 
5254 			// Draw the title and message
5255 			painter.drawText(Point(4 + iconWidth, 4), title);
5256 			painter.drawLine(
5257 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5258 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5259 			);
5260 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5261 
5262 			balloon.setEventHandlers(
5263 				(MouseEvent ev) {
5264 					if(ev.type == MouseEventType.buttonPressed) {
5265 						if(ev.x > balloon.width - 16 && ev.y < 16)
5266 							hideBalloon();
5267 						else if(onclick)
5268 							onclick();
5269 					}
5270 				}
5271 			);
5272 			balloon.show();
5273 
5274 			version(with_timer)
5275 			timer = new Timer(timeout, &hideBalloon);
5276 			else {} // FIXME
5277 		}
5278 		} else version(Windows) {
5279 			enum NIF_INFO = 0x00000010;
5280 
5281 			data.uFlags = NIF_INFO;
5282 
5283 			// FIXME: go back to the last valid unicode code point
5284 			if(title.length > 40)
5285 				title = title[0 .. 40];
5286 			if(message.length > 220)
5287 				message = message[0 .. 220];
5288 
5289 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5290 			enum NIIF_LARGE_ICON  = 0x00000020;
5291 			enum NIIF_NOSOUND = 0x00000010;
5292 			enum NIIF_USER = 0x00000004;
5293 			enum NIIF_ERROR = 0x00000003;
5294 			enum NIIF_WARNING = 0x00000002;
5295 			enum NIIF_INFO = 0x00000001;
5296 			enum NIIF_NONE = 0;
5297 
5298 			WCharzBuffer t = WCharzBuffer(title);
5299 			WCharzBuffer m = WCharzBuffer(message);
5300 
5301 			t.copyInto(data.szInfoTitle);
5302 			m.copyInto(data.szInfo);
5303 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5304 
5305 			if(icon !is null) {
5306 				auto i = new WindowsIcon(icon);
5307 				data.hBalloonIcon = i.hIcon;
5308 				data.dwInfoFlags |= NIIF_USER;
5309 			}
5310 
5311 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5312 		} else version(OSXCocoa) {
5313 			throw new NotYetImplementedException();
5314 		} else static assert(0);
5315 	}
5316 
5317 	///
5318 	//version(Windows)
5319 	void show() {
5320 		version(X11) {
5321 			if(!hidden)
5322 				return;
5323 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5324 			hidden = false;
5325 		} else version(Windows) {
5326 			data.uFlags = NIF_STATE;
5327 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5328 			data.dwStateMask = NIS_HIDDEN; // windows vista
5329 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5330 		} else version(OSXCocoa) {
5331 			throw new NotYetImplementedException();
5332 		} else static assert(0);
5333 	}
5334 
5335 	version(X11)
5336 		bool hidden = false;
5337 
5338 	///
5339 	//version(Windows)
5340 	void hide() {
5341 		version(X11) {
5342 			if(hidden)
5343 				return;
5344 			hidden = true;
5345 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5346 		} else version(Windows) {
5347 			data.uFlags = NIF_STATE;
5348 			data.dwState = NIS_HIDDEN; // windows vista
5349 			data.dwStateMask = NIS_HIDDEN; // windows vista
5350 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5351 		} else version(OSXCocoa) {
5352 			throw new NotYetImplementedException();
5353 		} else static assert(0);
5354 	}
5355 
5356 	///
5357 	void close () {
5358 		version(X11) {
5359 			if (active) {
5360 				active = false; // event handler will set this too, but meh
5361 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5362 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5363 				flushGui();
5364 			}
5365 		} else version(Windows) {
5366 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5367 		} else version(OSXCocoa) {
5368 			throw new NotYetImplementedException();
5369 		} else static assert(0);
5370 	}
5371 
5372 	~this() {
5373 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5374 		version(X11)
5375 			if(clippixmap != None)
5376 				XFreePixmap(XDisplayConnection.get, clippixmap);
5377 		close();
5378 	}
5379 }
5380 
5381 version(X11)
5382 /// Call `XFreePixmap` on the return value.
5383 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5384 	char[] data = new char[](i.width * i.height / 8 + 2);
5385 	data[] = 0;
5386 
5387 	int bitOffset = 0;
5388 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5389 		ubyte v = c.a > 128 ? 1 : 0;
5390 		data[bitOffset / 8] |= v << (bitOffset%8);
5391 		bitOffset++;
5392 	}
5393 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5394 	return handle;
5395 }
5396 
5397 
5398 // basic functions to make timers
5399 /**
5400 	A timer that will trigger your function on a given interval.
5401 
5402 
5403 	You create a timer with an interval and a callback. It will continue
5404 	to fire on the interval until it is destroyed.
5405 
5406 	There are currently no one-off timers (instead, just create one and
5407 	destroy it when it is triggered) nor are there pause/resume functions -
5408 	the timer must again be destroyed and recreated if you want to pause it.
5409 
5410 	auto timer = new Timer(50, { it happened!; });
5411 	timer.destroy();
5412 
5413 	Timers can only be expected to fire when the event loop is running and only
5414 	once per iteration through the event loop.
5415 
5416 	History:
5417 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5418 		slow could lock up the event loop. It now guarantees other things will
5419 		get a chance to run between timer calls, even if that means not keeping up
5420 		with the requested interval.
5421 */
5422 version(with_timer) {
5423 class Timer {
5424 // FIXME: needs pause and unpause
5425 	// FIXME: I might add overloads for ones that take a count of
5426 	// how many elapsed since last time (on Windows, it will divide
5427 	// the ticks thing given, on Linux it is just available) and
5428 	// maybe one that takes an instance of the Timer itself too
5429 	/// Create a timer with a callback when it triggers.
5430 	this(int intervalInMilliseconds, void delegate() onPulse) {
5431 		assert(onPulse !is null);
5432 
5433 		this.intervalInMilliseconds = intervalInMilliseconds;
5434 		this.onPulse = onPulse;
5435 
5436 		version(Windows) {
5437 			/*
5438 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5439 			if(handle == 0)
5440 				throw new WindowsApiException("SetTimer", GetLastError());
5441 			*/
5442 
5443 			// thanks to Archival 998 for the WaitableTimer blocks
5444 			handle = CreateWaitableTimer(null, false, null);
5445 			long initialTime = -intervalInMilliseconds;
5446 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5447 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5448 
5449 			mapping[handle] = this;
5450 
5451 		} else version(linux) {
5452 			static import ep = core.sys.linux.epoll;
5453 
5454 			import core.sys.linux.timerfd;
5455 
5456 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5457 			if(fd == -1)
5458 				throw new Exception("timer create failed");
5459 
5460 			mapping[fd] = this;
5461 
5462 			itimerspec value;
5463 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5464 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5465 
5466 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5467 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5468 
5469 			if(timerfd_settime(fd, 0, &value, null) == -1)
5470 				throw new Exception("couldn't make pulse timer");
5471 
5472 			version(with_eventloop) {
5473 				import arsd.eventloop;
5474 				addFileEventListeners(fd, &trigger, null, null);
5475 			} else {
5476 				prepareEventLoop();
5477 
5478 				ep.epoll_event ev = void;
5479 				ev.events = ep.EPOLLIN;
5480 				ev.data.fd = fd;
5481 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5482 			}
5483 		} else featureNotImplemented();
5484 	}
5485 
5486 	private int intervalInMilliseconds;
5487 
5488 	// just cuz I sometimes call it this.
5489 	alias dispose = destroy;
5490 
5491 	/// Stop and destroy the timer object.
5492 	void destroy() {
5493 		version(Windows) {
5494 			staticDestroy(handle);
5495 			handle = null;
5496 		} else version(linux) {
5497 			staticDestroy(fd);
5498 			fd = -1;
5499 		} else featureNotImplemented();
5500 	}
5501 
5502 	version(Windows)
5503 	static void staticDestroy(HANDLE handle) {
5504 		if(handle) {
5505 			// KillTimer(null, handle);
5506 			CancelWaitableTimer(cast(void*)handle);
5507 			mapping.remove(handle);
5508 			CloseHandle(handle);
5509 		}
5510 	}
5511 	else version(linux)
5512 	static void staticDestroy(int fd) {
5513 		if(fd != -1) {
5514 			import unix = core.sys.posix.unistd;
5515 			static import ep = core.sys.linux.epoll;
5516 
5517 			version(with_eventloop) {
5518 				import arsd.eventloop;
5519 				removeFileEventListeners(fd);
5520 			} else {
5521 				ep.epoll_event ev = void;
5522 				ev.events = ep.EPOLLIN;
5523 				ev.data.fd = fd;
5524 
5525 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5526 			}
5527 			unix.close(fd);
5528 			mapping.remove(fd);
5529 		}
5530 	}
5531 
5532 	~this() {
5533 		version(Windows) { if(handle)
5534 			cleanupQueue.queue!staticDestroy(handle);
5535 		} else version(linux) { if(fd != -1)
5536 			cleanupQueue.queue!staticDestroy(fd);
5537 		}
5538 	}
5539 
5540 
5541 	void changeTime(int intervalInMilliseconds)
5542 	{
5543 		this.intervalInMilliseconds = intervalInMilliseconds;
5544 		version(Windows)
5545 		{
5546 			if(handle)
5547 			{
5548 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5549 				long initialTime = -intervalInMilliseconds;
5550 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5551 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
5552 			}
5553 		}
5554 	}
5555 
5556 
5557 	private:
5558 
5559 	void delegate() onPulse;
5560 
5561 	int lastEventLoopRoundTriggered;
5562 
5563 	void trigger() {
5564 		version(linux) {
5565 			import unix = core.sys.posix.unistd;
5566 			long val;
5567 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5568 		} else version(Windows) {
5569 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5570 				return; // never try to actually run faster than the event loop
5571 			lastEventLoopRoundTriggered = eventLoopRound;
5572 		} else featureNotImplemented();
5573 
5574 		onPulse();
5575 	}
5576 
5577 	version(Windows)
5578 	void rearm() {
5579 
5580 	}
5581 
5582 	version(Windows)
5583 		extern(Windows)
5584 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5585 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5586 			if(Timer* t = timer in mapping) {
5587 				try
5588 				(*t).trigger();
5589 				catch(Exception e) { sdpy_abort(e); assert(0); }
5590 			}
5591 		}
5592 
5593 	version(Windows) {
5594 		//UINT_PTR handle;
5595 		//static Timer[UINT_PTR] mapping;
5596 		HANDLE handle;
5597 		__gshared Timer[HANDLE] mapping;
5598 	} else version(linux) {
5599 		int fd = -1;
5600 		__gshared Timer[int] mapping;
5601 	} else static assert(0, "timer not supported");
5602 }
5603 }
5604 
5605 version(Windows)
5606 private int eventLoopRound;
5607 
5608 version(Windows)
5609 /// 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
5610 class WindowsHandleReader {
5611 	///
5612 	this(void delegate() onReady, HANDLE handle) {
5613 		this.onReady = onReady;
5614 		this.handle = handle;
5615 
5616 		mapping[handle] = this;
5617 
5618 		enable();
5619 	}
5620 
5621 	///
5622 	void enable() {
5623 		auto el = EventLoop.get().impl;
5624 		el.handles ~= handle;
5625 	}
5626 
5627 	///
5628 	void disable() {
5629 		auto el = EventLoop.get().impl;
5630 		for(int i = 0; i < el.handles.length; i++) {
5631 			if(el.handles[i] is handle) {
5632 				el.handles[i] = el.handles[$-1];
5633 				el.handles = el.handles[0 .. $-1];
5634 				return;
5635 			}
5636 		}
5637 	}
5638 
5639 	void dispose() {
5640 		disable();
5641 		if(handle)
5642 			mapping.remove(handle);
5643 		handle = null;
5644 	}
5645 
5646 	void ready() {
5647 		if(onReady)
5648 			onReady();
5649 	}
5650 
5651 	HANDLE handle;
5652 	void delegate() onReady;
5653 
5654 	__gshared WindowsHandleReader[HANDLE] mapping;
5655 }
5656 
5657 version(Posix)
5658 /// Lets you add files to the event loop for reading. Use at your own risk.
5659 class PosixFdReader {
5660 	///
5661 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5662 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5663 	}
5664 
5665 	///
5666 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5667 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5668 	}
5669 
5670 	///
5671 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5672 		this.onReady = onReady;
5673 		this.fd = fd;
5674 		this.captureWrites = captureWrites;
5675 		this.captureReads = captureReads;
5676 
5677 		mapping[fd] = this;
5678 
5679 		version(with_eventloop) {
5680 			import arsd.eventloop;
5681 			addFileEventListeners(fd, &readyel);
5682 		} else {
5683 			enable();
5684 		}
5685 	}
5686 
5687 	bool captureReads;
5688 	bool captureWrites;
5689 
5690 	version(with_eventloop) {} else
5691 	///
5692 	void enable() {
5693 		prepareEventLoop();
5694 
5695 		enabled = true;
5696 
5697 		version(linux) {
5698 			static import ep = core.sys.linux.epoll;
5699 			ep.epoll_event ev = void;
5700 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5701 			//import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5702 			ev.data.fd = fd;
5703 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5704 		} else {
5705 
5706 		}
5707 	}
5708 
5709 	version(with_eventloop) {} else
5710 	///
5711 	void disable() {
5712 		prepareEventLoop();
5713 
5714 		enabled = false;
5715 
5716 		version(linux) {
5717 			static import ep = core.sys.linux.epoll;
5718 			ep.epoll_event ev = void;
5719 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5720 			//import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5721 			ev.data.fd = fd;
5722 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5723 		}
5724 	}
5725 
5726 	version(with_eventloop) {} else
5727 	///
5728 	void dispose() {
5729 		if(enabled)
5730 			disable();
5731 		if(fd != -1)
5732 			mapping.remove(fd);
5733 		fd = -1;
5734 	}
5735 
5736 	void delegate(int, bool, bool) onReady;
5737 
5738 	version(with_eventloop)
5739 	void readyel() {
5740 		onReady(fd, true, true);
5741 	}
5742 
5743 	void ready(uint flags) {
5744 		version(linux) {
5745 			static import ep = core.sys.linux.epoll;
5746 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5747 		} else {
5748 			import core.sys.posix.poll;
5749 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5750 		}
5751 	}
5752 
5753 	void hup(uint flags) {
5754 		if(onHup)
5755 			onHup();
5756 	}
5757 
5758 	void delegate() onHup;
5759 
5760 	int fd = -1;
5761 	private bool enabled;
5762 	__gshared PosixFdReader[int] mapping;
5763 }
5764 
5765 // basic functions to access the clipboard
5766 /+
5767 
5768 
5769 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5770 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5771 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5772 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5773 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5774 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5775 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5776 
5777 +/
5778 
5779 /++
5780 	this does a delegate because it is actually an async call on X...
5781 	the receiver may never be called if the clipboard is empty or unavailable
5782 	gets plain text from the clipboard.
5783 +/
5784 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5785 	version(Windows) {
5786 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5787 		if(OpenClipboard(hwndOwner) == 0)
5788 			throw new WindowsApiException("OpenClipboard", GetLastError());
5789 		scope(exit)
5790 			CloseClipboard();
5791 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5792 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5793 
5794 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5795 				scope(exit)
5796 					GlobalUnlock(dataHandle);
5797 
5798 				// FIXME: CR/LF conversions
5799 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5800 				int len = 0;
5801 				auto d = data;
5802 				while(*d) {
5803 					d++;
5804 					len++;
5805 				}
5806 				string s;
5807 				s.reserve(len);
5808 				foreach(dchar ch; data[0 .. len]) {
5809 					s ~= ch;
5810 				}
5811 				receiver(s);
5812 			}
5813 		}
5814 	} else version(X11) {
5815 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5816 	} else version(OSXCocoa) {
5817 		throw new NotYetImplementedException();
5818 	} else static assert(0);
5819 }
5820 
5821 // FIXME: a clipboard listener might be cool btw
5822 
5823 /++
5824 	this does a delegate because it is actually an async call on X...
5825 	the receiver may never be called if the clipboard is empty or unavailable
5826 	gets image from the clipboard.
5827 
5828 	templated because it introduces an optional dependency on arsd.bmp
5829 +/
5830 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5831 	version(Windows) {
5832 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5833 		if(OpenClipboard(hwndOwner) == 0)
5834 			throw new WindowsApiException("OpenClipboard", GetLastError());
5835 		scope(exit)
5836 			CloseClipboard();
5837 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5838 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5839 				scope(exit)
5840 					GlobalUnlock(dataHandle);
5841 
5842 				auto len = GlobalSize(dataHandle);
5843 
5844 				import arsd.bmp;
5845 				auto img = readBmp(data[0 .. len], false);
5846 				receiver(img);
5847 			}
5848 		}
5849 	} else version(X11) {
5850 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5851 	} else version(OSXCocoa) {
5852 		throw new NotYetImplementedException();
5853 	} else static assert(0);
5854 }
5855 
5856 /// Copies some text to the clipboard.
5857 void setClipboardText(SimpleWindow clipboardOwner, string text) {
5858 	assert(clipboardOwner !is null);
5859 	version(Windows) {
5860 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5861 			throw new WindowsApiException("OpenClipboard", GetLastError());
5862 		scope(exit)
5863 			CloseClipboard();
5864 		EmptyClipboard();
5865 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5866 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
5867 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
5868 		if(auto data = cast(wchar*) GlobalLock(handle)) {
5869 			auto slice = data[0 .. sz];
5870 			scope(failure)
5871 				GlobalUnlock(handle);
5872 
5873 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5874 
5875 			GlobalUnlock(handle);
5876 			SetClipboardData(CF_UNICODETEXT, handle);
5877 		}
5878 	} else version(X11) {
5879 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
5880 	} else version(OSXCocoa) {
5881 		throw new NotYetImplementedException();
5882 	} else static assert(0);
5883 }
5884 
5885 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
5886 	assert(clipboardOwner !is null);
5887 	version(Windows) {
5888 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5889 			throw new WindowsApiException("OpenClipboard", GetLastError());
5890 		scope(exit)
5891 			CloseClipboard();
5892 		EmptyClipboard();
5893 
5894 
5895 		import arsd.bmp;
5896 		ubyte[] mdata;
5897 		mdata.reserve(img.width * img.height);
5898 		void sink(ubyte b) {
5899 			mdata ~= b;
5900 		}
5901 		writeBmpIndirect(img, &sink, false);
5902 
5903 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
5904 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
5905 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
5906 			auto slice = data[0 .. mdata.length];
5907 			scope(failure)
5908 				GlobalUnlock(handle);
5909 
5910 			slice[] = mdata[];
5911 
5912 			GlobalUnlock(handle);
5913 			SetClipboardData(CF_DIB, handle);
5914 		}
5915 	} else version(X11) {
5916 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
5917 			mixin X11SetSelectionHandler_Basics;
5918 			private const(ubyte)[] mdata;
5919 			private const(ubyte)[] mdata_original;
5920 			this(MemoryImage img) {
5921 				import arsd.bmp;
5922 
5923 				mdata.reserve(img.width * img.height);
5924 				void sink(ubyte b) {
5925 					mdata ~= b;
5926 				}
5927 				writeBmpIndirect(img, &sink, true);
5928 
5929 				mdata_original = mdata;
5930 			}
5931 
5932 			Atom[] availableFormats() {
5933 				auto display = XDisplayConnection.get;
5934 				return [
5935 					GetAtom!"image/bmp"(display),
5936 					GetAtom!"TARGETS"(display)
5937 				];
5938 			}
5939 
5940 			ubyte[] getData(Atom format, return scope ubyte[] data) {
5941 				if(mdata.length < data.length) {
5942 					data[0 .. mdata.length] = mdata[];
5943 					auto ret = data[0 .. mdata.length];
5944 					mdata = mdata[$..$];
5945 					return ret;
5946 				} else {
5947 					data[] = mdata[0 .. data.length];
5948 					mdata = mdata[data.length .. $];
5949 					return data[];
5950 				}
5951 			}
5952 
5953 			void done() {
5954 				mdata = mdata_original;
5955 			}
5956 		}
5957 
5958 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
5959 	} else version(OSXCocoa) {
5960 		throw new NotYetImplementedException();
5961 	} else static assert(0);
5962 }
5963 
5964 
5965 version(X11) {
5966 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
5967 
5968 	private Atom*[] interredAtoms; // for discardAndRecreate
5969 
5970 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
5971 	/// Platform-specific for X11.
5972 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
5973 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
5974 		static Atom a;
5975 		if(!a) {
5976 			a = XInternAtom(display, name, !create);
5977 			interredAtoms ~= &a;
5978 		}
5979 		if(a == None)
5980 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
5981 		return a;
5982 	}
5983 
5984 	/// Platform-specific for X11 - gets atom names as a string.
5985 	string getAtomName(Atom atom, Display* display) {
5986 		auto got = XGetAtomName(display, atom);
5987 		scope(exit) XFree(got);
5988 		import core.stdc.string;
5989 		string s = got[0 .. strlen(got)].idup;
5990 		return s;
5991 	}
5992 
5993 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
5994 	void setPrimarySelection(SimpleWindow window, string text) {
5995 		setX11Selection!"PRIMARY"(window, text);
5996 	}
5997 
5998 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
5999 	void setSecondarySelection(SimpleWindow window, string text) {
6000 		setX11Selection!"SECONDARY"(window, text);
6001 	}
6002 
6003 	interface X11SetSelectionHandler {
6004 		// should include TARGETS right now
6005 		Atom[] availableFormats();
6006 		// Return the slice of data you filled, empty slice if done.
6007 		// this is to support the incremental thing
6008 		ubyte[] getData(Atom format, return scope ubyte[] data);
6009 
6010 		void done();
6011 
6012 		void handleRequest(XEvent);
6013 
6014 		bool matchesIncr(Window, Atom);
6015 		void sendMoreIncr(XPropertyEvent*);
6016 	}
6017 
6018 	mixin template X11SetSelectionHandler_Basics() {
6019 		Window incrWindow;
6020 		Atom incrAtom;
6021 		Atom selectionAtom;
6022 		Atom formatAtom;
6023 		ubyte[] toSend;
6024 		bool matchesIncr(Window w, Atom a) {
6025 			return incrAtom && incrAtom == a && w == incrWindow;
6026 		}
6027 		void sendMoreIncr(XPropertyEvent* event) {
6028 			auto display = XDisplayConnection.get;
6029 
6030 			XChangeProperty (display,
6031 				incrWindow,
6032 				incrAtom,
6033 				formatAtom,
6034 				8 /* bits */, PropModeReplace,
6035 				toSend.ptr, cast(int) toSend.length);
6036 
6037 			if(toSend.length != 0) {
6038 				toSend = this.getData(formatAtom, toSend[]);
6039 			} else {
6040 				this.done();
6041 				incrWindow = None;
6042 				incrAtom = None;
6043 				selectionAtom = None;
6044 				formatAtom = None;
6045 				toSend = null;
6046 			}
6047 		}
6048 		void handleRequest(XEvent ev) {
6049 
6050 			auto display = XDisplayConnection.get;
6051 
6052 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6053 			XSelectionEvent selectionEvent;
6054 			selectionEvent.type = EventType.SelectionNotify;
6055 			selectionEvent.display = event.display;
6056 			selectionEvent.requestor = event.requestor;
6057 			selectionEvent.selection = event.selection;
6058 			selectionEvent.time = event.time;
6059 			selectionEvent.target = event.target;
6060 
6061 			bool supportedType() {
6062 				foreach(t; this.availableFormats())
6063 					if(t == event.target)
6064 						return true;
6065 				return false;
6066 			}
6067 
6068 			if(event.property == None) {
6069 				selectionEvent.property = event.target;
6070 
6071 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6072 				XFlush(display);
6073 			} if(event.target == GetAtom!"TARGETS"(display)) {
6074 				/* respond with the supported types */
6075 				auto tlist = this.availableFormats();
6076 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6077 				selectionEvent.property = event.property;
6078 
6079 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6080 				XFlush(display);
6081 			} else if(supportedType()) {
6082 				auto buffer = new ubyte[](1024 * 64);
6083 				auto toSend = this.getData(event.target, buffer[]);
6084 
6085 				if(toSend.length < 32 * 1024) {
6086 					// small enough to send directly...
6087 					selectionEvent.property = event.property;
6088 					XChangeProperty (display,
6089 						selectionEvent.requestor,
6090 						selectionEvent.property,
6091 						event.target,
6092 						8 /* bits */, 0 /* PropModeReplace */,
6093 						toSend.ptr, cast(int) toSend.length);
6094 
6095 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6096 					XFlush(display);
6097 				} else {
6098 					// large, let's send incrementally
6099 					arch_ulong l = toSend.length;
6100 
6101 					// if I wanted other events from this window don't want to clear that out....
6102 					XWindowAttributes xwa;
6103 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6104 
6105 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6106 
6107 					incrWindow = event.requestor;
6108 					incrAtom = event.property;
6109 					formatAtom = event.target;
6110 					selectionAtom = event.selection;
6111 					this.toSend = toSend;
6112 
6113 					selectionEvent.property = event.property;
6114 					XChangeProperty (display,
6115 						selectionEvent.requestor,
6116 						selectionEvent.property,
6117 						GetAtom!"INCR"(display),
6118 						32 /* bits */, PropModeReplace,
6119 						&l, 1);
6120 
6121 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6122 					XFlush(display);
6123 				}
6124 				//if(after)
6125 					//after();
6126 			} else {
6127 				debug(sdpy_clip) {
6128 					import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display));
6129 				}
6130 				selectionEvent.property = None; // I don't know how to handle this type...
6131 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6132 				XFlush(display);
6133 			}
6134 		}
6135 	}
6136 
6137 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6138 		mixin X11SetSelectionHandler_Basics;
6139 		private const(ubyte)[] text;
6140 		private const(ubyte)[] text_original;
6141 		this(string text) {
6142 			this.text = cast(const ubyte[]) text;
6143 			this.text_original = this.text;
6144 		}
6145 		Atom[] availableFormats() {
6146 			auto display = XDisplayConnection.get;
6147 			return [
6148 				GetAtom!"UTF8_STRING"(display),
6149 				GetAtom!"text/plain"(display),
6150 				XA_STRING,
6151 				GetAtom!"TARGETS"(display)
6152 			];
6153 		}
6154 
6155 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6156 			if(text.length < data.length) {
6157 				data[0 .. text.length] = text[];
6158 				return data[0 .. text.length];
6159 			} else {
6160 				data[] = text[0 .. data.length];
6161 				text = text[data.length .. $];
6162 				return data[];
6163 			}
6164 		}
6165 
6166 		void done() {
6167 			text = text_original;
6168 		}
6169 	}
6170 
6171 	/// 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?!)
6172 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6173 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6174 	}
6175 
6176 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6177 		assert(window !is null);
6178 
6179 		auto display = XDisplayConnection.get();
6180 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6181 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6182 		else Atom a = GetAtom!atomName(display);
6183 
6184 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6185 
6186 		window.impl.setSelectionHandlers[a] = data;
6187 	}
6188 
6189 	///
6190 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6191 		getX11Selection!"PRIMARY"(window, handler);
6192 	}
6193 
6194 	// added July 28, 2020
6195 	// undocumented as experimental tho
6196 	interface X11GetSelectionHandler {
6197 		void handleData(Atom target, in ubyte[] data);
6198 		Atom findBestFormat(Atom[] answer);
6199 
6200 		void prepareIncremental(Window, Atom);
6201 		bool matchesIncr(Window, Atom);
6202 		void handleIncrData(Atom, in ubyte[] data);
6203 	}
6204 
6205 	mixin template X11GetSelectionHandler_Basics() {
6206 		Window incrWindow;
6207 		Atom incrAtom;
6208 
6209 		void prepareIncremental(Window w, Atom a) {
6210 			incrWindow = w;
6211 			incrAtom = a;
6212 		}
6213 		bool matchesIncr(Window w, Atom a) {
6214 			return incrWindow == w && incrAtom == a;
6215 		}
6216 
6217 		Atom incrFormatAtom;
6218 		ubyte[] incrData;
6219 		void handleIncrData(Atom format, in ubyte[] data) {
6220 			incrFormatAtom = format;
6221 
6222 			if(data.length)
6223 				incrData ~= data;
6224 			else
6225 				handleData(incrFormatAtom, incrData);
6226 
6227 		}
6228 	}
6229 
6230 	///
6231 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6232 		assert(window !is null);
6233 
6234 		auto display = XDisplayConnection.get();
6235 		auto atom = GetAtom!atomName(display);
6236 
6237 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6238 			this(void delegate(in char[]) handler) {
6239 				this.handler = handler;
6240 			}
6241 
6242 			mixin X11GetSelectionHandler_Basics;
6243 
6244 			void delegate(in char[]) handler;
6245 
6246 			void handleData(Atom target, in ubyte[] data) {
6247 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6248 					handler(cast(const char[]) data);
6249 			}
6250 
6251 			Atom findBestFormat(Atom[] answer) {
6252 				Atom best = None;
6253 				foreach(option; answer) {
6254 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6255 						best = option;
6256 						break;
6257 					} else if(option == XA_STRING) {
6258 						best = option;
6259 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6260 						best = option;
6261 					}
6262 				}
6263 				return best;
6264 			}
6265 		}
6266 
6267 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6268 
6269 		auto target = GetAtom!"TARGETS"(display);
6270 
6271 		// SDD_DATA is "simpledisplay.d data"
6272 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6273 	}
6274 
6275 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6276 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6277 		assert(window !is null);
6278 
6279 		auto display = XDisplayConnection.get();
6280 		auto atom = GetAtom!atomName(display);
6281 
6282 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6283 			this(void delegate(MemoryImage) handler) {
6284 				this.handler = handler;
6285 			}
6286 
6287 			mixin X11GetSelectionHandler_Basics;
6288 
6289 			void delegate(MemoryImage) handler;
6290 
6291 			void handleData(Atom target, in ubyte[] data) {
6292 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6293 					import arsd.bmp;
6294 					handler(readBmp(data));
6295 				}
6296 			}
6297 
6298 			Atom findBestFormat(Atom[] answer) {
6299 				Atom best = None;
6300 				foreach(option; answer) {
6301 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6302 						best = option;
6303 					}
6304 				}
6305 				return best;
6306 			}
6307 
6308 		}
6309 
6310 
6311 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6312 
6313 		auto target = GetAtom!"TARGETS"(display);
6314 
6315 		// SDD_DATA is "simpledisplay.d data"
6316 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6317 	}
6318 
6319 
6320 	///
6321 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6322 		Atom actualType;
6323 		int actualFormat;
6324 		arch_ulong actualItems;
6325 		arch_ulong bytesRemaining;
6326 		void* data;
6327 
6328 		auto display = XDisplayConnection.get();
6329 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6330 			if(actualFormat == 0)
6331 				return null;
6332 			else {
6333 				int byteLength;
6334 				if(actualFormat == 32) {
6335 					// 32 means it is a C long... which is variable length
6336 					actualFormat = cast(int) arch_long.sizeof * 8;
6337 				}
6338 
6339 				// then it is just a bit count
6340 				byteLength = cast(int) (actualItems * actualFormat / 8);
6341 
6342 				auto d = new ubyte[](byteLength);
6343 				d[] = cast(ubyte[]) data[0 .. byteLength];
6344 				XFree(data);
6345 				return d;
6346 			}
6347 		}
6348 		return null;
6349 	}
6350 
6351 	/* defined in the systray spec */
6352 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6353 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6354 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6355 
6356 
6357 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6358 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6359 	public class GlobalHotkey {
6360 		KeyEvent key;
6361 		void delegate () handler;
6362 
6363 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6364 
6365 		/// Create from initialzed KeyEvent object
6366 		this (KeyEvent akey, void delegate () ahandler=null) {
6367 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6368 			key = akey;
6369 			handler = ahandler;
6370 		}
6371 
6372 		/// Create from emacs-like key name ("C-M-Y", etc.)
6373 		this (const(char)[] akey, void delegate () ahandler=null) {
6374 			key = KeyEvent.parse(akey);
6375 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6376 			handler = ahandler;
6377 		}
6378 
6379 	}
6380 
6381 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6382 		//conwriteln("failed to grab key");
6383 		GlobalHotkeyManager.ghfailed = true;
6384 		return 0;
6385 	}
6386 
6387 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6388 		Image.impl.xshmfailed = true;
6389 		return 0;
6390 	}
6391 
6392 	private __gshared int errorHappened;
6393 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6394 		import core.stdc.stdio;
6395 		char[265] buffer;
6396 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6397 		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);
6398 		errorHappened = true;
6399 		return 0;
6400 	}
6401 
6402 	/++
6403 		Global hotkey manager. It contains static methods to manage global hotkeys.
6404 
6405 		---
6406 		 try {
6407 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6408 		} catch (Exception e) {
6409 			conwriteln("ERROR registering hotkey!");
6410 		}
6411 		EventLoop.get.run();
6412 		---
6413 
6414 		The key strings are based on Emacs. In practical terms,
6415 		`M` means `alt` and `H` means the Windows logo key. `C`
6416 		is `ctrl`.
6417 
6418 		$(WARNING
6419 			This is X-specific right now. If you are on
6420 			Windows, try [registerHotKey] instead.
6421 
6422 			We will probably merge these into a single
6423 			interface later.
6424 		)
6425 	+/
6426 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6427 		version(X11) {
6428 			void recreateAfterDisconnect() {
6429 				throw new Exception("NOT IMPLEMENTED");
6430 			}
6431 			void discardConnectionState() {
6432 				throw new Exception("NOT IMPLEMENTED");
6433 			}
6434 		}
6435 
6436 		private static immutable uint[8] masklist = [ 0,
6437 			KeyOrButtonMask.LockMask,
6438 			KeyOrButtonMask.Mod2Mask,
6439 			KeyOrButtonMask.Mod3Mask,
6440 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6441 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6442 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6443 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6444 		];
6445 		private __gshared GlobalHotkeyManager ghmanager;
6446 		private __gshared bool ghfailed = false;
6447 
6448 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6449 			if (modmask == 0) return false;
6450 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6451 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6452 			return true;
6453 		}
6454 
6455 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6456 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6457 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6458 			return modmask;
6459 		}
6460 
6461 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6462 			uint keycode = cast(uint)ke.key;
6463 			auto dpy = XDisplayConnection.get;
6464 			return XKeysymToKeycode(dpy, keycode);
6465 		}
6466 
6467 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6468 
6469 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6470 
6471 		NativeEventHandler getNativeEventHandler () {
6472 			return delegate int (XEvent e) {
6473 				if (e.type != EventType.KeyPress) return 1;
6474 				auto kev = cast(const(XKeyEvent)*)&e;
6475 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6476 				if (auto ghkp = hash in globalHotkeyList) {
6477 					try {
6478 						ghkp.doHandle();
6479 					} catch (Exception e) {
6480 						import core.stdc.stdio : stderr, fprintf;
6481 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6482 					}
6483 				}
6484 				return 1;
6485 			};
6486 		}
6487 
6488 		private this () {
6489 			auto dpy = XDisplayConnection.get;
6490 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6491 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6492 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6493 		}
6494 
6495 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6496 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6497 		static void register (GlobalHotkey gh) {
6498 			if (gh is null) return;
6499 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6500 
6501 			auto dpy = XDisplayConnection.get;
6502 			immutable keycode = keyEvent2KeyCode(gh.key);
6503 
6504 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6505 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6506 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6507 			XSync(dpy, 0/*False*/);
6508 
6509 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6510 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6511 			ghfailed = false;
6512 			foreach (immutable uint ormask; masklist[]) {
6513 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6514 			}
6515 			XSync(dpy, 0/*False*/);
6516 			XSetErrorHandler(savedErrorHandler);
6517 
6518 			if (ghfailed) {
6519 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6520 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6521 				XSync(dpy, 0/*False*/);
6522 				XSetErrorHandler(savedErrorHandler);
6523 				throw new Exception("cannot register global hotkey");
6524 			}
6525 
6526 			globalHotkeyList[hash] = gh;
6527 		}
6528 
6529 		/// Ditto
6530 		static void register (const(char)[] akey, void delegate () ahandler) {
6531 			register(new GlobalHotkey(akey, ahandler));
6532 		}
6533 
6534 		private static void removeByHash (ulong hash) {
6535 			if (auto ghp = hash in globalHotkeyList) {
6536 				auto dpy = XDisplayConnection.get;
6537 				immutable keycode = keyEvent2KeyCode(ghp.key);
6538 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6539 				XSync(dpy, 0/*False*/);
6540 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6541 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6542 				XSync(dpy, 0/*False*/);
6543 				XSetErrorHandler(savedErrorHandler);
6544 				globalHotkeyList.remove(hash);
6545 			}
6546 		}
6547 
6548 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6549 		/// It is safe to unregister unknown or invalid hotkey.
6550 		static void unregister (GlobalHotkey gh) {
6551 			//TODO: add second AA for faster search? prolly doesn't worth it.
6552 			if (gh is null) return;
6553 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6554 				if (kv.value is gh) {
6555 					removeByHash(kv.key);
6556 					return;
6557 				}
6558 			}
6559 		}
6560 
6561 		/// Ditto.
6562 		static void unregister (const(char)[] key) {
6563 			auto kev = KeyEvent.parse(key);
6564 			immutable keycode = keyEvent2KeyCode(kev);
6565 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6566 		}
6567 	}
6568 }
6569 
6570 version(Windows) {
6571 	/++
6572 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6573 
6574 		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).
6575 	+/
6576 	void sendSyntheticInput(wstring s) {
6577 			INPUT[] inputs;
6578 			inputs.reserve(s.length * 2);
6579 
6580 			foreach(wchar c; s) {
6581 				INPUT input;
6582 				input.type = INPUT_KEYBOARD;
6583 				input.ki.wScan = c;
6584 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6585 				inputs ~= input;
6586 
6587 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6588 				inputs ~= input;
6589 			}
6590 
6591 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6592 				throw new WindowsApiException("SendInput", GetLastError());
6593 			}
6594 
6595 	}
6596 
6597 
6598 	// global hotkey helper function
6599 
6600 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6601 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6602 		__gshared int hotkeyId = 0;
6603 		int id = ++hotkeyId;
6604 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6605 			throw new Exception("RegisterHotKey");
6606 
6607 		__gshared void delegate()[WPARAM][HWND] handlers;
6608 
6609 		handlers[window.impl.hwnd][id] = handler;
6610 
6611 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6612 
6613 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6614 			switch(msg) {
6615 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6616 				case WM_HOTKEY:
6617 					if(auto list = hwnd in handlers) {
6618 						if(auto h = wParam in *list) {
6619 							(*h)();
6620 							return 0;
6621 						}
6622 					}
6623 				goto default;
6624 				default:
6625 			}
6626 			if(oldHandler)
6627 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6628 			return 1; // pass it on
6629 		};
6630 
6631 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6632 			oldHandler = window.handleNativeEvent;
6633 			window.handleNativeEvent = nativeEventHandler;
6634 		}
6635 
6636 		return id;
6637 	}
6638 
6639 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6640 	void unregisterHotKey(SimpleWindow window, int id) {
6641 		if(!UnregisterHotKey(window.impl.hwnd, id))
6642 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
6643 	}
6644 }
6645 
6646 version (X11) {
6647 	pragma(lib, "dl");
6648 	import core.sys.posix.dlfcn;
6649 }
6650 
6651 /++
6652 	Allows for sending synthetic input to the X server via the Xtst
6653 	extension or on Windows using SendInput.
6654 
6655 	Please remember user input is meant to be user - don't use this
6656 	if you have some other alternative!
6657 
6658 	History:
6659 		Added May 17, 2020 with the X implementation.
6660 
6661 		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.)
6662 	Bugs:
6663 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6664 +/
6665 struct SyntheticInput {
6666 	@disable this();
6667 
6668 	private int* refcount;
6669 
6670 	version(X11) {
6671 		private void* lib;
6672 
6673 		private extern(C) {
6674 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6675 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6676 		}
6677 	}
6678 
6679 	/// The dummy param must be 0.
6680 	this(int dummy) {
6681 		version(X11) {
6682 			lib = dlopen("libXtst.so", RTLD_NOW);
6683 			if(lib is null)
6684 				throw new Exception("cannot load xtest lib extension");
6685 			scope(failure)
6686 				dlclose(lib);
6687 
6688 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6689 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6690 
6691 			if(XTestFakeKeyEvent is null)
6692 				throw new Exception("No XTestFakeKeyEvent");
6693 			if(XTestFakeButtonEvent is null)
6694 				throw new Exception("No XTestFakeButtonEvent");
6695 		}
6696 
6697 		refcount = new int;
6698 		*refcount = 1;
6699 	}
6700 
6701 	this(this) {
6702 		if(refcount)
6703 			*refcount += 1;
6704 	}
6705 
6706 	~this() {
6707 		if(refcount) {
6708 			*refcount -= 1;
6709 			if(*refcount == 0)
6710 				// I commented this because if I close the lib before
6711 				// XCloseDisplay, it is liable to segfault... so just
6712 				// gonna keep it loaded if it is loaded, no big deal
6713 				// anyway.
6714 				{} // dlclose(lib);
6715 		}
6716 	}
6717 
6718 	/++
6719 		Simulates typing a string into the keyboard.
6720 
6721 		Bugs:
6722 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6723 
6724 			Not implemented except on Windows and X11.
6725 	+/
6726 	void sendSyntheticInput(string s) {
6727 		version(Windows) {
6728 			INPUT[] inputs;
6729 			inputs.reserve(s.length * 2);
6730 
6731 			auto ei = GetMessageExtraInfo();
6732 
6733 			foreach(wchar c; s) {
6734 				INPUT input;
6735 				input.type = INPUT_KEYBOARD;
6736 				input.ki.wScan = c;
6737 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6738 				input.ki.dwExtraInfo = ei;
6739 				inputs ~= input;
6740 
6741 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6742 				inputs ~= input;
6743 			}
6744 
6745 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6746 				throw new WindowsApiException("SendInput", GetLastError());
6747 			}
6748 		} else version(X11) {
6749 			int delay = 0;
6750 			foreach(ch; s) {
6751 				pressKey(cast(Key) ch, true, delay);
6752 				pressKey(cast(Key) ch, false, delay);
6753 				delay += 5;
6754 			}
6755 		} else throw new NotYetImplementedException();
6756 	}
6757 
6758 	/++
6759 		Sends a fake press or release key event.
6760 
6761 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6762 
6763 		Bugs:
6764 			The `delay` parameter is not implemented yet on Windows.
6765 
6766 			Not implemented except on Windows and X11.
6767 	+/
6768 	void pressKey(Key key, bool pressed, int delay = 0) {
6769 		version(Windows) {
6770 			INPUT input;
6771 			input.type = INPUT_KEYBOARD;
6772 			input.ki.wVk = cast(ushort) key;
6773 
6774 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6775 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6776 
6777 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6778 				throw new WindowsApiException("SendInput", GetLastError());
6779 			}
6780 		} else version(X11) {
6781 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6782 		} else throw new NotYetImplementedException();
6783 	}
6784 
6785 	/++
6786 		Sends a fake mouse button press or release event.
6787 
6788 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6789 
6790 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6791 
6792 		Bugs:
6793 			The `delay` parameter is not implemented yet on Windows.
6794 
6795 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6796 
6797 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6798 	+/
6799 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6800 		version(Windows) {
6801 			INPUT input;
6802 			input.type = INPUT_MOUSE;
6803 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6804 
6805 			// input.mi.mouseData for a wheel event
6806 
6807 			switch(button) {
6808 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6809 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6810 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6811 				case MouseButton.wheelUp:
6812 				case MouseButton.wheelDown:
6813 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6814 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6815 				break;
6816 				case MouseButton.backButton: throw new NotYetImplementedException();
6817 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6818 				default:
6819 			}
6820 
6821 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6822 				throw new WindowsApiException("SendInput", GetLastError());
6823 			}
6824 		} else version(X11) {
6825 			int btn;
6826 
6827 			switch(button) {
6828 				case MouseButton.left: btn = 1; break;
6829 				case MouseButton.middle: btn = 2; break;
6830 				case MouseButton.right: btn = 3; break;
6831 				case MouseButton.wheelUp: btn = 4; break;
6832 				case MouseButton.wheelDown: btn = 5; break;
6833 				case MouseButton.backButton: btn = 8; break;
6834 				case MouseButton.forwardButton: btn = 9; break;
6835 				default:
6836 			}
6837 
6838 			assert(btn);
6839 
6840 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6841 		} else throw new NotYetImplementedException();
6842 	}
6843 
6844 	///
6845 	static void moveMouseArrowBy(int dx, int dy) {
6846 		version(Windows) {
6847 			INPUT input;
6848 			input.type = INPUT_MOUSE;
6849 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6850 			input.mi.dx = dx;
6851 			input.mi.dy = dy;
6852 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
6853 
6854 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6855 				throw new WindowsApiException("SendInput", GetLastError());
6856 			}
6857 		} else version(X11) {
6858 			auto disp = XDisplayConnection.get();
6859 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
6860 			XFlush(disp);
6861 		} else throw new NotYetImplementedException();
6862 	}
6863 
6864 	///
6865 	static void moveMouseArrowTo(int x, int y) {
6866 		version(Windows) {
6867 			INPUT input;
6868 			input.type = INPUT_MOUSE;
6869 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6870 			input.mi.dx = x;
6871 			input.mi.dy = y;
6872 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
6873 
6874 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6875 				throw new WindowsApiException("SendInput", GetLastError());
6876 			}
6877 		} else version(X11) {
6878 			auto disp = XDisplayConnection.get();
6879 			auto root = RootWindow(disp, DefaultScreen(disp));
6880 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
6881 			XFlush(disp);
6882 		} else throw new NotYetImplementedException();
6883 	}
6884 }
6885 
6886 
6887 
6888 /++
6889 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
6890 
6891 	See_Also:
6892 	$(LIST
6893 		*[ScreenPainter]
6894 		*[ScreenPainter.rasterOp]
6895 	)
6896 +/
6897 enum RasterOp {
6898 	normal, /// Replaces the pixel.
6899 	xor, /// Uses bitwise xor to draw.
6900 }
6901 
6902 // being phobos-free keeps the size WAY down
6903 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
6904 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
6905 package(arsd) const(wchar)* toWStringz(string s) {
6906 	wstring r;
6907 	foreach(dchar c; s)
6908 		r ~= c;
6909 	r ~= '\0';
6910 	return r.ptr;
6911 }
6912 private string[] split(in void[] a, char c) {
6913 		string[] ret;
6914 		size_t previous = 0;
6915 		foreach(i, char ch; cast(ubyte[]) a) {
6916 			if(ch == c) {
6917 				ret ~= cast(string) a[previous .. i];
6918 				previous = i + 1;
6919 			}
6920 		}
6921 		if(previous != a.length)
6922 			ret ~= cast(string) a[previous .. $];
6923 		return ret;
6924 	}
6925 
6926 version(without_opengl) {
6927 	enum OpenGlOptions {
6928 		no,
6929 	}
6930 } else {
6931 	/++
6932 		Determines if you want an OpenGL context created on the new window.
6933 
6934 
6935 		See more: [#topics-3d|in the 3d topic].
6936 
6937 		---
6938 		import arsd.simpledisplay;
6939 		void main() {
6940 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
6941 
6942 			// Set up the matrix
6943 			window.setAsCurrentOpenGlContext(); // make this window active
6944 
6945 			// This is called on each frame, we will draw our scene
6946 			window.redrawOpenGlScene = delegate() {
6947 
6948 			};
6949 
6950 			window.eventLoop(0);
6951 		}
6952 		---
6953 	+/
6954 	enum OpenGlOptions {
6955 		no, /// No OpenGL context is created
6956 		yes, /// Yes, create an OpenGL context
6957 	}
6958 
6959 	version(X11) {
6960 		static if (!SdpyIsUsingIVGLBinds) {
6961 
6962 
6963 			struct __GLXFBConfigRec {}
6964 			alias GLXFBConfig = __GLXFBConfigRec*;
6965 
6966 			//pragma(lib, "GL");
6967 			//pragma(lib, "GLU");
6968 			interface GLX {
6969 			extern(C) nothrow @nogc {
6970 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
6971 						const int *attrib_list);
6972 
6973 				 void glXCopyContext(Display *dpy, GLXContext src,
6974 						GLXContext dst, arch_ulong mask);
6975 
6976 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
6977 						GLXContext share_list, Bool direct);
6978 
6979 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
6980 						Pixmap pixmap);
6981 
6982 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
6983 
6984 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
6985 
6986 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
6987 						int attrib, int *value);
6988 
6989 				 GLXContext glXGetCurrentContext();
6990 
6991 				 GLXDrawable glXGetCurrentDrawable();
6992 
6993 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
6994 
6995 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
6996 						GLXContext ctx);
6997 
6998 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
6999 
7000 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7001 
7002 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7003 
7004 				 void glXUseXFont(Font font, int first, int count, int list_base);
7005 
7006 				 void glXWaitGL();
7007 
7008 				 void glXWaitX();
7009 
7010 
7011 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7012 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7013 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7014 
7015 				char* glXQueryExtensionsString (Display*, int);
7016 				void* glXGetProcAddress (const(char)*);
7017 
7018 			}
7019 			}
7020 
7021 			version(OSX)
7022 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7023 			else
7024 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7025 			shared static this() {
7026 				glx.loadDynamicLibrary();
7027 			}
7028 
7029 			alias glbindGetProcAddress = glXGetProcAddress;
7030 		}
7031 	} else version(Windows) {
7032 		/* it is done below by interface GL */
7033 	} else
7034 		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.");
7035 }
7036 
7037 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7038 alias Resizablity = Resizability;
7039 
7040 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7041 enum Resizability {
7042 	fixedSize, /// the window cannot be resized. If it is resized anyway, simpledisplay will position and truncate your drawn content without necessarily informing your program, maintaining the API illusion of a non-resizable window.
7043 	allowResizing, /// the window can be resized. The buffer (if there is one) will automatically adjust size, but not stretch the contents. the windowResized delegate will be called so you can respond to the new size yourself. This allows most control for both user and you as the library consumer, but you also have to do the most work to handle it well.
7044 	/++
7045 		$(PITFALL
7046 			Planned for the future but not implemented.
7047 		)
7048 
7049 		Allow the user to resize the window, but try to maintain the original aspect ratio of the client area. The simpledisplay library may letterbox your content if necessary but will not stretch it. The windowResized delegate and width and height members will be updated with the size.
7050 
7051 		History:
7052 			Added November 11, 2022, but not yet implemented and may not be for some time.
7053 	+/
7054 	/*@__future*/ allowResizingMaintainingAspectRatio,
7055 	/++
7056 		If possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size, letterboxing if needed to keep the aspect ratio. If this is impossible, it will fallback to [fixedSize]. The simpledisplay library will always provide the illusion that your window is the same size you requested, even if it scales things for you, meaning [width] and [height] will never change.
7057 
7058 		History:
7059 			Prior to November 11, 2022, width and height would change, which made this mode harder to use than intended. While I had documented this as a possiblity, I still considered it a bug, a leaky abstraction, and changed the code to tighten it up. After that date, the width and height members, as well as mouse coordinates, are always scaled to maintain the illusion of a fixed canvas size.
7060 
7061 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7062 	+/
7063 	automaticallyScaleIfPossible,
7064 }
7065 
7066 
7067 /++
7068 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7069 +/
7070 enum TextAlignment : uint {
7071 	Left = 0, ///
7072 	Center = 1, ///
7073 	Right = 2, ///
7074 
7075 	VerticalTop = 0, ///
7076 	VerticalCenter = 4, ///
7077 	VerticalBottom = 8, ///
7078 }
7079 
7080 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7081 alias Rectangle = arsd.color.Rectangle;
7082 
7083 
7084 /++
7085 	Keyboard press and release events.
7086 +/
7087 struct KeyEvent {
7088 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7089 	Key key;
7090 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7091 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7092 
7093 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7094 
7095 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7096 
7097 	SimpleWindow window; /// associated Window
7098 
7099 	/++
7100 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7101 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7102 		to predict if char events are actually coming..
7103 
7104 		Only available on X systems since this information is not given ahead of time elsewhere.
7105 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7106 
7107 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7108 		and potential quirks I'd recommend avoiding it.
7109 
7110 		History:
7111 			Added April 26, 2021 (dub v9.5)
7112 	+/
7113 	version(X11)
7114 		dchar[] charsPossible;
7115 
7116 	// convert key event to simplified string representation a-la emacs
7117 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7118 		uint dpos = 0;
7119 		void put (const(char)[] s...) nothrow @trusted {
7120 			static if (growdest) {
7121 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7122 			} else {
7123 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7124 			}
7125 		}
7126 
7127 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7128 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7129 		}
7130 
7131 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7132 
7133 		// put modifiers
7134 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7135 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7136 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7137 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7138 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7139 
7140 		if (this.key) {
7141 			foreach (string kn; __traits(allMembers, Key)) {
7142 				if (this.key == __traits(getMember, Key, kn)) {
7143 					// HACK!
7144 					static if (kn == "N0") put("0");
7145 					else static if (kn == "N1") put("1");
7146 					else static if (kn == "N2") put("2");
7147 					else static if (kn == "N3") put("3");
7148 					else static if (kn == "N4") put("4");
7149 					else static if (kn == "N5") put("5");
7150 					else static if (kn == "N6") put("6");
7151 					else static if (kn == "N7") put("7");
7152 					else static if (kn == "N8") put("8");
7153 					else static if (kn == "N9") put("9");
7154 					else put(kn);
7155 					return dest[0..dpos];
7156 				}
7157 			}
7158 			put("Unknown");
7159 		} else {
7160 			if (dpos && dest[dpos-1] == '+') --dpos;
7161 		}
7162 		return dest[0..dpos];
7163 	}
7164 
7165 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7166 
7167 	/** Parse string into key name with modifiers. It accepts things like:
7168 	 *
7169 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7170 	 *
7171 	 * Ctrl+Win+1 -- windows style
7172 	 *
7173 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7174 	 *
7175 	 * Ctrl Win 1 -- and space
7176 	 *
7177 	 * and even "Win + 1 + Ctrl".
7178 	 */
7179 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7180 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7181 
7182 		// remove trailing spaces
7183 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7184 
7185 		// tokens delimited by blank, '+', or '-'
7186 		// null on eol
7187 		const(char)[] getToken () nothrow @trusted @nogc {
7188 			// remove leading spaces and delimiters
7189 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7190 			if (name.length == 0) return null; // oops, no more tokens
7191 			// get token
7192 			size_t epos = 0;
7193 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7194 			assert(epos > 0 && epos <= name.length);
7195 			auto res = name[0..epos];
7196 			name = name[epos..$];
7197 			return res;
7198 		}
7199 
7200 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7201 			if (s0.length != s1.length) return false;
7202 			foreach (immutable ci, char c0; s0) {
7203 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7204 				char c1 = s1[ci];
7205 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7206 				if (c0 != c1) return false;
7207 			}
7208 			return true;
7209 		}
7210 
7211 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7212 		if (updown !is null) *updown = -1;
7213 		KeyEvent res;
7214 		res.key = cast(Key)0; // just in case
7215 		const(char)[] tk, tkn; // last token
7216 		bool allowEmascStyle = true;
7217 		bool ignoreModifiers = false;
7218 		tokenloop: for (;;) {
7219 			tk = tkn;
7220 			tkn = getToken();
7221 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7222 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7223 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7224 			if (allowEmascStyle && tkn.length != 0) {
7225 				if (tk.length == 1) {
7226 					char mdc = tk[0];
7227 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7228 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7229 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7230 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7231 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7232 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7233 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7234 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7235 				}
7236 			}
7237 			allowEmascStyle = false;
7238 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7239 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7240 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7241 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7242 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7243 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7244 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7245 			if (tk.length == 0) continue;
7246 			// try key name
7247 			if (res.key == 0) {
7248 				// little hack
7249 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7250 					final switch (tk[0]) {
7251 						case '0': tk = "N0"; break;
7252 						case '1': tk = "N1"; break;
7253 						case '2': tk = "N2"; break;
7254 						case '3': tk = "N3"; break;
7255 						case '4': tk = "N4"; break;
7256 						case '5': tk = "N5"; break;
7257 						case '6': tk = "N6"; break;
7258 						case '7': tk = "N7"; break;
7259 						case '8': tk = "N8"; break;
7260 						case '9': tk = "N9"; break;
7261 					}
7262 				}
7263 				foreach (string kn; __traits(allMembers, Key)) {
7264 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7265 				}
7266 			}
7267 			// unknown or duplicate key name, get out of here
7268 			break;
7269 		}
7270 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7271 		return res; // something
7272 	}
7273 
7274 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7275 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7276 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7277 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7278 		}
7279 		bool ignoreMods;
7280 		int updown;
7281 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7282 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7283 		if (this.key != ke.key) {
7284 			// things like "ctrl+alt" are complicated
7285 			uint tkm = this.modifierState&modmask;
7286 			uint kkm = ke.modifierState&modmask;
7287 			Key tk = this.key;
7288 			// ke
7289 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7290 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7291 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7292 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7293 			// this
7294 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7295 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7296 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7297 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7298 			return (tk == ke.key && tkm == kkm);
7299 		}
7300 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7301 	}
7302 }
7303 
7304 /// Sets the application name.
7305 @property string ApplicationName(string name) {
7306 	return _applicationName = name;
7307 }
7308 
7309 string _applicationName;
7310 
7311 /// ditto
7312 @property string ApplicationName() {
7313 	if(_applicationName is null) {
7314 		import core.runtime;
7315 		return Runtime.args[0];
7316 	}
7317 	return _applicationName;
7318 }
7319 
7320 
7321 /// Type of a [MouseEvent].
7322 enum MouseEventType : int {
7323 	motion = 0, /// The mouse moved inside the window
7324 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7325 	buttonReleased = 2, /// A mouse button was released
7326 }
7327 
7328 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7329 /++
7330 	Listen for this on your event listeners if you are interested in mouse action.
7331 
7332 	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.
7333 
7334 	Examples:
7335 
7336 	This will draw boxes on the window with the mouse as you hold the left button.
7337 	---
7338 	import arsd.simpledisplay;
7339 
7340 	void main() {
7341 		auto window = new SimpleWindow();
7342 
7343 		window.eventLoop(0,
7344 			(MouseEvent ev) {
7345 				if(ev.modifierState & ModifierState.leftButtonDown) {
7346 					auto painter = window.draw();
7347 					painter.fillColor = Color.red;
7348 					painter.outlineColor = Color.black;
7349 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7350 				}
7351 			}
7352 		);
7353 	}
7354 	---
7355 +/
7356 struct MouseEvent {
7357 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7358 
7359 	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.
7360 	int y; /// Current Y position of the cursor when the event fired.
7361 
7362 	int dx; /// Change in X position since last report
7363 	int dy; /// Change in Y position since last report
7364 
7365 	MouseButton button; /// See [MouseButton]
7366 	int modifierState; /// See [ModifierState]
7367 
7368 	version(X11)
7369 		private Time timestamp;
7370 
7371 	/// Returns a linear representation of mouse button,
7372 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7373 	///
7374 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7375 	@property ubyte buttonLinear() const {
7376 		import core.bitop;
7377 		if(button == 0)
7378 			return 0;
7379 		return (bsf(button) + 1) & 0b1111;
7380 	}
7381 
7382 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7383 
7384 	SimpleWindow window; /// The window in which the event happened.
7385 
7386 	Point globalCoordinates() {
7387 		Point p;
7388 		if(window is null)
7389 			throw new Exception("wtf");
7390 		static if(UsingSimpledisplayX11) {
7391 			Window child;
7392 			XTranslateCoordinates(
7393 				XDisplayConnection.get,
7394 				window.impl.window,
7395 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7396 				x, y, &p.x, &p.y, &child);
7397 			return p;
7398 		} else version(Windows) {
7399 			POINT[1] points;
7400 			points[0].x = x;
7401 			points[0].y = y;
7402 			MapWindowPoints(
7403 				window.impl.hwnd,
7404 				null,
7405 				points.ptr,
7406 				points.length
7407 			);
7408 			p.x = points[0].x;
7409 			p.y = points[0].y;
7410 
7411 			return p;
7412 		} else version(OSXCocoa) {
7413 			throw new NotYetImplementedException();
7414 		} else static assert(0);
7415 	}
7416 
7417 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7418 
7419 	/**
7420 	can contain emacs-like modifier prefix
7421 	case-insensitive names:
7422 		lmbX/leftX
7423 		rmbX/rightX
7424 		mmbX/middleX
7425 		wheelX
7426 		motion (no prefix allowed)
7427 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7428 	*/
7429 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7430 		if (str.length == 0) return false; // just in case
7431 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7432 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7433 		auto anchor = str;
7434 		uint mods = 0; // uint.max == any
7435 		// interesting bits in kmod
7436 		uint kmodmask =
7437 			ModifierState.shift|
7438 			ModifierState.ctrl|
7439 			ModifierState.alt|
7440 			ModifierState.windows|
7441 			ModifierState.leftButtonDown|
7442 			ModifierState.middleButtonDown|
7443 			ModifierState.rightButtonDown|
7444 			0;
7445 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7446 		bool wasButtons = false;
7447 		while (str.length) {
7448 			if (str.ptr[0] <= ' ') {
7449 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7450 				continue;
7451 			}
7452 			// one-letter modifier?
7453 			if (str.length >= 2 && str.ptr[1] == '-') {
7454 				switch (str.ptr[0]) {
7455 					case '*': // "any" modifier (cannot be undone)
7456 						mods = mods.max;
7457 						break;
7458 					case 'C': case 'c': // emacs "ctrl"
7459 						if (mods != mods.max) mods |= ModifierState.ctrl;
7460 						break;
7461 					case 'M': case 'm': // emacs "meta"
7462 						if (mods != mods.max) mods |= ModifierState.alt;
7463 						break;
7464 					case 'S': case 's': // emacs "shift"
7465 						if (mods != mods.max) mods |= ModifierState.shift;
7466 						break;
7467 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7468 						if (mods != mods.max) mods |= ModifierState.windows;
7469 						break;
7470 					default:
7471 						return false; // unknown modifier
7472 				}
7473 				str = str[2..$];
7474 				continue;
7475 			}
7476 			// word
7477 			char[16] buf = void; // locased
7478 			auto wep = 0;
7479 			while (str.length) {
7480 				immutable char ch = str.ptr[0];
7481 				if (ch <= ' ' || ch == '-') break;
7482 				str = str[1..$];
7483 				if (wep > buf.length) return false; // too long
7484 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7485 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7486 				else return false; // invalid char
7487 			}
7488 			if (wep == 0) return false; // just in case
7489 			uint bnum;
7490 			enum UpDown { None = -1, Up, Down, Any }
7491 			auto updown = UpDown.None; // 0: up; 1: down
7492 			switch (buf[0..wep]) {
7493 				// left button
7494 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7495 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7496 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7497 				case "lmb": case "left": bnum = 0; break;
7498 				// middle button
7499 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7500 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7501 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7502 				case "mmb": case "middle": bnum = 1; break;
7503 				// right button
7504 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7505 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7506 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7507 				case "rmb": case "right": bnum = 2; break;
7508 				// wheel
7509 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7510 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7511 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7512 				case "wheel": bnum = 3; break;
7513 				// motion
7514 				case "motion": bnum = 7; break;
7515 				// unknown
7516 				default: return false;
7517 			}
7518 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7519 			// parse possible "-up" or "-down"
7520 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7521 				wep = 0;
7522 				foreach (immutable idx, immutable char ch; str[1..$]) {
7523 					if (ch <= ' ' || ch == '-') break;
7524 					assert(idx == wep); // for now; trick
7525 					if (wep > buf.length) { wep = 0; break; } // too long
7526 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7527 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7528 					else { wep = 0; break; } // invalid char
7529 				}
7530 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7531 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7532 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7533 				// remove parsed part
7534 				if (updown != UpDown.None) str = str[wep+1..$];
7535 			}
7536 			if (updown == UpDown.None) {
7537 				updown = UpDown.Down;
7538 			}
7539 			wasButtons = wasButtons || (bnum <= 2);
7540 			//assert(updown != UpDown.None);
7541 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7542 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7543 			if (lastButt != lastButt.max) {
7544 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7545 				if (mods != mods.max) {
7546 					uint butbit = 0;
7547 					final switch (lastButt&0x03) {
7548 						case 0: butbit = ModifierState.leftButtonDown; break;
7549 						case 1: butbit = ModifierState.middleButtonDown; break;
7550 						case 2: butbit = ModifierState.rightButtonDown; break;
7551 					}
7552 					     if (lastButt&Flag.Down) mods |= butbit;
7553 					else if (lastButt&Flag.Up) mods &= ~butbit;
7554 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7555 				}
7556 			}
7557 			// remember last button
7558 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7559 		}
7560 		// no button -- nothing to do
7561 		if (lastButt == lastButt.max) return false;
7562 		// done parsing, check if something's left
7563 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7564 		// remove action button from mask
7565 		if ((lastButt&0xff) < 3) {
7566 			final switch (lastButt&0x03) {
7567 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7568 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7569 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7570 			}
7571 		}
7572 		// special case: "Motion" means "ignore buttons"
7573 		if ((lastButt&0xff) == 7 && !wasButtons) {
7574 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7575 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7576 		}
7577 		uint kmod = event.modifierState&kmodmask;
7578 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7579 		// check modifier state
7580 		if (mods != mods.max) {
7581 			if (kmod != mods) return false;
7582 		}
7583 		// now check type
7584 		if ((lastButt&0xff) == 7) {
7585 			// motion
7586 			if (event.type != MouseEventType.motion) return false;
7587 		} else if ((lastButt&0xff) == 3) {
7588 			// wheel
7589 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7590 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7591 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7592 			return false;
7593 		} else {
7594 			// buttons
7595 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7596 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7597 			{
7598 				return false;
7599 			}
7600 			// button number
7601 			switch (lastButt&0x03) {
7602 				case 0: if (event.button != MouseButton.left) return false; break;
7603 				case 1: if (event.button != MouseButton.middle) return false; break;
7604 				case 2: if (event.button != MouseButton.right) return false; break;
7605 				default: return false;
7606 			}
7607 		}
7608 		return true;
7609 	}
7610 }
7611 
7612 version(arsd_mevent_strcmp_test) unittest {
7613 	MouseEvent event;
7614 	event.type = MouseEventType.buttonPressed;
7615 	event.button = MouseButton.left;
7616 	event.modifierState = ModifierState.ctrl;
7617 	assert(event == "C-LMB");
7618 	assert(event != "C-LMBUP");
7619 	assert(event != "C-LMB-UP");
7620 	assert(event != "C-S-LMB");
7621 	assert(event == "*-LMB");
7622 	assert(event != "*-LMB-UP");
7623 
7624 	event.type = MouseEventType.buttonReleased;
7625 	assert(event != "C-LMB");
7626 	assert(event == "C-LMBUP");
7627 	assert(event == "C-LMB-UP");
7628 	assert(event != "C-S-LMB");
7629 	assert(event != "*-LMB");
7630 	assert(event == "*-LMB-UP");
7631 
7632 	event.button = MouseButton.right;
7633 	event.modifierState |= ModifierState.shift;
7634 	event.type = MouseEventType.buttonPressed;
7635 	assert(event != "C-LMB");
7636 	assert(event != "C-LMBUP");
7637 	assert(event != "C-LMB-UP");
7638 	assert(event != "C-S-LMB");
7639 	assert(event != "*-LMB");
7640 	assert(event != "*-LMB-UP");
7641 
7642 	assert(event != "C-RMB");
7643 	assert(event != "C-RMBUP");
7644 	assert(event != "C-RMB-UP");
7645 	assert(event == "C-S-RMB");
7646 	assert(event == "*-RMB");
7647 	assert(event != "*-RMB-UP");
7648 }
7649 
7650 /// This gives a few more options to drawing lines and such
7651 struct Pen {
7652 	Color color; /// the foreground color
7653 	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.
7654 	Style style; /// See [Style]
7655 /+
7656 // From X.h
7657 
7658 #define LineSolid		0
7659 #define LineOnOffDash		1
7660 #define LineDoubleDash		2
7661        LineDou-        The full path of the line is drawn, but the
7662        bleDash         even dashes are filled differently from the
7663                        odd dashes (see fill-style) with CapButt
7664                        style used where even and odd dashes meet.
7665 
7666 
7667 
7668 /* capStyle */
7669 
7670 #define CapNotLast		0
7671 #define CapButt			1
7672 #define CapRound		2
7673 #define CapProjecting		3
7674 
7675 /* joinStyle */
7676 
7677 #define JoinMiter		0
7678 #define JoinRound		1
7679 #define JoinBevel		2
7680 
7681 /* fillStyle */
7682 
7683 #define FillSolid		0
7684 #define FillTiled		1
7685 #define FillStippled		2
7686 #define FillOpaqueStippled	3
7687 
7688 
7689 +/
7690 	/// Style of lines drawn
7691 	enum Style {
7692 		Solid, /// a solid line
7693 		Dashed, /// a dashed line
7694 		Dotted, /// a dotted line
7695 	}
7696 }
7697 
7698 
7699 /++
7700 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7701 
7702 
7703 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7704 
7705 	$(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.)
7706 
7707 	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.
7708 
7709 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7710 
7711 	$(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.
7712 
7713 	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!
7714 
7715 	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!)
7716 
7717 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7718 
7719 	---
7720 		auto image = new Image(256, 256);
7721 		scope(exit) destroy(image);
7722 	---
7723 
7724 	As long as you don't hold on to it outside the scope.
7725 
7726 	I might change it to be an owned pointer at some point in the future.
7727 
7728 	)
7729 
7730 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7731 	you can also often get a fair amount of speedup by getting the raw data format and
7732 	writing some custom code.
7733 
7734 	FIXME INSERT EXAMPLES HERE
7735 
7736 
7737 +/
7738 final class Image {
7739 	///
7740 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7741 		this.width = width;
7742 		this.height = height;
7743 		this.enableAlpha = enableAlpha;
7744 
7745 		impl.createImage(width, height, forcexshm, enableAlpha);
7746 	}
7747 
7748 	///
7749 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7750 		this(size.width, size.height, forcexshm, enableAlpha);
7751 	}
7752 
7753 	private bool suppressDestruction;
7754 
7755 	version(X11)
7756 	this(XImage* handle) {
7757 		this.handle = handle;
7758 		this.rawData = cast(ubyte*) handle.data;
7759 		this.width = handle.width;
7760 		this.height = handle.height;
7761 		this.enableAlpha = handle.depth == 32;
7762 		suppressDestruction = true;
7763 	}
7764 
7765 	~this() {
7766 		if(suppressDestruction) return;
7767 		impl.dispose();
7768 	}
7769 
7770 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7771 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7772 	pure const @system nothrow {
7773 		/*
7774 			To use these to draw a blue rectangle with size WxH at position X,Y...
7775 
7776 			// make certain that it will fit before we proceed
7777 			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!
7778 
7779 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7780 			// (though calculating them isn't really that expensive).
7781 			auto nextLineAdjustment = img.adjustmentForNextLine();
7782 			auto offR = img.redByteOffset();
7783 			auto offB = img.blueByteOffset();
7784 			auto offG = img.greenByteOffset();
7785 			auto bpp = img.bytesPerPixel();
7786 
7787 			auto data = img.getDataPointer();
7788 
7789 			// figure out the starting byte offset
7790 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7791 
7792 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7793 
7794 			// and now our drawing loop for the rectangle
7795 			foreach(y; 0 .. H) {
7796 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7797 				foreach(x; 0 .. W) {
7798 					// write our color
7799 					data[offR] = 0;
7800 					data[offG] = 0;
7801 					data[offB] = 255;
7802 
7803 					data += bpp; // moving to the next pixel is just an addition...
7804 				}
7805 				startOfLine += nextLineAdjustment;
7806 			}
7807 
7808 
7809 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7810 
7811 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7812 			can be made into a bitmask or something so we can write them as *uint...
7813 		*/
7814 
7815 		///
7816 		int offsetForTopLeftPixel() {
7817 			version(X11) {
7818 				return 0;
7819 			} else version(Windows) {
7820 				if(enableAlpha) {
7821 					return (width * 4) * (height - 1);
7822 				} else {
7823 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7824 				}
7825 			} else version(OSXCocoa) {
7826 				return 0 ; //throw new NotYetImplementedException();
7827 			} else static assert(0, "fill in this info for other OSes");
7828 		}
7829 
7830 		///
7831 		int offsetForPixel(int x, int y) {
7832 			version(X11) {
7833 				auto offset = (y * width + x) * 4;
7834 				return offset;
7835 			} else version(Windows) {
7836 				if(enableAlpha) {
7837 					auto itemsPerLine = width * 4;
7838 					// remember, bmps are upside down
7839 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7840 					return offset;
7841 				} else {
7842 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7843 					// remember, bmps are upside down
7844 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7845 					return offset;
7846 				}
7847 			} else version(OSXCocoa) {
7848 				return 0 ; //throw new NotYetImplementedException();
7849 			} else static assert(0, "fill in this info for other OSes");
7850 		}
7851 
7852 		///
7853 		int adjustmentForNextLine() {
7854 			version(X11) {
7855 				return width * 4;
7856 			} else version(Windows) {
7857 				// windows bmps are upside down, so the adjustment is actually negative
7858 				if(enableAlpha)
7859 					return - (cast(int) width * 4);
7860 				else
7861 					return -((cast(int) width * 3 + 3) / 4) * 4;
7862 			} else version(OSXCocoa) {
7863 				return 0 ; //throw new NotYetImplementedException();
7864 			} else static assert(0, "fill in this info for other OSes");
7865 		}
7866 
7867 		/// once you have the position of a pixel, use these to get to the proper color
7868 		int redByteOffset() {
7869 			version(X11) {
7870 				return 2;
7871 			} else version(Windows) {
7872 				return 2;
7873 			} else version(OSXCocoa) {
7874 				return 0 ; //throw new NotYetImplementedException();
7875 			} else static assert(0, "fill in this info for other OSes");
7876 		}
7877 
7878 		///
7879 		int greenByteOffset() {
7880 			version(X11) {
7881 				return 1;
7882 			} else version(Windows) {
7883 				return 1;
7884 			} else version(OSXCocoa) {
7885 				return 0 ; //throw new NotYetImplementedException();
7886 			} else static assert(0, "fill in this info for other OSes");
7887 		}
7888 
7889 		///
7890 		int blueByteOffset() {
7891 			version(X11) {
7892 				return 0;
7893 			} else version(Windows) {
7894 				return 0;
7895 			} else version(OSXCocoa) {
7896 				return 0 ; //throw new NotYetImplementedException();
7897 			} else static assert(0, "fill in this info for other OSes");
7898 		}
7899 
7900 		/// Only valid if [enableAlpha] is true
7901 		int alphaByteOffset() {
7902 			version(X11) {
7903 				return 3;
7904 			} else version(Windows) {
7905 				return 3;
7906 			} else version(OSXCocoa) {
7907 				return 3; //throw new NotYetImplementedException();
7908 			} else static assert(0, "fill in this info for other OSes");
7909 		}
7910 	}
7911 
7912 	///
7913 	final void putPixel(int x, int y, Color c) {
7914 		if(x < 0 || x >= width)
7915 			return;
7916 		if(y < 0 || y >= height)
7917 			return;
7918 
7919 		impl.setPixel(x, y, c);
7920 	}
7921 
7922 	///
7923 	final Color getPixel(int x, int y) {
7924 		if(x < 0 || x >= width)
7925 			return Color.transparent;
7926 		if(y < 0 || y >= height)
7927 			return Color.transparent;
7928 
7929 		version(OSXCocoa) throw new NotYetImplementedException(); else
7930 		return impl.getPixel(x, y);
7931 	}
7932 
7933 	///
7934 	final void opIndexAssign(Color c, int x, int y) {
7935 		putPixel(x, y, c);
7936 	}
7937 
7938 	///
7939 	TrueColorImage toTrueColorImage() {
7940 		auto tci = new TrueColorImage(width, height);
7941 		convertToRgbaBytes(tci.imageData.bytes);
7942 		return tci;
7943 	}
7944 
7945 	///
7946 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) {
7947 		auto tci = i.getAsTrueColorImage();
7948 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
7949 		img.setRgbaBytes(tci.imageData.bytes);
7950 		return img;
7951 	}
7952 
7953 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
7954 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
7955 	/// if you pass null, it will allocate a new one.
7956 	ubyte[] getRgbaBytes(ubyte[] where = null) {
7957 		if(where is null)
7958 			where = new ubyte[this.width*this.height*4];
7959 		convertToRgbaBytes(where);
7960 		return where;
7961 	}
7962 
7963 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
7964 	void setRgbaBytes(in ubyte[] from ) {
7965 		assert(from.length == this.width * this.height * 4);
7966 		setFromRgbaBytes(from);
7967 	}
7968 
7969 	// FIXME: make properly cross platform by getting rgba right
7970 
7971 	/// warning: this is not portable across platforms because the data format can change
7972 	ubyte* getDataPointer() {
7973 		return impl.rawData;
7974 	}
7975 
7976 	/// for use with getDataPointer
7977 	final int bytesPerLine() const pure @safe nothrow {
7978 		version(Windows)
7979 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
7980 		else version(X11)
7981 			return 4 * width;
7982 		else version(OSXCocoa)
7983 			return 4 * width;
7984 		else static assert(0);
7985 	}
7986 
7987 	/// for use with getDataPointer
7988 	final int bytesPerPixel() const pure @safe nothrow {
7989 		version(Windows)
7990 			return enableAlpha ? 4 : 3;
7991 		else version(X11)
7992 			return 4;
7993 		else version(OSXCocoa)
7994 			return 4;
7995 		else static assert(0);
7996 	}
7997 
7998 	///
7999 	immutable int width;
8000 
8001 	///
8002 	immutable int height;
8003 
8004 	///
8005 	immutable bool enableAlpha;
8006     //private:
8007 	mixin NativeImageImplementation!() impl;
8008 }
8009 
8010 /++
8011 	A convenience function to pop up a window displaying the image.
8012 	If you pass a win, it will draw the image in it. Otherwise, it will
8013 	create a window with the size of the image and run its event loop, closing
8014 	when a key is pressed.
8015 
8016 	History:
8017 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8018 		always block until the application quit which could cause bizarre behavior
8019 		inside a more complex application. Now, the default is to block until
8020 		this window closes if it is the only event loop running, and otherwise,
8021 		not to block at all and just pop up the display window asynchronously.
8022 +/
8023 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8024 	if(win is null) {
8025 		win = new SimpleWindow(image);
8026 		{
8027 			auto p = win.draw;
8028 			p.drawImage(Point(0, 0), image);
8029 		}
8030 		win.eventLoopWithBlockingMode(
8031 			bm, 0,
8032 			(KeyEvent ev) {
8033 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8034 			} );
8035 	} else {
8036 		win.image = image;
8037 	}
8038 }
8039 
8040 enum FontWeight : int {
8041 	dontcare = 0,
8042 	thin = 100,
8043 	extralight = 200,
8044 	light = 300,
8045 	regular = 400,
8046 	medium = 500,
8047 	semibold = 600,
8048 	bold = 700,
8049 	extrabold = 800,
8050 	heavy = 900
8051 }
8052 
8053 /++
8054 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8055 
8056 	History:
8057 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8058 +/
8059 interface MeasurableFont {
8060 	/++
8061 		Returns true if it is a monospace font, meaning each of the
8062 		glyphs (at least the ascii characters) have matching width
8063 		and no kerning, so you can determine the display width of some
8064 		strings by simply multiplying the string width by [averageWidth].
8065 
8066 		(Please note that multiply doesn't $(I actually) work in general,
8067 		consider characters like tab and newline, but it does sometimes.)
8068 	+/
8069 	bool isMonospace();
8070 
8071 	/++
8072 		The average width of glyphs in the font, traditionally equal to the
8073 		width of the lowercase x. Can be used to estimate bounding boxes,
8074 		especially if the font [isMonospace].
8075 
8076 		Given in pixels.
8077 	+/
8078 	int averageWidth();
8079 	/++
8080 		The height of the bounding box of a line.
8081 	+/
8082 	int height();
8083 	/++
8084 		The maximum ascent of a glyph above the baseline.
8085 
8086 		Given in pixels.
8087 	+/
8088 	int ascent();
8089 	/++
8090 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8091 
8092 		Given in pixels.
8093 	+/
8094 	int descent();
8095 	/++
8096 		The display width of the given string, and if you provide a window, it will use it to
8097 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8098 
8099 		Given in pixels.
8100 	+/
8101 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8102 
8103 }
8104 
8105 // FIXME: i need a font cache and it needs to handle disconnects.
8106 
8107 /++
8108 	Represents a font loaded off the operating system or the X server.
8109 
8110 
8111 	While the api here is unified cross platform, the fonts are not necessarily
8112 	available, even across machines of the same platform, so be sure to always check
8113 	for null (using [isNull]) and have a fallback plan.
8114 
8115 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8116 
8117 	Worst case, a null font will automatically fall back to the default font loaded
8118 	for your system.
8119 +/
8120 class OperatingSystemFont : MeasurableFont {
8121 	// FIXME: when the X Connection is lost, these need to be invalidated!
8122 	// that means I need to store the original stuff again to reconstruct it too.
8123 
8124 	version(X11) {
8125 		XFontStruct* font;
8126 		XFontSet fontset;
8127 
8128 		version(with_xft) {
8129 			XftFont* xftFont;
8130 			bool isXft;
8131 		}
8132 	} else version(Windows) {
8133 		HFONT font;
8134 		int width_;
8135 		int height_;
8136 	} else version(OSXCocoa) {
8137 		// FIXME
8138 	} else static assert(0);
8139 
8140 	/++
8141 		Constructs the class and immediately calls [load].
8142 	+/
8143 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8144 		load(name, size, weight, italic);
8145 	}
8146 
8147 	/++
8148 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8149 
8150 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8151 
8152 		History:
8153 			Added January 24, 2021.
8154 	+/
8155 	this() {
8156 		// this space intentionally left blank
8157 	}
8158 
8159 	/++
8160 		Constructs a copy of the given font object.
8161 
8162 		History:
8163 			Added January 7, 2023.
8164 	+/
8165 	this(OperatingSystemFont font) {
8166 		if(font is null || font.loadedInfo is LoadedInfo.init)
8167 			loadDefault();
8168 		else
8169 			load(font.loadedInfo.tupleof);
8170 	}
8171 
8172 	/++
8173 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8174 
8175 		History:
8176 			Added November 13, 2020.
8177 	+/
8178 	version(with_xft)
8179 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8180 		unload();
8181 
8182 		if(!XftLibrary.attempted) {
8183 			XftLibrary.loadDynamicLibrary();
8184 		}
8185 
8186 		if(!XftLibrary.loadSuccessful)
8187 			return false;
8188 
8189 		auto display = XDisplayConnection.get;
8190 
8191 		char[256] nameBuffer = void;
8192 		int nbp = 0;
8193 
8194 		void add(in char[] a) {
8195 			nameBuffer[nbp .. nbp + a.length] = a[];
8196 			nbp += a.length;
8197 		}
8198 		add(name);
8199 
8200 		if(size) {
8201 			add(":size=");
8202 			add(toInternal!string(size));
8203 		}
8204 		if(weight != FontWeight.dontcare) {
8205 			add(":weight=");
8206 			add(weightToString(weight));
8207 		}
8208 		if(italic)
8209 			add(":slant=100");
8210 
8211 		nameBuffer[nbp] = 0;
8212 
8213 		this.xftFont = XftFontOpenName(
8214 			display,
8215 			DefaultScreen(display),
8216 			nameBuffer.ptr
8217 		);
8218 
8219 		this.isXft = true;
8220 
8221 		if(xftFont !is null) {
8222 			isMonospace_ = stringWidth("x") == stringWidth("M");
8223 			ascent_ = xftFont.ascent;
8224 			descent_ = xftFont.descent;
8225 		}
8226 
8227 		return !isNull();
8228 	}
8229 
8230 	/++
8231 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8232 
8233 
8234 		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.
8235 
8236 		If `pattern` is null, it returns all available font families.
8237 
8238 		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.
8239 
8240 		The format of the pattern is platform-specific.
8241 
8242 		History:
8243 			Added May 1, 2021 (dub v9.5)
8244 	+/
8245 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8246 		version(Windows) {
8247 			auto hdc = GetDC(null);
8248 			scope(exit) ReleaseDC(null, hdc);
8249 			LOGFONT logfont;
8250 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8251 				auto localHandler = *(cast(typeof(handler)*) p);
8252 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8253 			}
8254 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8255 		} else version(X11) {
8256 			//import core.stdc.stdio;
8257 			bool done = false;
8258 			version(with_xft) {
8259 				if(!XftLibrary.attempted) {
8260 					XftLibrary.loadDynamicLibrary();
8261 				}
8262 
8263 				if(!XftLibrary.loadSuccessful)
8264 					goto skipXft;
8265 
8266 				if(!FontConfigLibrary.attempted)
8267 					FontConfigLibrary.loadDynamicLibrary();
8268 				if(!FontConfigLibrary.loadSuccessful)
8269 					goto skipXft;
8270 
8271 				{
8272 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8273 					if(got is null)
8274 						goto skipXft;
8275 					scope(exit) FcFontSetDestroy(got);
8276 
8277 					auto fontPatterns = got.fonts[0 .. got.nfont];
8278 					foreach(candidate; fontPatterns) {
8279 						char* where, whereStyle;
8280 
8281 						char* pmg = FcNameUnparse(candidate);
8282 
8283 						//FcPatternGetString(candidate, "family", 0, &where);
8284 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8285 						//if(where && whereStyle) {
8286 						if(pmg) {
8287 							if(!handler(pmg.sliceCString))
8288 								return;
8289 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8290 						}
8291 					}
8292 				}
8293 			}
8294 
8295 			skipXft:
8296 
8297 			if(pattern is null)
8298 				pattern = "*";
8299 
8300 			int count;
8301 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8302 			scope(exit) XFreeFontNames(coreFontsRaw);
8303 
8304 			auto coreFonts = coreFontsRaw[0 .. count];
8305 
8306 			foreach(font; coreFonts) {
8307 				char[128] tmp;
8308 				tmp[0 ..5] = "core:";
8309 				auto cf = font.sliceCString;
8310 				if(5 + cf.length > tmp.length)
8311 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8312 				tmp[5 .. 5 + cf.length] = cf;
8313 				if(!handler(tmp[0 .. 5 + cf.length]))
8314 					return;
8315 			}
8316 		}
8317 	}
8318 
8319 	/++
8320 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8321 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8322 
8323 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8324 		underlying system doesn't support returning the raw bytes.
8325 
8326 		History:
8327 			Added September 10, 2021 (dub v10.3)
8328 	+/
8329 	ubyte[] getTtfBytes() {
8330 		if(isNull)
8331 			return null;
8332 
8333 		version(Windows) {
8334 			auto dc = GetDC(null);
8335 			auto orig = SelectObject(dc, font);
8336 
8337 			scope(exit) {
8338 				SelectObject(dc, orig);
8339 				ReleaseDC(null, dc);
8340 			}
8341 
8342 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8343 			if(res == GDI_ERROR)
8344 				return null;
8345 
8346 			ubyte[] buffer = new ubyte[](res);
8347 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8348 			if(res == GDI_ERROR)
8349 				return null; // wtf really tbh
8350 
8351 			return buffer;
8352 		} else version(with_xft) {
8353 			if(isXft && xftFont) {
8354 				if(!FontConfigLibrary.attempted)
8355 					FontConfigLibrary.loadDynamicLibrary();
8356 				if(!FontConfigLibrary.loadSuccessful)
8357 					return null;
8358 
8359 				char* file;
8360 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8361 					if (file !is null && file[0]) {
8362 						import core.stdc.stdio;
8363 						auto fp = fopen(file, "rb");
8364 						if(fp is null)
8365 							return null;
8366 						scope(exit)
8367 							fclose(fp);
8368 						fseek(fp, 0, SEEK_END);
8369 						ubyte[] buffer = new ubyte[](ftell(fp));
8370 						fseek(fp, 0, SEEK_SET);
8371 
8372 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8373 						if(got != buffer.length)
8374 							return null;
8375 
8376 						return buffer;
8377 					}
8378 				}
8379 			}
8380 			return null;
8381 		}
8382 	}
8383 
8384 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8385 
8386 	private string weightToString(FontWeight weight) {
8387 		with(FontWeight)
8388 		final switch(weight) {
8389 			case dontcare: return "*";
8390 			case thin: return "extralight";
8391 			case extralight: return "extralight";
8392 			case light: return "light";
8393 			case regular: return "regular";
8394 			case medium: return "medium";
8395 			case semibold: return "demibold";
8396 			case bold: return "bold";
8397 			case extrabold: return "demibold";
8398 			case heavy: return "black";
8399 		}
8400 	}
8401 
8402 	/++
8403 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8404 
8405 		History:
8406 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8407 	+/
8408 	version(X11)
8409 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8410 		unload();
8411 
8412 		string xfontstr;
8413 
8414 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8415 			// this is kinda a disgusting hack but if the user sends an exact
8416 			// string I'd like to honor it...
8417 			xfontstr = name;
8418 		} else {
8419 			string weightstr = weightToString(weight);
8420 			string sizestr;
8421 			if(size == 0)
8422 				sizestr = "*";
8423 			else
8424 				sizestr = toInternal!string(size);
8425 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8426 		}
8427 
8428 		//import std.stdio; writeln(xfontstr);
8429 
8430 		auto display = XDisplayConnection.get;
8431 
8432 		font = XLoadQueryFont(display, xfontstr.ptr);
8433 		if(font is null)
8434 			return false;
8435 
8436 		char** lol;
8437 		int lol2;
8438 		char* lol3;
8439 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8440 
8441 		prepareFontInfo();
8442 
8443 		return !isNull();
8444 	}
8445 
8446 	version(X11)
8447 	private void prepareFontInfo() {
8448 		if(font !is null) {
8449 			isMonospace_ = stringWidth("l") == stringWidth("M");
8450 			ascent_ = font.max_bounds.ascent;
8451 			descent_ = font.max_bounds.descent;
8452 		}
8453 	}
8454 
8455 	/++
8456 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8457 
8458 		History:
8459 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8460 	+/
8461 	version(Windows)
8462 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8463 		unload();
8464 
8465 		WCharzBuffer buffer = WCharzBuffer(name);
8466 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8467 
8468 		prepareFontInfo(hdc);
8469 
8470 		return !isNull();
8471 	}
8472 
8473 	version(Windows)
8474 	void prepareFontInfo(HDC hdc = null) {
8475 		if(font is null)
8476 			return;
8477 
8478 		TEXTMETRIC tm;
8479 		auto dc = hdc ? hdc : GetDC(null);
8480 		auto orig = SelectObject(dc, font);
8481 		GetTextMetrics(dc, &tm);
8482 		SelectObject(dc, orig);
8483 		if(hdc is null)
8484 			ReleaseDC(null, dc);
8485 
8486 		width_ = tm.tmAveCharWidth;
8487 		height_ = tm.tmHeight;
8488 		ascent_ = tm.tmAscent;
8489 		descent_ = tm.tmDescent;
8490 		// 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.
8491 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8492 	}
8493 
8494 
8495 	/++
8496 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8497 
8498 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8499 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8500 
8501 		On Windows, it forwards directly to [loadWin32].
8502 
8503 		Params:
8504 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8505 			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.
8506 			weight = approximate boldness, results may vary.
8507 			italic = try to get a slanted version of the given font.
8508 
8509 		History:
8510 			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.
8511 	+/
8512 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8513 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
8514 		version(X11) {
8515 			version(with_xft) {
8516 				if(name.length > 5 && name[0 .. 5] == "core:") {
8517 					goto core;
8518 				}
8519 
8520 				if(loadXft(name, size, weight, italic))
8521 					return true;
8522 				// if xft fails, fallback to core to avoid breaking
8523 				// code that already depended on this.
8524 			}
8525 
8526 			core:
8527 
8528 			if(name.length > 5 && name[0 .. 5] == "core:") {
8529 				name = name[5 .. $];
8530 			}
8531 
8532 			return loadCoreX(name, size, weight, italic);
8533 		} else version(Windows) {
8534 			return loadWin32(name, size, weight, italic);
8535 		} else version(OSXCocoa) {
8536 			// FIXME
8537 			return false;
8538 		} else static assert(0);
8539 	}
8540 
8541 	private struct LoadedInfo {
8542 		string name;
8543 		int size;
8544 		FontWeight weight;
8545 		bool italic;
8546 	}
8547 	private LoadedInfo loadedInfo;
8548 
8549 	///
8550 	void unload() {
8551 		if(isNull())
8552 			return;
8553 
8554 		version(X11) {
8555 			auto display = XDisplayConnection.display;
8556 
8557 			if(display is null)
8558 				return;
8559 
8560 			version(with_xft) {
8561 				if(isXft) {
8562 					if(xftFont)
8563 						XftFontClose(display, xftFont);
8564 					isXft = false;
8565 					xftFont = null;
8566 					return;
8567 				}
8568 			}
8569 
8570 			if(font && font !is ScreenPainterImplementation.defaultfont)
8571 				XFreeFont(display, font);
8572 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8573 				XFreeFontSet(display, fontset);
8574 
8575 			font = null;
8576 			fontset = null;
8577 		} else version(Windows) {
8578 			DeleteObject(font);
8579 			font = null;
8580 		} else version(OSXCocoa) {
8581 			// FIXME
8582 		} else static assert(0);
8583 	}
8584 
8585 	private bool isMonospace_;
8586 
8587 	/++
8588 		History:
8589 			Added January 16, 2021
8590 	+/
8591 	bool isMonospace() {
8592 		return isMonospace_;
8593 	}
8594 
8595 	/++
8596 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8597 
8598 		History:
8599 			Added March 26, 2020
8600 			Documented January 16, 2021
8601 	+/
8602 	int averageWidth() {
8603 		version(X11) {
8604 			return stringWidth("x");
8605 		} else version(Windows)
8606 			return width_;
8607 		else assert(0);
8608 	}
8609 
8610 	/++
8611 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8612 
8613 		History:
8614 			Added January 16, 2021
8615 	+/
8616 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8617 	// FIXME: what about tab?
8618 		if(isNull)
8619 			return 0;
8620 
8621 		version(X11) {
8622 			version(with_xft)
8623 				if(isXft && xftFont !is null) {
8624 					//return xftFont.max_advance_width;
8625 					XGlyphInfo extents;
8626 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8627 					//import std.stdio; writeln(extents);
8628 					return extents.xOff;
8629 				}
8630 			if(font is null)
8631 				return 0;
8632 			else if(fontset) {
8633 				XRectangle rect;
8634 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8635 
8636 				return rect.width;
8637 			} else {
8638 				return XTextWidth(font, s.ptr, cast(int) s.length);
8639 			}
8640 		} else version(Windows) {
8641 			WCharzBuffer buffer = WCharzBuffer(s);
8642 
8643 			return stringWidth(buffer.slice, window);
8644 		}
8645 		else assert(0);
8646 	}
8647 
8648 	version(Windows)
8649 	/// ditto
8650 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8651 		if(isNull)
8652 			return 0;
8653 		version(Windows) {
8654 			SIZE size;
8655 
8656 			prepareContext(window);
8657 			scope(exit) releaseContext();
8658 
8659 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8660 
8661 			return size.cx;
8662 		} else {
8663 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8664 			static assert(0, "not implemented yet");
8665 			//return stringWidth(s, window);
8666 		}
8667 	}
8668 
8669 	private {
8670 		int prepRefcount;
8671 
8672 		version(Windows) {
8673 			HDC dc;
8674 			HANDLE orig;
8675 			HWND hwnd;
8676 		}
8677 	}
8678 	/++
8679 		[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.
8680 
8681 		History:
8682 			Added January 23, 2021
8683 	+/
8684 	void prepareContext(SimpleWindow window = null) {
8685 		prepRefcount++;
8686 		if(prepRefcount == 1) {
8687 			version(Windows) {
8688 				hwnd = window is null ? null : window.impl.hwnd;
8689 				dc = GetDC(hwnd);
8690 				orig = SelectObject(dc, font);
8691 			}
8692 		}
8693 	}
8694 	/// ditto
8695 	void releaseContext() {
8696 		prepRefcount--;
8697 		if(prepRefcount == 0) {
8698 			version(Windows) {
8699 				SelectObject(dc, orig);
8700 				ReleaseDC(hwnd, dc);
8701 				hwnd = null;
8702 				dc = null;
8703 				orig = null;
8704 			}
8705 		}
8706 	}
8707 
8708 	/+
8709 		FIXME: I think I need advance and kerning pair
8710 
8711 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8712 	+/
8713 
8714 	/++
8715 		Returns the height of the font.
8716 
8717 		History:
8718 			Added March 26, 2020
8719 			Documented January 16, 2021
8720 	+/
8721 	int height() {
8722 		version(X11) {
8723 			version(with_xft)
8724 				if(isXft && xftFont !is null) {
8725 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8726 				}
8727 			if(font is null)
8728 				return 0;
8729 			return font.max_bounds.ascent + font.max_bounds.descent;
8730 		} else version(Windows)
8731 			return height_;
8732 		else assert(0);
8733 	}
8734 
8735 	private int ascent_;
8736 	private int descent_;
8737 
8738 	/++
8739 		Max ascent above the baseline.
8740 
8741 		History:
8742 			Added January 22, 2021
8743 	+/
8744 	int ascent() {
8745 		return ascent_;
8746 	}
8747 
8748 	/++
8749 		Max descent below the baseline.
8750 
8751 		History:
8752 			Added January 22, 2021
8753 	+/
8754 	int descent() {
8755 		return descent_;
8756 	}
8757 
8758 	/++
8759 		Loads the default font used by [ScreenPainter] if none others are loaded.
8760 
8761 		Returns:
8762 			This method mutates the `this` object, but then returns `this` for
8763 			easy chaining like:
8764 
8765 			---
8766 			auto font = foo.isNull ? foo : foo.loadDefault
8767 			---
8768 
8769 		History:
8770 			Added previously, but left unimplemented until January 24, 2021.
8771 	+/
8772 	OperatingSystemFont loadDefault() {
8773 		unload();
8774 
8775 		loadedInfo = LoadedInfo.init;
8776 
8777 		version(X11) {
8778 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8779 			// but meh since sdpy does its own thing, this should be ok too
8780 
8781 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8782 			this.font = ScreenPainterImplementation.defaultfont;
8783 			this.fontset = ScreenPainterImplementation.defaultfontset;
8784 
8785 			prepareFontInfo();
8786 		} else version(Windows) {
8787 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8788 			this.font = ScreenPainterImplementation.defaultGuiFont;
8789 
8790 			prepareFontInfo();
8791 		} else throw new NotYetImplementedException();
8792 
8793 		return this;
8794 	}
8795 
8796 	///
8797 	bool isNull() {
8798 		version(OSXCocoa) throw new NotYetImplementedException(); else {
8799 			version(with_xft)
8800 				if(isXft)
8801 					return xftFont is null;
8802 			return font is null;
8803 		}
8804 	}
8805 
8806 	/* Metrics */
8807 	/+
8808 		GetABCWidth
8809 		GetKerningPairs
8810 
8811 		if I do it right, I can size it all here, and match
8812 		what happens when I draw the full string with the OS functions.
8813 
8814 		subclasses might do the same thing while getting the glyphs on images
8815 	struct GlyphInfo {
8816 		int glyph;
8817 
8818 		size_t stringIdxStart;
8819 		size_t stringIdxEnd;
8820 
8821 		Rectangle boundingBox;
8822 	}
8823 	GlyphInfo[] getCharBoxes() {
8824 		// XftTextExtentsUtf8
8825 		return null;
8826 
8827 	}
8828 	+/
8829 
8830 	~this() {
8831 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
8832 		unload();
8833 	}
8834 }
8835 
8836 version(Windows)
8837 private string sliceCString(const(wchar)[] w) {
8838 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
8839 }
8840 
8841 private inout(char)[] sliceCString(inout(char)* s) {
8842 	import core.stdc.string;
8843 	auto len = strlen(s);
8844 	return s[0 .. len];
8845 }
8846 
8847 /**
8848 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
8849 	than constructing it directly. Then, it is reference counted so you can pass it
8850 	at around and when the last ref goes out of scope, the buffered drawing activities
8851 	are all carried out.
8852 
8853 
8854 	Most functions use the outlineColor instead of taking a color themselves.
8855 	ScreenPainter is reference counted and draws its buffer to the screen when its
8856 	final reference goes out of scope.
8857 */
8858 struct ScreenPainter {
8859 	CapableOfBeingDrawnUpon window;
8860 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) {
8861 		this.window = window;
8862 		if(window.closed)
8863 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
8864 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
8865 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
8866 		if(window.activeScreenPainter !is null) {
8867 			impl = window.activeScreenPainter;
8868 			if(impl.referenceCount == 0) {
8869 				impl.window = window;
8870 				impl.create(handle);
8871 			}
8872 			impl.manualInvalidations = manualInvalidations;
8873 			impl.referenceCount++;
8874 		//	writeln("refcount ++ ", impl.referenceCount);
8875 		} else {
8876 			impl = new ScreenPainterImplementation;
8877 			impl.window = window;
8878 			impl.create(handle);
8879 			impl.referenceCount = 1;
8880 			impl.manualInvalidations = manualInvalidations;
8881 			window.activeScreenPainter = impl;
8882 			//import std.stdio; writeln("constructed");
8883 		}
8884 
8885 		copyActiveOriginals();
8886 	}
8887 
8888 	/++
8889 		EXPERIMENTAL. subject to change.
8890 
8891 		When you draw a cursor, you can draw this to notify your window of where it is,
8892 		for IME systems to use.
8893 	+/
8894 	void notifyCursorPosition(int x, int y, int width, int height) {
8895 		if(auto w = cast(SimpleWindow) window) {
8896 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
8897 		}
8898 	}
8899 
8900 	/++
8901 		If you are using manual invalidations, this informs the
8902 		window system that a section needs to be redrawn.
8903 
8904 		If you didn't opt into manual invalidation, you don't
8905 		have to call this.
8906 
8907 		History:
8908 			Added December 30, 2021 (dub v10.5)
8909 	+/
8910 	void invalidateRect(Rectangle rect) {
8911 		if(impl is null) return;
8912 
8913 		// transform(rect)
8914 		rect.left += _originX;
8915 		rect.right += _originX;
8916 		rect.top += _originY;
8917 		rect.bottom += _originY;
8918 
8919 		impl.invalidateRect(rect);
8920 	}
8921 
8922 	private Pen originalPen;
8923 	private Color originalFillColor;
8924 	private arsd.color.Rectangle originalClipRectangle;
8925 	private OperatingSystemFont originalFont;
8926 	void copyActiveOriginals() {
8927 		if(impl is null) return;
8928 		originalPen = impl._activePen;
8929 		originalFillColor = impl._fillColor;
8930 		originalClipRectangle = impl._clipRectangle;
8931 		originalFont = impl._activeFont;
8932 	}
8933 
8934 	~this() {
8935 		if(impl is null) return;
8936 		impl.referenceCount--;
8937 		//writeln("refcount -- ", impl.referenceCount);
8938 		if(impl.referenceCount == 0) {
8939 			//import std.stdio; writeln("destructed");
8940 			impl.dispose();
8941 			*window.activeScreenPainter = ScreenPainterImplementation.init;
8942 			//import std.stdio; writeln("paint finished");
8943 		} else {
8944 			// there is still an active reference, reset stuff so the
8945 			// next user doesn't get weirdness via the reference
8946 			this.rasterOp = RasterOp.normal;
8947 			pen = originalPen;
8948 			fillColor = originalFillColor;
8949 			if(originalFont)
8950 				setFont(originalFont);
8951 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
8952 		}
8953 	}
8954 
8955 	this(this) {
8956 		if(impl is null) return;
8957 		impl.referenceCount++;
8958 		//writeln("refcount ++ ", impl.referenceCount);
8959 
8960 		copyActiveOriginals();
8961 	}
8962 
8963 	private int _originX;
8964 	private int _originY;
8965 	@property int originX() { return _originX; }
8966 	@property int originY() { return _originY; }
8967 	@property int originX(int a) {
8968 		_originX = a;
8969 		return _originX;
8970 	}
8971 	@property int originY(int a) {
8972 		_originY = a;
8973 		return _originY;
8974 	}
8975 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
8976 	private void transform(ref Point p) {
8977 		if(impl is null) return;
8978 		p.x += _originX;
8979 		p.y += _originY;
8980 	}
8981 
8982 	// this needs to be checked BEFORE the originX/Y transformation
8983 	private bool isClipped(Point p) {
8984 		return !currentClipRectangle.contains(p);
8985 	}
8986 	private bool isClipped(Point p, int width, int height) {
8987 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
8988 	}
8989 	private bool isClipped(Point p, Size s) {
8990 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
8991 	}
8992 	private bool isClipped(Point p, Point p2) {
8993 		// need to ensure the end points are actually included inside, so the +1 does that
8994 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
8995 	}
8996 
8997 
8998 	/++
8999 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9000 
9001 		Returns:
9002 			The old clip rectangle.
9003 
9004 		History:
9005 			Return value was `void` prior to May 10, 2021.
9006 
9007 	+/
9008 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9009 		if(impl is null) return currentClipRectangle;
9010 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9011 			return currentClipRectangle; // no need to do anything
9012 		auto old = currentClipRectangle;
9013 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9014 		transform(pt);
9015 
9016 		impl.setClipRectangle(pt.x, pt.y, width, height);
9017 
9018 		return old;
9019 	}
9020 
9021 	/// ditto
9022 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9023 		if(impl is null) return currentClipRectangle;
9024 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9025 	}
9026 
9027 	///
9028 	void setFont(OperatingSystemFont font) {
9029 		if(impl is null) return;
9030 		impl.setFont(font);
9031 	}
9032 
9033 	///
9034 	int fontHeight() {
9035 		if(impl is null) return 0;
9036 		return impl.fontHeight();
9037 	}
9038 
9039 	private Pen activePen;
9040 
9041 	///
9042 	@property void pen(Pen p) {
9043 		if(impl is null) return;
9044 		activePen = p;
9045 		impl.pen(p);
9046 	}
9047 
9048 	///
9049 	@scriptable
9050 	@property void outlineColor(Color c) {
9051 		if(impl is null) return;
9052 		if(activePen.color == c)
9053 			return;
9054 		activePen.color = c;
9055 		impl.pen(activePen);
9056 	}
9057 
9058 	///
9059 	@scriptable
9060 	@property void fillColor(Color c) {
9061 		if(impl is null) return;
9062 		impl.fillColor(c);
9063 	}
9064 
9065 	///
9066 	@property void rasterOp(RasterOp op) {
9067 		if(impl is null) return;
9068 		impl.rasterOp(op);
9069 	}
9070 
9071 
9072 	void updateDisplay() {
9073 		// FIXME this should do what the dtor does
9074 	}
9075 
9076 	/// 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)
9077 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9078 		if(impl is null) return;
9079 		if(isClipped(upperLeft, width, height)) return;
9080 		transform(upperLeft);
9081 		version(Windows) {
9082 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9083 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9084 			RECT clip = scroll;
9085 			RECT uncovered;
9086 			HRGN hrgn;
9087 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9088 				throw new WindowsApiException("ScrollDC", GetLastError());
9089 
9090 		} else version(X11) {
9091 			// FIXME: clip stuff outside this rectangle
9092 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9093 		} else version(OSXCocoa) {
9094 			throw new NotYetImplementedException();
9095 		} else static assert(0);
9096 	}
9097 
9098 	///
9099 	void clear(Color color = Color.white()) {
9100 		if(impl is null) return;
9101 		fillColor = color;
9102 		outlineColor = color;
9103 		drawRectangle(Point(0, 0), window.width, window.height);
9104 	}
9105 
9106 	/++
9107 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9108 
9109 		Params:
9110 			upperLeft = point on the window where the upper left corner of the image will be drawn
9111 			imageUpperLeft = point on the image to start the slice to draw
9112 			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.
9113 		History:
9114 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9115 	+/
9116 	version(OSXCocoa) {} else // NotYetImplementedException
9117 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9118 		if(impl is null) return;
9119 		if(isClipped(upperLeft, s.width, s.height)) return;
9120 		transform(upperLeft);
9121 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9122 	}
9123 
9124 	///
9125 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9126 		if(impl is null) return;
9127 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9128 		transform(upperLeft);
9129 		if(w == 0 || w > i.width)
9130 			w = i.width;
9131 		if(h == 0 || h > i.height)
9132 			h = i.height;
9133 		if(upperLeftOfImage.x < 0)
9134 			upperLeftOfImage.x = 0;
9135 		if(upperLeftOfImage.y < 0)
9136 			upperLeftOfImage.y = 0;
9137 
9138 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9139 	}
9140 
9141 	///
9142 	Size textSize(in char[] text) {
9143 		if(impl is null) return Size(0, 0);
9144 		return impl.textSize(text);
9145 	}
9146 
9147 	/++
9148 		Draws a string in the window with the set font (see [setFont] to change it).
9149 
9150 		Params:
9151 			upperLeft = the upper left point of the bounding box of the text
9152 			text = the string to draw
9153 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9154 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9155 	+/
9156 	@scriptable
9157 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9158 		if(impl is null) return;
9159 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9160 			if(isClipped(upperLeft, lowerRight)) return;
9161 			transform(lowerRight);
9162 		} else {
9163 			if(isClipped(upperLeft, textSize(text))) return;
9164 		}
9165 		transform(upperLeft);
9166 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9167 	}
9168 
9169 	/++
9170 		Draws text using a custom font.
9171 
9172 		This is still MAJOR work in progress.
9173 
9174 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9175 	+/
9176 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9177 		if(impl is null) return;
9178 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9179 		transform(upperLeft);
9180 		font.drawString(this, upperLeft, text);
9181 	}
9182 
9183 	version(Windows)
9184 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9185 		if(impl is null) return;
9186 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9187 		transform(upperLeft);
9188 
9189 		if(text.length && text[$-1] == '\n')
9190 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9191 
9192 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9193 	}
9194 
9195 	static struct TextDrawingContext {
9196 		Point boundingBoxUpperLeft;
9197 		Point boundingBoxLowerRight;
9198 
9199 		Point currentLocation;
9200 
9201 		Point lastDrewUpperLeft;
9202 		Point lastDrewLowerRight;
9203 
9204 		// how do i do right aligned rich text?
9205 		// i kinda want to do a pre-made drawing then right align
9206 		// draw the whole block.
9207 		//
9208 		// That's exactly the diff: inline vs block stuff.
9209 
9210 		// I need to get coordinates of an inline section out too,
9211 		// not just a bounding box, but a series of bounding boxes
9212 		// should be ok. Consider what's needed to detect a click
9213 		// on a link in the middle of a paragraph breaking a line.
9214 		//
9215 		// Generally, we should be able to get the rectangles of
9216 		// any portion we draw.
9217 		//
9218 		// It also needs to tell what text is left if it overflows
9219 		// out of the box, so we can do stuff like float images around
9220 		// it. It should not attempt to draw a letter that would be
9221 		// clipped.
9222 		//
9223 		// I might also turn off word wrap stuff.
9224 	}
9225 
9226 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9227 		if(impl is null) return;
9228 		// FIXME
9229 	}
9230 
9231 	/// Drawing an individual pixel is slow. Avoid it if possible.
9232 	void drawPixel(Point where) {
9233 		if(impl is null) return;
9234 		if(isClipped(where)) return;
9235 		transform(where);
9236 		impl.drawPixel(where.x, where.y);
9237 	}
9238 
9239 
9240 	/// Draws a pen using the current pen / outlineColor
9241 	@scriptable
9242 	void drawLine(Point starting, Point ending) {
9243 		if(impl is null) return;
9244 		if(isClipped(starting, ending)) return;
9245 		transform(starting);
9246 		transform(ending);
9247 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9248 	}
9249 
9250 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9251 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9252 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9253 	@scriptable
9254 	void drawRectangle(Point upperLeft, int width, int height) {
9255 		if(impl is null) return;
9256 		if(isClipped(upperLeft, width, height)) return;
9257 		transform(upperLeft);
9258 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9259 	}
9260 
9261 	/// ditto
9262 	void drawRectangle(Point upperLeft, Size size) {
9263 		if(impl is null) return;
9264 		if(isClipped(upperLeft, size.width, size.height)) return;
9265 		transform(upperLeft);
9266 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9267 	}
9268 
9269 	/// ditto
9270 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9271 		if(impl is null) return;
9272 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9273 		transform(upperLeft);
9274 		transform(lowerRightInclusive);
9275 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9276 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9277 	}
9278 
9279 	// overload added on May 12, 2021
9280 	/// ditto
9281 	void drawRectangle(Rectangle rect) {
9282 		drawRectangle(rect.upperLeft, rect.size);
9283 	}
9284 
9285 	/// Arguments are the points of the bounding rectangle
9286 	void drawEllipse(Point upperLeft, Point lowerRight) {
9287 		if(impl is null) return;
9288 		if(isClipped(upperLeft, lowerRight)) return;
9289 		transform(upperLeft);
9290 		transform(lowerRight);
9291 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9292 	}
9293 
9294 	/++
9295 		start and finish are units of degrees * 64
9296 
9297 		History:
9298 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9299 
9300 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9301 	+/
9302 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9303 		if(impl is null) return;
9304 		// FIXME: not actually implemented
9305 		if(isClipped(upperLeft, width, height)) return;
9306 		transform(upperLeft);
9307 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9308 	}
9309 
9310 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9311 	void drawCircle(Point upperLeft, int diameter) {
9312 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9313 	}
9314 
9315 	/// .
9316 	void drawPolygon(Point[] vertexes) {
9317 		if(impl is null) return;
9318 		assert(vertexes.length);
9319 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9320 		foreach(ref vertex; vertexes) {
9321 			if(vertex.x < minX)
9322 				minX = vertex.x;
9323 			if(vertex.y < minY)
9324 				minY = vertex.y;
9325 			if(vertex.x > maxX)
9326 				maxX = vertex.x;
9327 			if(vertex.y > maxY)
9328 				maxY = vertex.y;
9329 			transform(vertex);
9330 		}
9331 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9332 		impl.drawPolygon(vertexes);
9333 	}
9334 
9335 	/// ditto
9336 	void drawPolygon(Point[] vertexes...) {
9337 		if(impl is null) return;
9338 		drawPolygon(vertexes);
9339 	}
9340 
9341 
9342 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9343 
9344 	//mixin NativeScreenPainterImplementation!() impl;
9345 
9346 
9347 	// HACK: if I mixin the impl directly, it won't let me override the copy
9348 	// constructor! The linker complains about there being multiple definitions.
9349 	// I'll make the best of it and reference count it though.
9350 	ScreenPainterImplementation* impl;
9351 }
9352 
9353 	// HACK: I need a pointer to the implementation so it's separate
9354 	struct ScreenPainterImplementation {
9355 		CapableOfBeingDrawnUpon window;
9356 		int referenceCount;
9357 		mixin NativeScreenPainterImplementation!();
9358 	}
9359 
9360 // FIXME: i haven't actually tested the sprite class on MS Windows
9361 
9362 /**
9363 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9364 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9365 
9366 
9367 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9368 	though I'm not sure that's ideal and the implementation might change.
9369 
9370 	You create one by giving a window and an image. It optimizes for that window,
9371 	and copies the image into it to use as the initial picture. Creating a sprite
9372 	can be quite slow (especially over a network connection) so you should do it
9373 	as little as possible and just hold on to your sprite handles after making them.
9374 	simpledisplay does try to do its best though, using the XSHM extension if available,
9375 	but you should still write your code as if it will always be slow.
9376 
9377 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9378 	a fast operation - much faster than drawing the Image itself every time.
9379 
9380 	`Sprite` represents a scarce resource which should be freed when you
9381 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9382 	after it has been disposed. If you are unsure about this, don't take chances,
9383 	just let the garbage collector do it for you. But ideally, you can manage its
9384 	lifetime more efficiently.
9385 
9386 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9387 	support alpha blending in its drawing at this time. That might change in the
9388 	future, but if you need alpha blending right now, use OpenGL instead. See
9389 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9390 
9391 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9392 	in by setting the enableAlpha = true in the constructor.
9393 */
9394 version(OSXCocoa) {} else // NotYetImplementedException
9395 class Sprite : CapableOfBeingDrawnUpon {
9396 
9397 	///
9398 	ScreenPainter draw() {
9399 		return ScreenPainter(this, handle, false);
9400 	}
9401 
9402 	/++
9403 		Copies the sprite's current state into a [TrueColorImage].
9404 
9405 		Be warned: this can be a very slow operation
9406 
9407 		History:
9408 			Actually implemented on March 14, 2021
9409 	+/
9410 	TrueColorImage takeScreenshot() {
9411 		return trueColorImageFromNativeHandle(handle, width, height);
9412 	}
9413 
9414 	void delegate() paintingFinishedDg() { return null; }
9415 	bool closed() { return false; }
9416 	ScreenPainterImplementation* activeScreenPainter_;
9417 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9418 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9419 
9420 	version(Windows)
9421 		private ubyte* rawData;
9422 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9423 	// ditto on the XPicture stuff
9424 
9425 	version(X11) {
9426 		private static XRenderPictFormat* RGB24;
9427 		private static XRenderPictFormat* ARGB32;
9428 
9429 		private Picture xrenderPicture;
9430 	}
9431 
9432 	version(X11)
9433 	private static void requireXRender() {
9434 		if(!XRenderLibrary.loadAttempted) {
9435 			XRenderLibrary.loadDynamicLibrary();
9436 		}
9437 
9438 		if(!XRenderLibrary.loadSuccessful)
9439 			throw new Exception("XRender library load failure");
9440 
9441 		auto display = XDisplayConnection.get;
9442 
9443 		// FIXME: if we migrate X displays, these need to be changed
9444 		if(RGB24 is null)
9445 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9446 		if(ARGB32 is null)
9447 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9448 	}
9449 
9450 	protected this() {}
9451 
9452 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9453 		this._width = width;
9454 		this._height = height;
9455 		this.enableAlpha = enableAlpha;
9456 
9457 		version(X11) {
9458 			auto display = XDisplayConnection.get();
9459 
9460 			if(enableAlpha) {
9461 				requireXRender();
9462 			}
9463 
9464 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9465 
9466 			if(enableAlpha) {
9467 				XRenderPictureAttributes attrs;
9468 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9469 			}
9470 		} else version(Windows) {
9471 			version(CRuntime_DigitalMars) {
9472 				//if(enableAlpha)
9473 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9474 			}
9475 
9476 			BITMAPINFO infoheader;
9477 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9478 			infoheader.bmiHeader.biWidth = width;
9479 			infoheader.bmiHeader.biHeight = height;
9480 			infoheader.bmiHeader.biPlanes = 1;
9481 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9482 			infoheader.bmiHeader.biCompression = BI_RGB;
9483 
9484 			// FIXME: this should prolly be a device dependent bitmap...
9485 			handle = CreateDIBSection(
9486 				null,
9487 				&infoheader,
9488 				DIB_RGB_COLORS,
9489 				cast(void**) &rawData,
9490 				null,
9491 				0);
9492 
9493 			if(handle is null)
9494 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
9495 		}
9496 	}
9497 
9498 	/// Makes a sprite based on the image with the initial contents from the Image
9499 	this(SimpleWindow win, Image i) {
9500 		this(win, i.width, i.height, i.enableAlpha);
9501 
9502 		version(X11) {
9503 			auto display = XDisplayConnection.get();
9504 			auto gc = XCreateGC(display, this.handle, 0, null);
9505 			scope(exit) XFreeGC(display, gc);
9506 			if(i.usingXshm)
9507 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9508 			else
9509 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9510 		} else version(Windows) {
9511 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9512 			auto arrLength = itemsPerLine * height;
9513 			rawData[0..arrLength] = i.rawData[0..arrLength];
9514 		} else version(OSXCocoa) {
9515 			// FIXME: I have no idea if this is even any good
9516 			ubyte* rawData;
9517 
9518 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9519 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
9520 				colorSpace,
9521 				kCGImageAlphaPremultipliedLast
9522 				|kCGBitmapByteOrder32Big);
9523 			CGColorSpaceRelease(colorSpace);
9524 			rawData = CGBitmapContextGetData(context);
9525 
9526 			auto rdl = (width * height * 4);
9527 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9528 		} else static assert(0);
9529 	}
9530 
9531 	/++
9532 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9533 
9534 		Params:
9535 			where = point on the window where the upper left corner of the image will be drawn
9536 			imageUpperLeft = point on the image to start the slice to draw
9537 			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.
9538 		History:
9539 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9540 	+/
9541 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9542 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9543 	}
9544 
9545 	/// Call this when you're ready to get rid of it
9546 	void dispose() {
9547 		version(X11) {
9548 			staticDispose(xrenderPicture, handle);
9549 			xrenderPicture = None;
9550 			handle = None;
9551 		} else version(Windows) {
9552 			staticDispose(handle);
9553 			handle = null;
9554 		} else version(OSXCocoa) {
9555 			staticDispose(context);
9556 			context = null;
9557 		} else static assert(0);
9558 
9559 	}
9560 
9561 	version(X11)
9562 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9563 		if(xrenderPicture)
9564 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9565 		if(handle)
9566 			XFreePixmap(XDisplayConnection.get(), handle);
9567 	}
9568 	else version(Windows)
9569 	static void staticDispose(HBITMAP handle) {
9570 		if(handle)
9571 			DeleteObject(handle);
9572 	}
9573 	else version(OSXCocoa)
9574 	static void staticDispose(CGContextRef context) {
9575 		if(context)
9576 			CGContextRelease(context);
9577 	}
9578 
9579 	~this() {
9580 		version(X11) { if(xrenderPicture || handle)
9581 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9582 		} else version(Windows) { if(handle)
9583 			cleanupQueue.queue!staticDispose(handle);
9584 		} else version(OSXCocoa) { if(context)
9585 			cleanupQueue.queue!staticDispose(context);
9586 		} else static assert(0);
9587 	}
9588 
9589 	///
9590 	final @property int width() { return _width; }
9591 
9592 	///
9593 	final @property int height() { return _height; }
9594 
9595 	///
9596 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9597 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9598 	}
9599 
9600 	auto nativeHandle() {
9601 		return handle;
9602 	}
9603 
9604 	private:
9605 
9606 	int _width;
9607 	int _height;
9608 	bool enableAlpha;
9609 	version(X11)
9610 		Pixmap handle;
9611 	else version(Windows)
9612 		HBITMAP handle;
9613 	else version(OSXCocoa)
9614 		CGContextRef context;
9615 	else static assert(0);
9616 }
9617 
9618 /++
9619 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9620 
9621 	History:
9622 		Added November 20, 2021 (dub v10.4)
9623 +/
9624 abstract class Gradient : Sprite {
9625 	protected this(int w, int h) {
9626 		version(X11) {
9627 			Sprite.requireXRender();
9628 
9629 			super();
9630 			enableAlpha = true;
9631 			_width = w;
9632 			_height = h;
9633 		} else version(Windows) {
9634 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9635 		}
9636 	}
9637 
9638 	version(Windows)
9639 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9640 		auto ptr = rawData;
9641 		foreach(j; 0 .. _height)
9642 		foreach(i; 0 .. _width) {
9643 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9644 			*rawData = (color.a * color.b) / 255; rawData++;
9645 			*rawData = (color.a * color.g) / 255; rawData++;
9646 			*rawData = (color.a * color.r) / 255; rawData++;
9647 			*rawData = color.a; rawData++;
9648 		}
9649 	}
9650 
9651 	version(X11)
9652 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9653 		assert(stops.length > 0);
9654 		assert(stops.length <= 16, "I got lazy with buffers");
9655 
9656 		XFixed[16] stopsPositions = void;
9657 		XRenderColor[16] colors = void;
9658 
9659 		foreach(idx, stop; stops) {
9660 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9661 			auto c = stop.c;
9662 			colors[idx] = XRenderColor(
9663 				cast(ushort)(c.r * ushort.max / 255),
9664 				cast(ushort)(c.g * ushort.max / 255),
9665 				cast(ushort)(c.b * ushort.max / 255),
9666 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9667 			);
9668 		}
9669 
9670 		xrenderPicture = dg(stopsPositions, colors);
9671 	}
9672 
9673 	///
9674 	static struct Stop {
9675 		float percentage; /// between 0 and 1.0
9676 		Color c;
9677 	}
9678 }
9679 
9680 /++
9681 	Creates a linear gradient between p1 and p2.
9682 
9683 	X ONLY RIGHT NOW
9684 
9685 	History:
9686 		Added November 20, 2021 (dub v10.4)
9687 
9688 	Bugs:
9689 		Not yet implemented on Windows.
9690 +/
9691 class LinearGradient : Gradient {
9692 	/++
9693 
9694 	+/
9695 	this(Point p1, Point p2, Stop[] stops...) {
9696 		super(p2.x, p2.y);
9697 
9698 		version(X11) {
9699 			XLinearGradient gradient;
9700 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9701 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9702 
9703 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9704 				return XRenderCreateLinearGradient(
9705 					XDisplayConnection.get,
9706 					&gradient,
9707 					stopsPositions.ptr,
9708 					colors.ptr,
9709 					cast(int) stops.length);
9710 			});
9711 		} else version(Windows) {
9712 			// FIXME
9713 			forEachPixel((int x, int y) {
9714 				import core.stdc.math;
9715 
9716 				//sqrtf(
9717 
9718 				return Color.transparent;
9719 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9720 			});
9721 		}
9722 	}
9723 }
9724 
9725 /++
9726 	A conical gradient goes from color to color around a circumference from a center point.
9727 
9728 	X ONLY RIGHT NOW
9729 
9730 	History:
9731 		Added November 20, 2021 (dub v10.4)
9732 
9733 	Bugs:
9734 		Not yet implemented on Windows.
9735 +/
9736 class ConicalGradient : Gradient {
9737 	/++
9738 
9739 	+/
9740 	this(Point center, float angleInDegrees, Stop[] stops...) {
9741 		super(center.x * 2, center.y * 2);
9742 
9743 		version(X11) {
9744 			XConicalGradient gradient;
9745 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9746 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9747 
9748 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9749 				return XRenderCreateConicalGradient(
9750 					XDisplayConnection.get,
9751 					&gradient,
9752 					stopsPositions.ptr,
9753 					colors.ptr,
9754 					cast(int) stops.length);
9755 			});
9756 		} else version(Windows) {
9757 			// FIXME
9758 			forEachPixel((int x, int y) {
9759 				import core.stdc.math;
9760 
9761 				//sqrtf(
9762 
9763 				return Color.transparent;
9764 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9765 			});
9766 
9767 		}
9768 	}
9769 }
9770 
9771 /++
9772 	A radial gradient goes from color to color based on distance from the center.
9773 	It is like rings of color.
9774 
9775 	X ONLY RIGHT NOW
9776 
9777 
9778 	More specifically, you create two circles: an inner circle and an outer circle.
9779 	The gradient is only drawn in the area outside the inner circle but inside the outer
9780 	circle. The closest line between those two circles forms the line for the gradient
9781 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9782 
9783 	History:
9784 		Added November 20, 2021 (dub v10.4)
9785 
9786 	Bugs:
9787 		Not yet implemented on Windows.
9788 +/
9789 class RadialGradient : Gradient {
9790 	/++
9791 
9792 	+/
9793 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
9794 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
9795 
9796 		version(X11) {
9797 			XRadialGradient gradient;
9798 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
9799 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
9800 
9801 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9802 				return XRenderCreateRadialGradient(
9803 					XDisplayConnection.get,
9804 					&gradient,
9805 					stopsPositions.ptr,
9806 					colors.ptr,
9807 					cast(int) stops.length);
9808 			});
9809 		} else version(Windows) {
9810 			// FIXME
9811 			forEachPixel((int x, int y) {
9812 				import core.stdc.math;
9813 
9814 				//sqrtf(
9815 
9816 				return Color.transparent;
9817 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9818 			});
9819 		}
9820 	}
9821 }
9822 
9823 
9824 
9825 /+
9826 	NOT IMPLEMENTED
9827 
9828 	A display-stored image optimized for relatively quick drawing, like
9829 	[Sprite], but this one supports alpha channel blending and does NOT
9830 	support direct drawing upon it with a [ScreenPainter].
9831 
9832 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
9833 	plain [ScreenPainter]... sort of.
9834 
9835 	On X11, it requires the Xrender extension and library. This is available
9836 	almost everywhere though.
9837 
9838 	History:
9839 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
9840 +/
9841 version(none)
9842 class AlphaSprite {
9843 	/++
9844 		Copies the given image into it.
9845 	+/
9846 	this(MemoryImage img) {
9847 
9848 		if(!XRenderLibrary.loadAttempted) {
9849 			XRenderLibrary.loadDynamicLibrary();
9850 
9851 			// FIXME: this needs to be reconstructed when the X server changes
9852 			repopulateX();
9853 		}
9854 		if(!XRenderLibrary.loadSuccessful)
9855 			throw new Exception("XRender library load failure");
9856 
9857 		// I probably need to put the alpha mask in a separate Picture
9858 		// ugh
9859 		// maybe the Sprite itself can have an alpha bitmask anyway
9860 
9861 
9862 		auto display = XDisplayConnection.get();
9863 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
9864 
9865 
9866 		XRenderPictureAttributes attrs;
9867 
9868 		handle = XRenderCreatePicture(
9869 			XDisplayConnection.get,
9870 			pixmap,
9871 			RGBA,
9872 			0,
9873 			&attrs
9874 		);
9875 
9876 	}
9877 
9878 	// maybe i'll use the create gradient functions too with static factories..
9879 
9880 	void drawAt(ScreenPainter painter, Point where) {
9881 		//painter.drawPixmap(this, where);
9882 
9883 		XRenderPictureAttributes attrs;
9884 
9885 		auto pic = XRenderCreatePicture(
9886 			XDisplayConnection.get,
9887 			painter.impl.d,
9888 			RGB,
9889 			0,
9890 			&attrs
9891 		);
9892 
9893 		XRenderComposite(
9894 			XDisplayConnection.get,
9895 			3, // PictOpOver
9896 			handle,
9897 			None,
9898 			pic,
9899 			0, // src
9900 			0,
9901 			0, // mask
9902 			0,
9903 			10, // dest
9904 			10,
9905 			100, // width
9906 			100
9907 		);
9908 
9909 		/+
9910 		XRenderFreePicture(
9911 			XDisplayConnection.get,
9912 			pic
9913 		);
9914 
9915 		XRenderFreePicture(
9916 			XDisplayConnection.get,
9917 			fill
9918 		);
9919 		+/
9920 		// on Windows you can stretch but Xrender still can't :(
9921 	}
9922 
9923 	static XRenderPictFormat* RGB;
9924 	static XRenderPictFormat* RGBA;
9925 	static void repopulateX() {
9926 		auto display = XDisplayConnection.get;
9927 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
9928 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
9929 	}
9930 
9931 	XPixmap pixmap;
9932 	Picture handle;
9933 }
9934 
9935 ///
9936 interface CapableOfBeingDrawnUpon {
9937 	///
9938 	ScreenPainter draw();
9939 	///
9940 	int width();
9941 	///
9942 	int height();
9943 	protected ScreenPainterImplementation* activeScreenPainter();
9944 	protected void activeScreenPainter(ScreenPainterImplementation*);
9945 	bool closed();
9946 
9947 	void delegate() paintingFinishedDg();
9948 
9949 	/// Be warned: this can be a very slow operation
9950 	TrueColorImage takeScreenshot();
9951 }
9952 
9953 /// 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].
9954 void flushGui() {
9955 	version(X11) {
9956 		auto dpy = XDisplayConnection.get();
9957 		XLockDisplay(dpy);
9958 		scope(exit) XUnlockDisplay(dpy);
9959 		XFlush(dpy);
9960 	}
9961 }
9962 
9963 /++
9964 	Runs the given code in the GUI thread when its event loop
9965 	is available, blocking until it completes. This allows you
9966 	to create and manipulate windows from another thread without
9967 	invoking undefined behavior.
9968 
9969 	If this is the gui thread, it runs the code immediately.
9970 
9971 	If no gui thread exists yet, the current thread is assumed
9972 	to be it. Attempting to create windows or run the event loop
9973 	in any other thread will cause an assertion failure.
9974 
9975 
9976 	$(TIP
9977 		Did you know you can use UFCS on delegate literals?
9978 
9979 		() {
9980 			// code here
9981 		}.runInGuiThread;
9982 	)
9983 
9984 	Returns:
9985 		`true` if the function was called, `false` if it was not.
9986 		The function may not be called because the gui thread had
9987 		already terminated by the time you called this.
9988 
9989 	History:
9990 		Added April 10, 2020 (v7.2.0)
9991 
9992 		Return value added and implementation tweaked to avoid locking
9993 		at program termination on February 24, 2021 (v9.2.1).
9994 +/
9995 bool runInGuiThread(scope void delegate() dg) @trusted {
9996 	claimGuiThread();
9997 
9998 	if(thisIsGuiThread) {
9999 		dg();
10000 		return true;
10001 	}
10002 
10003 	if(guiThreadTerminating)
10004 		return false;
10005 
10006 	import core.sync.semaphore;
10007 	static Semaphore sc;
10008 	if(sc is null)
10009 		sc = new Semaphore();
10010 
10011 	static RunQueueMember* rqm;
10012 	if(rqm is null)
10013 		rqm = new RunQueueMember;
10014 	rqm.dg = cast(typeof(rqm.dg)) dg;
10015 	rqm.signal = sc;
10016 	rqm.thrown = null;
10017 
10018 	synchronized(runInGuiThreadLock) {
10019 		runInGuiThreadQueue ~= rqm;
10020 	}
10021 
10022 	if(!SimpleWindow.eventWakeUp())
10023 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10024 
10025 	rqm.signal.wait();
10026 	auto t = rqm.thrown;
10027 
10028 	if(t)
10029 		throw t;
10030 
10031 	return true;
10032 }
10033 
10034 // note it runs sync if this is the gui thread....
10035 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10036 	claimGuiThread();
10037 
10038 	try {
10039 
10040 		if(thisIsGuiThread) {
10041 			dg();
10042 			return;
10043 		}
10044 
10045 		if(guiThreadTerminating)
10046 			return;
10047 
10048 		RunQueueMember* rqm = new RunQueueMember;
10049 		rqm.dg = cast(typeof(rqm.dg)) dg;
10050 		rqm.signal = null;
10051 		rqm.thrown = null;
10052 
10053 		synchronized(runInGuiThreadLock) {
10054 			runInGuiThreadQueue ~= rqm;
10055 		}
10056 
10057 		if(!SimpleWindow.eventWakeUp())
10058 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10059 	} catch(Exception e) {
10060 		if(handleError)
10061 			handleError(e);
10062 	}
10063 }
10064 
10065 private void runPendingRunInGuiThreadDelegates() {
10066 	more:
10067 	RunQueueMember* next;
10068 	synchronized(runInGuiThreadLock) {
10069 		if(runInGuiThreadQueue.length) {
10070 			next = runInGuiThreadQueue[0];
10071 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10072 		} else {
10073 			next = null;
10074 		}
10075 	}
10076 
10077 	if(next) {
10078 		try {
10079 			next.dg();
10080 			next.thrown = null;
10081 		} catch(Throwable t) {
10082 			next.thrown = t;
10083 		}
10084 
10085 		if(next.signal)
10086 			next.signal.notify();
10087 
10088 		goto more;
10089 	}
10090 }
10091 
10092 private void claimGuiThread() nothrow {
10093 	import core.atomic;
10094 	if(cas(&guiThreadExists_, false, true))
10095 		thisIsGuiThread = true;
10096 }
10097 
10098 private struct RunQueueMember {
10099 	void delegate() dg;
10100 	import core.sync.semaphore;
10101 	Semaphore signal;
10102 	Throwable thrown;
10103 }
10104 
10105 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10106 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10107 private bool thisIsGuiThread = false;
10108 private shared bool guiThreadExists_ = false;
10109 private shared bool guiThreadTerminating = false;
10110 
10111 /++
10112 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10113 	event loop. All windows must be exclusively created and managed by a single thread.
10114 
10115 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10116 	when you call one of its constructors.
10117 
10118 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10119 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10120 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10121 
10122 	The reason this function is available is in case you want to message pass between a gui
10123 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10124 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10125 
10126 	History:
10127 		Added December 3, 2021 (dub v10.5)
10128 +/
10129 public bool guiThreadExists() {
10130 	return guiThreadExists_;
10131 }
10132 
10133 /++
10134 	Returns `true` if this thread is either running or set to be running the
10135 	simpledisplay.d gui core event loop because it owns windows.
10136 
10137 	It is important to keep gui-related functionality in the right thread, so you will
10138 	want to `runInGuiThread` when you call them (with some specific exceptions called
10139 	out in those specific functions' documentation). Notably, all windows must be
10140 	created and managed only from the gui thread.
10141 
10142 	Will return false if simpledisplay's other functions haven't been called
10143 	yet; check [guiThreadExists] in addition to this.
10144 
10145 	History:
10146 		Added December 3, 2021 (dub v10.5)
10147 +/
10148 public bool thisThreadRunningGui() {
10149 	return thisIsGuiThread;
10150 }
10151 
10152 /++
10153 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10154 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10155 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10156 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10157 	file instead if you are in one of those situations).
10158 
10159 	It does not support outputting very many types; just strings and ints are likely to actually work.
10160 
10161 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10162 	is unspecified meaning I can change it at any time. The only point of this function is to help
10163 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10164 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10165 	in those contexts.
10166 
10167 	$(WARNING
10168 		I reserve the right to change this function at any time. You can use it if it helps you
10169 		but do not rely on it for anything permanent.
10170 	)
10171 
10172 	History:
10173 		Added December 3, 2021. Not formally supported under any stable tag.
10174 +/
10175 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10176 	try {
10177 		version(Windows) {
10178 			import core.sys.windows.wincon;
10179 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10180 				AllocConsole();
10181 			const(char)* fn = "CONOUT$";
10182 		} else version(Posix) {
10183 			const(char)* fn = "/dev/tty";
10184 		} else static assert(0, "Function not implemented for your system");
10185 
10186 		if(fileOverride.length)
10187 			fn = fileOverride.ptr;
10188 
10189 		import core.stdc.stdio;
10190 		auto fp = fopen(fn, "wt");
10191 		if(fp is null) return;
10192 		scope(exit) fclose(fp);
10193 
10194 		string str;
10195 		foreach(item; t) {
10196 			static if(is(typeof(item) : const(char)[]))
10197 				str ~= item;
10198 			else
10199 				str ~= toInternal!string(item);
10200 			str ~= " ";
10201 		}
10202 		str ~= "\n";
10203 
10204 		fwrite(str.ptr, 1, str.length, fp);
10205 		fflush(fp);
10206 	} catch(Exception e) {
10207 		// sorry no hope
10208 	}
10209 }
10210 
10211 private void guiThreadFinalize() {
10212 	assert(thisIsGuiThread);
10213 
10214 	guiThreadTerminating = true; // don't add any more from this point on
10215 	runPendingRunInGuiThreadDelegates();
10216 }
10217 
10218 /+
10219 interface IPromise {
10220 	void reportProgress(int current, int max, string message);
10221 
10222 	/+ // not formally in cuz of templates but still
10223 	IPromise Then();
10224 	IPromise Catch();
10225 	IPromise Finally();
10226 	+/
10227 }
10228 
10229 /+
10230 	auto promise = async({ ... });
10231 	promise.Then(whatever).
10232 		Then(whateverelse).
10233 		Catch((exception) { });
10234 
10235 
10236 	A promise is run inside a fiber and it looks something like:
10237 
10238 	try {
10239 		auto res = whatever();
10240 		auto res2 = whateverelse(res);
10241 	} catch(Exception e) {
10242 		{ }(e);
10243 	}
10244 
10245 	When a thing succeeds, it is passed as an arg to the next
10246 +/
10247 class Promise(T) : IPromise {
10248 	auto Then() { return null; }
10249 	auto Catch() { return null; }
10250 	auto Finally() { return null; }
10251 
10252 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10253 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10254 	T await();
10255 }
10256 
10257 interface Task {
10258 }
10259 
10260 interface Resolvable(T) : Task {
10261 	void run();
10262 
10263 	void resolve(T);
10264 
10265 	Resolvable!T then(void delegate(T)); // returns a new promise
10266 	Resolvable!T error(Throwable); // js catch
10267 	Resolvable!T completed(); // js finally
10268 
10269 }
10270 
10271 /++
10272 	Runs `work` in a helper thread and sends its return value back to the main gui
10273 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10274 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10275 	kill the program.
10276 
10277 	You can call reportProgress(position, max, message) to update your parent window
10278 	on your progress.
10279 
10280 	I should also use `shared` methods. FIXME
10281 
10282 	History:
10283 		Added March 6, 2021 (dub version 9.3).
10284 +/
10285 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10286 	uponCompletion(work(null));
10287 }
10288 
10289 +/
10290 
10291 /// Used internal to dispatch events to various classes.
10292 interface CapableOfHandlingNativeEvent {
10293 	NativeEventHandler getNativeEventHandler();
10294 
10295 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10296 
10297 	version(X11) {
10298 		// if this is impossible, you are allowed to just throw from it
10299 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10300 		void recreateAfterDisconnect();
10301 		// discard any *connection specific* state, but keep enough that you
10302 		// can be recreated if possible. discardConnectionState() is always called immediately
10303 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10304 		// you need initialization order
10305 		void discardConnectionState();
10306 	}
10307 }
10308 
10309 version(X11)
10310 /++
10311 	State of keys on mouse events, especially motion.
10312 
10313 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10314 +/
10315 enum ModifierState : uint {
10316 	shift = 1, ///
10317 	capsLock = 2, ///
10318 	ctrl = 4, ///
10319 	alt = 8, /// Not always available on Windows
10320 	windows = 64, /// ditto
10321 	numLock = 16, ///
10322 
10323 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10324 	middleButtonDown = 512, /// ditto
10325 	rightButtonDown = 1024, /// ditto
10326 }
10327 else version(Windows)
10328 /// ditto
10329 enum ModifierState : uint {
10330 	shift = 4, ///
10331 	ctrl = 8, ///
10332 
10333 	// i'm not sure if the next two are available
10334 	alt = 256, /// not always available on Windows
10335 	windows = 512, /// ditto
10336 
10337 	capsLock = 1024, ///
10338 	numLock = 2048, ///
10339 
10340 	leftButtonDown = 1, /// not available on key events
10341 	middleButtonDown = 16, /// ditto
10342 	rightButtonDown = 2, /// ditto
10343 
10344 	backButtonDown = 0x20, /// not available on X
10345 	forwardButtonDown = 0x40, /// ditto
10346 }
10347 else version(OSXCocoa)
10348 // FIXME FIXME NotYetImplementedException
10349 enum ModifierState : uint {
10350 	shift = 1, ///
10351 	capsLock = 2, ///
10352 	ctrl = 4, ///
10353 	alt = 8, /// Not always available on Windows
10354 	windows = 64, /// ditto
10355 	numLock = 16, ///
10356 
10357 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10358 	middleButtonDown = 512, /// ditto
10359 	rightButtonDown = 1024, /// ditto
10360 }
10361 
10362 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10363 enum MouseButton : int {
10364 	none = 0,
10365 	left = 1, ///
10366 	right = 2, ///
10367 	middle = 4, ///
10368 	wheelUp = 8, ///
10369 	wheelDown = 16, ///
10370 	backButton = 32, /// often found on the thumb and used for back in browsers
10371 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10372 }
10373 
10374 version(X11) {
10375 	// FIXME: match ASCII whenever we can. Most of it is already there,
10376 	// but there's a few exceptions and mismatches with Windows
10377 
10378 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10379 	enum Key {
10380 		Escape = 0xff1b, ///
10381 		F1 = 0xffbe, ///
10382 		F2 = 0xffbf, ///
10383 		F3 = 0xffc0, ///
10384 		F4 = 0xffc1, ///
10385 		F5 = 0xffc2, ///
10386 		F6 = 0xffc3, ///
10387 		F7 = 0xffc4, ///
10388 		F8 = 0xffc5, ///
10389 		F9 = 0xffc6, ///
10390 		F10 = 0xffc7, ///
10391 		F11 = 0xffc8, ///
10392 		F12 = 0xffc9, ///
10393 		PrintScreen = 0xff61, ///
10394 		ScrollLock = 0xff14, ///
10395 		Pause = 0xff13, ///
10396 		Grave = 0x60, /// The $(BACKTICK) ~ key
10397 		// number keys across the top of the keyboard
10398 		N1 = 0x31, /// Number key atop the keyboard
10399 		N2 = 0x32, ///
10400 		N3 = 0x33, ///
10401 		N4 = 0x34, ///
10402 		N5 = 0x35, ///
10403 		N6 = 0x36, ///
10404 		N7 = 0x37, ///
10405 		N8 = 0x38, ///
10406 		N9 = 0x39, ///
10407 		N0 = 0x30, ///
10408 		Dash = 0x2d, ///
10409 		Equals = 0x3d, ///
10410 		Backslash = 0x5c, /// The \ | key
10411 		Backspace = 0xff08, ///
10412 		Insert = 0xff63, ///
10413 		Home = 0xff50, ///
10414 		PageUp = 0xff55, ///
10415 		Delete = 0xffff, ///
10416 		End = 0xff57, ///
10417 		PageDown = 0xff56, ///
10418 		Up = 0xff52, ///
10419 		Down = 0xff54, ///
10420 		Left = 0xff51, ///
10421 		Right = 0xff53, ///
10422 
10423 		Tab = 0xff09, ///
10424 		Q = 0x71, ///
10425 		W = 0x77, ///
10426 		E = 0x65, ///
10427 		R = 0x72, ///
10428 		T = 0x74, ///
10429 		Y = 0x79, ///
10430 		U = 0x75, ///
10431 		I = 0x69, ///
10432 		O = 0x6f, ///
10433 		P = 0x70, ///
10434 		LeftBracket = 0x5b, /// the [ { key
10435 		RightBracket = 0x5d, /// the ] } key
10436 		CapsLock = 0xffe5, ///
10437 		A = 0x61, ///
10438 		S = 0x73, ///
10439 		D = 0x64, ///
10440 		F = 0x66, ///
10441 		G = 0x67, ///
10442 		H = 0x68, ///
10443 		J = 0x6a, ///
10444 		K = 0x6b, ///
10445 		L = 0x6c, ///
10446 		Semicolon = 0x3b, ///
10447 		Apostrophe = 0x27, ///
10448 		Enter = 0xff0d, ///
10449 		Shift = 0xffe1, ///
10450 		Z = 0x7a, ///
10451 		X = 0x78, ///
10452 		C = 0x63, ///
10453 		V = 0x76, ///
10454 		B = 0x62, ///
10455 		N = 0x6e, ///
10456 		M = 0x6d, ///
10457 		Comma = 0x2c, ///
10458 		Period = 0x2e, ///
10459 		Slash = 0x2f, /// the / ? key
10460 		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
10461 		Ctrl = 0xffe3, ///
10462 		Windows = 0xffeb, ///
10463 		Alt = 0xffe9, ///
10464 		Space = 0x20, ///
10465 		Alt_r = 0xffea, /// ditto of shift_r
10466 		Windows_r = 0xffec, ///
10467 		Menu = 0xff67, ///
10468 		Ctrl_r = 0xffe4, ///
10469 
10470 		NumLock = 0xff7f, ///
10471 		Divide = 0xffaf, /// The / key on the number pad
10472 		Multiply = 0xffaa, /// The * key on the number pad
10473 		Minus = 0xffad, /// The - key on the number pad
10474 		Plus = 0xffab, /// The + key on the number pad
10475 		PadEnter = 0xff8d, /// Numberpad enter key
10476 		Pad1 = 0xff9c, /// Numberpad keys
10477 		Pad2 = 0xff99, ///
10478 		Pad3 = 0xff9b, ///
10479 		Pad4 = 0xff96, ///
10480 		Pad5 = 0xff9d, ///
10481 		Pad6 = 0xff98, ///
10482 		Pad7 = 0xff95, ///
10483 		Pad8 = 0xff97, ///
10484 		Pad9 = 0xff9a, ///
10485 		Pad0 = 0xff9e, ///
10486 		PadDot = 0xff9f, ///
10487 	}
10488 } else version(Windows) {
10489 	// the character here is for en-us layouts and for illustration only
10490 	// if you actually want to get characters, wait for character events
10491 	// (the argument to your event handler is simply a dchar)
10492 	// those will be converted by the OS for the right locale.
10493 
10494 	enum Key {
10495 		Escape = 0x1b,
10496 		F1 = 0x70,
10497 		F2 = 0x71,
10498 		F3 = 0x72,
10499 		F4 = 0x73,
10500 		F5 = 0x74,
10501 		F6 = 0x75,
10502 		F7 = 0x76,
10503 		F8 = 0x77,
10504 		F9 = 0x78,
10505 		F10 = 0x79,
10506 		F11 = 0x7a,
10507 		F12 = 0x7b,
10508 		PrintScreen = 0x2c,
10509 		ScrollLock = 0x91,
10510 		Pause = 0x13,
10511 		Grave = 0xc0,
10512 		// number keys across the top of the keyboard
10513 		N1 = 0x31,
10514 		N2 = 0x32,
10515 		N3 = 0x33,
10516 		N4 = 0x34,
10517 		N5 = 0x35,
10518 		N6 = 0x36,
10519 		N7 = 0x37,
10520 		N8 = 0x38,
10521 		N9 = 0x39,
10522 		N0 = 0x30,
10523 		Dash = 0xbd,
10524 		Equals = 0xbb,
10525 		Backslash = 0xdc,
10526 		Backspace = 0x08,
10527 		Insert = 0x2d,
10528 		Home = 0x24,
10529 		PageUp = 0x21,
10530 		Delete = 0x2e,
10531 		End = 0x23,
10532 		PageDown = 0x22,
10533 		Up = 0x26,
10534 		Down = 0x28,
10535 		Left = 0x25,
10536 		Right = 0x27,
10537 
10538 		Tab = 0x09,
10539 		Q = 0x51,
10540 		W = 0x57,
10541 		E = 0x45,
10542 		R = 0x52,
10543 		T = 0x54,
10544 		Y = 0x59,
10545 		U = 0x55,
10546 		I = 0x49,
10547 		O = 0x4f,
10548 		P = 0x50,
10549 		LeftBracket = 0xdb,
10550 		RightBracket = 0xdd,
10551 		CapsLock = 0x14,
10552 		A = 0x41,
10553 		S = 0x53,
10554 		D = 0x44,
10555 		F = 0x46,
10556 		G = 0x47,
10557 		H = 0x48,
10558 		J = 0x4a,
10559 		K = 0x4b,
10560 		L = 0x4c,
10561 		Semicolon = 0xba,
10562 		Apostrophe = 0xde,
10563 		Enter = 0x0d,
10564 		Shift = 0x10,
10565 		Z = 0x5a,
10566 		X = 0x58,
10567 		C = 0x43,
10568 		V = 0x56,
10569 		B = 0x42,
10570 		N = 0x4e,
10571 		M = 0x4d,
10572 		Comma = 0xbc,
10573 		Period = 0xbe,
10574 		Slash = 0xbf,
10575 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10576 		Ctrl = 0x11,
10577 		Windows = 0x5b,
10578 		Alt = -5, // FIXME
10579 		Space = 0x20,
10580 		Alt_r = 0xffea, // ditto of shift_r
10581 		Windows_r = 0x5c, // ditto of shift_r
10582 		Menu = 0x5d,
10583 		Ctrl_r = 0xa3, // ditto of shift_r
10584 
10585 		NumLock = 0x90,
10586 		Divide = 0x6f,
10587 		Multiply = 0x6a,
10588 		Minus = 0x6d,
10589 		Plus = 0x6b,
10590 		PadEnter = -8, // FIXME
10591 		Pad1 = 0x61,
10592 		Pad2 = 0x62,
10593 		Pad3 = 0x63,
10594 		Pad4 = 0x64,
10595 		Pad5 = 0x65,
10596 		Pad6 = 0x66,
10597 		Pad7 = 0x67,
10598 		Pad8 = 0x68,
10599 		Pad9 = 0x69,
10600 		Pad0 = 0x60,
10601 		PadDot = 0x6e,
10602 	}
10603 
10604 	// I'm keeping this around for reference purposes
10605 	// ideally all these buttons will be listed for all platforms,
10606 	// but now now I'm just focusing on my US keyboard
10607 	version(none)
10608 	enum Key {
10609 		LBUTTON = 0x01,
10610 		RBUTTON = 0x02,
10611 		CANCEL = 0x03,
10612 		MBUTTON = 0x04,
10613 		//static if (_WIN32_WINNT > =  0x500) {
10614 		XBUTTON1 = 0x05,
10615 		XBUTTON2 = 0x06,
10616 		//}
10617 		BACK = 0x08,
10618 		TAB = 0x09,
10619 		CLEAR = 0x0C,
10620 		RETURN = 0x0D,
10621 		SHIFT = 0x10,
10622 		CONTROL = 0x11,
10623 		MENU = 0x12,
10624 		PAUSE = 0x13,
10625 		CAPITAL = 0x14,
10626 		KANA = 0x15,
10627 		HANGEUL = 0x15,
10628 		HANGUL = 0x15,
10629 		JUNJA = 0x17,
10630 		FINAL = 0x18,
10631 		HANJA = 0x19,
10632 		KANJI = 0x19,
10633 		ESCAPE = 0x1B,
10634 		CONVERT = 0x1C,
10635 		NONCONVERT = 0x1D,
10636 		ACCEPT = 0x1E,
10637 		MODECHANGE = 0x1F,
10638 		SPACE = 0x20,
10639 		PRIOR = 0x21,
10640 		NEXT = 0x22,
10641 		END = 0x23,
10642 		HOME = 0x24,
10643 		LEFT = 0x25,
10644 		UP = 0x26,
10645 		RIGHT = 0x27,
10646 		DOWN = 0x28,
10647 		SELECT = 0x29,
10648 		PRINT = 0x2A,
10649 		EXECUTE = 0x2B,
10650 		SNAPSHOT = 0x2C,
10651 		INSERT = 0x2D,
10652 		DELETE = 0x2E,
10653 		HELP = 0x2F,
10654 		LWIN = 0x5B,
10655 		RWIN = 0x5C,
10656 		APPS = 0x5D,
10657 		SLEEP = 0x5F,
10658 		NUMPAD0 = 0x60,
10659 		NUMPAD1 = 0x61,
10660 		NUMPAD2 = 0x62,
10661 		NUMPAD3 = 0x63,
10662 		NUMPAD4 = 0x64,
10663 		NUMPAD5 = 0x65,
10664 		NUMPAD6 = 0x66,
10665 		NUMPAD7 = 0x67,
10666 		NUMPAD8 = 0x68,
10667 		NUMPAD9 = 0x69,
10668 		MULTIPLY = 0x6A,
10669 		ADD = 0x6B,
10670 		SEPARATOR = 0x6C,
10671 		SUBTRACT = 0x6D,
10672 		DECIMAL = 0x6E,
10673 		DIVIDE = 0x6F,
10674 		F1 = 0x70,
10675 		F2 = 0x71,
10676 		F3 = 0x72,
10677 		F4 = 0x73,
10678 		F5 = 0x74,
10679 		F6 = 0x75,
10680 		F7 = 0x76,
10681 		F8 = 0x77,
10682 		F9 = 0x78,
10683 		F10 = 0x79,
10684 		F11 = 0x7A,
10685 		F12 = 0x7B,
10686 		F13 = 0x7C,
10687 		F14 = 0x7D,
10688 		F15 = 0x7E,
10689 		F16 = 0x7F,
10690 		F17 = 0x80,
10691 		F18 = 0x81,
10692 		F19 = 0x82,
10693 		F20 = 0x83,
10694 		F21 = 0x84,
10695 		F22 = 0x85,
10696 		F23 = 0x86,
10697 		F24 = 0x87,
10698 		NUMLOCK = 0x90,
10699 		SCROLL = 0x91,
10700 		LSHIFT = 0xA0,
10701 		RSHIFT = 0xA1,
10702 		LCONTROL = 0xA2,
10703 		RCONTROL = 0xA3,
10704 		LMENU = 0xA4,
10705 		RMENU = 0xA5,
10706 		//static if (_WIN32_WINNT > =  0x500) {
10707 		BROWSER_BACK = 0xA6,
10708 		BROWSER_FORWARD = 0xA7,
10709 		BROWSER_REFRESH = 0xA8,
10710 		BROWSER_STOP = 0xA9,
10711 		BROWSER_SEARCH = 0xAA,
10712 		BROWSER_FAVORITES = 0xAB,
10713 		BROWSER_HOME = 0xAC,
10714 		VOLUME_MUTE = 0xAD,
10715 		VOLUME_DOWN = 0xAE,
10716 		VOLUME_UP = 0xAF,
10717 		MEDIA_NEXT_TRACK = 0xB0,
10718 		MEDIA_PREV_TRACK = 0xB1,
10719 		MEDIA_STOP = 0xB2,
10720 		MEDIA_PLAY_PAUSE = 0xB3,
10721 		LAUNCH_MAIL = 0xB4,
10722 		LAUNCH_MEDIA_SELECT = 0xB5,
10723 		LAUNCH_APP1 = 0xB6,
10724 		LAUNCH_APP2 = 0xB7,
10725 		//}
10726 		OEM_1 = 0xBA,
10727 		//static if (_WIN32_WINNT > =  0x500) {
10728 		OEM_PLUS = 0xBB,
10729 		OEM_COMMA = 0xBC,
10730 		OEM_MINUS = 0xBD,
10731 		OEM_PERIOD = 0xBE,
10732 		//}
10733 		OEM_2 = 0xBF,
10734 		OEM_3 = 0xC0,
10735 		OEM_4 = 0xDB,
10736 		OEM_5 = 0xDC,
10737 		OEM_6 = 0xDD,
10738 		OEM_7 = 0xDE,
10739 		OEM_8 = 0xDF,
10740 		//static if (_WIN32_WINNT > =  0x500) {
10741 		OEM_102 = 0xE2,
10742 		//}
10743 		PROCESSKEY = 0xE5,
10744 		//static if (_WIN32_WINNT > =  0x500) {
10745 		PACKET = 0xE7,
10746 		//}
10747 		ATTN = 0xF6,
10748 		CRSEL = 0xF7,
10749 		EXSEL = 0xF8,
10750 		EREOF = 0xF9,
10751 		PLAY = 0xFA,
10752 		ZOOM = 0xFB,
10753 		NONAME = 0xFC,
10754 		PA1 = 0xFD,
10755 		OEM_CLEAR = 0xFE,
10756 	}
10757 
10758 } else version(OSXCocoa) {
10759 	// FIXME
10760 	enum Key {
10761 		Escape = 0x1b,
10762 		F1 = 0x70,
10763 		F2 = 0x71,
10764 		F3 = 0x72,
10765 		F4 = 0x73,
10766 		F5 = 0x74,
10767 		F6 = 0x75,
10768 		F7 = 0x76,
10769 		F8 = 0x77,
10770 		F9 = 0x78,
10771 		F10 = 0x79,
10772 		F11 = 0x7a,
10773 		F12 = 0x7b,
10774 		PrintScreen = 0x2c,
10775 		ScrollLock = -2, // FIXME
10776 		Pause = -3, // FIXME
10777 		Grave = 0xc0,
10778 		// number keys across the top of the keyboard
10779 		N1 = 0x31,
10780 		N2 = 0x32,
10781 		N3 = 0x33,
10782 		N4 = 0x34,
10783 		N5 = 0x35,
10784 		N6 = 0x36,
10785 		N7 = 0x37,
10786 		N8 = 0x38,
10787 		N9 = 0x39,
10788 		N0 = 0x30,
10789 		Dash = 0xbd,
10790 		Equals = 0xbb,
10791 		Backslash = 0xdc,
10792 		Backspace = 0x08,
10793 		Insert = 0x2d,
10794 		Home = 0x24,
10795 		PageUp = 0x21,
10796 		Delete = 0x2e,
10797 		End = 0x23,
10798 		PageDown = 0x22,
10799 		Up = 0x26,
10800 		Down = 0x28,
10801 		Left = 0x25,
10802 		Right = 0x27,
10803 
10804 		Tab = 0x09,
10805 		Q = 0x51,
10806 		W = 0x57,
10807 		E = 0x45,
10808 		R = 0x52,
10809 		T = 0x54,
10810 		Y = 0x59,
10811 		U = 0x55,
10812 		I = 0x49,
10813 		O = 0x4f,
10814 		P = 0x50,
10815 		LeftBracket = 0xdb,
10816 		RightBracket = 0xdd,
10817 		CapsLock = 0x14,
10818 		A = 0x41,
10819 		S = 0x53,
10820 		D = 0x44,
10821 		F = 0x46,
10822 		G = 0x47,
10823 		H = 0x48,
10824 		J = 0x4a,
10825 		K = 0x4b,
10826 		L = 0x4c,
10827 		Semicolon = 0xba,
10828 		Apostrophe = 0xde,
10829 		Enter = 0x0d,
10830 		Shift = 0x10,
10831 		Z = 0x5a,
10832 		X = 0x58,
10833 		C = 0x43,
10834 		V = 0x56,
10835 		B = 0x42,
10836 		N = 0x4e,
10837 		M = 0x4d,
10838 		Comma = 0xbc,
10839 		Period = 0xbe,
10840 		Slash = 0xbf,
10841 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10842 		Ctrl = 0x11,
10843 		Windows = 0x5b,
10844 		Alt = -5, // FIXME
10845 		Space = 0x20,
10846 		Alt_r = 0xffea, // ditto of shift_r
10847 		Windows_r = -6, // FIXME
10848 		Menu = 0x5d,
10849 		Ctrl_r = -7, // FIXME
10850 
10851 		NumLock = 0x90,
10852 		Divide = 0x6f,
10853 		Multiply = 0x6a,
10854 		Minus = 0x6d,
10855 		Plus = 0x6b,
10856 		PadEnter = -8, // FIXME
10857 		// FIXME for the rest of these:
10858 		Pad1 = 0xff9c,
10859 		Pad2 = 0xff99,
10860 		Pad3 = 0xff9b,
10861 		Pad4 = 0xff96,
10862 		Pad5 = 0xff9d,
10863 		Pad6 = 0xff98,
10864 		Pad7 = 0xff95,
10865 		Pad8 = 0xff97,
10866 		Pad9 = 0xff9a,
10867 		Pad0 = 0xff9e,
10868 		PadDot = 0xff9f,
10869 	}
10870 
10871 }
10872 
10873 /* Additional utilities */
10874 
10875 
10876 Color fromHsl(real h, real s, real l) {
10877 	return arsd.color.fromHsl([h,s,l]);
10878 }
10879 
10880 
10881 
10882 /* ********** What follows is the system-specific implementations *********/
10883 version(Windows) {
10884 
10885 
10886 	// helpers for making HICONs from MemoryImages
10887 	class WindowsIcon {
10888 		struct Win32Icon(int colorCount) {
10889 		align(1):
10890 			uint biSize;
10891 			int biWidth;
10892 			int biHeight;
10893 			ushort biPlanes;
10894 			ushort biBitCount;
10895 			uint biCompression;
10896 			uint biSizeImage;
10897 			int biXPelsPerMeter;
10898 			int biYPelsPerMeter;
10899 			uint biClrUsed;
10900 			uint biClrImportant;
10901 			RGBQUAD[colorCount] biColors;
10902 			/* Pixels:
10903 			Uint8 pixels[]
10904 			*/
10905 			/* Mask:
10906 			Uint8 mask[]
10907 			*/
10908 
10909 			ubyte[4096] data;
10910 
10911 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
10912 				width = mi.width;
10913 				height = mi.height;
10914 
10915 				auto indexedImage = cast(IndexedImage) mi;
10916 				if(indexedImage is null)
10917 					indexedImage = quantize(mi.getAsTrueColorImage());
10918 
10919 				assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
10920 				assert(height %4 == 0);
10921 
10922 				int icon_plen = height*((width+3)&~3);
10923 				int icon_mlen = height*((((width+7)/8)+3)&~3);
10924 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
10925 
10926 				biSize = 40;
10927 				biWidth = width;
10928 				biHeight = height*2;
10929 				biPlanes = 1;
10930 				biBitCount = 8;
10931 				biSizeImage = icon_plen+icon_mlen;
10932 
10933 				int offset = 0;
10934 				int andOff = icon_plen * 8; // the and offset is in bits
10935 				for(int y = height - 1; y >= 0; y--) {
10936 					int off2 = y * width;
10937 					foreach(x; 0 .. width) {
10938 						const b = indexedImage.data[off2 + x];
10939 						data[offset] = b;
10940 						offset++;
10941 
10942 						const andBit = andOff % 8;
10943 						const andIdx = andOff / 8;
10944 						assert(b < indexedImage.palette.length);
10945 						// this is anded to the destination, since and 0 means erase,
10946 						// we want that to  be opaque, and 1 for transparent
10947 						auto transparent = (indexedImage.palette[b].a <= 127);
10948 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
10949 
10950 						andOff++;
10951 					}
10952 
10953 					andOff += andOff % 32;
10954 				}
10955 
10956 				foreach(idx, entry; indexedImage.palette) {
10957 					if(entry.a > 127) {
10958 						biColors[idx].rgbBlue = entry.b;
10959 						biColors[idx].rgbGreen = entry.g;
10960 						biColors[idx].rgbRed = entry.r;
10961 					} else {
10962 						biColors[idx].rgbBlue = 255;
10963 						biColors[idx].rgbGreen = 255;
10964 						biColors[idx].rgbRed = 255;
10965 					}
10966 				}
10967 
10968 				/*
10969 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
10970 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
10971 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
10972 				auto pngMap = fetchPaletteWin32(png);
10973 				biColors[0..pngMap.length] = pngMap[];
10974 				*/
10975 			}
10976 		}
10977 
10978 
10979 		Win32Icon!(256) icon_win32;
10980 
10981 
10982 		this(MemoryImage mi) {
10983 			int icon_len, width, height;
10984 
10985 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
10986 
10987 			/*
10988 			PNG* png = readPnpngData);
10989 			PNGHeader pngh = getHeader(png);
10990 			void* icon_win32;
10991 			if(pngh.depth == 4) {
10992 				auto i = new Win32Icon!(16);
10993 				i.fromPNG(png, pngh, icon_len, width, height);
10994 				icon_win32 = i;
10995 			}
10996 			else if(pngh.depth == 8) {
10997 				auto i = new Win32Icon!(256);
10998 				i.fromPNG(png, pngh, icon_len, width, height);
10999 				icon_win32 = i;
11000 			} else assert(0);
11001 			*/
11002 
11003 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
11004 
11005 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11006 		}
11007 
11008 		~this() {
11009 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11010 			DestroyIcon(hIcon);
11011 		}
11012 
11013 		HICON hIcon;
11014 	}
11015 
11016 
11017 
11018 
11019 
11020 
11021 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11022 	alias HWND NativeWindowHandle;
11023 
11024 	extern(Windows)
11025 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11026 		try {
11027 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11028 				// it returns zero if the message is handled, so we won't do anything more there
11029 				// do I like that though?
11030 				int mustReturn;
11031 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11032 				if(mustReturn)
11033 					return ret;
11034 			}
11035 
11036 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11037 				if(window.getNativeEventHandler !is null) {
11038 					int mustReturn;
11039 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11040 					if(mustReturn)
11041 						return ret;
11042 				}
11043 				if(auto w = cast(SimpleWindow) (*window))
11044 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11045 				else
11046 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11047 			} else {
11048 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11049 			}
11050 		} catch (Exception e) {
11051 			try {
11052 				sdpy_abort(e);
11053 				return 0;
11054 			} catch(Exception e) { assert(0); }
11055 		}
11056 	}
11057 
11058 	void sdpy_abort(Throwable e) nothrow {
11059 		try
11060 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11061 		catch(Exception e)
11062 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11063 		ExitProcess(1);
11064 	}
11065 
11066 	mixin template NativeScreenPainterImplementation() {
11067 		HDC hdc;
11068 		HWND hwnd;
11069 		//HDC windowHdc;
11070 		HBITMAP oldBmp;
11071 
11072 		void create(NativeWindowHandle window) {
11073 			hwnd = window;
11074 
11075 			if(auto sw = cast(SimpleWindow) this.window) {
11076 				// drawing on a window, double buffer
11077 				auto windowHdc = GetDC(hwnd);
11078 
11079 				auto buffer = sw.impl.buffer;
11080 				if(buffer is null) {
11081 					hdc = windowHdc;
11082 					windowDc = true;
11083 				} else {
11084 					hdc = CreateCompatibleDC(windowHdc);
11085 
11086 					ReleaseDC(hwnd, windowHdc);
11087 
11088 					oldBmp = SelectObject(hdc, buffer);
11089 				}
11090 			} else {
11091 				// drawing on something else, draw directly
11092 				hdc = CreateCompatibleDC(null);
11093 				SelectObject(hdc, window);
11094 			}
11095 
11096 			// X doesn't draw a text background, so neither should we
11097 			SetBkMode(hdc, TRANSPARENT);
11098 
11099 			ensureDefaultFontLoaded();
11100 
11101 			if(defaultGuiFont) {
11102 				SelectObject(hdc, defaultGuiFont);
11103 				// DeleteObject(defaultGuiFont);
11104 			}
11105 		}
11106 
11107 		static HFONT defaultGuiFont;
11108 		static void ensureDefaultFontLoaded() {
11109 			static bool triedDefaultGuiFont = false;
11110 			if(!triedDefaultGuiFont) {
11111 				NONCLIENTMETRICS params;
11112 				params.cbSize = params.sizeof;
11113 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11114 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11115 				}
11116 				triedDefaultGuiFont = true;
11117 			}
11118 		}
11119 
11120 		private OperatingSystemFont _activeFont;
11121 
11122 		void setFont(OperatingSystemFont font) {
11123 			_activeFont = font;
11124 			if(font && font.font) {
11125 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11126 					// error... how to handle tho?
11127 				} else {
11128 
11129 				}
11130 			}
11131 			else if(defaultGuiFont)
11132 				SelectObject(hdc, defaultGuiFont);
11133 		}
11134 
11135 		arsd.color.Rectangle _clipRectangle;
11136 
11137 		void setClipRectangle(int x, int y, int width, int height) {
11138 			auto old = _clipRectangle;
11139 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11140 			if(old == _clipRectangle)
11141 				return;
11142 
11143 			if(width == 0 || height == 0) {
11144 				SelectClipRgn(hdc, null);
11145 			} else {
11146 				auto region = CreateRectRgn(x, y, x + width, y + height);
11147 				SelectClipRgn(hdc, region);
11148 				DeleteObject(region);
11149 			}
11150 		}
11151 
11152 
11153 		// just because we can on Windows...
11154 		//void create(Image image);
11155 
11156 		void invalidateRect(Rectangle invalidRect) {
11157 			RECT rect;
11158 			rect.left = invalidRect.left;
11159 			rect.right = invalidRect.right;
11160 			rect.top = invalidRect.top;
11161 			rect.bottom = invalidRect.bottom;
11162 			InvalidateRect(hwnd, &rect, false);
11163 		}
11164 		bool manualInvalidations;
11165 
11166 		void dispose() {
11167 			// FIXME: this.window.width/height is probably wrong
11168 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11169 			// ReleaseDC(hwnd, windowHdc);
11170 
11171 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11172 			if(cast(SimpleWindow) this.window) {
11173 				if(!manualInvalidations)
11174 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11175 			}
11176 
11177 			if(originalPen !is null)
11178 				SelectObject(hdc, originalPen);
11179 			if(currentPen !is null)
11180 				DeleteObject(currentPen);
11181 			if(originalBrush !is null)
11182 				SelectObject(hdc, originalBrush);
11183 			if(currentBrush !is null)
11184 				DeleteObject(currentBrush);
11185 
11186 			SelectObject(hdc, oldBmp);
11187 
11188 			if(windowDc)
11189 				ReleaseDC(hwnd, hdc);
11190 			else
11191 				DeleteDC(hdc);
11192 
11193 			if(window.paintingFinishedDg !is null)
11194 				window.paintingFinishedDg()();
11195 		}
11196 
11197 		bool windowDc;
11198 		HPEN originalPen;
11199 		HPEN currentPen;
11200 
11201 		Pen _activePen;
11202 
11203 		Color _outlineColor;
11204 
11205 		@property void pen(Pen p) {
11206 			_activePen = p;
11207 			_outlineColor = p.color;
11208 
11209 			HPEN pen;
11210 			if(p.color.a == 0) {
11211 				pen = GetStockObject(NULL_PEN);
11212 			} else {
11213 				int style = PS_SOLID;
11214 				final switch(p.style) {
11215 					case Pen.Style.Solid:
11216 						style = PS_SOLID;
11217 					break;
11218 					case Pen.Style.Dashed:
11219 						style = PS_DASH;
11220 					break;
11221 					case Pen.Style.Dotted:
11222 						style = PS_DOT;
11223 					break;
11224 				}
11225 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11226 			}
11227 			auto orig = SelectObject(hdc, pen);
11228 			if(originalPen is null)
11229 				originalPen = orig;
11230 
11231 			if(currentPen !is null)
11232 				DeleteObject(currentPen);
11233 
11234 			currentPen = pen;
11235 
11236 			// the outline is like a foreground since it's done that way on X
11237 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11238 
11239 		}
11240 
11241 		@property void rasterOp(RasterOp op) {
11242 			int mode;
11243 			final switch(op) {
11244 				case RasterOp.normal:
11245 					mode = R2_COPYPEN;
11246 				break;
11247 				case RasterOp.xor:
11248 					mode = R2_XORPEN;
11249 				break;
11250 			}
11251 			SetROP2(hdc, mode);
11252 		}
11253 
11254 		HBRUSH originalBrush;
11255 		HBRUSH currentBrush;
11256 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11257 		@property void fillColor(Color c) {
11258 			if(c == _fillColor)
11259 				return;
11260 			_fillColor = c;
11261 			HBRUSH brush;
11262 			if(c.a == 0) {
11263 				brush = GetStockObject(HOLLOW_BRUSH);
11264 			} else {
11265 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11266 			}
11267 			auto orig = SelectObject(hdc, brush);
11268 			if(originalBrush is null)
11269 				originalBrush = orig;
11270 
11271 			if(currentBrush !is null)
11272 				DeleteObject(currentBrush);
11273 
11274 			currentBrush = brush;
11275 
11276 			// background color is NOT set because X doesn't draw text backgrounds
11277 			//   SetBkColor(hdc, RGB(255, 255, 255));
11278 		}
11279 
11280 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11281 			BITMAP bm;
11282 
11283 			HDC hdcMem = CreateCompatibleDC(hdc);
11284 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11285 
11286 			GetObject(i.handle, bm.sizeof, &bm);
11287 
11288 			// or should I AlphaBlend!??!?!
11289 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11290 
11291 			SelectObject(hdcMem, hbmOld);
11292 			DeleteDC(hdcMem);
11293 		}
11294 
11295 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11296 			BITMAP bm;
11297 
11298 			HDC hdcMem = CreateCompatibleDC(hdc);
11299 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11300 
11301 			GetObject(s.handle, bm.sizeof, &bm);
11302 
11303 			version(CRuntime_DigitalMars) goto noalpha;
11304 
11305 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11306 			if(s.enableAlpha) {
11307 				auto dw = w ? w : bm.bmWidth;
11308 				auto dh = h ? h : bm.bmHeight;
11309 				BLENDFUNCTION bf;
11310 				bf.BlendOp = AC_SRC_OVER;
11311 				bf.SourceConstantAlpha = 255;
11312 				bf.AlphaFormat = AC_SRC_ALPHA;
11313 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11314 			} else {
11315 				noalpha:
11316 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11317 			}
11318 
11319 			SelectObject(hdcMem, hbmOld);
11320 			DeleteDC(hdcMem);
11321 		}
11322 
11323 		Size textSize(scope const(char)[] text) {
11324 			bool dummyX;
11325 			if(text.length == 0) {
11326 				text = " ";
11327 				dummyX = true;
11328 			}
11329 			RECT rect;
11330 			WCharzBuffer buffer = WCharzBuffer(text);
11331 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11332 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11333 		}
11334 
11335 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11336 			if(text.length && text[$-1] == '\n')
11337 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11338 			if(text.length && text[$-1] == '\r')
11339 				text = text[0 .. $-1];
11340 
11341 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11342 			if(x2 == 0 && y2 == 0) {
11343 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11344 			} else {
11345 				RECT rect;
11346 				rect.left = x;
11347 				rect.top = y;
11348 				rect.right = x2;
11349 				rect.bottom = y2;
11350 
11351 				uint mode = DT_LEFT;
11352 				if(alignment & TextAlignment.Right)
11353 					mode = DT_RIGHT;
11354 				else if(alignment & TextAlignment.Center)
11355 					mode = DT_CENTER;
11356 
11357 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11358 				if(alignment & TextAlignment.VerticalCenter)
11359 					mode |= DT_VCENTER | DT_SINGLELINE;
11360 
11361 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11362 			}
11363 
11364 			/*
11365 			uint mode;
11366 
11367 			if(alignment & TextAlignment.Center)
11368 				mode = TA_CENTER;
11369 
11370 			SetTextAlign(hdc, mode);
11371 			*/
11372 		}
11373 
11374 		int fontHeight() {
11375 			TEXTMETRIC metric;
11376 			if(GetTextMetricsW(hdc, &metric)) {
11377 				return metric.tmHeight;
11378 			}
11379 
11380 			return 16; // idk just guessing here, maybe we should throw
11381 		}
11382 
11383 		void drawPixel(int x, int y) {
11384 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11385 		}
11386 
11387 		// The basic shapes, outlined
11388 
11389 		void drawLine(int x1, int y1, int x2, int y2) {
11390 			MoveToEx(hdc, x1, y1, null);
11391 			LineTo(hdc, x2, y2);
11392 		}
11393 
11394 		void drawRectangle(int x, int y, int width, int height) {
11395 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11396 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11397 		}
11398 
11399 		/// Arguments are the points of the bounding rectangle
11400 		void drawEllipse(int x1, int y1, int x2, int y2) {
11401 			Ellipse(hdc, x1, y1, x2, y2);
11402 		}
11403 
11404 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11405 			if((start % (360*64)) == (finish % (360*64)))
11406 				drawEllipse(x1, y1, x1 + width, y1 + height);
11407 			else {
11408 				import core.stdc.math;
11409 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11410 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11411 
11412 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11413 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11414 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11415 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11416 
11417 				if(_activePen.color.a)
11418 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11419 				if(_fillColor.a)
11420 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11421 			}
11422 		}
11423 
11424 		void drawPolygon(Point[] vertexes) {
11425 			POINT[] points;
11426 			points.length = vertexes.length;
11427 
11428 			foreach(i, p; vertexes) {
11429 				points[i].x = p.x;
11430 				points[i].y = p.y;
11431 			}
11432 
11433 			Polygon(hdc, points.ptr, cast(int) points.length);
11434 		}
11435 	}
11436 
11437 
11438 	// Mix this into the SimpleWindow class
11439 	mixin template NativeSimpleWindowImplementation() {
11440 		int curHidden = 0; // counter
11441 		__gshared static bool[string] knownWinClasses;
11442 		static bool altPressed = false;
11443 
11444 		HANDLE oldCursor;
11445 
11446 		void hideCursor () {
11447 			if(curHidden == 0)
11448 				oldCursor = SetCursor(null);
11449 			++curHidden;
11450 		}
11451 
11452 		void showCursor () {
11453 			--curHidden;
11454 			if(curHidden == 0) {
11455 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11456 			}
11457 		}
11458 
11459 
11460 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11461 
11462 		void setMinSize (int minwidth, int minheight) {
11463 			minWidth = minwidth;
11464 			minHeight = minheight;
11465 		}
11466 		void setMaxSize (int maxwidth, int maxheight) {
11467 			maxWidth = maxwidth;
11468 			maxHeight = maxheight;
11469 		}
11470 
11471 		// FIXME i'm not sure that Windows has this functionality
11472 		// though it is nonessential anyway.
11473 		void setResizeGranularity (int granx, int grany) {}
11474 
11475 		ScreenPainter getPainter(bool manualInvalidations) {
11476 			return ScreenPainter(this, hwnd, manualInvalidations);
11477 		}
11478 
11479 		HBITMAP buffer;
11480 
11481 		void setTitle(string title) {
11482 			WCharzBuffer bfr = WCharzBuffer(title);
11483 			SetWindowTextW(hwnd, bfr.ptr);
11484 		}
11485 
11486 		string getTitle() {
11487 			auto len = GetWindowTextLengthW(hwnd);
11488 			if (!len)
11489 				return null;
11490 			wchar[256] tmpBuffer;
11491 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11492 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11493 			auto str = buffer[0 .. len2];
11494 			return makeUtf8StringFromWindowsString(str);
11495 		}
11496 
11497 		void move(int x, int y) {
11498 			RECT rect;
11499 			GetWindowRect(hwnd, &rect);
11500 			// move it while maintaining the same size...
11501 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11502 		}
11503 
11504 		void resize(int w, int h) {
11505 			RECT rect;
11506 			GetWindowRect(hwnd, &rect);
11507 
11508 			RECT client;
11509 			GetClientRect(hwnd, &client);
11510 
11511 			rect.right = rect.right - client.right + w;
11512 			rect.bottom = rect.bottom - client.bottom + h;
11513 
11514 			// same position, new size for the client rectangle
11515 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11516 
11517 			updateOpenglViewportIfNeeded(w, h);
11518 		}
11519 
11520 		void moveResize (int x, int y, int w, int h) {
11521 			// what's given is the client rectangle, we need to adjust
11522 
11523 			RECT rect;
11524 			rect.left = x;
11525 			rect.top = y;
11526 			rect.right = w + x;
11527 			rect.bottom = h + y;
11528 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11529 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
11530 
11531 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11532 			updateOpenglViewportIfNeeded(w, h);
11533 			if (windowResized !is null) windowResized(w, h);
11534 		}
11535 
11536 		version(without_opengl) {} else {
11537 			HGLRC ghRC;
11538 			HDC ghDC;
11539 		}
11540 
11541 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11542 			string cnamec;
11543 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11544 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11545 				cnamec = "DSimpleWindow";
11546 			} else {
11547 				cnamec = sdpyWindowClass;
11548 			}
11549 
11550 			WCharzBuffer cn = WCharzBuffer(cnamec);
11551 
11552 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11553 
11554 			if(cnamec !in knownWinClasses) {
11555 				WNDCLASSEX wc;
11556 
11557 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11558 				// to the object. Maybe.
11559 				wc.cbSize = wc.sizeof;
11560 				wc.cbClsExtra = 0;
11561 				wc.cbWndExtra = 0;
11562 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11563 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11564 				wc.hIcon = LoadIcon(hInstance, null);
11565 				wc.hInstance = hInstance;
11566 				wc.lpfnWndProc = &WndProc;
11567 				wc.lpszClassName = cn.ptr;
11568 				wc.hIconSm = null;
11569 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11570 				if(!RegisterClassExW(&wc))
11571 					throw new WindowsApiException("RegisterClassExW", GetLastError());
11572 				knownWinClasses[cnamec] = true;
11573 			}
11574 
11575 			int style;
11576 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11577 
11578 			// FIXME: windowType and customizationFlags
11579 			final switch(windowType) {
11580 				case WindowTypes.normal:
11581 					if(resizability == Resizability.fixedSize) {
11582 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11583 					} else {
11584 						style = WS_OVERLAPPEDWINDOW;
11585 					}
11586 				break;
11587 				case WindowTypes.undecorated:
11588 					style = WS_POPUP | WS_SYSMENU;
11589 				break;
11590 				case WindowTypes.eventOnly:
11591 					_hidden = true;
11592 				break;
11593 				case WindowTypes.dropdownMenu:
11594 				case WindowTypes.popupMenu:
11595 				case WindowTypes.notification:
11596 					style = WS_POPUP;
11597 					flags |= WS_EX_NOACTIVATE;
11598 				break;
11599 				case WindowTypes.nestedChild:
11600 					style = WS_CHILD;
11601 				break;
11602 				case WindowTypes.minimallyWrapped:
11603 					assert(0, "construct minimally wrapped through the other ctor overlad");
11604 			}
11605 
11606 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11607 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11608 
11609 			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
11610 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11611 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11612 
11613 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11614 				setOpacity(255);
11615 
11616 			SimpleWindow.nativeMapping[hwnd] = this;
11617 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11618 
11619 			if(windowType == WindowTypes.eventOnly)
11620 				return;
11621 
11622 			HDC hdc = GetDC(hwnd);
11623 
11624 
11625 			version(without_opengl) {}
11626 			else {
11627 				if(opengl == OpenGlOptions.yes) {
11628 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11629 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11630 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11631 					ghDC = hdc;
11632 					PIXELFORMATDESCRIPTOR pfd;
11633 
11634 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11635 					pfd.nVersion = 1;
11636 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11637 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11638 					pfd.iPixelType = PFD_TYPE_RGBA;
11639 					pfd.cColorBits = 24;
11640 					pfd.cDepthBits = 24;
11641 					pfd.cAccumBits = 0;
11642 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11643 
11644 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11645 
11646 					if (pixelformat == 0)
11647 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
11648 
11649 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11650 						throw new WindowsApiException("SetPixelFormat", GetLastError());
11651 
11652 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11653 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11654 						// so we will create fake context to get that stupid address
11655 						auto tmpcc = wglCreateContext(ghDC);
11656 						if (tmpcc !is null) {
11657 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11658 							wglMakeCurrent(ghDC, tmpcc);
11659 							wglInitOtherFunctions();
11660 						}
11661 					}
11662 
11663 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11664 						int[9] contextAttribs = [
11665 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11666 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11667 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11668 							// for modern context, set "forward compatibility" flag too
11669 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11670 							0/*None*/,
11671 						];
11672 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11673 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11674 							// activate fallback mode
11675 							// 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;
11676 							ghRC = wglCreateContext(ghDC);
11677 						}
11678 						if (ghRC is null)
11679 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
11680 					} else {
11681 						// try to do at least something
11682 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11683 							sdpyOpenGLContextVersion = 0;
11684 							ghRC = wglCreateContext(ghDC);
11685 						}
11686 						if (ghRC is null)
11687 							throw new WindowsApiException("wglCreateContext", GetLastError());
11688 					}
11689 				}
11690 			}
11691 
11692 			if(opengl == OpenGlOptions.no) {
11693 				buffer = CreateCompatibleBitmap(hdc, width, height);
11694 
11695 				auto hdcBmp = CreateCompatibleDC(hdc);
11696 				// make sure it's filled with a blank slate
11697 				auto oldBmp = SelectObject(hdcBmp, buffer);
11698 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11699 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11700 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11701 				SelectObject(hdcBmp, oldBmp);
11702 				SelectObject(hdcBmp, oldBrush);
11703 				SelectObject(hdcBmp, oldPen);
11704 				DeleteDC(hdcBmp);
11705 
11706 				bmpWidth = width;
11707 				bmpHeight = height;
11708 
11709 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11710 			}
11711 
11712 			// We want the window's client area to match the image size
11713 			RECT rcClient, rcWindow;
11714 			POINT ptDiff;
11715 			GetClientRect(hwnd, &rcClient);
11716 			GetWindowRect(hwnd, &rcWindow);
11717 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11718 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11719 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11720 
11721 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11722 				ShowWindow(hwnd, SW_SHOWNORMAL);
11723 			} else {
11724 				_hidden = true;
11725 			}
11726 			this._visibleForTheFirstTimeCalled = false; // hack!
11727 		}
11728 
11729 
11730 		void dispose() {
11731 			if(buffer)
11732 				DeleteObject(buffer);
11733 		}
11734 
11735 		void closeWindow() {
11736 			DestroyWindow(hwnd);
11737 		}
11738 
11739 		bool setOpacity(ubyte alpha) {
11740 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11741 		}
11742 
11743 		HANDLE currentCursor;
11744 
11745 		// returns zero if it recognized the event
11746 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11747 			MouseEvent mouse;
11748 
11749 			void mouseEvent(bool isScreen, ulong mods) {
11750 				auto x = LOWORD(lParam);
11751 				auto y = HIWORD(lParam);
11752 				if(isScreen) {
11753 					POINT p;
11754 					p.x = x;
11755 					p.y = y;
11756 					ScreenToClient(hwnd, &p);
11757 					x = cast(ushort) p.x;
11758 					y = cast(ushort) p.y;
11759 				}
11760 
11761 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11762 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11763 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11764 				}
11765 
11766 				mouse.x = x + offsetX;
11767 				mouse.y = y + offsetY;
11768 
11769 				wind.mdx(mouse);
11770 				mouse.modifierState = cast(int) mods;
11771 				mouse.window = wind;
11772 
11773 				if(wind.handleMouseEvent)
11774 					wind.handleMouseEvent(mouse);
11775 			}
11776 
11777 			switch(msg) {
11778 				case WM_GETMINMAXINFO:
11779 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11780 
11781 					if(wind.minWidth > 0) {
11782 						RECT rect;
11783 						rect.left = 100;
11784 						rect.top = 100;
11785 						rect.right = wind.minWidth + 100;
11786 						rect.bottom = wind.minHeight + 100;
11787 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11788 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11789 
11790 						mmi.ptMinTrackSize.x = rect.right - rect.left;
11791 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
11792 					}
11793 
11794 					if(wind.maxWidth < int.max) {
11795 						RECT rect;
11796 						rect.left = 100;
11797 						rect.top = 100;
11798 						rect.right = wind.maxWidth + 100;
11799 						rect.bottom = wind.maxHeight + 100;
11800 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11801 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11802 
11803 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
11804 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
11805 					}
11806 				break;
11807 				case WM_CHAR:
11808 					wchar c = cast(wchar) wParam;
11809 					if(wind.handleCharEvent)
11810 						wind.handleCharEvent(cast(dchar) c);
11811 				break;
11812 				  case WM_SETFOCUS:
11813 				  case WM_KILLFOCUS:
11814 					wind._focused = (msg == WM_SETFOCUS);
11815 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
11816 					if(wind.onFocusChange)
11817 						wind.onFocusChange(msg == WM_SETFOCUS);
11818 				  break;
11819 
11820 				case WM_SYSKEYDOWN:
11821 					goto case;
11822 				case WM_SYSKEYUP:
11823 					if(lParam & (1 << 29)) {
11824 						goto case;
11825 					} else {
11826 						// no window has keyboard focus
11827 						goto default;
11828 					}
11829 				case WM_KEYDOWN:
11830 				case WM_KEYUP:
11831 					KeyEvent ev;
11832 					ev.key = cast(Key) wParam;
11833 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
11834 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
11835 
11836 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
11837 
11838 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
11839 						ev.modifierState |= ModifierState.shift;
11840 					//k8: this doesn't work; thanks for nothing, windows
11841 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
11842 						ev.modifierState |= ModifierState.alt;*/
11843 					// this never seems to actually be set
11844 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11845 
11846 					if (wParam == 0x12) {
11847 						altPressed = (msg == WM_SYSKEYDOWN);
11848 					}
11849 
11850 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
11851 						altPressed = false;
11852 					}
11853 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
11854 
11855 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11856 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
11857 						ev.modifierState |= ModifierState.ctrl;
11858 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
11859 						ev.modifierState |= ModifierState.windows;
11860 					if(GetKeyState(Key.NumLock))
11861 						ev.modifierState |= ModifierState.numLock;
11862 					if(GetKeyState(Key.CapsLock))
11863 						ev.modifierState |= ModifierState.capsLock;
11864 
11865 					/+
11866 					// we always want to send the character too, so let's convert it
11867 					ubyte[256] state;
11868 					wchar[16] buffer;
11869 					GetKeyboardState(state.ptr);
11870 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
11871 
11872 					foreach(dchar d; buffer) {
11873 						ev.character = d;
11874 						break;
11875 					}
11876 					+/
11877 
11878 					ev.window = wind;
11879 					if(wind.handleKeyEvent)
11880 						wind.handleKeyEvent(ev);
11881 				break;
11882 				case 0x020a /*WM_MOUSEWHEEL*/:
11883 					// send click
11884 					mouse.type = cast(MouseEventType) 1;
11885 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11886 					mouseEvent(true, LOWORD(wParam));
11887 
11888 					// also send release
11889 					mouse.type = cast(MouseEventType) 2;
11890 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11891 					mouseEvent(true, LOWORD(wParam));
11892 				break;
11893 				case WM_MOUSEMOVE:
11894 					mouse.type = cast(MouseEventType) 0;
11895 					mouseEvent(false, wParam);
11896 				break;
11897 				case WM_LBUTTONDOWN:
11898 				case WM_LBUTTONDBLCLK:
11899 					mouse.type = cast(MouseEventType) 1;
11900 					mouse.button = MouseButton.left;
11901 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
11902 					mouseEvent(false, wParam);
11903 				break;
11904 				case WM_LBUTTONUP:
11905 					mouse.type = cast(MouseEventType) 2;
11906 					mouse.button = MouseButton.left;
11907 					mouseEvent(false, wParam);
11908 				break;
11909 				case WM_RBUTTONDOWN:
11910 				case WM_RBUTTONDBLCLK:
11911 					mouse.type = cast(MouseEventType) 1;
11912 					mouse.button = MouseButton.right;
11913 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
11914 					mouseEvent(false, wParam);
11915 				break;
11916 				case WM_RBUTTONUP:
11917 					mouse.type = cast(MouseEventType) 2;
11918 					mouse.button = MouseButton.right;
11919 					mouseEvent(false, wParam);
11920 				break;
11921 				case WM_MBUTTONDOWN:
11922 				case WM_MBUTTONDBLCLK:
11923 					mouse.type = cast(MouseEventType) 1;
11924 					mouse.button = MouseButton.middle;
11925 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
11926 					mouseEvent(false, wParam);
11927 				break;
11928 				case WM_MBUTTONUP:
11929 					mouse.type = cast(MouseEventType) 2;
11930 					mouse.button = MouseButton.middle;
11931 					mouseEvent(false, wParam);
11932 				break;
11933 				case WM_XBUTTONDOWN:
11934 				case WM_XBUTTONDBLCLK:
11935 					mouse.type = cast(MouseEventType) 1;
11936 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11937 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
11938 					mouseEvent(false, wParam);
11939 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
11940 				case WM_XBUTTONUP:
11941 					mouse.type = cast(MouseEventType) 2;
11942 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11943 					mouseEvent(false, wParam);
11944 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
11945 
11946 				default: return 1;
11947 			}
11948 			return 0;
11949 		}
11950 
11951 		HWND hwnd;
11952 		private int oldWidth;
11953 		private int oldHeight;
11954 		private bool inSizeMove;
11955 
11956 		/++
11957 			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.
11958 
11959 			History:
11960 				Added November 23, 2021
11961 
11962 				Not fully stable, may be moved out of the impl struct.
11963 
11964 				Default value changed to `true` on February 15, 2021
11965 		+/
11966 		bool doLiveResizing = true;
11967 
11968 		package int bmpWidth;
11969 		package int bmpHeight;
11970 
11971 		// the extern(Windows) wndproc should just forward to this
11972 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
11973 		try {
11974 			assert(hwnd is this.hwnd);
11975 
11976 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
11977 			switch(msg) {
11978 				case WM_MENUCHAR: // menu active but key not associated with a thing.
11979 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
11980 					// The main things we can do are select, execute, close, or ignore
11981 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
11982 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
11983 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
11984 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
11985 
11986 					// returns the value in the *high order word* of the return value
11987 					// hence the << 16
11988 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
11989 				case WM_SETCURSOR:
11990 					if(cast(HWND) wParam !is hwnd)
11991 						return 0; // further processing elsewhere
11992 
11993 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
11994 						SetCursor(this.curHidden > 0 ? null : currentCursor);
11995 						return 1;
11996 					} else {
11997 						return DefWindowProc(hwnd, msg, wParam, lParam);
11998 					}
11999 				//break;
12000 
12001 				case WM_CLOSE:
12002 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12003 				break;
12004 				case WM_DESTROY:
12005 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12006 					SimpleWindow.nativeMapping.remove(hwnd);
12007 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12008 
12009 					bool anyImportant = false;
12010 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12011 						if(w.beingOpenKeepsAppOpen) {
12012 							anyImportant = true;
12013 							break;
12014 						}
12015 					if(!anyImportant) {
12016 						PostQuitMessage(0);
12017 					}
12018 				break;
12019 				case 0x02E0 /*WM_DPICHANGED*/:
12020 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12021 
12022 					RECT* prcNewWindow = cast(RECT*)lParam;
12023 					// docs say this is the recommended position and we should honor it
12024 					SetWindowPos(hwnd,
12025 							null,
12026 							prcNewWindow.left,
12027 							prcNewWindow.top,
12028 							prcNewWindow.right - prcNewWindow.left,
12029 							prcNewWindow.bottom - prcNewWindow.top,
12030 							SWP_NOZORDER | SWP_NOACTIVATE);
12031 
12032 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12033 					// im not sure it is completely correct
12034 					// but without it the tabs and such do look weird as things change.
12035 					if(SystemParametersInfoForDpi) {
12036 						LOGFONT lfText;
12037 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12038 						HFONT hFontNew = CreateFontIndirect(&lfText);
12039 						if (hFontNew)
12040 						{
12041 							//DeleteObject(hFontOld);
12042 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12043 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12044 								return TRUE;
12045 							}
12046 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12047 						}
12048 					}
12049 
12050 					if(this.onDpiChanged)
12051 						this.onDpiChanged();
12052 				break;
12053 				case WM_ENTERIDLE:
12054 					// when a menu is up, it stops normal event processing (modal message loop)
12055 					// but this at least gives us a chance to SOMETIMES catch up
12056 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12057 					SimpleWindow.processAllCustomEvents;
12058 					SimpleWindow.processAllCustomEvents;
12059 					SleepEx(0, true);
12060 					break;
12061 				case WM_SIZE:
12062 					if(wParam == 1 /* SIZE_MINIMIZED */)
12063 						break;
12064 					_width = LOWORD(lParam);
12065 					_height = HIWORD(lParam);
12066 
12067 					// I want to avoid tearing in the windows (my code is inefficient
12068 					// so this is a hack around that) so while sizing, we don't trigger,
12069 					// but we do want to trigger on events like mazimize.
12070 					if(!inSizeMove || doLiveResizing)
12071 						goto size_changed;
12072 				break;
12073 				/+
12074 				case WM_SIZING:
12075 					import std.stdio; writeln("size");
12076 				break;
12077 				+/
12078 				// I don't like the tearing I get when redrawing on WM_SIZE
12079 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12080 				// so instead it is going to redraw only at the end of a size.
12081 				case 0x0231: /* WM_ENTERSIZEMOVE */
12082 					inSizeMove = true;
12083 				break;
12084 				case 0x0232: /* WM_EXITSIZEMOVE */
12085 					inSizeMove = false;
12086 
12087 					size_changed:
12088 
12089 					// nothing relevant changed, don't bother redrawing
12090 					if(oldWidth == _width && oldHeight == _height) {
12091 						break;
12092 					}
12093 
12094 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12095 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12096 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12097 						// gotta get the double buffer bmp to match the window
12098 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12099 
12100 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12101 						if(resizability != Resizability.automaticallyScaleIfPossible)
12102 						if(_width > bmpWidth || _height > bmpHeight) {
12103 							auto hdc = GetDC(hwnd);
12104 							auto oldBuffer = buffer;
12105 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12106 
12107 							auto hdcBmp = CreateCompatibleDC(hdc);
12108 							auto oldBmp = SelectObject(hdcBmp, buffer);
12109 
12110 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12111 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12112 
12113 							/+
12114 							RECT r;
12115 							r.left = 0;
12116 							r.top = 0;
12117 							r.right = width;
12118 							r.bottom = height;
12119 							auto c = Color.green;
12120 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12121 							FillRect(hdcBmp, &r, brush);
12122 							DeleteObject(brush);
12123 							+/
12124 
12125 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12126 
12127 							bmpWidth = _width;
12128 							bmpHeight = _height;
12129 
12130 							SelectObject(hdcOldBmp, oldOldBmp);
12131 							DeleteDC(hdcOldBmp);
12132 
12133 							SelectObject(hdcBmp, oldBmp);
12134 							DeleteDC(hdcBmp);
12135 
12136 							ReleaseDC(hwnd, hdc);
12137 
12138 							DeleteObject(oldBuffer);
12139 						}
12140 					}
12141 
12142 					updateOpenglViewportIfNeeded(_width, _height);
12143 
12144 					if(resizability != Resizability.automaticallyScaleIfPossible)
12145 					if(windowResized !is null)
12146 						windowResized(_width, _height);
12147 
12148 					if(inSizeMove) {
12149 						SimpleWindow.processAllCustomEvents();
12150 						SimpleWindow.processAllCustomEvents();
12151 					} else {
12152 						// when it is all done, make sure everything is freshly drawn or there might be
12153 						// weird bugs left.
12154 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12155 					}
12156 
12157 					oldWidth = this._width;
12158 					oldHeight = this._height;
12159 				break;
12160 				case WM_ERASEBKGND:
12161 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12162 					if (!this._visibleForTheFirstTimeCalled) {
12163 						this._visibleForTheFirstTimeCalled = true;
12164 						if (this.visibleForTheFirstTime !is null) {
12165 							this.visibleForTheFirstTime();
12166 						}
12167 					}
12168 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12169 					version(without_opengl) {} else {
12170 						if (openglMode == OpenGlOptions.yes) return 1;
12171 					}
12172 					// call windows default handler, so it can paint standard controls
12173 					goto default;
12174 				case WM_CTLCOLORBTN:
12175 				case WM_CTLCOLORSTATIC:
12176 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12177 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12178 					GetSysColorBrush(COLOR_3DFACE);
12179 				//break;
12180 				case WM_SHOWWINDOW:
12181 					this._visible = (wParam != 0);
12182 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12183 						this._visibleForTheFirstTimeCalled = true;
12184 						if (this.visibleForTheFirstTime !is null) {
12185 							this.visibleForTheFirstTime();
12186 						}
12187 					}
12188 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12189 					break;
12190 				case WM_PAINT: {
12191 					if (!this._visibleForTheFirstTimeCalled) {
12192 						this._visibleForTheFirstTimeCalled = true;
12193 						if (this.visibleForTheFirstTime !is null) {
12194 							this.visibleForTheFirstTime();
12195 						}
12196 					}
12197 
12198 					BITMAP bm;
12199 					PAINTSTRUCT ps;
12200 
12201 					HDC hdc = BeginPaint(hwnd, &ps);
12202 
12203 					if(openglMode == OpenGlOptions.no) {
12204 
12205 						HDC hdcMem = CreateCompatibleDC(hdc);
12206 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12207 
12208 						GetObject(buffer, bm.sizeof, &bm);
12209 
12210 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12211 						if(resizability == Resizability.automaticallyScaleIfPossible)
12212 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12213 						else
12214 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12215 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12216 
12217 						SelectObject(hdcMem, hbmOld);
12218 						DeleteDC(hdcMem);
12219 						EndPaint(hwnd, &ps);
12220 					} else {
12221 						EndPaint(hwnd, &ps);
12222 						version(without_opengl) {} else
12223 							redrawOpenGlSceneSoon();
12224 					}
12225 				} break;
12226 				  default:
12227 					return DefWindowProc(hwnd, msg, wParam, lParam);
12228 			}
12229 			 return 0;
12230 
12231 		}
12232 		catch(Throwable t) {
12233 			sdpyPrintDebugString(t.toString);
12234 			return 0;
12235 		}
12236 		}
12237 	}
12238 
12239 	mixin template NativeImageImplementation() {
12240 		HBITMAP handle;
12241 		ubyte* rawData;
12242 
12243 	final:
12244 
12245 		Color getPixel(int x, int y) {
12246 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12247 			// remember, bmps are upside down
12248 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12249 
12250 			Color c;
12251 			if(enableAlpha)
12252 				c.a = rawData[offset + 3];
12253 			else
12254 				c.a = 255;
12255 			c.b = rawData[offset + 0];
12256 			c.g = rawData[offset + 1];
12257 			c.r = rawData[offset + 2];
12258 			c.unPremultiply();
12259 			return c;
12260 		}
12261 
12262 		void setPixel(int x, int y, Color c) {
12263 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12264 			// remember, bmps are upside down
12265 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12266 
12267 			if(enableAlpha)
12268 				c.premultiply();
12269 
12270 			rawData[offset + 0] = c.b;
12271 			rawData[offset + 1] = c.g;
12272 			rawData[offset + 2] = c.r;
12273 			if(enableAlpha)
12274 				rawData[offset + 3] = c.a;
12275 		}
12276 
12277 		void convertToRgbaBytes(ubyte[] where) {
12278 			assert(where.length == this.width * this.height * 4);
12279 
12280 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12281 			int idx = 0;
12282 			int offset = itemsPerLine * (height - 1);
12283 			// remember, bmps are upside down
12284 			for(int y = height - 1; y >= 0; y--) {
12285 				auto offsetStart = offset;
12286 				for(int x = 0; x < width; x++) {
12287 					where[idx + 0] = rawData[offset + 2]; // r
12288 					where[idx + 1] = rawData[offset + 1]; // g
12289 					where[idx + 2] = rawData[offset + 0]; // b
12290 					if(enableAlpha) {
12291 						where[idx + 3] = rawData[offset + 3]; // a
12292 						unPremultiplyRgba(where[idx .. idx + 4]);
12293 						offset++;
12294 					} else
12295 						where[idx + 3] = 255; // a
12296 					idx += 4;
12297 					offset += 3;
12298 				}
12299 
12300 				offset = offsetStart - itemsPerLine;
12301 			}
12302 		}
12303 
12304 		void setFromRgbaBytes(in ubyte[] what) {
12305 			assert(what.length == this.width * this.height * 4);
12306 
12307 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12308 			int idx = 0;
12309 			int offset = itemsPerLine * (height - 1);
12310 			// remember, bmps are upside down
12311 			for(int y = height - 1; y >= 0; y--) {
12312 				auto offsetStart = offset;
12313 				for(int x = 0; x < width; x++) {
12314 					if(enableAlpha) {
12315 						auto a = what[idx + 3];
12316 
12317 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12318 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12319 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12320 						rawData[offset + 3] = a; // a
12321 						//premultiplyBgra(rawData[offset .. offset + 4]);
12322 						offset++;
12323 					} else {
12324 						rawData[offset + 2] = what[idx + 0]; // r
12325 						rawData[offset + 1] = what[idx + 1]; // g
12326 						rawData[offset + 0] = what[idx + 2]; // b
12327 					}
12328 					idx += 4;
12329 					offset += 3;
12330 				}
12331 
12332 				offset = offsetStart - itemsPerLine;
12333 			}
12334 		}
12335 
12336 
12337 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12338 			BITMAPINFO infoheader;
12339 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12340 			infoheader.bmiHeader.biWidth = width;
12341 			infoheader.bmiHeader.biHeight = height;
12342 			infoheader.bmiHeader.biPlanes = 1;
12343 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12344 			infoheader.bmiHeader.biCompression = BI_RGB;
12345 
12346 			handle = CreateDIBSection(
12347 				null,
12348 				&infoheader,
12349 				DIB_RGB_COLORS,
12350 				cast(void**) &rawData,
12351 				null,
12352 				0);
12353 			if(handle is null)
12354 				throw new WindowsApiException("create image failed", GetLastError());
12355 
12356 		}
12357 
12358 		void dispose() {
12359 			DeleteObject(handle);
12360 		}
12361 	}
12362 
12363 	enum KEY_ESCAPE = 27;
12364 }
12365 version(X11) {
12366 	/// This is the default font used. You might change this before doing anything else with
12367 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12368 	/// for cross-platform compatibility.
12369 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12370 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12371 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12372 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12373 
12374 	alias int delegate(XEvent) NativeEventHandler;
12375 	alias Window NativeWindowHandle;
12376 
12377 	enum KEY_ESCAPE = 9;
12378 
12379 	mixin template NativeScreenPainterImplementation() {
12380 		Display* display;
12381 		Drawable d;
12382 		Drawable destiny;
12383 
12384 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12385 		GC gc;
12386 
12387 		__gshared bool fontAttempted;
12388 
12389 		__gshared XFontStruct* defaultfont;
12390 		__gshared XFontSet defaultfontset;
12391 
12392 		XFontStruct* font;
12393 		XFontSet fontset;
12394 
12395 		void create(NativeWindowHandle window) {
12396 			this.display = XDisplayConnection.get();
12397 
12398 			Drawable buffer = None;
12399 			if(auto sw = cast(SimpleWindow) this.window) {
12400 				buffer = sw.impl.buffer;
12401 				this.destiny = cast(Drawable) window;
12402 			} else {
12403 				buffer = cast(Drawable) window;
12404 				this.destiny = None;
12405 			}
12406 
12407 			this.d = cast(Drawable) buffer;
12408 
12409 			auto dgc = DefaultGC(display, DefaultScreen(display));
12410 
12411 			this.gc = XCreateGC(display, d, 0, null);
12412 
12413 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12414 
12415 			ensureDefaultFontLoaded();
12416 
12417 			font = defaultfont;
12418 			fontset = defaultfontset;
12419 
12420 			if(font) {
12421 				XSetFont(display, gc, font.fid);
12422 			}
12423 		}
12424 
12425 		static void ensureDefaultFontLoaded() {
12426 			if(!fontAttempted) {
12427 				auto display = XDisplayConnection.get;
12428 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12429 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12430 				if(font is null) {
12431 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12432 					font = XLoadQueryFont(display, xfontstr.ptr);
12433 				}
12434 
12435 				char** lol;
12436 				int lol2;
12437 				char* lol3;
12438 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12439 
12440 				fontAttempted = true;
12441 
12442 				defaultfont = font;
12443 				defaultfontset = fontset;
12444 			}
12445 		}
12446 
12447 		arsd.color.Rectangle _clipRectangle;
12448 		void setClipRectangle(int x, int y, int width, int height) {
12449 			auto old = _clipRectangle;
12450 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12451 			if(old == _clipRectangle)
12452 				return;
12453 
12454 			if(width == 0 || height == 0) {
12455 				XSetClipMask(display, gc, None);
12456 
12457 				if(xrenderPicturePainter) {
12458 
12459 					XRectangle[1] rects;
12460 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12461 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12462 				}
12463 
12464 				version(with_xft) {
12465 					if(xftFont is null || xftDraw is null)
12466 						return;
12467 					XftDrawSetClip(xftDraw, null);
12468 				}
12469 			} else {
12470 				XRectangle[1] rects;
12471 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12472 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12473 
12474 				if(xrenderPicturePainter)
12475 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12476 
12477 				version(with_xft) {
12478 					if(xftFont is null || xftDraw is null)
12479 						return;
12480 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12481 				}
12482 			}
12483 		}
12484 
12485 		version(with_xft) {
12486 			XftFont* xftFont;
12487 			XftDraw* xftDraw;
12488 
12489 			XftColor xftColor;
12490 
12491 			void updateXftColor() {
12492 				if(xftFont is null)
12493 					return;
12494 
12495 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12496 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12497 
12498 				XftColorAllocValue(
12499 					display,
12500 					DefaultVisual(display, DefaultScreen(display)),
12501 					DefaultColormap(display, 0),
12502 					&colorIn,
12503 					&xftColor
12504 				);
12505 			}
12506 		}
12507 
12508 		private OperatingSystemFont _activeFont;
12509 		void setFont(OperatingSystemFont font) {
12510 			_activeFont = font;
12511 			version(with_xft) {
12512 				if(font && font.isXft && font.xftFont)
12513 					this.xftFont = font.xftFont;
12514 				else
12515 					this.xftFont = null;
12516 
12517 				if(this.xftFont) {
12518 					if(xftDraw is null) {
12519 						xftDraw = XftDrawCreate(
12520 							display,
12521 							d,
12522 							DefaultVisual(display, DefaultScreen(display)),
12523 							DefaultColormap(display, 0)
12524 						);
12525 
12526 						updateXftColor();
12527 					}
12528 
12529 					return;
12530 				}
12531 			}
12532 
12533 			if(font && font.font) {
12534 				this.font = font.font;
12535 				this.fontset = font.fontset;
12536 				XSetFont(display, gc, font.font.fid);
12537 			} else {
12538 				this.font = defaultfont;
12539 				this.fontset = defaultfontset;
12540 			}
12541 
12542 		}
12543 
12544 		private Picture xrenderPicturePainter;
12545 
12546 		bool manualInvalidations;
12547 		void invalidateRect(Rectangle invalidRect) {
12548 			// FIXME if manualInvalidations
12549 		}
12550 
12551 		void dispose() {
12552 			this.rasterOp = RasterOp.normal;
12553 
12554 			if(xrenderPicturePainter) {
12555 				XRenderFreePicture(display, xrenderPicturePainter);
12556 				xrenderPicturePainter = None;
12557 			}
12558 
12559 			// FIXME: this.window.width/height is probably wrong
12560 
12561 			// src x,y     then dest x, y
12562 			if(destiny != None) {
12563 				// FIXME: if manual invalidations we can actually only copy some of the area.
12564 				// if(manualInvalidations)
12565 				XSetClipMask(display, gc, None);
12566 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12567 			}
12568 
12569 			XFreeGC(display, gc);
12570 
12571 			version(with_xft)
12572 			if(xftDraw) {
12573 				XftDrawDestroy(xftDraw);
12574 				xftDraw = null;
12575 			}
12576 
12577 			/+
12578 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12579 			if(font && font !is defaultfont) {
12580 				XFreeFont(display, font);
12581 				font = null;
12582 			}
12583 			if(fontset && fontset !is defaultfontset) {
12584 				XFreeFontSet(display, fontset);
12585 				fontset = null;
12586 			}
12587 			+/
12588 			XFlush(display);
12589 
12590 			if(window.paintingFinishedDg !is null)
12591 				window.paintingFinishedDg()();
12592 		}
12593 
12594 		bool backgroundIsNotTransparent = true;
12595 		bool foregroundIsNotTransparent = true;
12596 
12597 		bool _penInitialized = false;
12598 		Pen _activePen;
12599 
12600 		Color _outlineColor;
12601 		Color _fillColor;
12602 
12603 		@property void pen(Pen p) {
12604 			if(_penInitialized && p == _activePen) {
12605 				return;
12606 			}
12607 			_penInitialized = true;
12608 			_activePen = p;
12609 			_outlineColor = p.color;
12610 
12611 			int style;
12612 
12613 			byte dashLength;
12614 
12615 			final switch(p.style) {
12616 				case Pen.Style.Solid:
12617 					style = 0 /*LineSolid*/;
12618 				break;
12619 				case Pen.Style.Dashed:
12620 					style = 1 /*LineOnOffDash*/;
12621 					dashLength = 4;
12622 				break;
12623 				case Pen.Style.Dotted:
12624 					style = 1 /*LineOnOffDash*/;
12625 					dashLength = 1;
12626 				break;
12627 			}
12628 
12629 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
12630 			if(dashLength)
12631 				XSetDashes(display, gc, 0, &dashLength, 1);
12632 
12633 			if(p.color.a == 0) {
12634 				foregroundIsNotTransparent = false;
12635 				return;
12636 			}
12637 
12638 			foregroundIsNotTransparent = true;
12639 
12640 			XSetForeground(display, gc, colorToX(p.color, display));
12641 
12642 			version(with_xft)
12643 				updateXftColor();
12644 		}
12645 
12646 		RasterOp _currentRasterOp;
12647 		bool _currentRasterOpInitialized = false;
12648 		@property void rasterOp(RasterOp op) {
12649 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12650 				return;
12651 			_currentRasterOp = op;
12652 			_currentRasterOpInitialized = true;
12653 			int mode;
12654 			final switch(op) {
12655 				case RasterOp.normal:
12656 					mode = GXcopy;
12657 				break;
12658 				case RasterOp.xor:
12659 					mode = GXxor;
12660 				break;
12661 			}
12662 			XSetFunction(display, gc, mode);
12663 		}
12664 
12665 
12666 		bool _fillColorInitialized = false;
12667 
12668 		@property void fillColor(Color c) {
12669 			if(_fillColorInitialized && _fillColor == c)
12670 				return; // already good, no need to waste time calling it
12671 			_fillColor = c;
12672 			_fillColorInitialized = true;
12673 			if(c.a == 0) {
12674 				backgroundIsNotTransparent = false;
12675 				return;
12676 			}
12677 
12678 			backgroundIsNotTransparent = true;
12679 
12680 			XSetBackground(display, gc, colorToX(c, display));
12681 
12682 		}
12683 
12684 		void swapColors() {
12685 			auto tmp = _fillColor;
12686 			fillColor = _outlineColor;
12687 			auto newPen = _activePen;
12688 			newPen.color = tmp;
12689 			pen(newPen);
12690 		}
12691 
12692 		uint colorToX(Color c, Display* display) {
12693 			auto visual = DefaultVisual(display, DefaultScreen(display));
12694 			import core.bitop;
12695 			uint color = 0;
12696 			{
12697 			auto startBit = bsf(visual.red_mask);
12698 			auto lastBit = bsr(visual.red_mask);
12699 			auto r = cast(uint) c.r;
12700 			r >>= 7 - (lastBit - startBit);
12701 			r <<= startBit;
12702 			color |= r;
12703 			}
12704 			{
12705 			auto startBit = bsf(visual.green_mask);
12706 			auto lastBit = bsr(visual.green_mask);
12707 			auto g = cast(uint) c.g;
12708 			g >>= 7 - (lastBit - startBit);
12709 			g <<= startBit;
12710 			color |= g;
12711 			}
12712 			{
12713 			auto startBit = bsf(visual.blue_mask);
12714 			auto lastBit = bsr(visual.blue_mask);
12715 			auto b = cast(uint) c.b;
12716 			b >>= 7 - (lastBit - startBit);
12717 			b <<= startBit;
12718 			color |= b;
12719 			}
12720 
12721 
12722 
12723 			return color;
12724 		}
12725 
12726 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12727 			// source x, source y
12728 			if(ix >= i.width) return;
12729 			if(iy >= i.height) return;
12730 			if(ix + w > i.width) w = i.width - ix;
12731 			if(iy + h > i.height) h = i.height - iy;
12732 			if(i.usingXshm)
12733 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12734 			else
12735 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12736 		}
12737 
12738 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12739 			if(s.enableAlpha) {
12740 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12741 				if(this.xrenderPicturePainter == None) {
12742 					XRenderPictureAttributes attrs;
12743 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12744 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12745 
12746 					// need to initialize the clip
12747 					XRectangle[1] rects;
12748 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12749 
12750 					if(_clipRectangle != Rectangle.init)
12751 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12752 				}
12753 
12754 				XRenderComposite(
12755 					display,
12756 					3, // PicOpOver
12757 					s.xrenderPicture,
12758 					None,
12759 					this.xrenderPicturePainter,
12760 					ix,
12761 					iy,
12762 					0,
12763 					0,
12764 					x,
12765 					y,
12766 					w ? w : s.width,
12767 					h ? h : s.height
12768 				);
12769 			} else {
12770 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12771 			}
12772 		}
12773 
12774 		int fontHeight() {
12775 			version(with_xft)
12776 				if(xftFont !is null)
12777 					return xftFont.height;
12778 			if(font)
12779 				return font.max_bounds.ascent + font.max_bounds.descent;
12780 			return 12; // pretty common default...
12781 		}
12782 
12783 		int textWidth(in char[] line) {
12784 			version(with_xft)
12785 			if(xftFont) {
12786 				if(line.length == 0)
12787 					return 0;
12788 				XGlyphInfo extents;
12789 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12790 				return extents.width;
12791 			}
12792 
12793 			if(fontset) {
12794 				if(line.length == 0)
12795 					return 0;
12796 				XRectangle rect;
12797 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
12798 
12799 				return rect.width;
12800 			}
12801 
12802 			if(font)
12803 				// FIXME: unicode
12804 				return XTextWidth( font, line.ptr, cast(int) line.length);
12805 			else
12806 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
12807 		}
12808 
12809 		Size textSize(in char[] text) {
12810 			auto maxWidth = 0;
12811 			auto lineHeight = fontHeight;
12812 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
12813 			foreach(line; text.split('\n')) {
12814 				int textWidth = this.textWidth(line);
12815 				if(textWidth > maxWidth)
12816 					maxWidth = textWidth;
12817 				h += lineHeight + 4;
12818 			}
12819 			return Size(maxWidth, h);
12820 		}
12821 
12822 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
12823 			const(char)[] text;
12824 			version(with_xft)
12825 			if(xftFont) {
12826 				text = originalText;
12827 				goto loaded;
12828 			}
12829 
12830 			if(fontset)
12831 				text = originalText;
12832 			else {
12833 				text.reserve(originalText.length);
12834 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
12835 				// then strip the rest so there isn't garbage
12836 				foreach(dchar ch; originalText)
12837 					if(ch < 256)
12838 						text ~= cast(ubyte) ch;
12839 					else
12840 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
12841 			}
12842 			loaded:
12843 			if(text.length == 0)
12844 				return;
12845 
12846 			// FIXME: should we clip it to the bounding box?
12847 			int textHeight = fontHeight;
12848 
12849 			auto lines = text.split('\n');
12850 
12851 			const lineHeight = textHeight;
12852 			textHeight *= lines.length;
12853 
12854 			int cy = y;
12855 
12856 			if(alignment & TextAlignment.VerticalBottom) {
12857 				if(y2 <= 0)
12858 					return;
12859 				auto h = y2 - y;
12860 				if(h > textHeight) {
12861 					cy += h - textHeight;
12862 					cy -= lineHeight / 2;
12863 				}
12864 			} else if(alignment & TextAlignment.VerticalCenter) {
12865 				if(y2 <= 0)
12866 					return;
12867 				auto h = y2 - y;
12868 				if(textHeight < h) {
12869 					cy += (h - textHeight) / 2;
12870 					//cy -= lineHeight / 4;
12871 				}
12872 			}
12873 
12874 			foreach(line; text.split('\n')) {
12875 				int textWidth = this.textWidth(line);
12876 
12877 				int px = x, py = cy;
12878 
12879 				if(alignment & TextAlignment.Center) {
12880 					if(x2 <= 0)
12881 						return;
12882 					auto w = x2 - x;
12883 					if(w > textWidth)
12884 						px += (w - textWidth) / 2;
12885 				} else if(alignment & TextAlignment.Right) {
12886 					if(x2 <= 0)
12887 						return;
12888 					auto pos = x2 - textWidth;
12889 					if(pos > x)
12890 						px = pos;
12891 				}
12892 
12893 				version(with_xft)
12894 				if(xftFont) {
12895 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
12896 
12897 					goto carry_on;
12898 				}
12899 
12900 				if(fontset)
12901 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12902 				else
12903 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12904 				carry_on:
12905 				cy += lineHeight + 4;
12906 			}
12907 		}
12908 
12909 		void drawPixel(int x, int y) {
12910 			XDrawPoint(display, d, gc, x, y);
12911 		}
12912 
12913 		// The basic shapes, outlined
12914 
12915 		void drawLine(int x1, int y1, int x2, int y2) {
12916 			if(foregroundIsNotTransparent)
12917 				XDrawLine(display, d, gc, x1, y1, x2, y2);
12918 		}
12919 
12920 		void drawRectangle(int x, int y, int width, int height) {
12921 			if(backgroundIsNotTransparent) {
12922 				swapColors();
12923 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
12924 				swapColors();
12925 			}
12926 			// 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
12927 			if(foregroundIsNotTransparent)
12928 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
12929 		}
12930 
12931 		/// Arguments are the points of the bounding rectangle
12932 		void drawEllipse(int x1, int y1, int x2, int y2) {
12933 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
12934 		}
12935 
12936 		// NOTE: start and finish are in units of degrees * 64
12937 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
12938 			if(backgroundIsNotTransparent) {
12939 				swapColors();
12940 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
12941 				swapColors();
12942 			}
12943 			if(foregroundIsNotTransparent) {
12944 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
12945 				// Windows draws the straight lines on the edges too so FIXME sort of
12946 			}
12947 		}
12948 
12949 		void drawPolygon(Point[] vertexes) {
12950 			XPoint[16] pointsBuffer;
12951 			XPoint[] points;
12952 			if(vertexes.length <= pointsBuffer.length)
12953 				points = pointsBuffer[0 .. vertexes.length];
12954 			else
12955 				points.length = vertexes.length;
12956 
12957 			foreach(i, p; vertexes) {
12958 				points[i].x = cast(short) p.x;
12959 				points[i].y = cast(short) p.y;
12960 			}
12961 
12962 			if(backgroundIsNotTransparent) {
12963 				swapColors();
12964 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
12965 				swapColors();
12966 			}
12967 			if(foregroundIsNotTransparent) {
12968 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
12969 			}
12970 		}
12971 	}
12972 
12973 	/* XRender { */
12974 
12975 	struct XRenderColor {
12976 		ushort red;
12977 		ushort green;
12978 		ushort blue;
12979 		ushort alpha;
12980 	}
12981 
12982 	alias Picture = XID;
12983 	alias PictFormat = XID;
12984 
12985 	struct XGlyphInfo {
12986 		ushort width;
12987 		ushort height;
12988 		short x;
12989 		short y;
12990 		short xOff;
12991 		short yOff;
12992 	}
12993 
12994 struct XRenderDirectFormat {
12995     short   red;
12996     short   redMask;
12997     short   green;
12998     short   greenMask;
12999     short   blue;
13000     short   blueMask;
13001     short   alpha;
13002     short   alphaMask;
13003 }
13004 
13005 struct XRenderPictFormat {
13006     PictFormat		id;
13007     int			type;
13008     int			depth;
13009     XRenderDirectFormat	direct;
13010     Colormap		colormap;
13011 }
13012 
13013 enum PictFormatID	=   (1 << 0);
13014 enum PictFormatType	=   (1 << 1);
13015 enum PictFormatDepth	=   (1 << 2);
13016 enum PictFormatRed	=   (1 << 3);
13017 enum PictFormatRedMask  =(1 << 4);
13018 enum PictFormatGreen	=   (1 << 5);
13019 enum PictFormatGreenMask=(1 << 6);
13020 enum PictFormatBlue	=   (1 << 7);
13021 enum PictFormatBlueMask =(1 << 8);
13022 enum PictFormatAlpha	=   (1 << 9);
13023 enum PictFormatAlphaMask=(1 << 10);
13024 enum PictFormatColormap =(1 << 11);
13025 
13026 struct XRenderPictureAttributes {
13027 	int 		repeat;
13028 	Picture		alpha_map;
13029 	int			alpha_x_origin;
13030 	int			alpha_y_origin;
13031 	int			clip_x_origin;
13032 	int			clip_y_origin;
13033 	Pixmap		clip_mask;
13034 	Bool		graphics_exposures;
13035 	int			subwindow_mode;
13036 	int			poly_edge;
13037 	int			poly_mode;
13038 	Atom		dither;
13039 	Bool		component_alpha;
13040 }
13041 
13042 alias int XFixed;
13043 
13044 struct XPointFixed {
13045     XFixed  x, y;
13046 }
13047 
13048 struct XCircle {
13049     XFixed x;
13050     XFixed y;
13051     XFixed radius;
13052 }
13053 
13054 struct XTransform {
13055     XFixed[3][3]  matrix;
13056 }
13057 
13058 struct XFilters {
13059     int	    nfilter;
13060     char    **filter;
13061     int	    nalias;
13062     short   *alias_;
13063 }
13064 
13065 struct XIndexValue {
13066     c_ulong    pixel;
13067     ushort   red, green, blue, alpha;
13068 }
13069 
13070 struct XAnimCursor {
13071     Cursor	    cursor;
13072     c_ulong   delay;
13073 }
13074 
13075 struct XLinearGradient {
13076     XPointFixed p1;
13077     XPointFixed p2;
13078 }
13079 
13080 struct XRadialGradient {
13081     XCircle inner;
13082     XCircle outer;
13083 }
13084 
13085 struct XConicalGradient {
13086     XPointFixed center;
13087     XFixed angle; /* in degrees */
13088 }
13089 
13090 enum PictStandardARGB32  = 0;
13091 enum PictStandardRGB24   = 1;
13092 enum PictStandardA8	 =  2;
13093 enum PictStandardA4	 =  3;
13094 enum PictStandardA1	 =  4;
13095 enum PictStandardNUM	 =  5;
13096 
13097 interface XRender {
13098 extern(C) @nogc:
13099 
13100 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13101 
13102 	Status XRenderQueryVersion (Display *dpy,
13103 			int     *major_versionp,
13104 			int     *minor_versionp);
13105 
13106 	Status XRenderQueryFormats (Display *dpy);
13107 
13108 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13109 
13110 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13111 
13112 	XRenderPictFormat *
13113 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13114 
13115 	XRenderPictFormat *
13116 		XRenderFindFormat (Display			*dpy,
13117 				c_ulong		mask,
13118 				const XRenderPictFormat	*templ,
13119 				int				count);
13120 	XRenderPictFormat *
13121 		XRenderFindStandardFormat (Display		*dpy,
13122 				int			format);
13123 
13124 	XIndexValue *
13125 		XRenderQueryPictIndexValues(Display			*dpy,
13126 				const XRenderPictFormat	*format,
13127 				int				*num);
13128 
13129 	Picture XRenderCreatePicture(
13130 		Display *dpy,
13131 		Drawable drawable,
13132 		const XRenderPictFormat *format,
13133 		c_ulong valuemask,
13134 		const XRenderPictureAttributes *attributes);
13135 
13136 	void XRenderChangePicture (Display				*dpy,
13137 				Picture				picture,
13138 				c_ulong			valuemask,
13139 				const XRenderPictureAttributes  *attributes);
13140 
13141 	void
13142 		XRenderSetPictureClipRectangles (Display	    *dpy,
13143 				Picture	    picture,
13144 				int		    xOrigin,
13145 				int		    yOrigin,
13146 				const XRectangle *rects,
13147 				int		    n);
13148 
13149 	void
13150 		XRenderSetPictureClipRegion (Display	    *dpy,
13151 				Picture	    picture,
13152 				Region	    r);
13153 
13154 	void
13155 		XRenderSetPictureTransform (Display	    *dpy,
13156 				Picture	    picture,
13157 				XTransform	    *transform);
13158 
13159 	void
13160 		XRenderFreePicture (Display                   *dpy,
13161 				Picture                   picture);
13162 
13163 	void
13164 		XRenderComposite (Display   *dpy,
13165 				int	    op,
13166 				Picture   src,
13167 				Picture   mask,
13168 				Picture   dst,
13169 				int	    src_x,
13170 				int	    src_y,
13171 				int	    mask_x,
13172 				int	    mask_y,
13173 				int	    dst_x,
13174 				int	    dst_y,
13175 				uint	width,
13176 				uint	height);
13177 
13178 
13179 	Picture XRenderCreateSolidFill (Display *dpy,
13180 			const XRenderColor *color);
13181 
13182 	Picture XRenderCreateLinearGradient (Display *dpy,
13183 			const XLinearGradient *gradient,
13184 			const XFixed *stops,
13185 			const XRenderColor *colors,
13186 			int nstops);
13187 
13188 	Picture XRenderCreateRadialGradient (Display *dpy,
13189 			const XRadialGradient *gradient,
13190 			const XFixed *stops,
13191 			const XRenderColor *colors,
13192 			int nstops);
13193 
13194 	Picture XRenderCreateConicalGradient (Display *dpy,
13195 			const XConicalGradient *gradient,
13196 			const XFixed *stops,
13197 			const XRenderColor *colors,
13198 			int nstops);
13199 
13200 
13201 
13202 	Cursor
13203 		XRenderCreateCursor (Display	    *dpy,
13204 				Picture	    source,
13205 				uint   x,
13206 				uint   y);
13207 
13208 	XFilters *
13209 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13210 
13211 	void
13212 		XRenderSetPictureFilter (Display    *dpy,
13213 				Picture    picture,
13214 				const char *filter,
13215 				XFixed	    *params,
13216 				int	    nparams);
13217 
13218 	Cursor
13219 		XRenderCreateAnimCursor (Display	*dpy,
13220 				int		ncursor,
13221 				XAnimCursor	*cursors);
13222 }
13223 
13224 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13225 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13226 
13227 	/* XRender } */
13228 
13229 	/* Xrandr { */
13230 
13231 struct XRRMonitorInfo {
13232     Atom name;
13233     Bool primary;
13234     Bool automatic;
13235     int noutput;
13236     int x;
13237     int y;
13238     int width;
13239     int height;
13240     int mwidth;
13241     int mheight;
13242     /*RROutput*/ void *outputs;
13243 }
13244 
13245 struct XRRScreenChangeNotifyEvent {
13246     int type;                   /* event base */
13247     c_ulong serial;       /* # of last request processed by server */
13248     Bool send_event;            /* true if this came from a SendEvent request */
13249     Display *display;           /* Display the event was read from */
13250     Window window;              /* window which selected for this event */
13251     Window root;                /* Root window for changed screen */
13252     Time timestamp;             /* when the screen change occurred */
13253     Time config_timestamp;      /* when the last configuration change */
13254     ushort/*SizeID*/ size_index;
13255     ushort/*SubpixelOrder*/ subpixel_order;
13256     ushort/*Rotation*/ rotation;
13257     int width;
13258     int height;
13259     int mwidth;
13260     int mheight;
13261 }
13262 
13263 enum RRScreenChangeNotify = 0;
13264 
13265 enum RRScreenChangeNotifyMask = 1;
13266 
13267 __gshared int xrrEventBase = -1;
13268 
13269 
13270 interface XRandr {
13271 extern(C) @nogc:
13272 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13273 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13274 
13275 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13276 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13277 
13278 	void XRRSelectInput(Display *dpy, Window window, int mask);
13279 }
13280 
13281 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13282 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13283 	/* Xrandr } */
13284 
13285 	/* Xft { */
13286 
13287 	// actually freetype
13288 	alias void FT_Face;
13289 
13290 	// actually fontconfig
13291 	private alias FcBool = int;
13292 	alias void FcCharSet;
13293 	alias void FcPattern;
13294 	alias void FcResult;
13295 	enum FcEndian { FcEndianBig, FcEndianLittle }
13296 	struct FcFontSet {
13297 		int nfont;
13298 		int sfont;
13299 		FcPattern** fonts;
13300 	}
13301 
13302 	// actually XRegion
13303 	struct BOX {
13304 		short x1, x2, y1, y2;
13305 	}
13306 	struct _XRegion {
13307 		c_long size;
13308 		c_long numRects;
13309 		BOX* rects;
13310 		BOX extents;
13311 	}
13312 
13313 	alias Region = _XRegion*;
13314 
13315 	// ok actually Xft
13316 
13317 	struct XftFontInfo;
13318 
13319 	struct XftFont {
13320 		int         ascent;
13321 		int         descent;
13322 		int         height;
13323 		int         max_advance_width;
13324 		FcCharSet*  charset;
13325 		FcPattern*  pattern;
13326 	}
13327 
13328 	struct XftDraw;
13329 
13330 	struct XftColor {
13331 		c_ulong pixel;
13332 		XRenderColor color;
13333 	}
13334 
13335 	struct XftCharSpec {
13336 		dchar           ucs4;
13337 		short           x;
13338 		short           y;
13339 	}
13340 
13341 	struct XftCharFontSpec {
13342 		XftFont         *font;
13343 		dchar           ucs4;
13344 		short           x;
13345 		short           y;
13346 	}
13347 
13348 	struct XftGlyphSpec {
13349 		uint            glyph;
13350 		short           x;
13351 		short           y;
13352 	}
13353 
13354 	struct XftGlyphFontSpec {
13355 		XftFont         *font;
13356 		uint            glyph;
13357 		short           x;
13358 		short           y;
13359 	}
13360 
13361 	interface Xft {
13362 	extern(C) @nogc pure:
13363 
13364 	Bool XftColorAllocName (Display  *dpy,
13365 				const Visual   *visual,
13366 				Colormap cmap,
13367 				const char     *name,
13368 				XftColor *result);
13369 
13370 	Bool XftColorAllocValue (Display         *dpy,
13371 				Visual          *visual,
13372 				Colormap        cmap,
13373 				const XRenderColor    *color,
13374 				XftColor        *result);
13375 
13376 	void XftColorFree (Display   *dpy,
13377 				Visual    *visual,
13378 				Colormap  cmap,
13379 				XftColor  *color);
13380 
13381 	Bool XftDefaultHasRender (Display *dpy);
13382 
13383 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13384 
13385 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13386 
13387 	XftDraw * XftDrawCreate (Display   *dpy,
13388 		       Drawable  drawable,
13389 		       Visual    *visual,
13390 		       Colormap  colormap);
13391 
13392 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13393 			     Pixmap   bitmap);
13394 
13395 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13396 			    Pixmap  pixmap,
13397 			    int     depth);
13398 
13399 	void XftDrawChange (XftDraw  *draw,
13400 		       Drawable drawable);
13401 
13402 	Display * XftDrawDisplay (XftDraw *draw);
13403 
13404 	Drawable XftDrawDrawable (XftDraw *draw);
13405 
13406 	Colormap XftDrawColormap (XftDraw *draw);
13407 
13408 	Visual * XftDrawVisual (XftDraw *draw);
13409 
13410 	void XftDrawDestroy (XftDraw *draw);
13411 
13412 	Picture XftDrawPicture (XftDraw *draw);
13413 
13414 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13415 
13416 	void XftDrawGlyphs (XftDraw          *draw,
13417 				const XftColor *color,
13418 				XftFont          *pub,
13419 				int              x,
13420 				int              y,
13421 				const uint  *glyphs,
13422 				int              nglyphs);
13423 
13424 	void XftDrawString8 (XftDraw             *draw,
13425 				const XftColor    *color,
13426 				XftFont             *pub,
13427 				int                 x,
13428 				int                 y,
13429 				const char     *string,
13430 				int                 len);
13431 
13432 	void XftDrawString16 (XftDraw            *draw,
13433 				const XftColor   *color,
13434 				XftFont            *pub,
13435 				int                x,
13436 				int                y,
13437 				const wchar   *string,
13438 				int                len);
13439 
13440 	void XftDrawString32 (XftDraw            *draw,
13441 				const XftColor   *color,
13442 				XftFont            *pub,
13443 				int                x,
13444 				int                y,
13445 				const dchar   *string,
13446 				int                len);
13447 
13448 	void XftDrawStringUtf8 (XftDraw          *draw,
13449 				const XftColor *color,
13450 				XftFont          *pub,
13451 				int              x,
13452 				int              y,
13453 				const char  *string,
13454 				int              len);
13455 	void XftDrawStringUtf16 (XftDraw             *draw,
13456 				const XftColor    *color,
13457 				XftFont             *pub,
13458 				int                 x,
13459 				int                 y,
13460 				const char     *string,
13461 				FcEndian            endian,
13462 				int                 len);
13463 
13464 	void XftDrawCharSpec (XftDraw                *draw,
13465 				const XftColor       *color,
13466 				XftFont                *pub,
13467 				const XftCharSpec    *chars,
13468 				int                    len);
13469 
13470 	void XftDrawCharFontSpec (XftDraw                    *draw,
13471 				const XftColor           *color,
13472 				const XftCharFontSpec    *chars,
13473 				int                        len);
13474 
13475 	void XftDrawGlyphSpec (XftDraw               *draw,
13476 				const XftColor      *color,
13477 				XftFont               *pub,
13478 				const XftGlyphSpec  *glyphs,
13479 				int                   len);
13480 
13481 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13482 				const XftColor          *color,
13483 				const XftGlyphFontSpec  *glyphs,
13484 				int                       len);
13485 
13486 	void XftDrawRect (XftDraw            *draw,
13487 				const XftColor   *color,
13488 				int                x,
13489 				int                y,
13490 				uint       width,
13491 				uint       height);
13492 
13493 	Bool XftDrawSetClip (XftDraw     *draw,
13494 				Region      r);
13495 
13496 
13497 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13498 				int                   xOrigin,
13499 				int                   yOrigin,
13500 				const XRectangle    *rects,
13501 				int                   n);
13502 
13503 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13504 				int        mode);
13505 
13506 	void XftGlyphExtents (Display            *dpy,
13507 				XftFont            *pub,
13508 				const uint    *glyphs,
13509 				int                nglyphs,
13510 				XGlyphInfo         *extents);
13511 
13512 	void XftTextExtents8 (Display            *dpy,
13513 				XftFont            *pub,
13514 				const char    *string,
13515 				int                len,
13516 				XGlyphInfo         *extents);
13517 
13518 	void XftTextExtents16 (Display           *dpy,
13519 				XftFont           *pub,
13520 				const wchar  *string,
13521 				int               len,
13522 				XGlyphInfo        *extents);
13523 
13524 	void XftTextExtents32 (Display           *dpy,
13525 				XftFont           *pub,
13526 				const dchar  *string,
13527 				int               len,
13528 				XGlyphInfo        *extents);
13529 
13530 	void XftTextExtentsUtf8 (Display         *dpy,
13531 				XftFont         *pub,
13532 				const char *string,
13533 				int             len,
13534 				XGlyphInfo      *extents);
13535 
13536 	void XftTextExtentsUtf16 (Display            *dpy,
13537 				XftFont            *pub,
13538 				const char    *string,
13539 				FcEndian           endian,
13540 				int                len,
13541 				XGlyphInfo         *extents);
13542 
13543 	FcPattern * XftFontMatch (Display           *dpy,
13544 				int               screen,
13545 				const FcPattern *pattern,
13546 				FcResult          *result);
13547 
13548 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13549 
13550 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13551 
13552 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13553 
13554 	FT_Face XftLockFace (XftFont *pub);
13555 
13556 	void XftUnlockFace (XftFont *pub);
13557 
13558 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13559 
13560 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13561 
13562 	dchar XftFontInfoHash (const XftFontInfo *fi);
13563 
13564 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13565 
13566 	XftFont * XftFontOpenInfo (Display        *dpy,
13567 				FcPattern      *pattern,
13568 				XftFontInfo    *fi);
13569 
13570 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13571 
13572 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13573 
13574 	void XftFontClose (Display *dpy, XftFont *pub);
13575 
13576 	FcBool XftInitFtLibrary();
13577 	void XftFontLoadGlyphs (Display          *dpy,
13578 				XftFont          *pub,
13579 				FcBool           need_bitmaps,
13580 				const uint  *glyphs,
13581 				int              nglyph);
13582 
13583 	void XftFontUnloadGlyphs (Display            *dpy,
13584 				XftFont            *pub,
13585 				const uint    *glyphs,
13586 				int                nglyph);
13587 
13588 	FcBool XftFontCheckGlyph (Display  *dpy,
13589 				XftFont  *pub,
13590 				FcBool   need_bitmaps,
13591 				uint  glyph,
13592 				uint  *missing,
13593 				int      *nmissing);
13594 
13595 	FcBool XftCharExists (Display      *dpy,
13596 				XftFont      *pub,
13597 				dchar    ucs4);
13598 
13599 	uint XftCharIndex (Display       *dpy,
13600 				XftFont       *pub,
13601 				dchar      ucs4);
13602 	FcBool XftInit (const char *config);
13603 
13604 	int XftGetVersion ();
13605 
13606 	FcFontSet * XftListFonts (Display   *dpy,
13607 				int       screen,
13608 				...);
13609 
13610 	FcPattern *XftNameParse (const char *name);
13611 
13612 	void XftGlyphRender (Display         *dpy,
13613 				int             op,
13614 				Picture         src,
13615 				XftFont         *pub,
13616 				Picture         dst,
13617 				int             srcx,
13618 				int             srcy,
13619 				int             x,
13620 				int             y,
13621 				const uint *glyphs,
13622 				int             nglyphs);
13623 
13624 	void XftGlyphSpecRender (Display                 *dpy,
13625 				int                     op,
13626 				Picture                 src,
13627 				XftFont                 *pub,
13628 				Picture                 dst,
13629 				int                     srcx,
13630 				int                     srcy,
13631 				const XftGlyphSpec    *glyphs,
13632 				int                     nglyphs);
13633 
13634 	void XftCharSpecRender (Display              *dpy,
13635 				int                  op,
13636 				Picture              src,
13637 				XftFont              *pub,
13638 				Picture              dst,
13639 				int                  srcx,
13640 				int                  srcy,
13641 				const XftCharSpec  *chars,
13642 				int                  len);
13643 	void XftGlyphFontSpecRender (Display                     *dpy,
13644 				int                         op,
13645 				Picture                     src,
13646 				Picture                     dst,
13647 				int                         srcx,
13648 				int                         srcy,
13649 				const XftGlyphFontSpec    *glyphs,
13650 				int                         nglyphs);
13651 
13652 	void XftCharFontSpecRender (Display                  *dpy,
13653 				int                      op,
13654 				Picture                  src,
13655 				Picture                  dst,
13656 				int                      srcx,
13657 				int                      srcy,
13658 				const XftCharFontSpec  *chars,
13659 				int                      len);
13660 
13661 	void XftTextRender8 (Display         *dpy,
13662 				int             op,
13663 				Picture         src,
13664 				XftFont         *pub,
13665 				Picture         dst,
13666 				int             srcx,
13667 				int             srcy,
13668 				int             x,
13669 				int             y,
13670 				const char *string,
13671 				int             len);
13672 	void XftTextRender16 (Display            *dpy,
13673 				int                op,
13674 				Picture            src,
13675 				XftFont            *pub,
13676 				Picture            dst,
13677 				int                srcx,
13678 				int                srcy,
13679 				int                x,
13680 				int                y,
13681 				const wchar   *string,
13682 				int                len);
13683 
13684 	void XftTextRender16BE (Display          *dpy,
13685 				int              op,
13686 				Picture          src,
13687 				XftFont          *pub,
13688 				Picture          dst,
13689 				int              srcx,
13690 				int              srcy,
13691 				int              x,
13692 				int              y,
13693 				const char  *string,
13694 				int              len);
13695 
13696 	void XftTextRender16LE (Display          *dpy,
13697 				int              op,
13698 				Picture          src,
13699 				XftFont          *pub,
13700 				Picture          dst,
13701 				int              srcx,
13702 				int              srcy,
13703 				int              x,
13704 				int              y,
13705 				const char  *string,
13706 				int              len);
13707 
13708 	void XftTextRender32 (Display            *dpy,
13709 				int                op,
13710 				Picture            src,
13711 				XftFont            *pub,
13712 				Picture            dst,
13713 				int                srcx,
13714 				int                srcy,
13715 				int                x,
13716 				int                y,
13717 				const dchar   *string,
13718 				int                len);
13719 
13720 	void XftTextRender32BE (Display          *dpy,
13721 				int              op,
13722 				Picture          src,
13723 				XftFont          *pub,
13724 				Picture          dst,
13725 				int              srcx,
13726 				int              srcy,
13727 				int              x,
13728 				int              y,
13729 				const char  *string,
13730 				int              len);
13731 
13732 	void XftTextRender32LE (Display          *dpy,
13733 				int              op,
13734 				Picture          src,
13735 				XftFont          *pub,
13736 				Picture          dst,
13737 				int              srcx,
13738 				int              srcy,
13739 				int              x,
13740 				int              y,
13741 				const char  *string,
13742 				int              len);
13743 
13744 	void XftTextRenderUtf8 (Display          *dpy,
13745 				int              op,
13746 				Picture          src,
13747 				XftFont          *pub,
13748 				Picture          dst,
13749 				int              srcx,
13750 				int              srcy,
13751 				int              x,
13752 				int              y,
13753 				const char  *string,
13754 				int              len);
13755 
13756 	void XftTextRenderUtf16 (Display         *dpy,
13757 				int             op,
13758 				Picture         src,
13759 				XftFont         *pub,
13760 				Picture         dst,
13761 				int             srcx,
13762 				int             srcy,
13763 				int             x,
13764 				int             y,
13765 				const char *string,
13766 				FcEndian        endian,
13767 				int             len);
13768 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13769 
13770 	}
13771 
13772 	interface FontConfig {
13773 	extern(C) @nogc pure:
13774 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13775 		void FcFontSetDestroy(FcFontSet*);
13776 		char* FcNameUnparse(const FcPattern *);
13777 	}
13778 
13779 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13780 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13781 
13782 
13783 	/* Xft } */
13784 
13785 	class XDisconnectException : Exception {
13786 		bool userRequested;
13787 		this(bool userRequested = true) {
13788 			this.userRequested = userRequested;
13789 			super("X disconnected");
13790 		}
13791 	}
13792 
13793 	/++
13794 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
13795 
13796 		Please note that it returns
13797 	+/
13798 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
13799 
13800 		static XErrorEvent[] errorBuffer;
13801 
13802 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
13803 			errorBuffer ~= *evt;
13804 			return 0;
13805 		}
13806 
13807 		auto savedErrorHandler = XSetErrorHandler(&handler);
13808 
13809 		try {
13810 			dg();
13811 		} finally {
13812 			XSync(XDisplayConnection.get, 0/*False*/);
13813 			XSetErrorHandler(savedErrorHandler);
13814 		}
13815 
13816 		auto bfr = errorBuffer;
13817 		errorBuffer = null;
13818 
13819 		return bfr;
13820 	}
13821 
13822 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
13823 	class XDisplayConnection {
13824 		private __gshared Display* display;
13825 		private __gshared XIM xim;
13826 		private __gshared char* displayName;
13827 
13828 		private __gshared int connectionSequence_;
13829 		private __gshared bool isLocal_;
13830 
13831 		/// use this for lazy caching when reconnection
13832 		static int connectionSequenceNumber() { return connectionSequence_; }
13833 
13834 		/++
13835 			Guesses if the connection appears to be local.
13836 
13837 			History:
13838 				Added June 3, 2021
13839 		+/
13840 		static @property bool isLocal() nothrow @trusted @nogc {
13841 			return isLocal_;
13842 		}
13843 
13844 		/// Attempts recreation of state, may require application assistance
13845 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
13846 		/// then call this, and if successful, reenter the loop.
13847 		static void discardAndRecreate(string newDisplayString = null) {
13848 			if(insideXEventLoop)
13849 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
13850 
13851 			// 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
13852 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
13853 
13854 			foreach(handle; chnenhm) {
13855 				handle.discardConnectionState();
13856 			}
13857 
13858 			discardState();
13859 
13860 			if(newDisplayString !is null)
13861 				setDisplayName(newDisplayString);
13862 
13863 			auto display = get();
13864 
13865 			foreach(handle; chnenhm) {
13866 				handle.recreateAfterDisconnect();
13867 			}
13868 		}
13869 
13870 		private __gshared EventMask rootEventMask;
13871 
13872 		/++
13873 			Requests the specified input from the root window on the connection, in addition to any other request.
13874 
13875 
13876 			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.
13877 
13878 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
13879 		+/
13880 		static void addRootInput(EventMask mask) {
13881 			auto old = rootEventMask;
13882 			rootEventMask |= mask;
13883 			get(); // to ensure display connected
13884 			if(display !is null && rootEventMask != old)
13885 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
13886 		}
13887 
13888 		static void discardState() {
13889 			freeImages();
13890 
13891 			foreach(atomPtr; interredAtoms)
13892 				*atomPtr = 0;
13893 			interredAtoms = null;
13894 			interredAtoms.assumeSafeAppend();
13895 
13896 			ScreenPainterImplementation.fontAttempted = false;
13897 			ScreenPainterImplementation.defaultfont = null;
13898 			ScreenPainterImplementation.defaultfontset = null;
13899 
13900 			Image.impl.xshmQueryCompleted = false;
13901 			Image.impl._xshmAvailable = false;
13902 
13903 			SimpleWindow.nativeMapping = null;
13904 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
13905 			// GlobalHotkeyManager
13906 
13907 			display = null;
13908 			xim = null;
13909 		}
13910 
13911 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
13912 		private static void createXIM () {
13913 			import core.stdc.locale : setlocale, LC_ALL;
13914 			import core.stdc.stdio : stderr, fprintf;
13915 			import core.stdc.stdlib : free;
13916 			import core.stdc.string : strdup;
13917 
13918 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
13919 
13920 			auto olocale = strdup(setlocale(LC_ALL, null));
13921 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
13922 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
13923 
13924 			//fprintf(stderr, "opening IM...\n");
13925 			foreach (string s; mtry) {
13926 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
13927 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
13928 			}
13929 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
13930 		}
13931 
13932 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
13933 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
13934 		static struct ImgList {
13935 			size_t img; // class; hide it from GC
13936 			ImgList* next;
13937 		}
13938 
13939 		static __gshared ImgList* imglist = null;
13940 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
13941 
13942 		static void registerImage (Image img) {
13943 			if (!imglistLocked && img !is null) {
13944 				import core.stdc.stdlib : malloc;
13945 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
13946 				assert(it !is null); // do proper checks
13947 				it.img = cast(size_t)cast(void*)img;
13948 				it.next = imglist;
13949 				imglist = it;
13950 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
13951 			}
13952 		}
13953 
13954 		static void unregisterImage (Image img) {
13955 			if (!imglistLocked && img !is null) {
13956 				import core.stdc.stdlib : free;
13957 				ImgList* prev = null;
13958 				ImgList* cur = imglist;
13959 				while (cur !is null) {
13960 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
13961 					prev = cur;
13962 					cur = cur.next;
13963 				}
13964 				if (cur !is null) {
13965 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
13966 					free(cur);
13967 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
13968 				} else {
13969 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
13970 				}
13971 			}
13972 		}
13973 
13974 		static void freeImages () { // needed for discardAndRecreate
13975 			imglistLocked = true;
13976 			scope(exit) imglistLocked = false;
13977 			ImgList* cur = imglist;
13978 			ImgList* next = null;
13979 			while (cur !is null) {
13980 				import core.stdc.stdlib : free;
13981 				next = cur.next;
13982 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
13983 				(cast(Image)cast(void*)cur.img).dispose();
13984 				free(cur);
13985 				cur = next;
13986 			}
13987 			imglist = null;
13988 		}
13989 
13990 		/// can be used to override normal handling of display name
13991 		/// from environment and/or command line
13992 		static setDisplayName(string newDisplayName) {
13993 			displayName = cast(char*) (newDisplayName ~ '\0');
13994 		}
13995 
13996 		/// resets to the default display string
13997 		static resetDisplayName() {
13998 			displayName = null;
13999 		}
14000 
14001 		///
14002 		static Display* get() {
14003 			if(display is null) {
14004 				if(!librariesSuccessfullyLoaded)
14005 					throw new Exception("Unable to load X11 client libraries");
14006 				display = XOpenDisplay(displayName);
14007 
14008 				isLocal_ = false;
14009 
14010 				connectionSequence_++;
14011 				if(display is null)
14012 					throw new Exception("Unable to open X display");
14013 
14014 				auto str = display.display_name;
14015 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14016 				// and otherwise it probably isn't
14017 				if(str is null || (str[0] != ':' && str[0] != '/'))
14018 					isLocal_ = false;
14019 				else
14020 					isLocal_ = true;
14021 
14022 				debug(sdpy_x_errors) {
14023 					XSetErrorHandler(&adrlogger);
14024 					XSynchronize(display, true);
14025 
14026 					extern(C) int wtf() {
14027 						if(errorHappened) {
14028 							asm { int 3; }
14029 							errorHappened = false;
14030 						}
14031 						return 0;
14032 					}
14033 					XSetAfterFunction(display, &wtf);
14034 				}
14035 
14036 
14037 				XSetIOErrorHandler(&x11ioerrCB);
14038 				Bool sup;
14039 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14040 				createXIM();
14041 				version(with_eventloop) {
14042 					import arsd.eventloop;
14043 					addFileEventListeners(display.fd, &eventListener, null, null);
14044 				}
14045 			}
14046 
14047 			return display;
14048 		}
14049 
14050 		extern(C)
14051 		static int x11ioerrCB(Display* dpy) {
14052 			throw new XDisconnectException(false);
14053 		}
14054 
14055 		version(with_eventloop) {
14056 			import arsd.eventloop;
14057 			static void eventListener(OsFileHandle fd) {
14058 				//this.mtLock();
14059 				//scope(exit) this.mtUnlock();
14060 				while(XPending(display))
14061 					doXNextEvent(display);
14062 			}
14063 		}
14064 
14065 		// close connection on program exit -- we need this to properly free all images
14066 		static ~this () {
14067 			// the gui thread must clean up after itself or else Xlib might deadlock
14068 			// using this flag on any thread destruction is the easiest way i know of
14069 			// (shared static this is run by the LAST thread to exit, which may not be
14070 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14071 			if(thisIsGuiThread)
14072 				close();
14073 		}
14074 
14075 		///
14076 		static void close() {
14077 			if(display is null)
14078 				return;
14079 
14080 			version(with_eventloop) {
14081 				import arsd.eventloop;
14082 				removeFileEventListeners(display.fd);
14083 			}
14084 
14085 			// now remove all registered images to prevent shared memory leaks
14086 			freeImages();
14087 
14088 			// tbh I don't know why it is doing this but like if this happens to run
14089 			// from the other thread there's frequent hanging inside here.
14090 			if(thisIsGuiThread)
14091 				XCloseDisplay(display);
14092 			display = null;
14093 		}
14094 	}
14095 
14096 	mixin template NativeImageImplementation() {
14097 		XImage* handle;
14098 		ubyte* rawData;
14099 
14100 		XShmSegmentInfo shminfo;
14101 
14102 		__gshared bool xshmQueryCompleted;
14103 		__gshared bool _xshmAvailable;
14104 		public static @property bool xshmAvailable() {
14105 			if(!xshmQueryCompleted) {
14106 				int i1, i2, i3;
14107 				xshmQueryCompleted = true;
14108 
14109 				if(!XDisplayConnection.isLocal)
14110 					_xshmAvailable = false;
14111 				else
14112 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14113 			}
14114 			return _xshmAvailable;
14115 		}
14116 
14117 		bool usingXshm;
14118 	final:
14119 
14120 		private __gshared bool xshmfailed;
14121 
14122 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14123 			auto display = XDisplayConnection.get();
14124 			assert(display !is null);
14125 			auto screen = DefaultScreen(display);
14126 
14127 			// it will only use shared memory for somewhat largish images,
14128 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14129 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14130 
14131 
14132 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14133 				// the actual use still fails. For example, if the program is in a container and permission denied
14134 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14135 				//
14136 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14137 
14138 
14139 				// synchronize so preexisting buffers are clear
14140 				XSync(display, false);
14141 				xshmfailed = false;
14142 
14143 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14144 
14145 
14146 				usingXshm = true;
14147 				handle = XShmCreateImage(
14148 					display,
14149 					DefaultVisual(display, screen),
14150 					enableAlpha ? 32: 24,
14151 					ImageFormat.ZPixmap,
14152 					null,
14153 					&shminfo,
14154 					width, height);
14155 				if(handle is null)
14156 					goto abortXshm1;
14157 
14158 				if(handle.bytes_per_line != 4 * width)
14159 					goto abortXshm2;
14160 
14161 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14162 				if(shminfo.shmid < 0)
14163 					goto abortXshm3;
14164 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14165 				if(rawData == cast(ubyte*) -1)
14166 					goto abortXshm4;
14167 				shminfo.readOnly = 0;
14168 				XShmAttach(display, &shminfo);
14169 
14170 				// and now to the final error check to ensure it actually worked.
14171 				XSync(display, false);
14172 				if(xshmfailed)
14173 					goto abortXshm5;
14174 
14175 				XSetErrorHandler(oldErrorHandler);
14176 
14177 				XDisplayConnection.registerImage(this);
14178 				// if I don't flush here there's a chance the dtor will run before the
14179 				// ctor and lead to a bad value X error. While this hurts the efficiency
14180 				// it is local anyway so prolly better to keep it simple
14181 				XFlush(display);
14182 
14183 				return;
14184 
14185 				abortXshm5:
14186 					shmdt(shminfo.shmaddr);
14187 					rawData = null;
14188 
14189 				abortXshm4:
14190 					shmctl(shminfo.shmid, IPC_RMID, null);
14191 
14192 				abortXshm3:
14193 					// nothing needed, the shmget failed so there's nothing to free
14194 
14195 				abortXshm2:
14196 					XDestroyImage(handle);
14197 					handle = null;
14198 
14199 				abortXshm1:
14200 					XSetErrorHandler(oldErrorHandler);
14201 					usingXshm = false;
14202 					handle = null;
14203 
14204 					shminfo = typeof(shminfo).init;
14205 
14206 					_xshmAvailable = false; // don't try again in the future
14207 
14208 					//import std.stdio; writeln("fallingback");
14209 
14210 					goto fallback;
14211 
14212 			} else {
14213 				fallback:
14214 
14215 				if (forcexshm) throw new Exception("can't create XShm Image");
14216 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14217 				import core.stdc.stdlib : malloc;
14218 				rawData = cast(ubyte*) malloc(width * height * 4);
14219 
14220 				handle = XCreateImage(
14221 					display,
14222 					DefaultVisual(display, screen),
14223 					enableAlpha ? 32 : 24, // bpp
14224 					ImageFormat.ZPixmap,
14225 					0, // offset
14226 					rawData,
14227 					width, height,
14228 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14229 			}
14230 		}
14231 
14232 		void dispose() {
14233 			// note: this calls free(rawData) for us
14234 			if(handle) {
14235 				if (usingXshm) {
14236 					XDisplayConnection.unregisterImage(this);
14237 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14238 				}
14239 				XDestroyImage(handle);
14240 				if(usingXshm) {
14241 					shmdt(shminfo.shmaddr);
14242 					shmctl(shminfo.shmid, IPC_RMID, null);
14243 				}
14244 				handle = null;
14245 			}
14246 		}
14247 
14248 		Color getPixel(int x, int y) {
14249 			auto offset = (y * width + x) * 4;
14250 			Color c;
14251 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14252 			c.b = rawData[offset + 0];
14253 			c.g = rawData[offset + 1];
14254 			c.r = rawData[offset + 2];
14255 			if(enableAlpha)
14256 				c.unPremultiply;
14257 			return c;
14258 		}
14259 
14260 		void setPixel(int x, int y, Color c) {
14261 			if(enableAlpha)
14262 				c.premultiply();
14263 			auto offset = (y * width + x) * 4;
14264 			rawData[offset + 0] = c.b;
14265 			rawData[offset + 1] = c.g;
14266 			rawData[offset + 2] = c.r;
14267 			if(enableAlpha)
14268 				rawData[offset + 3] = c.a;
14269 		}
14270 
14271 		void convertToRgbaBytes(ubyte[] where) {
14272 			assert(where.length == this.width * this.height * 4);
14273 
14274 			// if rawData had a length....
14275 			//assert(rawData.length == where.length);
14276 			for(int idx = 0; idx < where.length; idx += 4) {
14277 				where[idx + 0] = rawData[idx + 2]; // r
14278 				where[idx + 1] = rawData[idx + 1]; // g
14279 				where[idx + 2] = rawData[idx + 0]; // b
14280 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14281 
14282 				if(enableAlpha)
14283 					unPremultiplyRgba(where[idx .. idx + 4]);
14284 			}
14285 		}
14286 
14287 		void setFromRgbaBytes(in ubyte[] where) {
14288 			assert(where.length == this.width * this.height * 4);
14289 
14290 			// if rawData had a length....
14291 			//assert(rawData.length == where.length);
14292 			for(int idx = 0; idx < where.length; idx += 4) {
14293 				rawData[idx + 2] = where[idx + 0]; // r
14294 				rawData[idx + 1] = where[idx + 1]; // g
14295 				rawData[idx + 0] = where[idx + 2]; // b
14296 				if(enableAlpha) {
14297 					rawData[idx + 3] = where[idx + 3]; // a
14298 					premultiplyBgra(rawData[idx .. idx + 4]);
14299 				}
14300 			}
14301 		}
14302 
14303 	}
14304 
14305 	mixin template NativeSimpleWindowImplementation() {
14306 		GC gc;
14307 		Window window;
14308 		Display* display;
14309 
14310 		Pixmap buffer;
14311 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14312 		XIC xic; // input context
14313 		int curHidden = 0; // counter
14314 		Cursor blankCurPtr = 0;
14315 		int cursorSequenceNumber = 0;
14316 		int warpEventCount = 0; // number of mouse movement events to eat
14317 
14318 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14319 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14320 
14321 		version(without_opengl) {} else
14322 		GLXContext glc;
14323 
14324 		private void fixFixedSize(bool forced=false) (int width, int height) {
14325 			if (forced || this.resizability == Resizability.fixedSize) {
14326 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14327 				XSizeHints sh;
14328 				static if (!forced) {
14329 					c_long spr;
14330 					XGetWMNormalHints(display, window, &sh, &spr);
14331 					sh.flags |= PMaxSize | PMinSize;
14332 				} else {
14333 					sh.flags = PMaxSize | PMinSize;
14334 				}
14335 				sh.min_width = width;
14336 				sh.min_height = height;
14337 				sh.max_width = width;
14338 				sh.max_height = height;
14339 				XSetWMNormalHints(display, window, &sh);
14340 				//XFlush(display);
14341 			}
14342 		}
14343 
14344 		ScreenPainter getPainter(bool manualInvalidations) {
14345 			return ScreenPainter(this, window, manualInvalidations);
14346 		}
14347 
14348 		void move(int x, int y) {
14349 			XMoveWindow(display, window, x, y);
14350 		}
14351 
14352 		void resize(int w, int h) {
14353 			if (w < 1) w = 1;
14354 			if (h < 1) h = 1;
14355 			XResizeWindow(display, window, w, h);
14356 
14357 			// calling this now to avoid waiting for the server to
14358 			// acknowledge the resize; draws without returning to the
14359 			// event loop will thus actually work. the server's event
14360 			// btw might overrule this and resize it again
14361 			recordX11Resize(display, this, w, h);
14362 
14363 			updateOpenglViewportIfNeeded(w, h);
14364 		}
14365 
14366 		void moveResize (int x, int y, int w, int h) {
14367 			if (w < 1) w = 1;
14368 			if (h < 1) h = 1;
14369 			XMoveResizeWindow(display, window, x, y, w, h);
14370 			updateOpenglViewportIfNeeded(w, h);
14371 		}
14372 
14373 		void hideCursor () {
14374 			if (curHidden++ == 0) {
14375 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14376 					static const(char)[1] cmbmp = 0;
14377 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14378 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14379 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14380 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14381 					XFreePixmap(display, pm);
14382 				}
14383 				XDefineCursor(display, window, blankCurPtr);
14384 			}
14385 		}
14386 
14387 		void showCursor () {
14388 			if (--curHidden == 0) XUndefineCursor(display, window);
14389 		}
14390 
14391 		void warpMouse (int x, int y) {
14392 			// here i will send dummy "ignore next mouse motion" event,
14393 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14394 			// and we don't need to report it to the user (as warping is
14395 			// used when the user needs movement deltas).
14396 			//XClientMessageEvent xclient;
14397 			XEvent e;
14398 			e.xclient.type = EventType.ClientMessage;
14399 			e.xclient.window = window;
14400 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14401 			e.xclient.format = 32;
14402 			e.xclient.data.l[0] = 0;
14403 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14404 			//{ 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]); }
14405 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14406 			// now warp pointer...
14407 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14408 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14409 			// ...and flush
14410 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14411 			XFlush(display);
14412 		}
14413 
14414 		void sendDummyEvent () {
14415 			// here i will send dummy event to ping event queue
14416 			XEvent e;
14417 			e.xclient.type = EventType.ClientMessage;
14418 			e.xclient.window = window;
14419 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14420 			e.xclient.format = 32;
14421 			e.xclient.data.l[0] = 0;
14422 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14423 			XFlush(display);
14424 		}
14425 
14426 		void setTitle(string title) {
14427 			if (title.ptr is null) title = "";
14428 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14429 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14430 			XTextProperty windowName;
14431 			windowName.value = title.ptr;
14432 			windowName.encoding = XA_UTF8; //XA_STRING;
14433 			windowName.format = 8;
14434 			windowName.nitems = cast(uint)title.length;
14435 			XSetWMName(display, window, &windowName);
14436 			char[1024] namebuf = 0;
14437 			auto maxlen = namebuf.length-1;
14438 			if (maxlen > title.length) maxlen = title.length;
14439 			namebuf[0..maxlen] = title[0..maxlen];
14440 			XStoreName(display, window, namebuf.ptr);
14441 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14442 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14443 		}
14444 
14445 		string[] getTitles() {
14446 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14447 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14448 			XTextProperty textProp;
14449 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14450 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14451 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14452 				} else
14453 					return [];
14454 			} else
14455 				return null;
14456 		}
14457 
14458 		string getTitle() {
14459 			auto titles = getTitles();
14460 			return titles.length ? titles[0] : null;
14461 		}
14462 
14463 		void setMinSize (int minwidth, int minheight) {
14464 			import core.stdc.config : c_long;
14465 			if (minwidth < 1) minwidth = 1;
14466 			if (minheight < 1) minheight = 1;
14467 			XSizeHints sh;
14468 			c_long spr;
14469 			XGetWMNormalHints(display, window, &sh, &spr);
14470 			sh.min_width = minwidth;
14471 			sh.min_height = minheight;
14472 			sh.flags |= PMinSize;
14473 			XSetWMNormalHints(display, window, &sh);
14474 			flushGui();
14475 		}
14476 
14477 		void setMaxSize (int maxwidth, int maxheight) {
14478 			import core.stdc.config : c_long;
14479 			if (maxwidth < 1) maxwidth = 1;
14480 			if (maxheight < 1) maxheight = 1;
14481 			XSizeHints sh;
14482 			c_long spr;
14483 			XGetWMNormalHints(display, window, &sh, &spr);
14484 			sh.max_width = maxwidth;
14485 			sh.max_height = maxheight;
14486 			sh.flags |= PMaxSize;
14487 			XSetWMNormalHints(display, window, &sh);
14488 			flushGui();
14489 		}
14490 
14491 		void setResizeGranularity (int granx, int grany) {
14492 			import core.stdc.config : c_long;
14493 			if (granx < 1) granx = 1;
14494 			if (grany < 1) grany = 1;
14495 			XSizeHints sh;
14496 			c_long spr;
14497 			XGetWMNormalHints(display, window, &sh, &spr);
14498 			sh.width_inc = granx;
14499 			sh.height_inc = grany;
14500 			sh.flags |= PResizeInc;
14501 			XSetWMNormalHints(display, window, &sh);
14502 			flushGui();
14503 		}
14504 
14505 		void setOpacity (uint opacity) {
14506 			arch_ulong o = opacity;
14507 			if (opacity == uint.max)
14508 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14509 			else
14510 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14511 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14512 		}
14513 
14514 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14515 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14516 			display = XDisplayConnection.get();
14517 			auto screen = DefaultScreen(display);
14518 
14519 			bool overrideRedirect = false;
14520 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14521 				overrideRedirect = true;
14522 
14523 			version(without_opengl) {}
14524 			else {
14525 				if(opengl == OpenGlOptions.yes) {
14526 					GLXFBConfig fbconf = null;
14527 					XVisualInfo* vi = null;
14528 					bool useLegacy = false;
14529 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14530 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14531 						int[23] visualAttribs = [
14532 							GLX_X_RENDERABLE , 1/*True*/,
14533 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14534 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14535 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14536 							GLX_RED_SIZE     , 8,
14537 							GLX_GREEN_SIZE   , 8,
14538 							GLX_BLUE_SIZE    , 8,
14539 							GLX_ALPHA_SIZE   , 8,
14540 							GLX_DEPTH_SIZE   , 24,
14541 							GLX_STENCIL_SIZE , 8,
14542 							GLX_DOUBLEBUFFER , 1/*True*/,
14543 							0/*None*/,
14544 						];
14545 						int fbcount;
14546 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14547 						if (fbcount == 0) {
14548 							useLegacy = true; // try to do at least something
14549 						} else {
14550 							// pick the FB config/visual with the most samples per pixel
14551 							int bestidx = -1, bestns = -1;
14552 							foreach (int fbi; 0..fbcount) {
14553 								int sb, samples;
14554 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14555 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14556 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14557 							}
14558 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14559 							fbconf = fbc[bestidx];
14560 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14561 							XFree(fbc);
14562 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14563 						}
14564 					}
14565 					if (vi is null || useLegacy) {
14566 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14567 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14568 						useLegacy = true;
14569 					}
14570 					if (vi is null) throw new Exception("no open gl visual found");
14571 
14572 					XSetWindowAttributes swa;
14573 					auto root = RootWindow(display, screen);
14574 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14575 
14576 					swa.override_redirect = overrideRedirect;
14577 
14578 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14579 						0, 0, width, height,
14580 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14581 
14582 					// now try to use `glXCreateContextAttribsARB()` if it's here
14583 					if (!useLegacy) {
14584 						// request fairly advanced context, even with stencil buffer!
14585 						int[9] contextAttribs = [
14586 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14587 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14588 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14589 							// for modern context, set "forward compatibility" flag too
14590 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14591 							0/*None*/,
14592 						];
14593 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14594 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14595 							sdpyOpenGLContextVersion = 0;
14596 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14597 						}
14598 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14599 					} else {
14600 						// fallback to old GLX call
14601 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14602 							sdpyOpenGLContextVersion = 0;
14603 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14604 						}
14605 					}
14606 					// sync to ensure any errors generated are processed
14607 					XSync(display, 0/*False*/);
14608 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14609 					if(glc is null)
14610 						throw new Exception("glc");
14611 				}
14612 			}
14613 
14614 			if(opengl == OpenGlOptions.no) {
14615 
14616 				XSetWindowAttributes swa;
14617 				swa.background_pixel = WhitePixel(display, screen);
14618 				swa.border_pixel = BlackPixel(display, screen);
14619 				swa.override_redirect = overrideRedirect;
14620 				auto root = RootWindow(display, screen);
14621 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14622 
14623 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14624 					0, 0, width, height,
14625 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14626 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14627 
14628 
14629 
14630 				/*
14631 				window = XCreateSimpleWindow(
14632 					display,
14633 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14634 					0, 0, // x, y
14635 					width, height,
14636 					1, // border width
14637 					BlackPixel(display, screen), // border
14638 					WhitePixel(display, screen)); // background
14639 				*/
14640 
14641 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14642 				bufferw = width;
14643 				bufferh = height;
14644 
14645 				gc = DefaultGC(display, screen);
14646 
14647 				// clear out the buffer to get us started...
14648 				XSetForeground(display, gc, WhitePixel(display, screen));
14649 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14650 				XSetForeground(display, gc, BlackPixel(display, screen));
14651 			}
14652 
14653 			// input context
14654 			//TODO: create this only for top-level windows, and reuse that?
14655 			populateXic();
14656 
14657 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14658 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14659 			// window class
14660 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14661 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14662 				XClassHint klass;
14663 				XWMHints wh;
14664 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14665 					wh.input = true;
14666 					wh.flags |= InputHint;
14667 				}
14668 				XSizeHints size;
14669 				klass.res_name = sdpyWindowClassStr;
14670 				klass.res_class = sdpyWindowClassStr;
14671 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14672 			}
14673 
14674 			setTitle(title);
14675 			SimpleWindow.nativeMapping[window] = this;
14676 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14677 
14678 			// This gives our window a close button
14679 			if (windowType != WindowTypes.eventOnly) {
14680 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14681 				int useAtoms;
14682 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14683 					useAtoms = 2;
14684 				} else {
14685 					useAtoms = 1;
14686 				}
14687 				assert(useAtoms <= atoms.length);
14688 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14689 			}
14690 
14691 			// FIXME: windowType and customizationFlags
14692 			Atom[8] wsatoms; // here, due to goto
14693 			int wmsacount = 0; // here, due to goto
14694 
14695 			try
14696 			final switch(windowType) {
14697 				case WindowTypes.normal:
14698 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14699 				break;
14700 				case WindowTypes.undecorated:
14701 					motifHideDecorations();
14702 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14703 				break;
14704 				case WindowTypes.eventOnly:
14705 					_hidden = true;
14706 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14707 					goto hiddenWindow;
14708 				//break;
14709 				case WindowTypes.nestedChild:
14710 					// handled in XCreateWindow calls
14711 				break;
14712 
14713 				case WindowTypes.dropdownMenu:
14714 					motifHideDecorations();
14715 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14716 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14717 				break;
14718 				case WindowTypes.popupMenu:
14719 					motifHideDecorations();
14720 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14721 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14722 				break;
14723 				case WindowTypes.notification:
14724 					motifHideDecorations();
14725 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14726 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14727 				break;
14728 				case WindowTypes.minimallyWrapped:
14729 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14730 				/+
14731 				case WindowTypes.menu:
14732 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14733 					motifHideDecorations();
14734 				break;
14735 				case WindowTypes.desktop:
14736 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14737 				break;
14738 				case WindowTypes.dock:
14739 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14740 				break;
14741 				case WindowTypes.toolbar:
14742 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14743 				break;
14744 				case WindowTypes.menu:
14745 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14746 				break;
14747 				case WindowTypes.utility:
14748 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14749 				break;
14750 				case WindowTypes.splash:
14751 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14752 				break;
14753 				case WindowTypes.dialog:
14754 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14755 				break;
14756 				case WindowTypes.tooltip:
14757 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14758 				break;
14759 				case WindowTypes.notification:
14760 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14761 				break;
14762 				case WindowTypes.combo:
14763 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
14764 				break;
14765 				case WindowTypes.dnd:
14766 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
14767 				break;
14768 				+/
14769 			}
14770 			catch(Exception e) {
14771 				// XInternAtom failed, prolly a WM
14772 				// that doesn't support these things
14773 			}
14774 
14775 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
14776 			// the two following flags may be ignored by WM
14777 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
14778 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
14779 
14780 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
14781 
14782 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
14783 
14784 			// What would be ideal here is if they only were
14785 			// selected if there was actually an event handler
14786 			// for them...
14787 
14788 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
14789 
14790 			hiddenWindow:
14791 
14792 			// set the pid property for lookup later by window managers
14793 			// a standard convenience
14794 			import core.sys.posix.unistd;
14795 			arch_ulong pid = getpid();
14796 
14797 			XChangeProperty(
14798 				display,
14799 				impl.window,
14800 				GetAtom!("_NET_WM_PID", true)(display),
14801 				XA_CARDINAL,
14802 				32 /* bits */,
14803 				0 /*PropModeReplace*/,
14804 				&pid,
14805 				1);
14806 
14807 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
14808 				if(parent is null) assert(0);
14809 				XChangeProperty(
14810 					display,
14811 					impl.window,
14812 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
14813 					XA_WINDOW,
14814 					32 /* bits */,
14815 					0 /*PropModeReplace*/,
14816 					&parent.impl.window,
14817 					1);
14818 
14819 			}
14820 
14821 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
14822 				XMapWindow(display, window);
14823 			} else {
14824 				_hidden = true;
14825 			}
14826 		}
14827 
14828 		void populateXic() {
14829 			if (XDisplayConnection.xim !is null) {
14830 				xic = XCreateIC(XDisplayConnection.xim,
14831 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
14832 						/*XNClientWindow*/"clientWindow".ptr, window,
14833 						/*XNFocusWindow*/"focusWindow".ptr, window,
14834 						null);
14835 				if (xic is null) {
14836 					import core.stdc.stdio : stderr, fprintf;
14837 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
14838 				}
14839 			}
14840 		}
14841 
14842 		void selectDefaultInput(bool forceIncludeMouseMotion) {
14843 			auto mask = EventMask.ExposureMask |
14844 				EventMask.KeyPressMask |
14845 				EventMask.KeyReleaseMask |
14846 				EventMask.PropertyChangeMask |
14847 				EventMask.FocusChangeMask |
14848 				EventMask.StructureNotifyMask |
14849 				EventMask.SubstructureNotifyMask |
14850 				EventMask.VisibilityChangeMask
14851 				| EventMask.ButtonPressMask
14852 				| EventMask.ButtonReleaseMask
14853 			;
14854 
14855 			// xshm is our shortcut for local connections
14856 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
14857 				mask |= EventMask.PointerMotionMask;
14858 			else
14859 				mask |= EventMask.ButtonMotionMask;
14860 
14861 			XSelectInput(display, window, mask);
14862 		}
14863 
14864 
14865 		void setNetWMWindowType(Atom type) {
14866 			Atom[2] atoms;
14867 
14868 			atoms[0] = type;
14869 			// generic fallback
14870 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
14871 
14872 			XChangeProperty(
14873 				display,
14874 				impl.window,
14875 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
14876 				XA_ATOM,
14877 				32 /* bits */,
14878 				0 /*PropModeReplace*/,
14879 				atoms.ptr,
14880 				cast(int) atoms.length);
14881 		}
14882 
14883 		void motifHideDecorations(bool hide = true) {
14884 			MwmHints hints;
14885 			hints.flags = MWM_HINTS_DECORATIONS;
14886 			hints.decorations = hide ? 0 : 1;
14887 
14888 			XChangeProperty(
14889 				display,
14890 				impl.window,
14891 				GetAtom!"_MOTIF_WM_HINTS"(display),
14892 				GetAtom!"_MOTIF_WM_HINTS"(display),
14893 				32 /* bits */,
14894 				0 /*PropModeReplace*/,
14895 				&hints,
14896 				hints.sizeof / 4);
14897 		}
14898 
14899 		/*k8: unused
14900 		void createOpenGlContext() {
14901 
14902 		}
14903 		*/
14904 
14905 		void closeWindow() {
14906 			// I can't close this or a child window closing will
14907 			// break events for everyone. So I'm just leaking it right
14908 			// now and that is probably perfectly fine...
14909 			version(none)
14910 			if (customEventFDRead != -1) {
14911 				import core.sys.posix.unistd : close;
14912 				auto same = customEventFDRead == customEventFDWrite;
14913 
14914 				close(customEventFDRead);
14915 				if(!same)
14916 					close(customEventFDWrite);
14917 				customEventFDRead = -1;
14918 				customEventFDWrite = -1;
14919 			}
14920 			if(buffer)
14921 				XFreePixmap(display, buffer);
14922 			bufferw = bufferh = 0;
14923 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
14924 			XDestroyWindow(display, window);
14925 			XFlush(display);
14926 		}
14927 
14928 		void dispose() {
14929 		}
14930 
14931 		bool destroyed = false;
14932 	}
14933 
14934 	bool insideXEventLoop;
14935 }
14936 
14937 version(X11) {
14938 
14939 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
14940 
14941 	private class ResizeEvent {
14942 		int width, height;
14943 	}
14944 
14945 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
14946 		if(win.windowType == WindowTypes.minimallyWrapped)
14947 			return;
14948 
14949 		if(win.pendingResizeEvent is null) {
14950 			win.pendingResizeEvent = new ResizeEvent();
14951 			win.addEventListener((ResizeEvent re) {
14952 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
14953 			});
14954 		}
14955 		win.pendingResizeEvent.width = width;
14956 		win.pendingResizeEvent.height = height;
14957 		if(!win.eventQueued!ResizeEvent) {
14958 			win.postEvent(win.pendingResizeEvent);
14959 		}
14960 	}
14961 
14962 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
14963 		if(win.windowType == WindowTypes.minimallyWrapped)
14964 			return;
14965 		if(win.closed)
14966 			return;
14967 
14968 		if(width != win.width || height != win.height) {
14969 
14970 		// import std.stdio; writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
14971 			win._width = width;
14972 			win._height = height;
14973 
14974 			if(win.openglMode == OpenGlOptions.no) {
14975 				// FIXME: could this be more efficient?
14976 
14977 				if (win.bufferw < width || win.bufferh < height) {
14978 					//{ 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); }
14979 					// grow the internal buffer to match the window...
14980 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
14981 					{
14982 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
14983 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
14984 						scope(exit) XFreeGC(win.display, xgc);
14985 						XSetClipMask(win.display, xgc, None);
14986 						XSetForeground(win.display, xgc, 0);
14987 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
14988 					}
14989 					XCopyArea(display,
14990 						cast(Drawable) win.buffer,
14991 						cast(Drawable) newPixmap,
14992 						win.gc, 0, 0,
14993 						win.bufferw < width ? win.bufferw : win.width,
14994 						win.bufferh < height ? win.bufferh : win.height,
14995 						0, 0);
14996 
14997 					XFreePixmap(display, win.buffer);
14998 					win.buffer = newPixmap;
14999 					win.bufferw = width;
15000 					win.bufferh = height;
15001 				}
15002 
15003 				// clear unused parts of the buffer
15004 				if (win.bufferw > width || win.bufferh > height) {
15005 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15006 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15007 					scope(exit) XFreeGC(win.display, xgc);
15008 					XSetClipMask(win.display, xgc, None);
15009 					XSetForeground(win.display, xgc, 0);
15010 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15011 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15012 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15013 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15014 				}
15015 
15016 			}
15017 
15018 			win.updateOpenglViewportIfNeeded(width, height);
15019 
15020 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15021 
15022 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15023 			if(win.windowResized !is null) {
15024 				XUnlockDisplay(display);
15025 				scope(exit) XLockDisplay(display);
15026 				win.windowResized(width, height);
15027 			}
15028 		}
15029 	}
15030 
15031 
15032 	/// Platform-specific, you might use it when doing a custom event loop.
15033 	bool doXNextEvent(Display* display) {
15034 		bool done;
15035 		XEvent e;
15036 		XNextEvent(display, &e);
15037 		version(sddddd) {
15038 			import std.stdio, std.conv : to;
15039 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15040 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15041 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15042 			}
15043 		}
15044 
15045 		// filter out compose events
15046 		if (XFilterEvent(&e, None)) {
15047 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15048 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15049 			return false;
15050 		}
15051 		// process keyboard mapping changes
15052 		if (e.type == EventType.KeymapNotify) {
15053 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15054 			XRefreshKeyboardMapping(&e.xmapping);
15055 			return false;
15056 		}
15057 
15058 		version(with_eventloop)
15059 			import arsd.eventloop;
15060 
15061 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15062 			// see windows impl's comments
15063 			XUnlockDisplay(display);
15064 			scope(exit) XLockDisplay(display);
15065 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15066 			if(ret == 0)
15067 				return done;
15068 		}
15069 
15070 
15071 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15072 			if(win.getNativeEventHandler !is null) {
15073 				XUnlockDisplay(display);
15074 				scope(exit) XLockDisplay(display);
15075 				auto ret = win.getNativeEventHandler()(e);
15076 				if(ret == 0)
15077 					return done;
15078 			}
15079 		}
15080 
15081 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15082 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15083 				// we get this because of the RRScreenChangeNotifyMask
15084 
15085 				// this isn't actually an ideal way to do it since it wastes time
15086 				// but meh it is simple and it works.
15087 				win.actualDpiLoadAttempted = false;
15088 				SimpleWindow.xRandrInfoLoadAttemped = false;
15089 				win.updateActualDpi(); // trigger a reload
15090 			}
15091 		}
15092 
15093 		switch(e.type) {
15094 		  case EventType.SelectionClear:
15095 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15096 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15097 				//import std.stdio; writeln("SelectionClear");
15098 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15099 			}
15100 		  break;
15101 		  case EventType.SelectionRequest:
15102 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15103 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15104 				// import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15105 				XUnlockDisplay(display);
15106 				scope(exit) XLockDisplay(display);
15107 				(*ssh).handleRequest(e);
15108 			}
15109 		  break;
15110 		  case EventType.PropertyNotify:
15111 			// import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15112 
15113 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15114 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15115 					ssh.sendMoreIncr(&e.xproperty);
15116 			}
15117 
15118 
15119 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15120 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15121 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15122 					Atom target;
15123 					int format;
15124 					arch_ulong bytesafter, length;
15125 					void* value;
15126 
15127 					ubyte[] s;
15128 					Atom targetToKeep;
15129 
15130 					XGetWindowProperty(
15131 						e.xproperty.display,
15132 						e.xproperty.window,
15133 						e.xproperty.atom,
15134 						0,
15135 						100000 /* length */,
15136 						true, /* erase it to signal we got it and want more */
15137 						0 /*AnyPropertyType*/,
15138 						&target, &format, &length, &bytesafter, &value);
15139 
15140 					if(!targetToKeep)
15141 						targetToKeep = target;
15142 
15143 					auto id = (cast(ubyte*) value)[0 .. length];
15144 
15145 					handler.handleIncrData(targetToKeep, id);
15146 
15147 					XFree(value);
15148 				}
15149 			}
15150 		  break;
15151 		  case EventType.SelectionNotify:
15152 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15153 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15154 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15155 					XUnlockDisplay(display);
15156 					scope(exit) XLockDisplay(display);
15157 					handler.handleData(None, null);
15158 				} else {
15159 					Atom target;
15160 					int format;
15161 					arch_ulong bytesafter, length;
15162 					void* value;
15163 					XGetWindowProperty(
15164 						e.xselection.display,
15165 						e.xselection.requestor,
15166 						e.xselection.property,
15167 						0,
15168 						100000 /* length */,
15169 						//false, /* don't erase it */
15170 						true, /* do erase it lol */
15171 						0 /*AnyPropertyType*/,
15172 						&target, &format, &length, &bytesafter, &value);
15173 
15174 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15175 
15176 					{
15177 						XUnlockDisplay(display);
15178 						scope(exit) XLockDisplay(display);
15179 
15180 						if(target == XA_ATOM) {
15181 							// initial request, see what they are able to work with and request the best one
15182 							// we can handle, if available
15183 
15184 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15185 							Atom best = handler.findBestFormat(answer);
15186 
15187 							/+
15188 							writeln("got ", answer);
15189 							foreach(a; answer)
15190 								printf("%s\n", XGetAtomName(display, a));
15191 							writeln("best ", best);
15192 							+/
15193 
15194 							if(best != None) {
15195 								// actually request the best format
15196 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15197 							}
15198 						} else if(target == GetAtom!"INCR"(display)) {
15199 							// incremental
15200 
15201 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15202 
15203 							// signal the sending program that we see
15204 							// the incr and are ready to receive more.
15205 							XDeleteProperty(
15206 								e.xselection.display,
15207 								e.xselection.requestor,
15208 								e.xselection.property);
15209 						} else {
15210 							// unsupported type... maybe, forward
15211 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15212 						}
15213 					}
15214 					XFree(value);
15215 					/*
15216 					XDeleteProperty(
15217 						e.xselection.display,
15218 						e.xselection.requestor,
15219 						e.xselection.property);
15220 					*/
15221 				}
15222 			}
15223 		  break;
15224 		  case EventType.ConfigureNotify:
15225 			auto event = e.xconfigure;
15226 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15227 				if(win.windowType == WindowTypes.minimallyWrapped)
15228 					break;
15229 					//version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); }
15230 
15231 				/+
15232 					The ICCCM says window managers must send a synthetic event when the window
15233 					is moved but NOT when it is resized. In the resize case, an event is sent
15234 					with position (0, 0) which can be wrong and break the dpi calculations.
15235 
15236 					So we only consider the synthetic events from the WM and otherwise
15237 					need to wait for some other event to get the position which... sucks.
15238 
15239 					I'd rather not have windows changing their layout on mouse motion after
15240 					switching monitors... might be forced to but for now just ignoring it.
15241 
15242 					Easiest way to switch monitors without sending a size position is by
15243 					maximize or fullscreen in a setup like mine, but on most setups those
15244 					work on the monitor it is already living on, so it should be ok most the
15245 					time.
15246 				+/
15247 				if(event.send_event) {
15248 					win.screenPositionKnown = true;
15249 					win.screenPositionX = event.x;
15250 					win.screenPositionY = event.y;
15251 					win.updateActualDpi();
15252 				}
15253 
15254 				win.updateIMEPopupLocation();
15255 				recordX11ResizeAsync(display, *win, event.width, event.height);
15256 			}
15257 		  break;
15258 		  case EventType.Expose:
15259 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15260 				if(win.windowType == WindowTypes.minimallyWrapped)
15261 					break;
15262 				// if it is closing from a popup menu, it can get
15263 				// an Expose event right by the end and trigger a
15264 				// BadDrawable error ... we'll just check
15265 				// closed to handle that.
15266 				if((*win).closed) break;
15267 				if((*win).openglMode == OpenGlOptions.no) {
15268 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15269 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15270 					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);
15271 				} else {
15272 					// need to redraw the scene somehow
15273 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15274 						XUnlockDisplay(display);
15275 						scope(exit) XLockDisplay(display);
15276 						version(without_opengl) {} else
15277 						win.redrawOpenGlSceneSoon();
15278 					}
15279 				}
15280 			}
15281 		  break;
15282 		  case EventType.FocusIn:
15283 		  case EventType.FocusOut:
15284 
15285 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15286 				/+
15287 
15288 				void info(string detail) {
15289 					string s;
15290 					import std.conv;
15291 					import std.datetime;
15292 					s ~= to!string(Clock.currTime);
15293 					s ~= " ";
15294 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15295 					s ~= " ";
15296 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15297 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15298 					s ~= detail;
15299 					s ~= " ";
15300 
15301 					sdpyPrintDebugString(s);
15302 
15303 				}
15304 
15305 				switch(e.xfocus.detail) {
15306 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15307 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15308 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15309 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15310 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15311 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15312 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15313 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15314 					default:
15315 
15316 				}
15317 				+/
15318 
15319 
15320 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15321 					break; // just ignore these they seem irrelevant
15322 
15323 				auto old = win._focused;
15324 				win._focused = e.type == EventType.FocusIn;
15325 
15326 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15327 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15328 					win._focused = true;
15329 
15330 				if(win.demandingAttention)
15331 					demandAttention(*win, false);
15332 
15333 				win.updateIMEFocused();
15334 
15335 				if(old != win._focused && win.onFocusChange) {
15336 					XUnlockDisplay(display);
15337 					scope(exit) XLockDisplay(display);
15338 					win.onFocusChange(win._focused);
15339 				}
15340 			}
15341 		  break;
15342 		  case EventType.VisibilityNotify:
15343 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15344 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15345 						if (win.visibilityChanged !is null) {
15346 								XUnlockDisplay(display);
15347 								scope(exit) XLockDisplay(display);
15348 								win.visibilityChanged(false);
15349 							}
15350 					} else {
15351 						if (win.visibilityChanged !is null) {
15352 							XUnlockDisplay(display);
15353 							scope(exit) XLockDisplay(display);
15354 							win.visibilityChanged(true);
15355 						}
15356 					}
15357 				}
15358 				break;
15359 		  case EventType.ClientMessage:
15360 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15361 					// "ignore next mouse motion" event, increment ignore counter for teh window
15362 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15363 						++(*win).warpEventCount;
15364 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15365 					} else {
15366 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15367 					}
15368 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15369 					// user clicked the close button on the window manager
15370 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15371 						XUnlockDisplay(display);
15372 						scope(exit) XLockDisplay(display);
15373 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15374 					}
15375 
15376 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15377 					//import std.stdio; writeln("HAPPENED");
15378 					// user clicked the close button on the window manager
15379 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15380 						XUnlockDisplay(display);
15381 						scope(exit) XLockDisplay(display);
15382 
15383 						auto setTo = *win;
15384 
15385 						if(win.setRequestedInputFocus !is null) {
15386 							auto s = win.setRequestedInputFocus();
15387 							if(s !is null) {
15388 								setTo = s;
15389 							}
15390 						}
15391 
15392 						assert(setTo !is null);
15393 
15394 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15395 
15396 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15397 					}
15398 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15399 					foreach(nai; NotificationAreaIcon.activeIcons)
15400 						nai.newManager();
15401 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15402 
15403 					bool xDragWindow = true;
15404 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15405 						//XDefineCursor(display, xDragWindow.impl.window,
15406 							//import std.stdio; writeln("XdndStatus ", e.xclient.data.l);
15407 					}
15408 					if(auto dh = win.dropHandler) {
15409 
15410 						static Atom[3] xFormatsBuffer;
15411 						static Atom[] xFormats;
15412 
15413 						void resetXFormats() {
15414 							xFormatsBuffer[] = 0;
15415 							xFormats = xFormatsBuffer[];
15416 						}
15417 
15418 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15419 							// on Windows it is supposed to return the effect you actually do FIXME
15420 
15421 							auto sourceWindow =  e.xclient.data.l[0];
15422 
15423 							xFormatsBuffer[0] = e.xclient.data.l[2];
15424 							xFormatsBuffer[1] = e.xclient.data.l[3];
15425 							xFormatsBuffer[2] = e.xclient.data.l[4];
15426 
15427 							if(e.xclient.data.l[1] & 1) {
15428 								// can just grab it all but like we don't necessarily need them...
15429 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15430 							} else {
15431 								int len;
15432 								foreach(fmt; xFormatsBuffer)
15433 									if(fmt) len++;
15434 								xFormats = xFormatsBuffer[0 .. len];
15435 							}
15436 
15437 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15438 
15439 							dh.dragEnter(&pkg);
15440 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15441 
15442 							auto pack = e.xclient.data.l[2];
15443 
15444 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15445 
15446 
15447 							XClientMessageEvent xclient;
15448 
15449 							xclient.type = EventType.ClientMessage;
15450 							xclient.window = e.xclient.data.l[0];
15451 							xclient.message_type = GetAtom!"XdndStatus"(display);
15452 							xclient.format = 32;
15453 							xclient.data.l[0] = win.impl.window;
15454 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15455 							auto r = result.consistentWithin;
15456 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15457 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15458 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15459 
15460 							XSendEvent(
15461 								display,
15462 								e.xclient.data.l[0],
15463 								false,
15464 								EventMask.NoEventMask,
15465 								cast(XEvent*) &xclient
15466 							);
15467 
15468 
15469 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15470 							//import std.stdio; writeln("XdndLeave");
15471 							// drop cancelled.
15472 							// data.l[0] is the source window
15473 							dh.dragLeave();
15474 
15475 							resetXFormats();
15476 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15477 							// drop happening, should fetch data, then send finished
15478 							//import std.stdio; writeln("XdndDrop");
15479 
15480 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15481 
15482 							dh.drop(&pkg);
15483 
15484 							resetXFormats();
15485 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15486 							// import std.stdio; writeln("XdndFinished");
15487 
15488 							dh.finish();
15489 						}
15490 
15491 					}
15492 				}
15493 		  break;
15494 		  case EventType.MapNotify:
15495 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15496 					(*win)._visible = true;
15497 					if (!(*win)._visibleForTheFirstTimeCalled) {
15498 						(*win)._visibleForTheFirstTimeCalled = true;
15499 						if ((*win).visibleForTheFirstTime !is null) {
15500 							XUnlockDisplay(display);
15501 							scope(exit) XLockDisplay(display);
15502 							(*win).visibleForTheFirstTime();
15503 						}
15504 					}
15505 					if ((*win).visibilityChanged !is null) {
15506 						XUnlockDisplay(display);
15507 						scope(exit) XLockDisplay(display);
15508 						(*win).visibilityChanged(true);
15509 					}
15510 				}
15511 		  break;
15512 		  case EventType.UnmapNotify:
15513 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15514 					win._visible = false;
15515 					if (win.visibilityChanged !is null) {
15516 						XUnlockDisplay(display);
15517 						scope(exit) XLockDisplay(display);
15518 						win.visibilityChanged(false);
15519 					}
15520 			}
15521 		  break;
15522 		  case EventType.DestroyNotify:
15523 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15524 				if(win.destroyed)
15525 					break; // might get a notification both for itself and from its parent
15526 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15527 				win._closed = true; // just in case
15528 				win.destroyed = true;
15529 				if (win.xic !is null) {
15530 					XDestroyIC(win.xic);
15531 					win.xic = null; // just in case
15532 				}
15533 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15534 				bool anyImportant = false;
15535 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15536 					if(w.beingOpenKeepsAppOpen) {
15537 						anyImportant = true;
15538 						break;
15539 					}
15540 				if(!anyImportant) {
15541 					EventLoop.quitApplication();
15542 					done = true;
15543 				}
15544 			}
15545 			auto window = e.xdestroywindow.window;
15546 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15547 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15548 
15549 			version(with_eventloop) {
15550 				if(done) exit();
15551 			}
15552 		  break;
15553 
15554 		  case EventType.MotionNotify:
15555 			MouseEvent mouse;
15556 			auto event = e.xmotion;
15557 
15558 			mouse.type = MouseEventType.motion;
15559 			mouse.x = event.x;
15560 			mouse.y = event.y;
15561 			mouse.modifierState = event.state;
15562 
15563 			mouse.timestamp = event.time;
15564 
15565 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15566 				mouse.window = *win;
15567 				if (win.warpEventCount > 0) {
15568 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15569 					--(*win).warpEventCount;
15570 					(*win).mdx(mouse); // so deltas will be correctly updated
15571 				} else {
15572 					win.warpEventCount = 0; // just in case
15573 					(*win).mdx(mouse);
15574 					if((*win).handleMouseEvent) {
15575 						XUnlockDisplay(display);
15576 						scope(exit) XLockDisplay(display);
15577 						(*win).handleMouseEvent(mouse);
15578 					}
15579 				}
15580 			}
15581 
15582 		  	version(with_eventloop)
15583 				send(mouse);
15584 		  break;
15585 		  case EventType.ButtonPress:
15586 		  case EventType.ButtonRelease:
15587 			MouseEvent mouse;
15588 			auto event = e.xbutton;
15589 
15590 			mouse.timestamp = event.time;
15591 
15592 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15593 			mouse.x = event.x;
15594 			mouse.y = event.y;
15595 
15596 			static Time lastMouseDownTime = 0;
15597 			static int lastMouseDownButton = -1;
15598 
15599 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15600 			if(e.type == EventType.ButtonPress) {
15601 				lastMouseDownTime = event.time;
15602 				lastMouseDownButton = event.button;
15603 			}
15604 
15605 			switch(event.button) {
15606 				case 1: mouse.button = MouseButton.left; break; // left
15607 				case 2: mouse.button = MouseButton.middle; break; // middle
15608 				case 3: mouse.button = MouseButton.right; break; // right
15609 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15610 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15611 				case 6: break; // idk
15612 				case 7: break; // idk
15613 				case 8: mouse.button = MouseButton.backButton; break;
15614 				case 9: mouse.button = MouseButton.forwardButton; break;
15615 				default:
15616 			}
15617 
15618 			// FIXME: double check this
15619 			mouse.modifierState = event.state;
15620 
15621 			//mouse.modifierState = event.detail;
15622 
15623 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15624 				mouse.window = *win;
15625 				(*win).mdx(mouse);
15626 				if((*win).handleMouseEvent) {
15627 					XUnlockDisplay(display);
15628 					scope(exit) XLockDisplay(display);
15629 					(*win).handleMouseEvent(mouse);
15630 				}
15631 			}
15632 			version(with_eventloop)
15633 				send(mouse);
15634 		  break;
15635 
15636 		  case EventType.KeyPress:
15637 		  case EventType.KeyRelease:
15638 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15639 			KeyEvent ke;
15640 			ke.pressed = e.type == EventType.KeyPress;
15641 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15642 
15643 			auto sym = XKeycodeToKeysym(
15644 				XDisplayConnection.get(),
15645 				e.xkey.keycode,
15646 				0);
15647 
15648 			ke.key = cast(Key) sym;//e.xkey.keycode;
15649 
15650 			ke.modifierState = e.xkey.state;
15651 
15652 			// import std.stdio; writefln("%x", sym);
15653 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15654 			int charbuflen = 0; // return value of XwcLookupString
15655 			if (ke.pressed) {
15656 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15657 				if (win !is null && win.xic !is null) {
15658 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15659 					Status status;
15660 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15661 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15662 				} else {
15663 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15664 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15665 					char[16] buffer;
15666 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15667 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15668 				}
15669 			}
15670 
15671 			// if there's no char, subst one
15672 			if (charbuflen == 0) {
15673 				switch (sym) {
15674 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15675 					case 0xff8d: // keypad enter
15676 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15677 					default : // ignore
15678 				}
15679 			}
15680 
15681 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15682 				ke.window = *win;
15683 
15684 
15685 				if(win.inputProxy)
15686 					win = &win.inputProxy;
15687 
15688 				// char events are separate since they are on Windows too
15689 				// also, xcompose can generate long char sequences
15690 				// don't send char events if Meta and/or Hyper is pressed
15691 				// TODO: ctrl+char should only send control chars; not yet
15692 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15693 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15694 				}
15695 
15696 				dchar[32] charsComingBuffer;
15697 				int charsComingPosition;
15698 				dchar[] charsComing = charsComingBuffer[];
15699 
15700 				if (ke.pressed && charbuflen > 0) {
15701 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15702 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15703 						if(charsComingPosition >= charsComing.length)
15704 							charsComing.length = charsComingPosition + 8;
15705 
15706 						charsComing[charsComingPosition++] = ch;
15707 					}
15708 
15709 					charsComing = charsComing[0 .. charsComingPosition];
15710 				} else {
15711 					charsComing = null;
15712 				}
15713 
15714 				ke.charsPossible = charsComing;
15715 
15716 				if (win.handleKeyEvent) {
15717 					XUnlockDisplay(display);
15718 					scope(exit) XLockDisplay(display);
15719 					win.handleKeyEvent(ke);
15720 				}
15721 
15722 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15723 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15724 					XUnlockDisplay(display);
15725 					scope(exit) XLockDisplay(display);
15726 					foreach(ch; charsComing)
15727 						win.handleCharEvent(ch);
15728 				}
15729 			}
15730 
15731 			version(with_eventloop)
15732 				send(ke);
15733 		  break;
15734 		  default:
15735 		}
15736 
15737 		return done;
15738 	}
15739 }
15740 
15741 /* *************************************** */
15742 /*      Done with simpledisplay stuff      */
15743 /* *************************************** */
15744 
15745 // Necessary C library bindings follow
15746 version(Windows) {} else
15747 version(X11) {
15748 
15749 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15750 
15751 // X11 bindings needed here
15752 /*
15753 	A little of this is from the bindings project on
15754 	D Source and some of it is copy/paste from the C
15755 	header.
15756 
15757 	The DSource listing consistently used D's long
15758 	where C used long. That's wrong - C long is 32 bit, so
15759 	it should be int in D. I changed that here.
15760 
15761 	Note:
15762 	This isn't complete, just took what I needed for myself.
15763 */
15764 
15765 import core.stdc.stddef : wchar_t;
15766 
15767 interface XLib {
15768 extern(C) nothrow @nogc {
15769 	char* XResourceManagerString(Display*);
15770 	void XrmInitialize();
15771 	XrmDatabase XrmGetStringDatabase(char* data);
15772 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
15773 
15774 	Cursor XCreateFontCursor(Display*, uint shape);
15775 	int XDefineCursor(Display* display, Window w, Cursor cursor);
15776 	int XUndefineCursor(Display* display, Window w);
15777 
15778 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
15779 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
15780 	int XFreeCursor(Display* display, Cursor cursor);
15781 
15782 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
15783 
15784 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
15785 
15786 	XVaNestedList XVaCreateNestedList(int unused, ...);
15787 
15788 	char *XKeysymToString(KeySym keysym);
15789 	KeySym XKeycodeToKeysym(
15790 		Display*		/* display */,
15791 		KeyCode		/* keycode */,
15792 		int			/* index */
15793 	);
15794 
15795 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
15796 
15797 	int XFree(void*);
15798 	int XDeleteProperty(Display *display, Window w, Atom property);
15799 
15800 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements);
15801 
15802 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
15803 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
15804 		*actual_type_return, int *actual_format_return, arch_ulong
15805 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
15806 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
15807 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
15808 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
15809 
15810 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
15811 
15812 	Window XGetSelectionOwner(Display *display, Atom selection);
15813 
15814 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
15815 
15816 	char** XListFonts(Display*, const char*, int, int*);
15817 	void XFreeFontNames(char**);
15818 
15819 	Display* XOpenDisplay(const char*);
15820 	int XCloseDisplay(Display*);
15821 
15822 	int function() XSynchronize(Display*, bool);
15823 	int function() XSetAfterFunction(Display*, int function() proc);
15824 
15825 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
15826 
15827 	Bool XSupportsLocale();
15828 	char* XSetLocaleModifiers(const(char)* modifier_list);
15829 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15830 	Status XCloseOM(XOM om);
15831 
15832 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
15833 	Status XCloseIM(XIM im);
15834 
15835 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15836 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
15837 	Display* XDisplayOfIM(XIM im);
15838 	char* XLocaleOfIM(XIM im);
15839 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
15840 	void XDestroyIC(XIC ic);
15841 	void XSetICFocus(XIC ic);
15842 	void XUnsetICFocus(XIC ic);
15843 	//wchar_t* XwcResetIC(XIC ic);
15844 	char* XmbResetIC(XIC ic);
15845 	char* Xutf8ResetIC(XIC ic);
15846 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15847 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
15848 	XIM XIMOfIC(XIC ic);
15849 
15850 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
15851 
15852 
15853 	XFontStruct *XLoadQueryFont(Display *display, in char *name);
15854 	int XFreeFont(Display *display, XFontStruct *font_struct);
15855 	int XSetFont(Display* display, GC gc, Font font);
15856 	int XTextWidth(XFontStruct*, in char*, int);
15857 
15858 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
15859 	int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n);
15860 
15861 	Window XCreateSimpleWindow(
15862 		Display*	/* display */,
15863 		Window		/* parent */,
15864 		int			/* x */,
15865 		int			/* y */,
15866 		uint		/* width */,
15867 		uint		/* height */,
15868 		uint		/* border_width */,
15869 		uint		/* border */,
15870 		uint		/* background */
15871 	);
15872 	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);
15873 
15874 	int XReparentWindow(Display*, Window, Window, int, int);
15875 	int XClearWindow(Display*, Window);
15876 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
15877 	int XMoveWindow(Display*, Window, int, int);
15878 	int XResizeWindow(Display *display, Window w, uint width, uint height);
15879 
15880 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
15881 
15882 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
15883 
15884 	XImage *XCreateImage(
15885 		Display*		/* display */,
15886 		Visual*		/* visual */,
15887 		uint	/* depth */,
15888 		int			/* format */,
15889 		int			/* offset */,
15890 		ubyte*		/* data */,
15891 		uint	/* width */,
15892 		uint	/* height */,
15893 		int			/* bitmap_pad */,
15894 		int			/* bytes_per_line */
15895 	);
15896 
15897 	Status XInitImage (XImage* image);
15898 
15899 	Atom XInternAtom(
15900 		Display*		/* display */,
15901 		const char*	/* atom_name */,
15902 		Bool		/* only_if_exists */
15903 	);
15904 
15905 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
15906 	char* XGetAtomName(Display*, Atom);
15907 	Status XGetAtomNames(Display*, Atom*, int count, char**);
15908 
15909 	int XPutImage(
15910 		Display*	/* display */,
15911 		Drawable	/* d */,
15912 		GC			/* gc */,
15913 		XImage*	/* image */,
15914 		int			/* src_x */,
15915 		int			/* src_y */,
15916 		int			/* dest_x */,
15917 		int			/* dest_y */,
15918 		uint		/* width */,
15919 		uint		/* height */
15920 	);
15921 
15922 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
15923 
15924 
15925 	int XDestroyWindow(
15926 		Display*	/* display */,
15927 		Window		/* w */
15928 	);
15929 
15930 	int XDestroyImage(XImage*);
15931 
15932 	int XSelectInput(
15933 		Display*	/* display */,
15934 		Window		/* w */,
15935 		EventMask	/* event_mask */
15936 	);
15937 
15938 	int XMapWindow(
15939 		Display*	/* display */,
15940 		Window		/* w */
15941 	);
15942 
15943 	Status XIconifyWindow(Display*, Window, int);
15944 	int XMapRaised(Display*, Window);
15945 	int XMapSubwindows(Display*, Window);
15946 
15947 	int XNextEvent(
15948 		Display*	/* display */,
15949 		XEvent*		/* event_return */
15950 	);
15951 
15952 	int XMaskEvent(Display*, arch_long, XEvent*);
15953 
15954 	Bool XFilterEvent(XEvent *event, Window window);
15955 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
15956 
15957 	Status XSetWMProtocols(
15958 		Display*	/* display */,
15959 		Window		/* w */,
15960 		Atom*		/* protocols */,
15961 		int			/* count */
15962 	);
15963 
15964 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
15965 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
15966 
15967 
15968 	Status XInitThreads();
15969 	void XLockDisplay (Display* display);
15970 	void XUnlockDisplay (Display* display);
15971 
15972 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
15973 
15974 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
15975 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
15976 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
15977 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
15978 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
15979 
15980 
15981 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
15982 	int XDrawString(Display*, Drawable, GC, int, int, in char*, int);
15983 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
15984 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
15985 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
15986 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
15987 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
15988 	int XDrawPoint(Display*, Drawable, GC, int, int);
15989 	int XSetForeground(Display*, GC, uint);
15990 	int XSetBackground(Display*, GC, uint);
15991 
15992 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
15993 	void XFreeFontSet(Display*, XFontSet);
15994 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int);
15995 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
15996 
15997 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
15998 
15999 
16000 //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);
16001 
16002 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16003 	int XSetFunction(Display*, GC, int);
16004 
16005 	GC XCreateGC(Display*, Drawable, uint, void*);
16006 	int XCopyGC(Display*, GC, uint, GC);
16007 	int XFreeGC(Display*, GC);
16008 
16009 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16010 	bool XCheckMaskEvent(Display*, int, XEvent*);
16011 
16012 	int XPending(Display*);
16013 	int XEventsQueued(Display* display, int mode);
16014 
16015 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16016 	int XFreePixmap(Display*, Pixmap);
16017 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16018 	int XFlush(Display*);
16019 	int XBell(Display*, int);
16020 	int XSync(Display*, bool);
16021 
16022 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16023 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16024 
16025 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16026 	int XUngrabKeyboard(Display*, Time);
16027 
16028 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16029 
16030 	KeySym XStringToKeysym(const char *string);
16031 
16032 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16033 
16034 	Window XDefaultRootWindow(Display*);
16035 
16036 	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);
16037 
16038 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16039 
16040 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16041 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16042 
16043 	Status XAllocColor(Display*, Colormap, XColor*);
16044 
16045 	int XWithdrawWindow(Display*, Window, int);
16046 	int XUnmapWindow(Display*, Window);
16047 	int XLowerWindow(Display*, Window);
16048 	int XRaiseWindow(Display*, Window);
16049 
16050 	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);
16051 	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);
16052 
16053 	int XGetInputFocus(Display*, Window*, int*);
16054 	int XSetInputFocus(Display*, Window, int, Time);
16055 
16056 	XErrorHandler XSetErrorHandler(XErrorHandler);
16057 
16058 	int XGetErrorText(Display*, int, char*, int);
16059 
16060 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16061 
16062 
16063 	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);
16064 	int XUngrabPointer(Display *display, Time time);
16065 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16066 
16067 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16068 
16069 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16070 	int XSetClipMask(Display*, GC, Pixmap);
16071 	int XSetClipOrigin(Display*, GC, int, int);
16072 
16073 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16074 
16075 	void XSetWMName(Display*, Window, XTextProperty*);
16076 	Status XGetWMName(Display*, Window, XTextProperty*);
16077 	int XStoreName(Display* display, Window w, const(char)* window_name);
16078 
16079 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16080 
16081 }
16082 }
16083 
16084 interface Xext {
16085 extern(C) nothrow @nogc {
16086 	Status XShmAttach(Display*, XShmSegmentInfo*);
16087 	Status XShmDetach(Display*, XShmSegmentInfo*);
16088 	Status XShmPutImage(
16089 		Display*            /* dpy */,
16090 		Drawable            /* d */,
16091 		GC                  /* gc */,
16092 		XImage*             /* image */,
16093 		int                 /* src_x */,
16094 		int                 /* src_y */,
16095 		int                 /* dst_x */,
16096 		int                 /* dst_y */,
16097 		uint        /* src_width */,
16098 		uint        /* src_height */,
16099 		Bool                /* send_event */
16100 	);
16101 
16102 	Status XShmQueryExtension(Display*);
16103 
16104 	XImage *XShmCreateImage(
16105 		Display*            /* dpy */,
16106 		Visual*             /* visual */,
16107 		uint        /* depth */,
16108 		int                 /* format */,
16109 		char*               /* data */,
16110 		XShmSegmentInfo*    /* shminfo */,
16111 		uint        /* width */,
16112 		uint        /* height */
16113 	);
16114 
16115 	Pixmap XShmCreatePixmap(
16116 		Display*            /* dpy */,
16117 		Drawable            /* d */,
16118 		char*               /* data */,
16119 		XShmSegmentInfo*    /* shminfo */,
16120 		uint        /* width */,
16121 		uint        /* height */,
16122 		uint        /* depth */
16123 	);
16124 
16125 }
16126 }
16127 
16128 	// this requires -lXpm
16129 	//int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16130 
16131 
16132 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16133 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16134 shared static this() {
16135 	xlib.loadDynamicLibrary();
16136 	xext.loadDynamicLibrary();
16137 }
16138 
16139 
16140 extern(C) nothrow @nogc {
16141 
16142 alias XrmDatabase = void*;
16143 struct XrmValue {
16144 	uint size;
16145 	void* addr;
16146 }
16147 
16148 struct XVisualInfo {
16149 	Visual* visual;
16150 	VisualID visualid;
16151 	int screen;
16152 	uint depth;
16153 	int c_class;
16154 	c_ulong red_mask;
16155 	c_ulong green_mask;
16156 	c_ulong blue_mask;
16157 	int colormap_size;
16158 	int bits_per_rgb;
16159 }
16160 
16161 enum VisualNoMask=	0x0;
16162 enum VisualIDMask=	0x1;
16163 enum VisualScreenMask=0x2;
16164 enum VisualDepthMask=	0x4;
16165 enum VisualClassMask=	0x8;
16166 enum VisualRedMaskMask=0x10;
16167 enum VisualGreenMaskMask=0x20;
16168 enum VisualBlueMaskMask=0x40;
16169 enum VisualColormapSizeMask=0x80;
16170 enum VisualBitsPerRGBMask=0x100;
16171 enum VisualAllMask=	0x1FF;
16172 
16173 enum AnyKey = 0;
16174 enum AnyModifier = 1 << 15;
16175 
16176 // XIM and other crap
16177 struct _XOM {}
16178 struct _XIM {}
16179 struct _XIC {}
16180 alias XOM = _XOM*;
16181 alias XIM = _XIM*;
16182 alias XIC = _XIC*;
16183 
16184 alias XVaNestedList = void*;
16185 
16186 alias XIMStyle = arch_ulong;
16187 enum : arch_ulong {
16188 	XIMPreeditArea      = 0x0001,
16189 	XIMPreeditCallbacks = 0x0002,
16190 	XIMPreeditPosition  = 0x0004,
16191 	XIMPreeditNothing   = 0x0008,
16192 	XIMPreeditNone      = 0x0010,
16193 	XIMStatusArea       = 0x0100,
16194 	XIMStatusCallbacks  = 0x0200,
16195 	XIMStatusNothing    = 0x0400,
16196 	XIMStatusNone       = 0x0800,
16197 }
16198 
16199 
16200 /* X Shared Memory Extension functions */
16201 	//pragma(lib, "Xshm");
16202 	alias arch_ulong ShmSeg;
16203 	struct XShmSegmentInfo {
16204 		ShmSeg shmseg;
16205 		int shmid;
16206 		ubyte* shmaddr;
16207 		Bool readOnly;
16208 	}
16209 
16210 	// and the necessary OS functions
16211 	int shmget(int, size_t, int);
16212 	void* shmat(int, in void*, int);
16213 	int shmdt(in void*);
16214 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16215 
16216 	enum IPC_PRIVATE = 0;
16217 	enum IPC_CREAT = 512;
16218 	enum IPC_RMID = 0;
16219 
16220 /* MIT-SHM end */
16221 
16222 
16223 enum MappingType:int {
16224 	MappingModifier		=0,
16225 	MappingKeyboard		=1,
16226 	MappingPointer		=2
16227 }
16228 
16229 /* ImageFormat -- PutImage, GetImage */
16230 enum ImageFormat:int {
16231 	XYBitmap	=0,	/* depth 1, XYFormat */
16232 	XYPixmap	=1,	/* depth == drawable depth */
16233 	ZPixmap	=2	/* depth == drawable depth */
16234 }
16235 
16236 enum ModifierName:int {
16237 	ShiftMapIndex	=0,
16238 	LockMapIndex	=1,
16239 	ControlMapIndex	=2,
16240 	Mod1MapIndex	=3,
16241 	Mod2MapIndex	=4,
16242 	Mod3MapIndex	=5,
16243 	Mod4MapIndex	=6,
16244 	Mod5MapIndex	=7
16245 }
16246 
16247 enum ButtonMask:int {
16248 	Button1Mask	=1<<8,
16249 	Button2Mask	=1<<9,
16250 	Button3Mask	=1<<10,
16251 	Button4Mask	=1<<11,
16252 	Button5Mask	=1<<12,
16253 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16254 }
16255 
16256 enum KeyOrButtonMask:uint {
16257 	ShiftMask	=1<<0,
16258 	LockMask	=1<<1,
16259 	ControlMask	=1<<2,
16260 	Mod1Mask	=1<<3,
16261 	Mod2Mask	=1<<4,
16262 	Mod3Mask	=1<<5,
16263 	Mod4Mask	=1<<6,
16264 	Mod5Mask	=1<<7,
16265 	Button1Mask	=1<<8,
16266 	Button2Mask	=1<<9,
16267 	Button3Mask	=1<<10,
16268 	Button4Mask	=1<<11,
16269 	Button5Mask	=1<<12,
16270 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16271 }
16272 
16273 enum ButtonName:int {
16274 	Button1	=1,
16275 	Button2	=2,
16276 	Button3	=3,
16277 	Button4	=4,
16278 	Button5	=5
16279 }
16280 
16281 /* Notify modes */
16282 enum NotifyModes:int
16283 {
16284 	NotifyNormal		=0,
16285 	NotifyGrab			=1,
16286 	NotifyUngrab		=2,
16287 	NotifyWhileGrabbed	=3
16288 }
16289 enum NotifyHint = 1;	/* for MotionNotify events */
16290 
16291 /* Notify detail */
16292 enum NotifyDetail:int
16293 {
16294 	NotifyAncestor			=0,
16295 	NotifyVirtual			=1,
16296 	NotifyInferior			=2,
16297 	NotifyNonlinear			=3,
16298 	NotifyNonlinearVirtual	=4,
16299 	NotifyPointer			=5,
16300 	NotifyPointerRoot		=6,
16301 	NotifyDetailNone		=7
16302 }
16303 
16304 /* Visibility notify */
16305 
16306 enum VisibilityNotify:int
16307 {
16308 VisibilityUnobscured		=0,
16309 VisibilityPartiallyObscured	=1,
16310 VisibilityFullyObscured		=2
16311 }
16312 
16313 
16314 enum WindowStackingMethod:int
16315 {
16316 	Above		=0,
16317 	Below		=1,
16318 	TopIf		=2,
16319 	BottomIf	=3,
16320 	Opposite	=4
16321 }
16322 
16323 /* Circulation request */
16324 enum CirculationRequest:int
16325 {
16326 	PlaceOnTop		=0,
16327 	PlaceOnBottom	=1
16328 }
16329 
16330 enum PropertyNotification:int
16331 {
16332 	PropertyNewValue	=0,
16333 	PropertyDelete		=1
16334 }
16335 
16336 enum ColorMapNotification:int
16337 {
16338 	ColormapUninstalled	=0,
16339 	ColormapInstalled		=1
16340 }
16341 
16342 
16343 	struct _XPrivate {}
16344 	struct _XrmHashBucketRec {}
16345 
16346 	alias void* XPointer;
16347 	alias void* XExtData;
16348 
16349 	version( X86_64 ) {
16350 		alias ulong XID;
16351 		alias ulong arch_ulong;
16352 		alias long arch_long;
16353 	} else version (AArch64) {
16354 		alias ulong XID;
16355 		alias ulong arch_ulong;
16356 		alias long arch_long;
16357 	} else {
16358 		alias uint XID;
16359 		alias uint arch_ulong;
16360 		alias int arch_long;
16361 	}
16362 
16363 	alias XID Window;
16364 	alias XID Drawable;
16365 	alias XID Pixmap;
16366 
16367 	alias arch_ulong Atom;
16368 	alias int Bool;
16369 	alias Display XDisplay;
16370 
16371 	alias int ByteOrder;
16372 	alias arch_ulong Time;
16373 	alias void ScreenFormat;
16374 
16375 	struct XImage {
16376 		int width, height;			/* size of image */
16377 		int xoffset;				/* number of pixels offset in X direction */
16378 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16379 		void *data;					/* pointer to image data */
16380 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16381 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16382 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16383 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16384 		int depth;					/* depth of image */
16385 		int bytes_per_line;			/* accelarator to next line */
16386 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16387 		arch_ulong red_mask;	/* bits in z arrangment */
16388 		arch_ulong green_mask;
16389 		arch_ulong blue_mask;
16390 		XPointer obdata;			/* hook for the object routines to hang on */
16391 		static struct F {				/* image manipulation routines */
16392 			XImage* function(
16393 				XDisplay* 			/* display */,
16394 				Visual*				/* visual */,
16395 				uint				/* depth */,
16396 				int					/* format */,
16397 				int					/* offset */,
16398 				ubyte*				/* data */,
16399 				uint				/* width */,
16400 				uint				/* height */,
16401 				int					/* bitmap_pad */,
16402 				int					/* bytes_per_line */) create_image;
16403 			int function(XImage *) destroy_image;
16404 			arch_ulong function(XImage *, int, int) get_pixel;
16405 			int function(XImage *, int, int, arch_ulong) put_pixel;
16406 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16407 			int function(XImage *, arch_long) add_pixel;
16408 		}
16409 		F f;
16410 	}
16411 	version(X86_64) static assert(XImage.sizeof == 136);
16412 	else version(X86) static assert(XImage.sizeof == 88);
16413 
16414 struct XCharStruct {
16415 	short       lbearing;       /* origin to left edge of raster */
16416 	short       rbearing;       /* origin to right edge of raster */
16417 	short       width;          /* advance to next char's origin */
16418 	short       ascent;         /* baseline to top edge of raster */
16419 	short       descent;        /* baseline to bottom edge of raster */
16420 	ushort attributes;  /* per char flags (not predefined) */
16421 }
16422 
16423 /*
16424  * To allow arbitrary information with fonts, there are additional properties
16425  * returned.
16426  */
16427 struct XFontProp {
16428 	Atom name;
16429 	arch_ulong card32;
16430 }
16431 
16432 alias Atom Font;
16433 
16434 struct XFontStruct {
16435 	XExtData *ext_data;           /* Hook for extension to hang data */
16436 	Font fid;                     /* Font ID for this font */
16437 	uint direction;           /* Direction the font is painted */
16438 	uint min_char_or_byte2;   /* First character */
16439 	uint max_char_or_byte2;   /* Last character */
16440 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16441 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16442 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16443 	uint default_char;        /* Char to print for undefined character */
16444 	int n_properties;             /* How many properties there are */
16445 	XFontProp *properties;        /* Pointer to array of additional properties*/
16446 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16447 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16448 	XCharStruct *per_char;        /* first_char to last_char information */
16449 	int ascent;                   /* Max extent above baseline for spacing */
16450 	int descent;                  /* Max descent below baseline for spacing */
16451 }
16452 
16453 
16454 /*
16455  * Definitions of specific events.
16456  */
16457 struct XKeyEvent
16458 {
16459 	int type;			/* of event */
16460 	arch_ulong serial;		/* # of last request processed by server */
16461 	Bool send_event;	/* true if this came from a SendEvent request */
16462 	Display *display;	/* Display the event was read from */
16463 	Window window;	        /* "event" window it is reported relative to */
16464 	Window root;	        /* root window that the event occurred on */
16465 	Window subwindow;	/* child window */
16466 	Time time;		/* milliseconds */
16467 	int x, y;		/* pointer x, y coordinates in event window */
16468 	int x_root, y_root;	/* coordinates relative to root */
16469 	KeyOrButtonMask state;	/* key or button mask */
16470 	uint keycode;	/* detail */
16471 	Bool same_screen;	/* same screen flag */
16472 }
16473 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16474 alias XKeyEvent XKeyPressedEvent;
16475 alias XKeyEvent XKeyReleasedEvent;
16476 
16477 struct XButtonEvent
16478 {
16479 	int type;		/* of event */
16480 	arch_ulong serial;	/* # of last request processed by server */
16481 	Bool send_event;	/* true if this came from a SendEvent request */
16482 	Display *display;	/* Display the event was read from */
16483 	Window window;	        /* "event" window it is reported relative to */
16484 	Window root;	        /* root window that the event occurred on */
16485 	Window subwindow;	/* child window */
16486 	Time time;		/* milliseconds */
16487 	int x, y;		/* pointer x, y coordinates in event window */
16488 	int x_root, y_root;	/* coordinates relative to root */
16489 	KeyOrButtonMask state;	/* key or button mask */
16490 	uint button;	/* detail */
16491 	Bool same_screen;	/* same screen flag */
16492 }
16493 alias XButtonEvent XButtonPressedEvent;
16494 alias XButtonEvent XButtonReleasedEvent;
16495 
16496 struct XMotionEvent{
16497 	int type;		/* of event */
16498 	arch_ulong serial;	/* # of last request processed by server */
16499 	Bool send_event;	/* true if this came from a SendEvent request */
16500 	Display *display;	/* Display the event was read from */
16501 	Window window;	        /* "event" window reported relative to */
16502 	Window root;	        /* root window that the event occurred on */
16503 	Window subwindow;	/* child window */
16504 	Time time;		/* milliseconds */
16505 	int x, y;		/* pointer x, y coordinates in event window */
16506 	int x_root, y_root;	/* coordinates relative to root */
16507 	KeyOrButtonMask state;	/* key or button mask */
16508 	byte is_hint;		/* detail */
16509 	Bool same_screen;	/* same screen flag */
16510 }
16511 alias XMotionEvent XPointerMovedEvent;
16512 
16513 struct XCrossingEvent{
16514 	int type;		/* of event */
16515 	arch_ulong serial;	/* # of last request processed by server */
16516 	Bool send_event;	/* true if this came from a SendEvent request */
16517 	Display *display;	/* Display the event was read from */
16518 	Window window;	        /* "event" window reported relative to */
16519 	Window root;	        /* root window that the event occurred on */
16520 	Window subwindow;	/* child window */
16521 	Time time;		/* milliseconds */
16522 	int x, y;		/* pointer x, y coordinates in event window */
16523 	int x_root, y_root;	/* coordinates relative to root */
16524 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16525 	NotifyDetail detail;
16526 	/*
16527 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16528 	 * NotifyNonlinear,NotifyNonlinearVirtual
16529 	 */
16530 	Bool same_screen;	/* same screen flag */
16531 	Bool focus;		/* Boolean focus */
16532 	KeyOrButtonMask state;	/* key or button mask */
16533 }
16534 alias XCrossingEvent XEnterWindowEvent;
16535 alias XCrossingEvent XLeaveWindowEvent;
16536 
16537 struct XFocusChangeEvent{
16538 	int type;		/* FocusIn or FocusOut */
16539 	arch_ulong serial;	/* # of last request processed by server */
16540 	Bool send_event;	/* true if this came from a SendEvent request */
16541 	Display *display;	/* Display the event was read from */
16542 	Window window;		/* window of event */
16543 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16544 				   NotifyGrab, NotifyUngrab */
16545 	NotifyDetail detail;
16546 	/*
16547 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16548 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16549 	 * NotifyPointerRoot, NotifyDetailNone
16550 	 */
16551 }
16552 alias XFocusChangeEvent XFocusInEvent;
16553 alias XFocusChangeEvent XFocusOutEvent;
16554 
16555 enum CWBackPixmap              = (1L<<0);
16556 enum CWBackPixel               = (1L<<1);
16557 enum CWBorderPixmap            = (1L<<2);
16558 enum CWBorderPixel             = (1L<<3);
16559 enum CWBitGravity              = (1L<<4);
16560 enum CWWinGravity              = (1L<<5);
16561 enum CWBackingStore            = (1L<<6);
16562 enum CWBackingPlanes           = (1L<<7);
16563 enum CWBackingPixel            = (1L<<8);
16564 enum CWOverrideRedirect        = (1L<<9);
16565 enum CWSaveUnder               = (1L<<10);
16566 enum CWEventMask               = (1L<<11);
16567 enum CWDontPropagate           = (1L<<12);
16568 enum CWColormap                = (1L<<13);
16569 enum CWCursor                  = (1L<<14);
16570 
16571 struct XWindowAttributes {
16572 	int x, y;			/* location of window */
16573 	int width, height;		/* width and height of window */
16574 	int border_width;		/* border width of window */
16575 	int depth;			/* depth of window */
16576 	Visual *visual;			/* the associated visual structure */
16577 	Window root;			/* root of screen containing window */
16578 	int class_;			/* InputOutput, InputOnly*/
16579 	int bit_gravity;		/* one of the bit gravity values */
16580 	int win_gravity;		/* one of the window gravity values */
16581 	int backing_store;		/* NotUseful, WhenMapped, Always */
16582 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16583 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16584 	Bool save_under;		/* boolean, should bits under be saved? */
16585 	Colormap colormap;		/* color map to be associated with window */
16586 	Bool map_installed;		/* boolean, is color map currently installed*/
16587 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16588 	arch_long all_event_masks;		/* set of events all people have interest in*/
16589 	arch_long your_event_mask;		/* my event mask */
16590 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16591 	Bool override_redirect;		/* boolean value for override-redirect */
16592 	Screen *screen;			/* back pointer to correct screen */
16593 }
16594 
16595 enum IsUnmapped = 0;
16596 enum IsUnviewable = 1;
16597 enum IsViewable = 2;
16598 
16599 struct XSetWindowAttributes {
16600 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16601 	arch_ulong background_pixel;/* background pixel */
16602 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16603 	arch_ulong border_pixel;/* border pixel value */
16604 	int bit_gravity;         /* one of bit gravity values */
16605 	int win_gravity;         /* one of the window gravity values */
16606 	int backing_store;       /* NotUseful, WhenMapped, Always */
16607 	arch_ulong backing_planes;/* planes to be preserved if possible */
16608 	arch_ulong backing_pixel;/* value to use in restoring planes */
16609 	Bool save_under;         /* should bits under be saved? (popups) */
16610 	arch_long event_mask;         /* set of events that should be saved */
16611 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16612 	Bool override_redirect;  /* boolean value for override_redirect */
16613 	Colormap colormap;       /* color map to be associated with window */
16614 	Cursor cursor;           /* cursor to be displayed (or None) */
16615 }
16616 
16617 
16618 alias int Status;
16619 
16620 
16621 enum EventMask:int
16622 {
16623 	NoEventMask				=0,
16624 	KeyPressMask			=1<<0,
16625 	KeyReleaseMask			=1<<1,
16626 	ButtonPressMask			=1<<2,
16627 	ButtonReleaseMask		=1<<3,
16628 	EnterWindowMask			=1<<4,
16629 	LeaveWindowMask			=1<<5,
16630 	PointerMotionMask		=1<<6,
16631 	PointerMotionHintMask	=1<<7,
16632 	Button1MotionMask		=1<<8,
16633 	Button2MotionMask		=1<<9,
16634 	Button3MotionMask		=1<<10,
16635 	Button4MotionMask		=1<<11,
16636 	Button5MotionMask		=1<<12,
16637 	ButtonMotionMask		=1<<13,
16638 	KeymapStateMask		=1<<14,
16639 	ExposureMask			=1<<15,
16640 	VisibilityChangeMask	=1<<16,
16641 	StructureNotifyMask		=1<<17,
16642 	ResizeRedirectMask		=1<<18,
16643 	SubstructureNotifyMask	=1<<19,
16644 	SubstructureRedirectMask=1<<20,
16645 	FocusChangeMask			=1<<21,
16646 	PropertyChangeMask		=1<<22,
16647 	ColormapChangeMask		=1<<23,
16648 	OwnerGrabButtonMask		=1<<24
16649 }
16650 
16651 struct MwmHints {
16652 	c_ulong flags;
16653 	c_ulong functions;
16654 	c_ulong decorations;
16655 	c_long input_mode;
16656 	c_ulong status;
16657 }
16658 
16659 enum {
16660 	MWM_HINTS_FUNCTIONS = (1L << 0),
16661 	MWM_HINTS_DECORATIONS =  (1L << 1),
16662 
16663 	MWM_FUNC_ALL = (1L << 0),
16664 	MWM_FUNC_RESIZE = (1L << 1),
16665 	MWM_FUNC_MOVE = (1L << 2),
16666 	MWM_FUNC_MINIMIZE = (1L << 3),
16667 	MWM_FUNC_MAXIMIZE = (1L << 4),
16668 	MWM_FUNC_CLOSE = (1L << 5),
16669 
16670 	MWM_DECOR_ALL = (1L << 0),
16671 	MWM_DECOR_BORDER = (1L << 1),
16672 	MWM_DECOR_RESIZEH = (1L << 2),
16673 	MWM_DECOR_TITLE = (1L << 3),
16674 	MWM_DECOR_MENU = (1L << 4),
16675 	MWM_DECOR_MINIMIZE = (1L << 5),
16676 	MWM_DECOR_MAXIMIZE = (1L << 6),
16677 }
16678 
16679 import core.stdc.config : c_long, c_ulong;
16680 
16681 	/* Size hints mask bits */
16682 
16683 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16684 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16685 	enum   PPosition   = (1L << 2)          /* program specified position */;
16686 	enum   PSize       = (1L << 3)          /* program specified size */;
16687 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16688 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16689 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16690 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16691 	enum   PBaseSize   = (1L << 8);
16692 	enum   PWinGravity = (1L << 9);
16693 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16694 	struct XSizeHints {
16695 		arch_long flags;         /* marks which fields in this structure are defined */
16696 		int x, y;           /* Obsolete */
16697 		int width, height;  /* Obsolete */
16698 		int min_width, min_height;
16699 		int max_width, max_height;
16700 		int width_inc, height_inc;
16701 		struct Aspect {
16702 			int x;       /* numerator */
16703 			int y;       /* denominator */
16704 		}
16705 
16706 		Aspect min_aspect;
16707 		Aspect max_aspect;
16708 		int base_width, base_height;
16709 		int win_gravity;
16710 		/* this structure may be extended in the future */
16711 	}
16712 
16713 
16714 
16715 enum EventType:int
16716 {
16717 	KeyPress			=2,
16718 	KeyRelease			=3,
16719 	ButtonPress			=4,
16720 	ButtonRelease		=5,
16721 	MotionNotify		=6,
16722 	EnterNotify			=7,
16723 	LeaveNotify			=8,
16724 	FocusIn				=9,
16725 	FocusOut			=10,
16726 	KeymapNotify		=11,
16727 	Expose				=12,
16728 	GraphicsExpose		=13,
16729 	NoExpose			=14,
16730 	VisibilityNotify	=15,
16731 	CreateNotify		=16,
16732 	DestroyNotify		=17,
16733 	UnmapNotify		=18,
16734 	MapNotify			=19,
16735 	MapRequest			=20,
16736 	ReparentNotify		=21,
16737 	ConfigureNotify		=22,
16738 	ConfigureRequest	=23,
16739 	GravityNotify		=24,
16740 	ResizeRequest		=25,
16741 	CirculateNotify		=26,
16742 	CirculateRequest	=27,
16743 	PropertyNotify		=28,
16744 	SelectionClear		=29,
16745 	SelectionRequest	=30,
16746 	SelectionNotify		=31,
16747 	ColormapNotify		=32,
16748 	ClientMessage		=33,
16749 	MappingNotify		=34,
16750 	LASTEvent			=35	/* must be bigger than any event # */
16751 }
16752 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16753 struct XKeymapEvent
16754 {
16755 	int type;
16756 	arch_ulong serial;	/* # of last request processed by server */
16757 	Bool send_event;	/* true if this came from a SendEvent request */
16758 	Display *display;	/* Display the event was read from */
16759 	Window window;
16760 	byte[32] key_vector;
16761 }
16762 
16763 struct XExposeEvent
16764 {
16765 	int type;
16766 	arch_ulong serial;	/* # of last request processed by server */
16767 	Bool send_event;	/* true if this came from a SendEvent request */
16768 	Display *display;	/* Display the event was read from */
16769 	Window window;
16770 	int x, y;
16771 	int width, height;
16772 	int count;		/* if non-zero, at least this many more */
16773 }
16774 
16775 struct XGraphicsExposeEvent{
16776 	int type;
16777 	arch_ulong serial;	/* # of last request processed by server */
16778 	Bool send_event;	/* true if this came from a SendEvent request */
16779 	Display *display;	/* Display the event was read from */
16780 	Drawable drawable;
16781 	int x, y;
16782 	int width, height;
16783 	int count;		/* if non-zero, at least this many more */
16784 	int major_code;		/* core is CopyArea or CopyPlane */
16785 	int minor_code;		/* not defined in the core */
16786 }
16787 
16788 struct XNoExposeEvent{
16789 	int type;
16790 	arch_ulong serial;	/* # of last request processed by server */
16791 	Bool send_event;	/* true if this came from a SendEvent request */
16792 	Display *display;	/* Display the event was read from */
16793 	Drawable drawable;
16794 	int major_code;		/* core is CopyArea or CopyPlane */
16795 	int minor_code;		/* not defined in the core */
16796 }
16797 
16798 struct XVisibilityEvent{
16799 	int type;
16800 	arch_ulong serial;	/* # of last request processed by server */
16801 	Bool send_event;	/* true if this came from a SendEvent request */
16802 	Display *display;	/* Display the event was read from */
16803 	Window window;
16804 	VisibilityNotify state;		/* Visibility state */
16805 }
16806 
16807 struct XCreateWindowEvent{
16808 	int type;
16809 	arch_ulong serial;	/* # of last request processed by server */
16810 	Bool send_event;	/* true if this came from a SendEvent request */
16811 	Display *display;	/* Display the event was read from */
16812 	Window parent;		/* parent of the window */
16813 	Window window;		/* window id of window created */
16814 	int x, y;		/* window location */
16815 	int width, height;	/* size of window */
16816 	int border_width;	/* border width */
16817 	Bool override_redirect;	/* creation should be overridden */
16818 }
16819 
16820 struct XDestroyWindowEvent
16821 {
16822 	int type;
16823 	arch_ulong serial;		/* # of last request processed by server */
16824 	Bool send_event;	/* true if this came from a SendEvent request */
16825 	Display *display;	/* Display the event was read from */
16826 	Window event;
16827 	Window window;
16828 }
16829 
16830 struct XUnmapEvent
16831 {
16832 	int type;
16833 	arch_ulong serial;		/* # of last request processed by server */
16834 	Bool send_event;	/* true if this came from a SendEvent request */
16835 	Display *display;	/* Display the event was read from */
16836 	Window event;
16837 	Window window;
16838 	Bool from_configure;
16839 }
16840 
16841 struct XMapEvent
16842 {
16843 	int type;
16844 	arch_ulong serial;		/* # of last request processed by server */
16845 	Bool send_event;	/* true if this came from a SendEvent request */
16846 	Display *display;	/* Display the event was read from */
16847 	Window event;
16848 	Window window;
16849 	Bool override_redirect;	/* Boolean, is override set... */
16850 }
16851 
16852 struct XMapRequestEvent
16853 {
16854 	int type;
16855 	arch_ulong serial;	/* # of last request processed by server */
16856 	Bool send_event;	/* true if this came from a SendEvent request */
16857 	Display *display;	/* Display the event was read from */
16858 	Window parent;
16859 	Window window;
16860 }
16861 
16862 struct XReparentEvent
16863 {
16864 	int type;
16865 	arch_ulong serial;	/* # of last request processed by server */
16866 	Bool send_event;	/* true if this came from a SendEvent request */
16867 	Display *display;	/* Display the event was read from */
16868 	Window event;
16869 	Window window;
16870 	Window parent;
16871 	int x, y;
16872 	Bool override_redirect;
16873 }
16874 
16875 struct XConfigureEvent
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 event;
16882 	Window window;
16883 	int x, y;
16884 	int width, height;
16885 	int border_width;
16886 	Window above;
16887 	Bool override_redirect;
16888 }
16889 
16890 struct XGravityEvent
16891 {
16892 	int type;
16893 	arch_ulong serial;	/* # of last request processed by server */
16894 	Bool send_event;	/* true if this came from a SendEvent request */
16895 	Display *display;	/* Display the event was read from */
16896 	Window event;
16897 	Window window;
16898 	int x, y;
16899 }
16900 
16901 struct XResizeRequestEvent
16902 {
16903 	int type;
16904 	arch_ulong serial;	/* # of last request processed by server */
16905 	Bool send_event;	/* true if this came from a SendEvent request */
16906 	Display *display;	/* Display the event was read from */
16907 	Window window;
16908 	int width, height;
16909 }
16910 
16911 struct  XConfigureRequestEvent
16912 {
16913 	int type;
16914 	arch_ulong serial;	/* # of last request processed by server */
16915 	Bool send_event;	/* true if this came from a SendEvent request */
16916 	Display *display;	/* Display the event was read from */
16917 	Window parent;
16918 	Window window;
16919 	int x, y;
16920 	int width, height;
16921 	int border_width;
16922 	Window above;
16923 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
16924 	arch_ulong value_mask;
16925 }
16926 
16927 struct XCirculateEvent
16928 {
16929 	int type;
16930 	arch_ulong serial;	/* # of last request processed by server */
16931 	Bool send_event;	/* true if this came from a SendEvent request */
16932 	Display *display;	/* Display the event was read from */
16933 	Window event;
16934 	Window window;
16935 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16936 }
16937 
16938 struct XCirculateRequestEvent
16939 {
16940 	int type;
16941 	arch_ulong serial;	/* # of last request processed by server */
16942 	Bool send_event;	/* true if this came from a SendEvent request */
16943 	Display *display;	/* Display the event was read from */
16944 	Window parent;
16945 	Window window;
16946 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16947 }
16948 
16949 struct XPropertyEvent
16950 {
16951 	int type;
16952 	arch_ulong serial;	/* # of last request processed by server */
16953 	Bool send_event;	/* true if this came from a SendEvent request */
16954 	Display *display;	/* Display the event was read from */
16955 	Window window;
16956 	Atom atom;
16957 	Time time;
16958 	PropertyNotification state;		/* NewValue, Deleted */
16959 }
16960 
16961 struct XSelectionClearEvent
16962 {
16963 	int type;
16964 	arch_ulong serial;	/* # of last request processed by server */
16965 	Bool send_event;	/* true if this came from a SendEvent request */
16966 	Display *display;	/* Display the event was read from */
16967 	Window window;
16968 	Atom selection;
16969 	Time time;
16970 }
16971 
16972 struct XSelectionRequestEvent
16973 {
16974 	int type;
16975 	arch_ulong serial;	/* # of last request processed by server */
16976 	Bool send_event;	/* true if this came from a SendEvent request */
16977 	Display *display;	/* Display the event was read from */
16978 	Window owner;
16979 	Window requestor;
16980 	Atom selection;
16981 	Atom target;
16982 	Atom property;
16983 	Time time;
16984 }
16985 
16986 struct XSelectionEvent
16987 {
16988 	int type;
16989 	arch_ulong serial;	/* # of last request processed by server */
16990 	Bool send_event;	/* true if this came from a SendEvent request */
16991 	Display *display;	/* Display the event was read from */
16992 	Window requestor;
16993 	Atom selection;
16994 	Atom target;
16995 	Atom property;		/* ATOM or None */
16996 	Time time;
16997 }
16998 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
16999 
17000 struct XColormapEvent
17001 {
17002 	int type;
17003 	arch_ulong serial;	/* # of last request processed by server */
17004 	Bool send_event;	/* true if this came from a SendEvent request */
17005 	Display *display;	/* Display the event was read from */
17006 	Window window;
17007 	Colormap colormap;	/* COLORMAP or None */
17008 	Bool new_;		/* C++ */
17009 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17010 }
17011 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17012 
17013 struct XClientMessageEvent
17014 {
17015 	int type;
17016 	arch_ulong serial;	/* # of last request processed by server */
17017 	Bool send_event;	/* true if this came from a SendEvent request */
17018 	Display *display;	/* Display the event was read from */
17019 	Window window;
17020 	Atom message_type;
17021 	int format;
17022 	union Data{
17023 		byte[20] b;
17024 		short[10] s;
17025 		arch_ulong[5] l;
17026 	}
17027 	Data data;
17028 
17029 }
17030 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17031 
17032 struct XMappingEvent
17033 {
17034 	int type;
17035 	arch_ulong serial;	/* # of last request processed by server */
17036 	Bool send_event;	/* true if this came from a SendEvent request */
17037 	Display *display;	/* Display the event was read from */
17038 	Window window;		/* unused */
17039 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17040 				   MappingPointer */
17041 	int first_keycode;	/* first keycode */
17042 	int count;		/* defines range of change w. first_keycode*/
17043 }
17044 
17045 struct XErrorEvent
17046 {
17047 	int type;
17048 	Display *display;	/* Display the event was read from */
17049 	XID resourceid;		/* resource id */
17050 	arch_ulong serial;	/* serial number of failed request */
17051 	ubyte error_code;	/* error code of failed request */
17052 	ubyte request_code;	/* Major op-code of failed request */
17053 	ubyte minor_code;	/* Minor op-code of failed request */
17054 }
17055 
17056 struct XAnyEvent
17057 {
17058 	int type;
17059 	arch_ulong serial;	/* # of last request processed by server */
17060 	Bool send_event;	/* true if this came from a SendEvent request */
17061 	Display *display;/* Display the event was read from */
17062 	Window window;	/* window on which event was requested in event mask */
17063 }
17064 
17065 union XEvent{
17066 	int type;		/* must not be changed; first element */
17067 	XAnyEvent xany;
17068 	XKeyEvent xkey;
17069 	XButtonEvent xbutton;
17070 	XMotionEvent xmotion;
17071 	XCrossingEvent xcrossing;
17072 	XFocusChangeEvent xfocus;
17073 	XExposeEvent xexpose;
17074 	XGraphicsExposeEvent xgraphicsexpose;
17075 	XNoExposeEvent xnoexpose;
17076 	XVisibilityEvent xvisibility;
17077 	XCreateWindowEvent xcreatewindow;
17078 	XDestroyWindowEvent xdestroywindow;
17079 	XUnmapEvent xunmap;
17080 	XMapEvent xmap;
17081 	XMapRequestEvent xmaprequest;
17082 	XReparentEvent xreparent;
17083 	XConfigureEvent xconfigure;
17084 	XGravityEvent xgravity;
17085 	XResizeRequestEvent xresizerequest;
17086 	XConfigureRequestEvent xconfigurerequest;
17087 	XCirculateEvent xcirculate;
17088 	XCirculateRequestEvent xcirculaterequest;
17089 	XPropertyEvent xproperty;
17090 	XSelectionClearEvent xselectionclear;
17091 	XSelectionRequestEvent xselectionrequest;
17092 	XSelectionEvent xselection;
17093 	XColormapEvent xcolormap;
17094 	XClientMessageEvent xclient;
17095 	XMappingEvent xmapping;
17096 	XErrorEvent xerror;
17097 	XKeymapEvent xkeymap;
17098 	arch_ulong[24] pad;
17099 }
17100 
17101 
17102 	struct Display {
17103 		XExtData *ext_data;	/* hook for extension to hang data */
17104 		_XPrivate *private1;
17105 		int fd;			/* Network socket. */
17106 		int private2;
17107 		int proto_major_version;/* major version of server's X protocol */
17108 		int proto_minor_version;/* minor version of servers X protocol */
17109 		char *vendor;		/* vendor of the server hardware */
17110 	    	XID private3;
17111 		XID private4;
17112 		XID private5;
17113 		int private6;
17114 		XID function(Display*)resource_alloc;/* allocator function */
17115 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17116 		int bitmap_unit;	/* padding and data requirements */
17117 		int bitmap_pad;		/* padding requirements on bitmaps */
17118 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17119 		int nformats;		/* number of pixmap formats in list */
17120 		ScreenFormat *pixmap_format;	/* pixmap format list */
17121 		int private8;
17122 		int release;		/* release of the server */
17123 		_XPrivate *private9;
17124 		_XPrivate *private10;
17125 		int qlen;		/* Length of input event queue */
17126 		arch_ulong last_request_read; /* seq number of last event read */
17127 		arch_ulong request;	/* sequence number of last request. */
17128 		XPointer private11;
17129 		XPointer private12;
17130 		XPointer private13;
17131 		XPointer private14;
17132 		uint max_request_size; /* maximum number 32 bit words in request*/
17133 		_XrmHashBucketRec *db;
17134 		int function  (Display*)private15;
17135 		char *display_name;	/* "host:display" string used on this connect*/
17136 		int default_screen;	/* default screen for operations */
17137 		int nscreens;		/* number of screens on this server*/
17138 		Screen *screens;	/* pointer to list of screens */
17139 		arch_ulong motion_buffer;	/* size of motion buffer */
17140 		arch_ulong private16;
17141 		int min_keycode;	/* minimum defined keycode */
17142 		int max_keycode;	/* maximum defined keycode */
17143 		XPointer private17;
17144 		XPointer private18;
17145 		int private19;
17146 		byte *xdefaults;	/* contents of defaults from server */
17147 		/* there is more to this structure, but it is private to Xlib */
17148 	}
17149 
17150 	// I got these numbers from a C program as a sanity test
17151 	version(X86_64) {
17152 		static assert(Display.sizeof == 296);
17153 		static assert(XPointer.sizeof == 8);
17154 		static assert(XErrorEvent.sizeof == 40);
17155 		static assert(XAnyEvent.sizeof == 40);
17156 		static assert(XMappingEvent.sizeof == 56);
17157 		static assert(XEvent.sizeof == 192);
17158     	} else version (AArch64) {
17159         	// omit check for aarch64
17160 	} else {
17161 		static assert(Display.sizeof == 176);
17162 		static assert(XPointer.sizeof == 4);
17163 		static assert(XEvent.sizeof == 96);
17164 	}
17165 
17166 struct Depth
17167 {
17168 	int depth;		/* this depth (Z) of the depth */
17169 	int nvisuals;		/* number of Visual types at this depth */
17170 	Visual *visuals;	/* list of visuals possible at this depth */
17171 }
17172 
17173 alias void* GC;
17174 alias c_ulong VisualID;
17175 alias XID Colormap;
17176 alias XID Cursor;
17177 alias XID KeySym;
17178 alias uint KeyCode;
17179 enum None = 0;
17180 }
17181 
17182 version(without_opengl) {}
17183 else {
17184 extern(C) nothrow @nogc {
17185 
17186 
17187 static if(!SdpyIsUsingIVGLBinds) {
17188 enum GLX_USE_GL=            1;       /* support GLX rendering */
17189 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17190 enum GLX_LEVEL=             3;       /* level in plane stacking */
17191 enum GLX_RGBA=              4;       /* true if RGBA mode */
17192 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17193 enum GLX_STEREO=            6;       /* stereo buffering supported */
17194 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17195 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17196 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17197 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17198 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17199 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17200 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17201 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17202 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17203 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17204 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17205 
17206 
17207 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17208 
17209 
17210 
17211 enum GL_TRUE = 1;
17212 enum GL_FALSE = 0;
17213 alias int GLint;
17214 }
17215 
17216 alias XID GLXContextID;
17217 alias XID GLXPixmap;
17218 alias XID GLXDrawable;
17219 alias XID GLXPbuffer;
17220 alias XID GLXWindow;
17221 alias XID GLXFBConfigID;
17222 alias void* GLXContext;
17223 
17224 }
17225 }
17226 
17227 enum AllocNone = 0;
17228 
17229 extern(C) {
17230 	/* WARNING, this type not in Xlib spec */
17231 	extern(C) alias XIOErrorHandler = int function (Display* display);
17232 }
17233 
17234 extern(C) nothrow
17235 alias XErrorHandler = int function(Display*, XErrorEvent*);
17236 
17237 extern(C) nothrow @nogc {
17238 struct Screen{
17239 	XExtData *ext_data;		/* hook for extension to hang data */
17240 	Display *display;		/* back pointer to display structure */
17241 	Window root;			/* Root window id. */
17242 	int width, height;		/* width and height of screen */
17243 	int mwidth, mheight;	/* width and height of  in millimeters */
17244 	int ndepths;			/* number of depths possible */
17245 	Depth *depths;			/* list of allowable depths on the screen */
17246 	int root_depth;			/* bits per pixel */
17247 	Visual *root_visual;	/* root visual */
17248 	GC default_gc;			/* GC for the root root visual */
17249 	Colormap cmap;			/* default color map */
17250 	uint white_pixel;
17251 	uint black_pixel;		/* White and Black pixel values */
17252 	int max_maps, min_maps;	/* max and min color maps */
17253 	int backing_store;		/* Never, WhenMapped, Always */
17254 	bool save_unders;
17255 	int root_input_mask;	/* initial root input mask */
17256 }
17257 
17258 struct Visual
17259 {
17260 	XExtData *ext_data;	/* hook for extension to hang data */
17261 	VisualID visualid;	/* visual id of this visual */
17262 	int class_;			/* class of screen (monochrome, etc.) */
17263 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17264 	int bits_per_rgb;	/* log base 2 of distinct color values */
17265 	int map_entries;	/* color map entries */
17266 }
17267 
17268 	alias Display* _XPrivDisplay;
17269 
17270 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17271 		assert(dpy !is null);
17272 		return &dpy.screens[scr];
17273 	}
17274 
17275 	extern(D) Window RootWindow(Display *dpy,int scr) {
17276 		return ScreenOfDisplay(dpy,scr).root;
17277 	}
17278 
17279 	struct XWMHints {
17280 		arch_long flags;
17281 		Bool input;
17282 		int initial_state;
17283 		Pixmap icon_pixmap;
17284 		Window icon_window;
17285 		int icon_x, icon_y;
17286 		Pixmap icon_mask;
17287 		XID window_group;
17288 	}
17289 
17290 	struct XClassHint {
17291 		char* res_name;
17292 		char* res_class;
17293 	}
17294 
17295 	extern(D) int DefaultScreen(Display *dpy) {
17296 		return dpy.default_screen;
17297 	}
17298 
17299 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17300 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17301 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17302 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17303 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17304 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17305 
17306 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17307 
17308 	enum int AnyPropertyType = 0;
17309 	enum int Success = 0;
17310 
17311 	enum int RevertToNone = None;
17312 	enum int PointerRoot = 1;
17313 	enum Time CurrentTime = 0;
17314 	enum int RevertToPointerRoot = PointerRoot;
17315 	enum int RevertToParent = 2;
17316 
17317 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17318 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17319 	}
17320 
17321 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17322 		return ScreenOfDisplay(dpy,scr).root_visual;
17323 	}
17324 
17325 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17326 		return ScreenOfDisplay(dpy,scr).default_gc;
17327 	}
17328 
17329 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17330 		return ScreenOfDisplay(dpy,scr).black_pixel;
17331 	}
17332 
17333 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17334 		return ScreenOfDisplay(dpy,scr).white_pixel;
17335 	}
17336 
17337 	alias void* XFontSet; // i think
17338 	struct XmbTextItem {
17339 		char* chars;
17340 		int nchars;
17341 		int delta;
17342 		XFontSet font_set;
17343 	}
17344 
17345 	struct XTextItem {
17346 		char* chars;
17347 		int nchars;
17348 		int delta;
17349 		Font font;
17350 	}
17351 
17352 	enum {
17353 		GXclear        = 0x0, /* 0 */
17354 		GXand          = 0x1, /* src AND dst */
17355 		GXandReverse   = 0x2, /* src AND NOT dst */
17356 		GXcopy         = 0x3, /* src */
17357 		GXandInverted  = 0x4, /* NOT src AND dst */
17358 		GXnoop         = 0x5, /* dst */
17359 		GXxor          = 0x6, /* src XOR dst */
17360 		GXor           = 0x7, /* src OR dst */
17361 		GXnor          = 0x8, /* NOT src AND NOT dst */
17362 		GXequiv        = 0x9, /* NOT src XOR dst */
17363 		GXinvert       = 0xa, /* NOT dst */
17364 		GXorReverse    = 0xb, /* src OR NOT dst */
17365 		GXcopyInverted = 0xc, /* NOT src */
17366 		GXorInverted   = 0xd, /* NOT src OR dst */
17367 		GXnand         = 0xe, /* NOT src OR NOT dst */
17368 		GXset          = 0xf, /* 1 */
17369 	}
17370 	enum QueueMode : int {
17371 		QueuedAlready,
17372 		QueuedAfterReading,
17373 		QueuedAfterFlush
17374 	}
17375 
17376 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17377 
17378 	struct XPoint {
17379 		short x;
17380 		short y;
17381 	}
17382 
17383 	enum CoordMode:int {
17384 		CoordModeOrigin = 0,
17385 		CoordModePrevious = 1
17386 	}
17387 
17388 	enum PolygonShape:int {
17389 		Complex = 0,
17390 		Nonconvex = 1,
17391 		Convex = 2
17392 	}
17393 
17394 	struct XTextProperty {
17395 		const(char)* value;		/* same as Property routines */
17396 		Atom encoding;			/* prop type */
17397 		int format;				/* prop data format: 8, 16, or 32 */
17398 		arch_ulong nitems;		/* number of data items in value */
17399 	}
17400 
17401 	version( X86_64 ) {
17402 		static assert(XTextProperty.sizeof == 32);
17403 	}
17404 
17405 
17406 	struct XGCValues {
17407 		int function_;           /* logical operation */
17408 		arch_ulong plane_mask;/* plane mask */
17409 		arch_ulong foreground;/* foreground pixel */
17410 		arch_ulong background;/* background pixel */
17411 		int line_width;         /* line width */
17412 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17413 		int cap_style;          /* CapNotLast, CapButt,
17414 					   CapRound, CapProjecting */
17415 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17416 		int fill_style;         /* FillSolid, FillTiled,
17417 					   FillStippled, FillOpaeueStippled */
17418 		int fill_rule;          /* EvenOddRule, WindingRule */
17419 		int arc_mode;           /* ArcChord, ArcPieSlice */
17420 		Pixmap tile;            /* tile pixmap for tiling operations */
17421 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17422 		int ts_x_origin;        /* offset for tile or stipple operations */
17423 		int ts_y_origin;
17424 		Font font;              /* default text font for text operations */
17425 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17426 		Bool graphics_exposures;/* boolean, should exposures be generated */
17427 		int clip_x_origin;      /* origin for clipping */
17428 		int clip_y_origin;
17429 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17430 		int dash_offset;        /* patterned/dashed line information */
17431 		char dashes;
17432 	}
17433 
17434 	struct XColor {
17435 		arch_ulong pixel;
17436 		ushort red, green, blue;
17437 		byte flags;
17438 		byte pad;
17439 	}
17440 
17441 	struct XRectangle {
17442 		short x;
17443 		short y;
17444 		ushort width;
17445 		ushort height;
17446 	}
17447 
17448 	enum ClipByChildren = 0;
17449 	enum IncludeInferiors = 1;
17450 
17451 	enum Atom XA_PRIMARY = 1;
17452 	enum Atom XA_SECONDARY = 2;
17453 	enum Atom XA_STRING = 31;
17454 	enum Atom XA_CARDINAL = 6;
17455 	enum Atom XA_WM_NAME = 39;
17456 	enum Atom XA_ATOM = 4;
17457 	enum Atom XA_WINDOW = 33;
17458 	enum Atom XA_WM_HINTS = 35;
17459 	enum int PropModeAppend = 2;
17460 	enum int PropModeReplace = 0;
17461 	enum int PropModePrepend = 1;
17462 
17463 	enum int CopyFromParent = 0;
17464 	enum int InputOutput = 1;
17465 
17466 	// XWMHints
17467 	enum InputHint = 1 << 0;
17468 	enum StateHint = 1 << 1;
17469 	enum IconPixmapHint = (1L << 2);
17470 	enum IconWindowHint = (1L << 3);
17471 	enum IconPositionHint = (1L << 4);
17472 	enum IconMaskHint = (1L << 5);
17473 	enum WindowGroupHint = (1L << 6);
17474 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17475 	enum XUrgencyHint = (1L << 8);
17476 
17477 	// GC Components
17478 	enum GCFunction           =   (1L<<0);
17479 	enum GCPlaneMask         =    (1L<<1);
17480 	enum GCForeground       =     (1L<<2);
17481 	enum GCBackground      =      (1L<<3);
17482 	enum GCLineWidth      =       (1L<<4);
17483 	enum GCLineStyle     =        (1L<<5);
17484 	enum GCCapStyle     =         (1L<<6);
17485 	enum GCJoinStyle   =          (1L<<7);
17486 	enum GCFillStyle  =           (1L<<8);
17487 	enum GCFillRule  =            (1L<<9);
17488 	enum GCTile     =             (1L<<10);
17489 	enum GCStipple           =    (1L<<11);
17490 	enum GCTileStipXOrigin  =     (1L<<12);
17491 	enum GCTileStipYOrigin =      (1L<<13);
17492 	enum GCFont               =   (1L<<14);
17493 	enum GCSubwindowMode     =    (1L<<15);
17494 	enum GCGraphicsExposures=     (1L<<16);
17495 	enum GCClipXOrigin     =      (1L<<17);
17496 	enum GCClipYOrigin    =       (1L<<18);
17497 	enum GCClipMask      =        (1L<<19);
17498 	enum GCDashOffset   =         (1L<<20);
17499 	enum GCDashList    =          (1L<<21);
17500 	enum GCArcMode    =           (1L<<22);
17501 	enum GCLastBit   =            22;
17502 
17503 
17504 	enum int WithdrawnState = 0;
17505 	enum int NormalState = 1;
17506 	enum int IconicState = 3;
17507 
17508 }
17509 } else version (OSXCocoa) {
17510 private:
17511 	alias void* id;
17512 	alias void* Class;
17513 	alias void* SEL;
17514 	alias void* IMP;
17515 	alias void* Ivar;
17516 	alias byte BOOL;
17517 	alias const(void)* CFStringRef;
17518 	alias const(void)* CFAllocatorRef;
17519 	alias const(void)* CFTypeRef;
17520 	alias const(void)* CGContextRef;
17521 	alias const(void)* CGColorSpaceRef;
17522 	alias const(void)* CGImageRef;
17523 	alias ulong CGBitmapInfo;
17524 
17525 	struct objc_super {
17526 		id self;
17527 		Class superclass;
17528 	}
17529 
17530 	struct CFRange {
17531 		long location, length;
17532 	}
17533 
17534 	struct NSPoint {
17535 		double x, y;
17536 
17537 		static fromTuple(T)(T tupl) {
17538 			return NSPoint(tupl.tupleof);
17539 		}
17540 	}
17541 	struct NSSize {
17542 		double width, height;
17543 	}
17544 	struct NSRect {
17545 		NSPoint origin;
17546 		NSSize size;
17547 	}
17548 	alias NSPoint CGPoint;
17549 	alias NSSize CGSize;
17550 	alias NSRect CGRect;
17551 
17552 	struct CGAffineTransform {
17553 		double a, b, c, d, tx, ty;
17554 	}
17555 
17556 	enum NSApplicationActivationPolicyRegular = 0;
17557 	enum NSBackingStoreBuffered = 2;
17558 	enum kCFStringEncodingUTF8 = 0x08000100;
17559 
17560 	enum : size_t {
17561 		NSBorderlessWindowMask = 0,
17562 		NSTitledWindowMask = 1 << 0,
17563 		NSClosableWindowMask = 1 << 1,
17564 		NSMiniaturizableWindowMask = 1 << 2,
17565 		NSResizableWindowMask = 1 << 3,
17566 		NSTexturedBackgroundWindowMask = 1 << 8
17567 	}
17568 
17569 	enum : ulong {
17570 		kCGImageAlphaNone,
17571 		kCGImageAlphaPremultipliedLast,
17572 		kCGImageAlphaPremultipliedFirst,
17573 		kCGImageAlphaLast,
17574 		kCGImageAlphaFirst,
17575 		kCGImageAlphaNoneSkipLast,
17576 		kCGImageAlphaNoneSkipFirst
17577 	}
17578 	enum : ulong {
17579 		kCGBitmapAlphaInfoMask = 0x1F,
17580 		kCGBitmapFloatComponents = (1 << 8),
17581 		kCGBitmapByteOrderMask = 0x7000,
17582 		kCGBitmapByteOrderDefault = (0 << 12),
17583 		kCGBitmapByteOrder16Little = (1 << 12),
17584 		kCGBitmapByteOrder32Little = (2 << 12),
17585 		kCGBitmapByteOrder16Big = (3 << 12),
17586 		kCGBitmapByteOrder32Big = (4 << 12)
17587 	}
17588 	enum CGPathDrawingMode {
17589 		kCGPathFill,
17590 		kCGPathEOFill,
17591 		kCGPathStroke,
17592 		kCGPathFillStroke,
17593 		kCGPathEOFillStroke
17594 	}
17595 	enum objc_AssociationPolicy : size_t {
17596 		OBJC_ASSOCIATION_ASSIGN = 0,
17597 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
17598 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
17599 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
17600 		OBJC_ASSOCIATION_COPY = 0x303 //01403
17601 	}
17602 
17603 	extern(C) {
17604 		id objc_msgSend(id receiver, SEL selector, ...);
17605 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
17606 		id objc_getClass(const(char)* name);
17607 		SEL sel_registerName(const(char)* str);
17608 		Class objc_allocateClassPair(Class superclass, const(char)* name,
17609 									 size_t extra_bytes);
17610 		void objc_registerClassPair(Class cls);
17611 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
17612 		id objc_getAssociatedObject(id object, void* key);
17613 		void objc_setAssociatedObject(id object, void* key, id value,
17614 									  objc_AssociationPolicy policy);
17615 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
17616 		id object_getIvar(id object, Ivar ivar);
17617 		void object_setIvar(id object, Ivar ivar, id value);
17618 		BOOL class_addIvar(Class cls, const(char)* name,
17619 						   size_t size, ubyte alignment, const(char)* types);
17620 
17621 		extern __gshared id NSApp;
17622 
17623 		void CFRelease(CFTypeRef obj);
17624 
17625 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
17626 											const(char)* bytes, long numBytes,
17627 											long encoding,
17628 											BOOL isExternalRepresentation);
17629 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
17630 							 char lossByte, bool isExternalRepresentation,
17631 							 char* buffer, long maxBufLen, long* usedBufLen);
17632 		long CFStringGetLength(CFStringRef theString);
17633 
17634 		CGContextRef CGBitmapContextCreate(void* data,
17635 										   size_t width, size_t height,
17636 										   size_t bitsPerComponent,
17637 										   size_t bytesPerRow,
17638 										   CGColorSpaceRef colorspace,
17639 										   CGBitmapInfo bitmapInfo);
17640 		void CGContextRelease(CGContextRef c);
17641 		ubyte* CGBitmapContextGetData(CGContextRef c);
17642 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
17643 		size_t CGBitmapContextGetWidth(CGContextRef c);
17644 		size_t CGBitmapContextGetHeight(CGContextRef c);
17645 
17646 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
17647 		void CGColorSpaceRelease(CGColorSpaceRef cs);
17648 
17649 		void CGContextSetRGBStrokeColor(CGContextRef c,
17650 										double red, double green, double blue,
17651 										double alpha);
17652 		void CGContextSetRGBFillColor(CGContextRef c,
17653 									  double red, double green, double blue,
17654 									  double alpha);
17655 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
17656 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
17657 									  const(char)* str, size_t length);
17658 		void CGContextStrokeLineSegments(CGContextRef c,
17659 										 const(CGPoint)* points, size_t count);
17660 
17661 		void CGContextBeginPath(CGContextRef c);
17662 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
17663 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
17664 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
17665 							 double startAngle, double endAngle, long clockwise);
17666 		void CGContextAddRect(CGContextRef c, CGRect rect);
17667 		void CGContextAddLines(CGContextRef c,
17668 							   const(CGPoint)* points, size_t count);
17669 		void CGContextSaveGState(CGContextRef c);
17670 		void CGContextRestoreGState(CGContextRef c);
17671 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
17672 								 ulong textEncoding);
17673 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
17674 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
17675 
17676 		void CGImageRelease(CGImageRef image);
17677 	}
17678 
17679 private:
17680     // A convenient method to create a CFString (=NSString) from a D string.
17681     CFStringRef createCFString(string str) {
17682         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
17683                                              kCFStringEncodingUTF8, false);
17684     }
17685 
17686     // Objective-C calls.
17687     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
17688         auto _cmd = sel_registerName(selector.ptr);
17689         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17690         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
17691     }
17692     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
17693         auto _cmd = sel_registerName(selector.ptr);
17694         auto cls = objc_getClass(className);
17695         alias extern(C) RetType function(id, SEL, T) ExpectedType;
17696         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
17697     }
17698     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
17699         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
17700     }
17701 
17702     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
17703     alias objc_msgSend_classMethod!("alloc", id) alloc;
17704     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
17705                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
17706     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
17707     alias objc_msgSend_specialized!("center", void) center;
17708     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
17709     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
17710     alias objc_msgSend_specialized!("release", void) release;
17711     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
17712     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
17713     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
17714     alias objc_msgSend_specialized!("invalidate", void) invalidate;
17715     alias objc_msgSend_specialized!("close", void) close;
17716     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
17717                                     id, double, id, SEL, id, BOOL) scheduledTimer;
17718     alias objc_msgSend_specialized!("run", void) run;
17719     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
17720                                     id) currentNSGraphicsContext;
17721     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
17722     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
17723     alias objc_msgSend_specialized!("superclass", Class) superclass;
17724     alias objc_msgSend_specialized!("init", id) init;
17725     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
17726     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
17727     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
17728                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
17729     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
17730     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
17731     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
17732                                     void, BOOL) activateIgnoringOtherApps;
17733     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
17734                                     id) sharedNSApplication;
17735     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
17736 } else static assert(0, "Unsupported operating system");
17737 
17738 
17739 version(OSXCocoa) {
17740 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
17741 	//
17742 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
17743 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
17744 	//
17745 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
17746 	// Probably won't even fully compile right now
17747 
17748     import std.math : PI;
17749     import std.algorithm : map;
17750     import std.array : array;
17751 
17752     alias SimpleWindow NativeWindowHandle;
17753     alias void delegate(id) NativeEventHandler;
17754 
17755     __gshared Ivar simpleWindowIvar;
17756 
17757     enum KEY_ESCAPE = 27;
17758 
17759     mixin template NativeImageImplementation() {
17760         CGContextRef context;
17761         ubyte* rawData;
17762     final:
17763 
17764 	void convertToRgbaBytes(ubyte[] where) {
17765 		assert(where.length == this.width * this.height * 4);
17766 
17767 		// if rawData had a length....
17768 		//assert(rawData.length == where.length);
17769 		for(long idx = 0; idx < where.length; idx += 4) {
17770 			auto alpha = rawData[idx + 3];
17771 			if(alpha == 255) {
17772 				where[idx + 0] = rawData[idx + 0]; // r
17773 				where[idx + 1] = rawData[idx + 1]; // g
17774 				where[idx + 2] = rawData[idx + 2]; // b
17775 				where[idx + 3] = rawData[idx + 3]; // a
17776 			} else {
17777 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
17778 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
17779 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
17780 				where[idx + 3] = rawData[idx + 3]; // a
17781 
17782 			}
17783 		}
17784 	}
17785 
17786 	void setFromRgbaBytes(in ubyte[] where) {
17787 		// FIXME: this is probably wrong
17788 		assert(where.length == this.width * this.height * 4);
17789 
17790 		// if rawData had a length....
17791 		//assert(rawData.length == where.length);
17792 		for(long idx = 0; idx < where.length; idx += 4) {
17793 			auto alpha = rawData[idx + 3];
17794 			if(alpha == 255) {
17795 				rawData[idx + 0] = where[idx + 0]; // r
17796 				rawData[idx + 1] = where[idx + 1]; // g
17797 				rawData[idx + 2] = where[idx + 2]; // b
17798 				rawData[idx + 3] = where[idx + 3]; // a
17799 			} else {
17800 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
17801 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
17802 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
17803 				rawData[idx + 3] = where[idx + 3]; // a
17804 
17805 			}
17806 		}
17807 	}
17808 
17809 
17810         void createImage(int width, int height, bool forcexshm=false) {
17811             auto colorSpace = CGColorSpaceCreateDeviceRGB();
17812             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
17813                                             colorSpace,
17814                                             kCGImageAlphaPremultipliedLast
17815                                                    |kCGBitmapByteOrder32Big);
17816             CGColorSpaceRelease(colorSpace);
17817             rawData = CGBitmapContextGetData(context);
17818         }
17819         void dispose() {
17820             CGContextRelease(context);
17821         }
17822 
17823         void setPixel(int x, int y, Color c) {
17824             auto offset = (y * width + x) * 4;
17825             if (c.a == 255) {
17826                 rawData[offset + 0] = c.r;
17827                 rawData[offset + 1] = c.g;
17828                 rawData[offset + 2] = c.b;
17829                 rawData[offset + 3] = c.a;
17830             } else {
17831                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
17832                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
17833                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
17834                 rawData[offset + 3] = c.a;
17835             }
17836         }
17837     }
17838 
17839     mixin template NativeScreenPainterImplementation() {
17840         CGContextRef context;
17841         ubyte[4] _outlineComponents;
17842 	id view;
17843 
17844         void create(NativeWindowHandle window) {
17845             context = window.drawingContext;
17846 	    view = window.view;
17847         }
17848 
17849         void dispose() {
17850             	setNeedsDisplay(view, true);
17851         }
17852 
17853 	bool manualInvalidations;
17854 	void invalidateRect(Rectangle invalidRect) { }
17855 
17856 	// NotYetImplementedException
17857 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
17858 	void rasterOp(RasterOp op) {}
17859 	Pen _activePen;
17860 	Color _fillColor;
17861 	Rectangle _clipRectangle;
17862 	void setClipRectangle(int, int, int, int) {}
17863 	void setFont(OperatingSystemFont) {}
17864 	int fontHeight() { return 14; }
17865 
17866 	// end
17867 
17868         void pen(Pen pen) {
17869 	    _activePen = pen;
17870 	    auto color = pen.color; // FIXME
17871             double alphaComponent = color.a/255.0f;
17872             CGContextSetRGBStrokeColor(context,
17873                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
17874 
17875             if (color.a != 255) {
17876                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
17877                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
17878                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
17879                 _outlineComponents[3] = color.a;
17880             } else {
17881                 _outlineComponents[0] = color.r;
17882                 _outlineComponents[1] = color.g;
17883                 _outlineComponents[2] = color.b;
17884                 _outlineComponents[3] = color.a;
17885             }
17886         }
17887 
17888         @property void fillColor(Color color) {
17889             CGContextSetRGBFillColor(context,
17890                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
17891         }
17892 
17893         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
17894 		// NotYetImplementedException for upper left/width/height
17895             auto cgImage = CGBitmapContextCreateImage(image.context);
17896             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17897                                CGBitmapContextGetHeight(image.context));
17898             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17899             CGImageRelease(cgImage);
17900         }
17901 
17902 	version(OSXCocoa) {} else // NotYetImplementedException
17903         void drawPixmap(Sprite image, int x, int y) {
17904 		// FIXME: is this efficient?
17905             auto cgImage = CGBitmapContextCreateImage(image.context);
17906             auto size = CGSize(CGBitmapContextGetWidth(image.context),
17907                                CGBitmapContextGetHeight(image.context));
17908             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
17909             CGImageRelease(cgImage);
17910         }
17911 
17912 
17913         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
17914 		// FIXME: alignment
17915             if (_outlineComponents[3] != 0) {
17916                 CGContextSaveGState(context);
17917                 auto invAlpha = 1.0f/_outlineComponents[3];
17918                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
17919                                                   _outlineComponents[1]*invAlpha,
17920                                                   _outlineComponents[2]*invAlpha,
17921                                                   _outlineComponents[3]/255.0f);
17922                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
17923 // auto cfstr = cast(id)createCFString(text);
17924 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
17925 // NSPoint(x, y), null);
17926 // CFRelease(cfstr);
17927                 CGContextRestoreGState(context);
17928             }
17929         }
17930 
17931         void drawPixel(int x, int y) {
17932             auto rawData = CGBitmapContextGetData(context);
17933             auto width = CGBitmapContextGetWidth(context);
17934             auto height = CGBitmapContextGetHeight(context);
17935             auto offset = ((height - y - 1) * width + x) * 4;
17936             rawData[offset .. offset+4] = _outlineComponents;
17937         }
17938 
17939         void drawLine(int x1, int y1, int x2, int y2) {
17940             CGPoint[2] linePoints;
17941             linePoints[0] = CGPoint(x1, y1);
17942             linePoints[1] = CGPoint(x2, y2);
17943             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
17944         }
17945 
17946         void drawRectangle(int x, int y, int width, int height) {
17947             CGContextBeginPath(context);
17948             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
17949             CGContextAddRect(context, rect);
17950             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17951         }
17952 
17953         void drawEllipse(int x1, int y1, int x2, int y2) {
17954             CGContextBeginPath(context);
17955             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
17956             CGContextAddEllipseInRect(context, rect);
17957             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17958         }
17959 
17960         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
17961             // @@@BUG@@@ Does not support elliptic arc (width != height).
17962             CGContextBeginPath(context);
17963             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
17964                             start*PI/(180*64), finish*PI/(180*64), 0);
17965             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17966         }
17967 
17968         void drawPolygon(Point[] intPoints) {
17969             CGContextBeginPath(context);
17970             auto points = array(map!(CGPoint.fromTuple)(intPoints));
17971             CGContextAddLines(context, points.ptr, points.length);
17972             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17973         }
17974     }
17975 
17976     mixin template NativeSimpleWindowImplementation() {
17977         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
17978             synchronized {
17979                 if (NSApp == null) initializeApp();
17980             }
17981 
17982             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
17983 
17984             // create the window.
17985             window = initWithContentRect(alloc("NSWindow"),
17986                                          contentRect,
17987                                          NSTitledWindowMask
17988                                             |NSClosableWindowMask
17989                                             |NSMiniaturizableWindowMask
17990                                             |NSResizableWindowMask,
17991                                          NSBackingStoreBuffered,
17992                                          true);
17993 
17994             // set the title & move the window to center.
17995             auto windowTitle = createCFString(title);
17996             setTitle(window, windowTitle);
17997             CFRelease(windowTitle);
17998             center(window);
17999 
18000             // create area to draw on.
18001             auto colorSpace = CGColorSpaceCreateDeviceRGB();
18002             drawingContext = CGBitmapContextCreate(null, width, height,
18003                                                    8, 4*width, colorSpace,
18004                                                    kCGImageAlphaPremultipliedLast
18005                                                       |kCGBitmapByteOrder32Big);
18006             CGColorSpaceRelease(colorSpace);
18007             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18008             auto matrix = CGContextGetTextMatrix(drawingContext);
18009             matrix.c = -matrix.c;
18010             matrix.d = -matrix.d;
18011             CGContextSetTextMatrix(drawingContext, matrix);
18012 
18013             // create the subview that things will be drawn on.
18014             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
18015             setContentView(window, view);
18016             object_setIvar(view, simpleWindowIvar, cast(id)this);
18017             release(view);
18018 
18019             setBackgroundColor(window, whiteNSColor);
18020             makeKeyAndOrderFront(window, null);
18021         }
18022         void dispose() {
18023             closeWindow();
18024             release(window);
18025         }
18026         void closeWindow() {
18027             invalidate(timer);
18028             .close(window);
18029         }
18030 
18031         ScreenPainter getPainter(bool manualInvalidations) {
18032 		return ScreenPainter(this, this, manualInvalidations);
18033 	}
18034 
18035         id window;
18036         id timer;
18037         id view;
18038         CGContextRef drawingContext;
18039     }
18040 
18041     extern(C) {
18042     private:
18043         BOOL returnTrue3(id self, SEL _cmd, id app) {
18044             return true;
18045         }
18046         BOOL returnTrue2(id self, SEL _cmd) {
18047             return true;
18048         }
18049 
18050         void pulse(id self, SEL _cmd) {
18051             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18052             simpleWindow.handlePulse();
18053             setNeedsDisplay(self, true);
18054         }
18055         void drawRect(id self, SEL _cmd, NSRect rect) {
18056             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18057             auto curCtx = graphicsPort(currentNSGraphicsContext);
18058             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18059             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
18060                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
18061             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18062             CGImageRelease(cgImage);
18063         }
18064         void keyDown(id self, SEL _cmd, id event) {
18065             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
18066 
18067             // the event may have multiple characters, and we send them all at
18068             // once.
18069             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
18070                 auto chars = characters(event);
18071                 auto range = CFRange(0, CFStringGetLength(chars));
18072                 auto buffer = new char[range.length*3];
18073                 long actualLength;
18074                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
18075                                  buffer.ptr, cast(int) buffer.length, &actualLength);
18076                 foreach (dchar dc; buffer[0..actualLength]) {
18077                     if (simpleWindow.handleCharEvent)
18078                         simpleWindow.handleCharEvent(dc);
18079 		    // NotYetImplementedException
18080                     //if (simpleWindow.handleKeyEvent)
18081                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
18082                 }
18083             }
18084 
18085             // the event's 'keyCode' is hardware-dependent. I don't think people
18086             // will like it. Let's leave it to the native handler.
18087 
18088             // perform the default action.
18089 
18090 	    // so the default action is to make a bomp sound and i dont want that
18091 	    // sooooooooo yeah not gonna do that.
18092 
18093             //auto superData = objc_super(self, superclass(self));
18094             //alias extern(C) void function(objc_super*, SEL, id) T;
18095             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
18096         }
18097     }
18098 
18099     // initialize the app so that it can be interacted with the user.
18100     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
18101     private void initializeApp() {
18102         // push an autorelease pool to avoid leaking.
18103         init(alloc("NSAutoreleasePool"));
18104 
18105         // create a new NSApp instance
18106         sharedNSApplication;
18107         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
18108 
18109         // create the "Quit" menu.
18110         auto menuBar = init(alloc("NSMenu"));
18111         auto appMenuItem = init(alloc("NSMenuItem"));
18112         addItem(menuBar, appMenuItem);
18113         setMainMenu(NSApp, menuBar);
18114         release(appMenuItem);
18115         release(menuBar);
18116 
18117         auto appMenu = init(alloc("NSMenu"));
18118         auto quitTitle = createCFString("Quit");
18119         auto q = createCFString("q");
18120         auto quitItem = initWithTitle(alloc("NSMenuItem"),
18121                                       quitTitle, sel_registerName("terminate:"), q);
18122         addItem(appMenu, quitItem);
18123         setSubmenu(appMenuItem, appMenu);
18124         release(quitItem);
18125         release(appMenu);
18126         CFRelease(q);
18127         CFRelease(quitTitle);
18128 
18129         // assign a delegate for the application, allow it to quit when the last
18130         // window is closed.
18131         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
18132                                                     "SDWindowCloseDelegate", 0);
18133         class_addMethod(delegateClass,
18134                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
18135                         &returnTrue3, "c@:@");
18136         objc_registerClassPair(delegateClass);
18137 
18138         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
18139         setDelegate(NSApp, appDelegate);
18140         activateIgnoringOtherApps(NSApp, true);
18141 
18142         // create a new view that draws the graphics and respond to keyDown
18143         // events.
18144         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
18145                                                 "SDGraphicsView", (void*).sizeof);
18146         class_addIvar(viewClass, "simpledisplay_simpleWindow",
18147                       (void*).sizeof, (void*).alignof, "^v");
18148         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
18149                         &pulse, "v@:");
18150         class_addMethod(viewClass, sel_registerName("drawRect:"),
18151                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
18152         class_addMethod(viewClass, sel_registerName("isFlipped"),
18153                         &returnTrue2, "c@:");
18154         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
18155                         &returnTrue2, "c@:");
18156         class_addMethod(viewClass, sel_registerName("keyDown:"),
18157                         &keyDown, "v@:@");
18158         objc_registerClassPair(viewClass);
18159         simpleWindowIvar = class_getInstanceVariable(viewClass,
18160                                                      "simpledisplay_simpleWindow");
18161     }
18162 }
18163 
18164 version(without_opengl) {} else
18165 extern(System) nothrow @nogc {
18166 	//enum uint GL_VERSION = 0x1F02;
18167 	//const(char)* glGetString (/*GLenum*/uint);
18168 	version(X11) {
18169 	static if (!SdpyIsUsingIVGLBinds) {
18170 
18171 		enum GLX_X_RENDERABLE = 0x8012;
18172 		enum GLX_DRAWABLE_TYPE = 0x8010;
18173 		enum GLX_RENDER_TYPE = 0x8011;
18174 		enum GLX_X_VISUAL_TYPE = 0x22;
18175 		enum GLX_TRUE_COLOR = 0x8002;
18176 		enum GLX_WINDOW_BIT = 0x00000001;
18177 		enum GLX_RGBA_BIT = 0x00000001;
18178 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18179 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18180 		enum GLX_SAMPLES = 0x186a1;
18181 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18182 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18183 	}
18184 
18185 		// GLX_EXT_swap_control
18186 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18187 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18188 
18189 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18190 		extern(System) {
18191 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18192 		}
18193 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18194 
18195 		// this made public so we don't have to get it again and again
18196 		public bool glXCreateContextAttribsARB_present () {
18197 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18198 				// get it
18199 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18200 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18201 			}
18202 			return (glXCreateContextAttribsARBFn !is null);
18203 		}
18204 
18205 		// this made public so we don't have to get it again and again
18206 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18207 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18208 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18209 		}
18210 
18211 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18212 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18213 
18214 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18215 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18216 			if (_glx_swapInterval_fn is null) {
18217 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18218 				if (_glx_swapInterval_fn is null) {
18219 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18220 					return;
18221 				}
18222 				version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); }
18223 			}
18224 
18225 			if(glXSwapIntervalMESA is null) {
18226 				// it seems to require both to actually take effect on many computers
18227 				// idk why
18228 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18229 				if(glXSwapIntervalMESA is null)
18230 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18231 			}
18232 
18233 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18234 				glXSwapIntervalMESA(wait ? 1 : 0);
18235 
18236 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18237 		}
18238 	} else version(Windows) {
18239 	static if (!SdpyIsUsingIVGLBinds) {
18240 	enum GL_TRUE = 1;
18241 	enum GL_FALSE = 0;
18242 	alias int GLint;
18243 
18244 	public void* glbindGetProcAddress (const(char)* name) {
18245 		void* res = wglGetProcAddress(name);
18246 		if (res is null) {
18247 			/+
18248 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18249 			import core.sys.windows.windef, core.sys.windows.winbase;
18250 			__gshared HINSTANCE dll = null;
18251 			if (dll is null) {
18252 				dll = LoadLibraryA("opengl32.dll");
18253 				if (dll is null) return null; // <32, but idc
18254 			}
18255 			res = GetProcAddress(dll, name);
18256 			+/
18257 			res = GetProcAddress(gl.libHandle, name);
18258 		}
18259 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18260 		return res;
18261 	}
18262 	}
18263 
18264 
18265  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18266         void wglSetVSync(bool wait) {
18267 		if(wglSwapIntervalEXT is null) {
18268 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18269 			if(wglSwapIntervalEXT is null)
18270 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18271 		}
18272 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18273 			return;
18274 
18275 		wglSwapIntervalEXT(wait ? 1 : 0);
18276 	}
18277 
18278 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18279 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18280 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18281 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18282 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18283 
18284 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18285 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18286 
18287 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18288 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18289 
18290 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18291 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18292 
18293 		void wglInitOtherFunctions () {
18294 			if (wglCreateContextAttribsARB is null) {
18295 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18296 			}
18297 		}
18298 	}
18299 
18300 	static if (!SdpyIsUsingIVGLBinds) {
18301 
18302 	interface GL {
18303 		extern(System) @nogc nothrow:
18304 
18305 		void glGetIntegerv(int, void*);
18306 		void glMatrixMode(int);
18307 		void glPushMatrix();
18308 		void glLoadIdentity();
18309 		void glOrtho(double, double, double, double, double, double);
18310 		void glFrustum(double, double, double, double, double, double);
18311 
18312 		void glPopMatrix();
18313 		void glEnable(int);
18314 		void glDisable(int);
18315 		void glClear(int);
18316 		void glBegin(int);
18317 		void glVertex2f(float, float);
18318 		void glVertex3f(float, float, float);
18319 		void glEnd();
18320 		void glColor3b(byte, byte, byte);
18321 		void glColor3ub(ubyte, ubyte, ubyte);
18322 		void glColor4b(byte, byte, byte, byte);
18323 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18324 		void glColor3i(int, int, int);
18325 		void glColor3ui(uint, uint, uint);
18326 		void glColor4i(int, int, int, int);
18327 		void glColor4ui(uint, uint, uint, uint);
18328 		void glColor3f(float, float, float);
18329 		void glColor4f(float, float, float, float);
18330 		void glTranslatef(float, float, float);
18331 		void glScalef(float, float, float);
18332 		version(X11) {
18333 			void glSecondaryColor3b(byte, byte, byte);
18334 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18335 			void glSecondaryColor3i(int, int, int);
18336 			void glSecondaryColor3ui(uint, uint, uint);
18337 			void glSecondaryColor3f(float, float, float);
18338 		}
18339 
18340 		void glDrawElements(int, int, int, void*);
18341 
18342 		void glRotatef(float, float, float, float);
18343 
18344 		uint glGetError();
18345 
18346 		void glDeleteTextures(int, uint*);
18347 
18348 
18349 		void glRasterPos2i(int, int);
18350 		void glDrawPixels(int, int, uint, uint, void*);
18351 		void glClearColor(float, float, float, float);
18352 
18353 
18354 		void glPixelStorei(uint, int);
18355 
18356 		void glGenTextures(uint, uint*);
18357 		void glBindTexture(int, int);
18358 		void glTexParameteri(uint, uint, int);
18359 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18360 		void glTexImage2D(int, int, int, int, int, int, int, int, in void*);
18361 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18362 			/*GLsizei*/int width, /*GLsizei*/int height,
18363 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
18364 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18365 
18366 		void glLineWidth(int);
18367 
18368 
18369 		void glTexCoord2f(float, float);
18370 		void glVertex2i(int, int);
18371 		void glBlendFunc (int, int);
18372 		void glDepthFunc (int);
18373 		void glViewport(int, int, int, int);
18374 
18375 		void glClearDepth(double);
18376 
18377 		void glReadBuffer(uint);
18378 		void glReadPixels(int, int, int, int, int, int, void*);
18379 
18380 		void glFlush();
18381 		void glFinish();
18382 
18383 		version(Windows) {
18384 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18385 			HGLRC wglCreateContext(HDC);
18386 			HGLRC wglCreateLayerContext(HDC, int);
18387 			BOOL wglDeleteContext(HGLRC);
18388 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18389 			HGLRC wglGetCurrentContext();
18390 			HDC wglGetCurrentDC();
18391 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18392 			PROC wglGetProcAddress(LPCSTR);
18393 			BOOL wglMakeCurrent(HDC, HGLRC);
18394 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18395 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18396 			BOOL wglShareLists(HGLRC, HGLRC);
18397 			BOOL wglSwapLayerBuffers(HDC, UINT);
18398 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18399 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18400 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18401 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18402 		}
18403 
18404 	}
18405 
18406 	interface GL3 {
18407 		extern(System) @nogc nothrow:
18408 
18409 		void glGenVertexArrays(GLsizei, GLuint*);
18410 		void glBindVertexArray(GLuint);
18411 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18412 		void glGenerateMipmap(GLenum);
18413 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18414 		void glStencilMask(GLuint);
18415 		void glStencilFunc(GLenum, GLint, GLuint);
18416 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18417 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18418 		GLuint glCreateProgram();
18419 		GLuint glCreateShader(GLenum);
18420 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18421 		void glCompileShader(GLuint);
18422 		void glGetShaderiv(GLuint, GLenum, GLint*);
18423 		void glAttachShader(GLuint, GLuint);
18424 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18425 		void glLinkProgram(GLuint);
18426 		void glGetProgramiv(GLuint, GLenum, GLint*);
18427 		void glDeleteProgram(GLuint);
18428 		void glDeleteShader(GLuint);
18429 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18430 		void glGenBuffers(GLsizei, GLuint*);
18431 
18432 		void glUniform1f(GLint location, GLfloat v0);
18433 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18434 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18435 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18436 		void glUniform1i(GLint location, GLint v0);
18437 		void glUniform2i(GLint location, GLint v0, GLint v1);
18438 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18439 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18440 		void glUniform1ui(GLint location, GLuint v0);
18441 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18442 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18443 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18444 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18445 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18446 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18447 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18448 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18449 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18450 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18451 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18452 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18453 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18454 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18455 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18456 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18457 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18458 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18459 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18460 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18461 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18462 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18463 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18464 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18465 
18466 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18467 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18468 		void glDrawArrays(GLenum, GLint, GLsizei);
18469 		void glStencilOp(GLenum, GLenum, GLenum);
18470 		void glUseProgram(GLuint);
18471 		void glCullFace(GLenum);
18472 		void glFrontFace(GLenum);
18473 		void glActiveTexture(GLenum);
18474 		void glBindBuffer(GLenum, GLuint);
18475 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18476 		void glEnableVertexAttribArray(GLuint);
18477 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18478 		void glUniform1i(GLint, GLint);
18479 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18480 		void glDisableVertexAttribArray(GLuint);
18481 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18482 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18483 		void glLogicOp (GLenum opcode);
18484 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18485 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18486 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18487 		GLenum glCheckFramebufferStatus (GLenum target);
18488 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18489 	}
18490 
18491 	interface GL4 {
18492 		extern(System) @nogc nothrow:
18493 
18494 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18495 			/*GLsizei*/int width, /*GLsizei*/int height,
18496 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
18497 	}
18498 
18499 	interface GLU {
18500 		extern(System) @nogc nothrow:
18501 
18502 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18503 		void gluPerspective(double, double, double, double);
18504 
18505 		char* gluErrorString(uint);
18506 	}
18507 
18508 
18509 	enum GL_RED = 0x1903;
18510 	enum GL_ALPHA = 0x1906;
18511 
18512 	enum uint GL_FRONT = 0x0404;
18513 
18514 	enum uint GL_BLEND = 0x0be2;
18515 	enum uint GL_LEQUAL = 0x0203;
18516 
18517 
18518 	enum uint GL_RGB = 0x1907;
18519 	enum uint GL_BGRA = 0x80e1;
18520 	enum uint GL_RGBA = 0x1908;
18521 	enum uint GL_TEXTURE_2D =   0x0DE1;
18522 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18523 	enum uint GL_NEAREST = 0x2600;
18524 	enum uint GL_LINEAR = 0x2601;
18525 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18526 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18527 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18528 	enum uint GL_REPEAT = 0x2901;
18529 	enum uint GL_CLAMP = 0x2900;
18530 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18531 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18532 	enum uint GL_DECAL = 0x2101;
18533 	enum uint GL_MODULATE = 0x2100;
18534 	enum uint GL_TEXTURE_ENV = 0x2300;
18535 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18536 	enum uint GL_REPLACE = 0x1E01;
18537 	enum uint GL_LIGHTING = 0x0B50;
18538 	enum uint GL_DITHER = 0x0BD0;
18539 
18540 	enum uint GL_NO_ERROR = 0;
18541 
18542 
18543 
18544 	enum int GL_VIEWPORT = 0x0BA2;
18545 	enum int GL_MODELVIEW = 0x1700;
18546 	enum int GL_TEXTURE = 0x1702;
18547 	enum int GL_PROJECTION = 0x1701;
18548 	enum int GL_DEPTH_TEST = 0x0B71;
18549 
18550 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18551 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18552 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18553 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18554 
18555 	enum int GL_POINTS = 0x0000;
18556 	enum int GL_LINES =  0x0001;
18557 	enum int GL_LINE_LOOP = 0x0002;
18558 	enum int GL_LINE_STRIP = 0x0003;
18559 	enum int GL_TRIANGLES = 0x0004;
18560 	enum int GL_TRIANGLE_STRIP = 5;
18561 	enum int GL_TRIANGLE_FAN = 6;
18562 	enum int GL_QUADS = 7;
18563 	enum int GL_QUAD_STRIP = 8;
18564 	enum int GL_POLYGON = 9;
18565 
18566 	alias GLvoid = void;
18567 	alias GLboolean = ubyte;
18568 	alias GLuint = uint;
18569 	alias GLenum = uint;
18570 	alias GLchar = char;
18571 	alias GLsizei = int;
18572 	alias GLfloat = float;
18573 	alias GLintptr = size_t;
18574 	alias GLsizeiptr = ptrdiff_t;
18575 
18576 
18577 	enum uint GL_INVALID_ENUM = 0x0500;
18578 
18579 	enum uint GL_ZERO = 0;
18580 	enum uint GL_ONE = 1;
18581 
18582 	enum uint GL_BYTE = 0x1400;
18583 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18584 	enum uint GL_SHORT = 0x1402;
18585 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18586 	enum uint GL_INT = 0x1404;
18587 	enum uint GL_UNSIGNED_INT = 0x1405;
18588 	enum uint GL_FLOAT = 0x1406;
18589 	enum uint GL_2_BYTES = 0x1407;
18590 	enum uint GL_3_BYTES = 0x1408;
18591 	enum uint GL_4_BYTES = 0x1409;
18592 	enum uint GL_DOUBLE = 0x140A;
18593 
18594 	enum uint GL_STREAM_DRAW = 0x88E0;
18595 
18596 	enum uint GL_CCW = 0x0901;
18597 
18598 	enum uint GL_STENCIL_TEST = 0x0B90;
18599 	enum uint GL_SCISSOR_TEST = 0x0C11;
18600 
18601 	enum uint GL_EQUAL = 0x0202;
18602 	enum uint GL_NOTEQUAL = 0x0205;
18603 
18604 	enum uint GL_ALWAYS = 0x0207;
18605 	enum uint GL_KEEP = 0x1E00;
18606 
18607 	enum uint GL_INCR = 0x1E02;
18608 
18609 	enum uint GL_INCR_WRAP = 0x8507;
18610 	enum uint GL_DECR_WRAP = 0x8508;
18611 
18612 	enum uint GL_CULL_FACE = 0x0B44;
18613 	enum uint GL_BACK = 0x0405;
18614 
18615 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18616 	enum uint GL_VERTEX_SHADER = 0x8B31;
18617 
18618 	enum uint GL_COMPILE_STATUS = 0x8B81;
18619 	enum uint GL_LINK_STATUS = 0x8B82;
18620 
18621 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18622 
18623 	enum uint GL_STATIC_DRAW = 0x88E4;
18624 
18625 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18626 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18627 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18628 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18629 
18630 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18631 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18632 
18633 	enum uint GL_TEXTURE0 = 0x84C0U;
18634 	enum uint GL_TEXTURE1 = 0x84C1U;
18635 
18636 	enum uint GL_ARRAY_BUFFER = 0x8892;
18637 
18638 	enum uint GL_SRC_COLOR = 0x0300;
18639 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18640 	enum uint GL_SRC_ALPHA = 0x0302;
18641 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18642 	enum uint GL_DST_ALPHA = 0x0304;
18643 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18644 	enum uint GL_DST_COLOR = 0x0306;
18645 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18646 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18647 
18648 	enum uint GL_INVERT = 0x150AU;
18649 
18650 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18651 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18652 
18653 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18654 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18655 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18656 
18657 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18658 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18659 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18660 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18661 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18662 
18663 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18664 	enum uint GL_CLEAR = 0x1500U;
18665 	enum uint GL_COPY = 0x1503U;
18666 	enum uint GL_XOR = 0x1506U;
18667 
18668 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18669 
18670 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18671 
18672 	}
18673 }
18674 
18675 /++
18676 	History:
18677 		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.
18678 +/
18679 __gshared bool gluSuccessfullyLoaded = true;
18680 
18681 version(without_opengl) {} else {
18682 static if(!SdpyIsUsingIVGLBinds) {
18683 	version(Windows) {
18684 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18685 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18686 	} else {
18687 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18688 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18689 	}
18690 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18691 
18692 
18693 	shared static this() {
18694 		gl.loadDynamicLibrary();
18695 
18696 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18697 		// unless those functions are actually used
18698 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18699 		glu.loadDynamicLibrary();
18700 	}
18701 }
18702 }
18703 
18704 /++
18705 	Convenience method for converting D arrays to opengl buffer data
18706 
18707 	I would LOVE to overload it with the original glBufferData, but D won't
18708 	let me since glBufferData is a function pointer :(
18709 
18710 	Added: August 25, 2020 (version 8.5)
18711 +/
18712 version(without_opengl) {} else
18713 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
18714 	glBufferData(target, data.length, data.ptr, usage);
18715 }
18716 
18717 /+
18718 /++
18719 	A matrix for simple uses that easily integrates with [OpenGlShader].
18720 
18721 	Might not be useful to you since it only as some simple functions and
18722 	probably isn't that fast.
18723 
18724 	Note it uses an inline static array for its storage, so copying it
18725 	may be expensive.
18726 +/
18727 struct BasicMatrix(int columns, int rows, T = float) {
18728 	import core.stdc.math;
18729 
18730 	T[columns * rows] data = 0.0;
18731 
18732 	/++
18733 		Basic operations that operate *in place*.
18734 	+/
18735 	void translate() {
18736 
18737 	}
18738 
18739 	/// ditto
18740 	void scale() {
18741 
18742 	}
18743 
18744 	/// ditto
18745 	void rotate() {
18746 
18747 	}
18748 
18749 	/++
18750 
18751 	+/
18752 	static if(columns == rows)
18753 	static BasicMatrix identity() {
18754 		BasicMatrix m;
18755 		foreach(i; 0 .. columns)
18756 			data[0 + i + i * columns] = 1.0;
18757 		return m;
18758 	}
18759 
18760 	static BasicMatrix ortho() {
18761 		return BasicMatrix.init;
18762 	}
18763 }
18764 +/
18765 
18766 /++
18767 	Convenience class for using opengl shaders.
18768 
18769 	Ensure that you've loaded opengl 3+ and set your active
18770 	context before trying to use this.
18771 
18772 	Added: August 25, 2020 (version 8.5)
18773 +/
18774 version(without_opengl) {} else
18775 final class OpenGlShader {
18776 	private int shaderProgram_;
18777 	private @property void shaderProgram(int a) {
18778 		shaderProgram_ = a;
18779 	}
18780 	/// Get the program ID for use in OpenGL functions.
18781 	public @property int shaderProgram() {
18782 		return shaderProgram_;
18783 	}
18784 
18785 	/++
18786 
18787 	+/
18788 	static struct Source {
18789 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
18790 		string code; ///
18791 	}
18792 
18793 	/++
18794 		Helper method to just compile some shader code and check for errors
18795 		while you do glCreateShader, etc. on the outside yourself.
18796 
18797 		This just does `glShaderSource` and `glCompileShader` for the given code.
18798 
18799 		If you the OpenGlShader class constructor, you never need to call this yourself.
18800 	+/
18801 	static void compile(int sid, Source code) {
18802 		const(char)*[1] buffer;
18803 		int[1] lengthBuffer;
18804 
18805 		buffer[0] = code.code.ptr;
18806 		lengthBuffer[0] = cast(int) code.code.length;
18807 
18808 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
18809 		glCompileShader(sid);
18810 
18811 		int success;
18812 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
18813 		if(!success) {
18814 			char[512] info;
18815 			int len;
18816 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
18817 
18818 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
18819 		}
18820 	}
18821 
18822 	/++
18823 		Calls `glLinkProgram` and throws if error a occurs.
18824 
18825 		If you the OpenGlShader class constructor, you never need to call this yourself.
18826 	+/
18827 	static void link(int shaderProgram) {
18828 		glLinkProgram(shaderProgram);
18829 		int success;
18830 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
18831 		if(!success) {
18832 			char[512] info;
18833 			int len;
18834 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
18835 
18836 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
18837 		}
18838 	}
18839 
18840 	/++
18841 		Constructs the shader object by calling `glCreateProgram`, then
18842 		compiling each given [Source], and finally, linking them together.
18843 
18844 		Throws: on compile or link failure.
18845 	+/
18846 	this(Source[] codes...) {
18847 		shaderProgram = glCreateProgram();
18848 
18849 		int[16] shadersBufferStack;
18850 
18851 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
18852 			shadersBufferStack[0 .. codes.length] :
18853 			new int[](codes.length);
18854 
18855 		foreach(idx, code; codes) {
18856 			shadersBuffer[idx] = glCreateShader(code.type);
18857 
18858 			compile(shadersBuffer[idx], code);
18859 
18860 			glAttachShader(shaderProgram, shadersBuffer[idx]);
18861 		}
18862 
18863 		link(shaderProgram);
18864 
18865 		foreach(s; shadersBuffer)
18866 			glDeleteShader(s);
18867 	}
18868 
18869 	/// Calls `glUseProgram(this.shaderProgram)`
18870 	void use() {
18871 		glUseProgram(this.shaderProgram);
18872 	}
18873 
18874 	/// Deletes the program.
18875 	void delete_() {
18876 		glDeleteProgram(shaderProgram);
18877 		shaderProgram = 0;
18878 	}
18879 
18880 	/++
18881 		[OpenGlShader.uniforms].name gives you one of these.
18882 
18883 		You can get the id out of it or just assign
18884 	+/
18885 	static struct Uniform {
18886 		/// the id passed to glUniform*
18887 		int id;
18888 
18889 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
18890 		void opAssign(float x, float y, float z, float w) {
18891 			if(id != -1)
18892 			glUniform4f(id, x, y, z, w);
18893 		}
18894 
18895 		void opAssign(float x) {
18896 			if(id != -1)
18897 			glUniform1f(id, x);
18898 		}
18899 
18900 		void opAssign(float x, float y) {
18901 			if(id != -1)
18902 			glUniform2f(id, x, y);
18903 		}
18904 
18905 		void opAssign(T)(T t) {
18906 			t.glUniform(id);
18907 		}
18908 	}
18909 
18910 	static struct UniformsHelper {
18911 		OpenGlShader _shader;
18912 
18913 		@property Uniform opDispatch(string name)() {
18914 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
18915 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
18916 			//if(i == -1)
18917 				//throw new Exception("Could not find uniform " ~ name);
18918 			return Uniform(i);
18919 		}
18920 
18921 		@property void opDispatch(string name, T)(T t) {
18922 			Uniform f = this.opDispatch!name;
18923 			t.glUniform(f);
18924 		}
18925 	}
18926 
18927 	/++
18928 		Gives access to the uniforms through dot access.
18929 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
18930 	+/
18931 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
18932 }
18933 
18934 version(without_opengl) {} else {
18935 /++
18936 	A static container of experimental types and value constructors for opengl 3+ shaders.
18937 
18938 
18939 	You can declare variables like:
18940 
18941 	```
18942 	OGL.vec3f something;
18943 	```
18944 
18945 	But generally it would be used with [OpenGlShader]'s uniform helpers like
18946 
18947 	```
18948 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
18949 	```
18950 
18951 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
18952 
18953 
18954 	History:
18955 		Added December 7, 2021. Not yet stable.
18956 +/
18957 final class OGL {
18958 	static:
18959 
18960 	private template typeFromSpecifier(string specifier) {
18961 		static if(specifier == "f")
18962 			alias typeFromSpecifier = GLfloat;
18963 		else static if(specifier == "i")
18964 			alias typeFromSpecifier = GLint;
18965 		else static if(specifier == "ui")
18966 			alias typeFromSpecifier = GLuint;
18967 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
18968 	}
18969 
18970 	private template CommonType(T...) {
18971 		static if(T.length == 1)
18972 			alias CommonType = T[0];
18973 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
18974 			alias CommonType = CommonType!(C, T[2 .. $]);
18975 	}
18976 
18977 	private template typesToSpecifier(T...) {
18978 		static if(is(CommonType!T == float))
18979 			enum typesToSpecifier = "f";
18980 		else static if(is(CommonType!T == int))
18981 			enum typesToSpecifier = "i";
18982 		else static if(is(CommonType!T == uint))
18983 			enum typesToSpecifier = "ui";
18984 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
18985 	}
18986 
18987 	private template genNames(size_t dim, size_t dim2 = 0) {
18988 		string helper() {
18989 			string s;
18990 			if(dim2) {
18991 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
18992 			} else {
18993 				if(dim > 0) s ~= "type x = 0;";
18994 				if(dim > 1) s ~= "type y = 0;";
18995 				if(dim > 2) s ~= "type z = 0;";
18996 				if(dim > 3) s ~= "type w = 0;";
18997 			}
18998 			return s;
18999 		}
19000 
19001 		enum genNames = helper();
19002 	}
19003 
19004 	// there's vec, arrays of vec, mat, and arrays of mat
19005 	template opDispatch(string name)
19006 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19007 	{
19008 		static if(name[4] == 'x') {
19009 			enum dimX = cast(int) (name[3] - '0');
19010 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19011 
19012 			enum dimY = cast(int) (name[5] - '0');
19013 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19014 
19015 			enum isArray = name[$ - 1] == 'v';
19016 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19017 			alias type = typeFromSpecifier!typeSpecifier;
19018 		} else {
19019 			enum dim = cast(int) (name[3] - '0');
19020 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19021 			enum isArray = name[$ - 1] == 'v';
19022 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19023 			alias type = typeFromSpecifier!typeSpecifier;
19024 		}
19025 
19026 		align(1)
19027 		struct opDispatch {
19028 			align(1):
19029 			static if(name[4] == 'x')
19030 				mixin(genNames!(dimX, dimY));
19031 			else
19032 				mixin(genNames!dim);
19033 
19034 			private void glUniform(OpenGlShader.Uniform assignTo) {
19035 				glUniform(assignTo.id);
19036 			}
19037 			private void glUniform(int assignTo) {
19038 				static if(name[4] == 'x') {
19039 					// FIXME
19040 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19041 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19042 				} else
19043 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19044 			}
19045 		}
19046 	}
19047 
19048 	auto vec(T...)(T members) {
19049 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19050 	}
19051 }
19052 }
19053 
19054 version(linux) {
19055 	version(with_eventloop) {} else {
19056 		private int epollFd = -1;
19057 		void prepareEventLoop() {
19058 			if(epollFd != -1)
19059 				return; // already initialized, no need to do it again
19060 			import ep = core.sys.linux.epoll;
19061 
19062 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19063 			if(epollFd == -1)
19064 				throw new Exception("epoll create failure");
19065 		}
19066 	}
19067 } else version(Posix) {
19068 	void prepareEventLoop() {}
19069 }
19070 
19071 version(X11) {
19072 	import core.stdc.locale : LC_ALL; // rdmd fix
19073 	__gshared bool sdx_isUTF8Locale;
19074 
19075 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19076 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19077 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19078 	// anal magic is here. I (Ketmar) hope you like it.
19079 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19080 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19081 	// later.
19082 
19083 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19084 	shared static this () {
19085 		if(!librariesSuccessfullyLoaded)
19086 			return;
19087 
19088 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19089 
19090 		// this doesn't hurt; it may add some locking, but the speed is still
19091 		// allows doing 60 FPS videogames; also, ignore the result, as most
19092 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19093 		// never seen this failing).
19094 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19095 
19096 		setlocale(LC_ALL, "");
19097 		// check if out locale is UTF-8
19098 		auto lct = setlocale(LC_CTYPE, null);
19099 		if (lct is null) {
19100 			sdx_isUTF8Locale = false;
19101 		} else {
19102 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19103 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19104 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19105 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19106 				{
19107 					sdx_isUTF8Locale = true;
19108 					break;
19109 				}
19110 			}
19111 		}
19112 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19113 	}
19114 }
19115 
19116 class ExperimentalTextComponent2 {
19117 	/+
19118 		Stage 1: get it working monospace
19119 		Stage 2: use proportional font
19120 		Stage 3: allow changes in inline style
19121 		Stage 4: allow new fonts and sizes in the middle
19122 		Stage 5: optimize gap buffer
19123 		Stage 6: optimize layout
19124 		Stage 7: word wrap
19125 		Stage 8: justification
19126 		Stage 9: editing, selection, etc.
19127 
19128 			Operations:
19129 				insert text
19130 				overstrike text
19131 				select
19132 				cut
19133 				modify
19134 	+/
19135 
19136 	/++
19137 		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.
19138 	+/
19139 	this(SimpleWindow window) {
19140 		this.window = window;
19141 	}
19142 
19143 	private SimpleWindow window;
19144 
19145 
19146 	/++
19147 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19148 		representing the internal parts. The first pass is focused on the x parameter, then the
19149 		renderer is responsible for going back to the parts in the current line and calling
19150 		adjustDownForAscent to change the y params.
19151 	+/
19152 	static interface ComponentRenderHelper {
19153 
19154 		/+
19155 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19156 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19157 			to move (adjust y to make room for new line) until you get back to the same position,
19158 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19159 
19160 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19161 			once you reach something that is unchanged, you can stop.
19162 		+/
19163 
19164 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19165 
19166 		int ascent() const;
19167 		int descent() const;
19168 
19169 		int advance() const;
19170 
19171 		bool endsWithExplititLineBreak() const;
19172 	}
19173 
19174 	static interface RenderResult {
19175 		/++
19176 			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.
19177 		+/
19178 		void popFront();
19179 		@property bool empty() const;
19180 		@property ComponentRenderHelper front() const;
19181 
19182 		void repositionForNextLine(Point baseline, int availableWidth);
19183 	}
19184 
19185 	static interface ComponentInFlow {
19186 		void draw(ScreenPainter painter);
19187 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19188 
19189 		bool startsWithExplicitLineBreak() const;
19190 	}
19191 
19192 	static class TextFlowComponent : ComponentInFlow {
19193 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19194 
19195 		Color foreground;
19196 		Color background;
19197 
19198 		OperatingSystemFont font; // should NEVER be null
19199 
19200 		ubyte attributes; // underline, strike through, display on new block
19201 
19202 		version(Windows)
19203 			const(wchar)[] content;
19204 		else
19205 			const(char)[] content; // this should NEVER have a newline, except at the end
19206 
19207 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19208 
19209 		// could prolly put some spacing around it too like margin / padding
19210 
19211 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19212 			in { assert(font !is null);
19213 			     assert(!font.isNull); }
19214 			do
19215 		{
19216 			this.foreground = f;
19217 			this.background = b;
19218 			this.font = font;
19219 
19220 			this.attributes = attr;
19221 			version(Windows) {
19222 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19223 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19224 				auto buffer = new wchar[](sz);
19225 				this.content = makeWindowsString(c, buffer, conversionFlags);
19226 			} else {
19227 				this.content = c.dup;
19228 			}
19229 		}
19230 
19231 		void draw(ScreenPainter painter) {
19232 			painter.setFont(this.font);
19233 			painter.outlineColor = this.foreground;
19234 			painter.fillColor = Color.transparent;
19235 			foreach(rendered; this.rendered) {
19236 				// the component works in term of baseline,
19237 				// but the painter works in term of upper left bounding box
19238 				// so need to translate that
19239 
19240 				if(this.background.a) {
19241 					painter.fillColor = this.background;
19242 					painter.outlineColor = this.background;
19243 
19244 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19245 
19246 					painter.outlineColor = this.foreground;
19247 					painter.fillColor = Color.transparent;
19248 				}
19249 
19250 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19251 
19252 				// FIXME: strike through, underline, highlight selection, etc.
19253 			}
19254 		}
19255 	}
19256 
19257 	// I could split the parts into words on render
19258 	// for easier word-wrap, each one being an unbreakable "inline-block"
19259 	private TextFlowComponent[] parts;
19260 	private int needsRerenderFrom;
19261 
19262 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19263 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19264 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19265 	}
19266 
19267 	static struct RenderedComponent {
19268 		int startX;
19269 		int startY;
19270 		short width;
19271 		// 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!
19272 		// for individual chars in here you've gotta process on demand
19273 		version(Windows)
19274 			const(wchar)[] slice;
19275 		else
19276 			const(char)[] slice;
19277 	}
19278 
19279 
19280 	void rerender(Rectangle boundingBox) {
19281 		Point baseline = boundingBox.upperLeft;
19282 
19283 		this.boundingBox.left = boundingBox.left;
19284 		this.boundingBox.top = boundingBox.top;
19285 
19286 		auto remainingParts = parts;
19287 
19288 		int largestX;
19289 
19290 
19291 		foreach(part; parts)
19292 			part.font.prepareContext(window);
19293 		scope(exit)
19294 		foreach(part; parts)
19295 			part.font.releaseContext();
19296 
19297 		calculateNextLine:
19298 
19299 		int nextLineHeight = 0;
19300 		int nextBiggestDescent = 0;
19301 
19302 		foreach(part; remainingParts) {
19303 			auto height = part.font.ascent;
19304 			if(height > nextLineHeight)
19305 				nextLineHeight = height;
19306 			if(part.font.descent > nextBiggestDescent)
19307 				nextBiggestDescent = part.font.descent;
19308 			if(part.content.length && part.content[$-1] == '\n')
19309 				break;
19310 		}
19311 
19312 		baseline.y += nextLineHeight;
19313 		auto lineStart = baseline;
19314 
19315 		while(remainingParts.length) {
19316 			remainingParts[0].rendered = null;
19317 
19318 			bool eol;
19319 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19320 				eol = true;
19321 
19322 			// FIXME: word wrap
19323 			auto font = remainingParts[0].font;
19324 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19325 			auto width = font.stringWidth(slice, window);
19326 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19327 
19328 			remainingParts = remainingParts[1 .. $];
19329 			baseline.x += width;
19330 
19331 			if(eol) {
19332 				baseline.y += nextBiggestDescent;
19333 				if(baseline.x > largestX)
19334 					largestX = baseline.x;
19335 				baseline.x = lineStart.x;
19336 				goto calculateNextLine;
19337 			}
19338 		}
19339 
19340 		if(baseline.x > largestX)
19341 			largestX = baseline.x;
19342 
19343 		this.boundingBox.right = largestX;
19344 		this.boundingBox.bottom = baseline.y;
19345 	}
19346 
19347 	// you must call rerender first!
19348 	void draw(ScreenPainter painter) {
19349 		foreach(part; parts) {
19350 			part.draw(painter);
19351 		}
19352 	}
19353 
19354 	struct IdentifyResult {
19355 		TextFlowComponent part;
19356 		int charIndexInPart;
19357 		int totalCharIndex = -1; // if this is -1, it just means the end
19358 
19359 		Rectangle boundingBox;
19360 	}
19361 
19362 	IdentifyResult identify(Point pt, bool exact = false) {
19363 		if(parts.length == 0)
19364 			return IdentifyResult(null, 0);
19365 
19366 		if(pt.y < boundingBox.top) {
19367 			if(exact)
19368 				return IdentifyResult(null, 1);
19369 			return IdentifyResult(parts[0], 0);
19370 		}
19371 		if(pt.y > boundingBox.bottom) {
19372 			if(exact)
19373 				return IdentifyResult(null, 2);
19374 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19375 		}
19376 
19377 		int tci = 0;
19378 
19379 		// I should probably like binary search this or something...
19380 		foreach(ref part; parts) {
19381 			foreach(rendered; part.rendered) {
19382 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19383 				if(rect.contains(pt)) {
19384 					auto x = pt.x - rendered.startX;
19385 					auto estimatedIdx = x / part.font.averageWidth;
19386 
19387 					if(estimatedIdx < 0)
19388 						estimatedIdx = 0;
19389 
19390 					if(estimatedIdx > rendered.slice.length)
19391 						estimatedIdx = cast(int) rendered.slice.length;
19392 
19393 					int idx;
19394 					int x1, x2;
19395 					if(part.font.isMonospace) {
19396 						auto w = part.font.averageWidth;
19397 						if(!exact && x > (estimatedIdx + 1) * w)
19398 							return IdentifyResult(null, 4);
19399 						idx = estimatedIdx;
19400 						x1 = idx * w;
19401 						x2 = (idx + 1) * w;
19402 					} else {
19403 						idx = estimatedIdx;
19404 
19405 						part.font.prepareContext(window);
19406 						scope(exit) part.font.releaseContext();
19407 
19408 						// int iterations;
19409 
19410 						while(true) {
19411 							// iterations++;
19412 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19413 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19414 
19415 							x1 += rendered.startX;
19416 							x2 += rendered.startX;
19417 
19418 							if(pt.x < x1) {
19419 								if(idx == 0) {
19420 									if(exact)
19421 										return IdentifyResult(null, 6);
19422 									else
19423 										break;
19424 								}
19425 								idx--;
19426 							} else if(pt.x > x2) {
19427 								idx++;
19428 								if(idx > rendered.slice.length) {
19429 									if(exact)
19430 										return IdentifyResult(null, 5);
19431 									else
19432 										break;
19433 								}
19434 							} else if(pt.x >= x1 && pt.x <= x2) {
19435 								if(idx)
19436 									idx--; // point it at the original index
19437 								break; // we fit
19438 							}
19439 						}
19440 
19441 						// import std.stdio; writeln(iterations)
19442 					}
19443 
19444 
19445 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19446 				}
19447 			}
19448 			tci += cast(int) part.content.length; // FIXME: utf-8?
19449 		}
19450 		return IdentifyResult(null, 3);
19451 	}
19452 
19453 	Rectangle boundingBox; // only set after [rerender]
19454 
19455 	// text will be positioned around the exclusion zone
19456 	static struct ExclusionZone {
19457 
19458 	}
19459 
19460 	ExclusionZone[] exclusionZones;
19461 }
19462 
19463 
19464 // Don't use this yet. When I'm happy with it, I will move it to the
19465 // regular module namespace.
19466 mixin template ExperimentalTextComponent() {
19467 
19468 static:
19469 
19470 	alias Rectangle = arsd.color.Rectangle;
19471 
19472 	struct ForegroundColor {
19473 		Color color;
19474 		alias color this;
19475 
19476 		this(Color c) {
19477 			color = c;
19478 		}
19479 
19480 		this(int r, int g, int b, int a = 255) {
19481 			color = Color(r, g, b, a);
19482 		}
19483 
19484 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19485 			return ForegroundColor(mixin("Color." ~ s));
19486 		}
19487 	}
19488 
19489 	struct BackgroundColor {
19490 		Color color;
19491 		alias color this;
19492 
19493 		this(Color c) {
19494 			color = c;
19495 		}
19496 
19497 		this(int r, int g, int b, int a = 255) {
19498 			color = Color(r, g, b, a);
19499 		}
19500 
19501 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19502 			return BackgroundColor(mixin("Color." ~ s));
19503 		}
19504 	}
19505 
19506 	static class InlineElement {
19507 		string text;
19508 
19509 		BlockElement containingBlock;
19510 
19511 		Color color = Color.black;
19512 		Color backgroundColor = Color.transparent;
19513 		ushort styles;
19514 
19515 		string font;
19516 		int fontSize;
19517 
19518 		int lineHeight;
19519 
19520 		void* identifier;
19521 
19522 		Rectangle boundingBox;
19523 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19524 
19525 		bool isMergeCompatible(InlineElement other) {
19526 			return
19527 				containingBlock is other.containingBlock &&
19528 				color == other.color &&
19529 				backgroundColor == other.backgroundColor &&
19530 				styles == other.styles &&
19531 				font == other.font &&
19532 				fontSize == other.fontSize &&
19533 				lineHeight == other.lineHeight &&
19534 				true;
19535 		}
19536 
19537 		int xOfIndex(size_t index) {
19538 			if(index < letterXs.length)
19539 				return letterXs[index];
19540 			else
19541 				return boundingBox.right;
19542 		}
19543 
19544 		InlineElement clone() {
19545 			auto ie = new InlineElement();
19546 			ie.tupleof = this.tupleof;
19547 			return ie;
19548 		}
19549 
19550 		InlineElement getPreviousInlineElement() {
19551 			InlineElement prev = null;
19552 			foreach(ie; this.containingBlock.parts) {
19553 				if(ie is this)
19554 					break;
19555 				prev = ie;
19556 			}
19557 			if(prev is null) {
19558 				BlockElement pb;
19559 				BlockElement cb = this.containingBlock;
19560 				moar:
19561 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19562 					if(ie is cb)
19563 						break;
19564 					pb = ie;
19565 				}
19566 				if(pb is null)
19567 					return null;
19568 				if(pb.parts.length == 0) {
19569 					cb = pb;
19570 					goto moar;
19571 				}
19572 
19573 				prev = pb.parts[$-1];
19574 
19575 			}
19576 			return prev;
19577 		}
19578 
19579 		InlineElement getNextInlineElement() {
19580 			InlineElement next = null;
19581 			foreach(idx, ie; this.containingBlock.parts) {
19582 				if(ie is this) {
19583 					if(idx + 1 < this.containingBlock.parts.length)
19584 						next = this.containingBlock.parts[idx + 1];
19585 					break;
19586 				}
19587 			}
19588 			if(next is null) {
19589 				BlockElement n;
19590 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19591 					if(ie is this.containingBlock) {
19592 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19593 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19594 						break;
19595 					}
19596 				}
19597 				if(n is null)
19598 					return null;
19599 
19600 				if(n.parts.length)
19601 					next = n.parts[0];
19602 				else {} // FIXME
19603 
19604 			}
19605 			return next;
19606 		}
19607 
19608 	}
19609 
19610 	// Block elements are used entirely for positioning inline elements,
19611 	// which are the things that are actually drawn.
19612 	class BlockElement {
19613 		InlineElement[] parts;
19614 		uint alignment;
19615 
19616 		int whiteSpace; // pre, pre-wrap, wrap
19617 
19618 		TextLayout containingLayout;
19619 
19620 		// inputs
19621 		Point where;
19622 		Size minimumSize;
19623 		Size maximumSize;
19624 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19625 		void* identifier;
19626 
19627 		Rectangle margin;
19628 		Rectangle padding;
19629 
19630 		// outputs
19631 		Rectangle[] boundingBoxes;
19632 	}
19633 
19634 	struct TextIdentifyResult {
19635 		InlineElement element;
19636 		int offset;
19637 
19638 		private TextIdentifyResult fixupNewline() {
19639 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19640 				offset--;
19641 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19642 				offset--;
19643 			}
19644 			return this;
19645 		}
19646 	}
19647 
19648 	class TextLayout {
19649 		BlockElement[] blocks;
19650 		Rectangle boundingBox_;
19651 		Rectangle boundingBox() { return boundingBox_; }
19652 		void boundingBox(Rectangle r) {
19653 			if(r != boundingBox_) {
19654 				boundingBox_ = r;
19655 				layoutInvalidated = true;
19656 			}
19657 		}
19658 
19659 		Rectangle contentBoundingBox() {
19660 			Rectangle r;
19661 			foreach(block; blocks)
19662 			foreach(ie; block.parts) {
19663 				if(ie.boundingBox.right > r.right)
19664 					r.right = ie.boundingBox.right;
19665 				if(ie.boundingBox.bottom > r.bottom)
19666 					r.bottom = ie.boundingBox.bottom;
19667 			}
19668 			return r;
19669 		}
19670 
19671 		BlockElement[] getBlocks() {
19672 			return blocks;
19673 		}
19674 
19675 		InlineElement[] getTexts() {
19676 			InlineElement[] elements;
19677 			foreach(block; blocks)
19678 				elements ~= block.parts;
19679 			return elements;
19680 		}
19681 
19682 		string getPlainText() {
19683 			string text;
19684 			foreach(block; blocks)
19685 				foreach(part; block.parts)
19686 					text ~= part.text;
19687 			return text;
19688 		}
19689 
19690 		string getHtml() {
19691 			return null; // FIXME
19692 		}
19693 
19694 		this(Rectangle boundingBox) {
19695 			this.boundingBox = boundingBox;
19696 		}
19697 
19698 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19699 			auto be = new BlockElement();
19700 			be.containingLayout = this;
19701 			if(after is null)
19702 				blocks ~= be;
19703 			else {
19704 				foreach(idx, b; blocks) {
19705 					if(b is after.containingBlock) {
19706 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19707 						break;
19708 					}
19709 				}
19710 			}
19711 			return be;
19712 		}
19713 
19714 		void clear() {
19715 			blocks = null;
19716 			selectionStart = selectionEnd = caret = Caret.init;
19717 		}
19718 
19719 		void addText(Args...)(Args args) {
19720 			if(blocks.length == 0)
19721 				addBlock();
19722 
19723 			InlineElement ie = new InlineElement();
19724 			foreach(idx, arg; args) {
19725 				static if(is(typeof(arg) == ForegroundColor))
19726 					ie.color = arg;
19727 				else static if(is(typeof(arg) == TextFormat)) {
19728 					if(arg & 0x8000) // ~TextFormat.something turns it off
19729 						ie.styles &= arg;
19730 					else
19731 						ie.styles |= arg;
19732 				} else static if(is(typeof(arg) == string)) {
19733 					static if(idx == 0 && args.length > 1)
19734 						static assert(0, "Put styles before the string.");
19735 					size_t lastLineIndex;
19736 					foreach(cidx, char a; arg) {
19737 						if(a == '\n') {
19738 							ie.text = arg[lastLineIndex .. cidx + 1];
19739 							lastLineIndex = cidx + 1;
19740 							ie.containingBlock = blocks[$-1];
19741 							blocks[$-1].parts ~= ie.clone;
19742 							ie.text = null;
19743 						} else {
19744 
19745 						}
19746 					}
19747 
19748 					ie.text = arg[lastLineIndex .. $];
19749 					ie.containingBlock = blocks[$-1];
19750 					blocks[$-1].parts ~= ie.clone;
19751 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
19752 				}
19753 			}
19754 
19755 			invalidateLayout();
19756 		}
19757 
19758 		void tryMerge(InlineElement into, InlineElement what) {
19759 			if(!into.isMergeCompatible(what)) {
19760 				return; // cannot merge, different configs
19761 			}
19762 
19763 			// cool, can merge, bring text together...
19764 			into.text ~= what.text;
19765 
19766 			// and remove what
19767 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
19768 				if(what.containingBlock.parts[a] is what) {
19769 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
19770 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
19771 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
19772 
19773 				}
19774 			}
19775 
19776 			// FIXME: ensure no other carets have a reference to it
19777 		}
19778 
19779 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
19780 		TextIdentifyResult identify(int x, int y, bool exact = false) {
19781 			TextIdentifyResult inexactMatch;
19782 			foreach(block; blocks) {
19783 				foreach(part; block.parts) {
19784 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
19785 
19786 						// FIXME binary search
19787 						int tidx;
19788 						int lastX;
19789 						foreach_reverse(idxo, lx; part.letterXs) {
19790 							int idx = cast(int) idxo;
19791 							if(lx <= x) {
19792 								if(lastX && lastX - x < x - lx)
19793 									tidx = idx + 1;
19794 								else
19795 									tidx = idx;
19796 								break;
19797 							}
19798 							lastX = lx;
19799 						}
19800 
19801 						return TextIdentifyResult(part, tidx).fixupNewline;
19802 					} else if(!exact) {
19803 						// we're not in the box, but are we on the same line?
19804 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
19805 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
19806 					}
19807 				}
19808 			}
19809 
19810 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
19811 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
19812 
19813 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
19814 		}
19815 
19816 		void moveCaretToPixelCoordinates(int x, int y) {
19817 			auto result = identify(x, y);
19818 			caret.inlineElement = result.element;
19819 			caret.offset = result.offset;
19820 		}
19821 
19822 		void selectToPixelCoordinates(int x, int y) {
19823 			auto result = identify(x, y);
19824 
19825 			if(y < caretLastDrawnY1) {
19826 				// on a previous line, carat is selectionEnd
19827 				selectionEnd = caret;
19828 
19829 				selectionStart = Caret(this, result.element, result.offset);
19830 			} else if(y > caretLastDrawnY2) {
19831 				// on a later line
19832 				selectionStart = caret;
19833 
19834 				selectionEnd = Caret(this, result.element, result.offset);
19835 			} else {
19836 				// on the same line...
19837 				if(x <= caretLastDrawnX) {
19838 					selectionEnd = caret;
19839 					selectionStart = Caret(this, result.element, result.offset);
19840 				} else {
19841 					selectionStart = caret;
19842 					selectionEnd = Caret(this, result.element, result.offset);
19843 				}
19844 
19845 			}
19846 		}
19847 
19848 
19849 		/// Call this if the inputs change. It will reflow everything
19850 		void redoLayout(ScreenPainter painter) {
19851 			//painter.setClipRectangle(boundingBox);
19852 			auto pos = Point(boundingBox.left, boundingBox.top);
19853 
19854 			int lastHeight;
19855 			void nl() {
19856 				pos.x = boundingBox.left;
19857 				pos.y += lastHeight;
19858 			}
19859 			foreach(block; blocks) {
19860 				nl();
19861 				foreach(part; block.parts) {
19862 					part.letterXs = null;
19863 
19864 					auto size = painter.textSize(part.text);
19865 					version(Windows)
19866 						if(part.text.length && part.text[$-1] == '\n')
19867 							size.height /= 2; // windows counts the new line at the end, but we don't want that
19868 
19869 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
19870 
19871 					foreach(idx, char c; part.text) {
19872 							// FIXME: unicode
19873 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
19874 					}
19875 
19876 					pos.x += size.width;
19877 					if(pos.x >= boundingBox.right) {
19878 						pos.y += size.height;
19879 						pos.x = boundingBox.left;
19880 						lastHeight = 0;
19881 					} else {
19882 						lastHeight = size.height;
19883 					}
19884 
19885 					if(part.text.length && part.text[$-1] == '\n')
19886 						nl();
19887 				}
19888 			}
19889 
19890 			layoutInvalidated = false;
19891 		}
19892 
19893 		bool layoutInvalidated = true;
19894 		void invalidateLayout() {
19895 			layoutInvalidated = true;
19896 		}
19897 
19898 // FIXME: caret can remain sometimes when inserting
19899 // FIXME: inserting at the beginning once you already have something can eff it up.
19900 		void drawInto(ScreenPainter painter, bool focused = false) {
19901 			if(layoutInvalidated)
19902 				redoLayout(painter);
19903 			foreach(block; blocks) {
19904 				foreach(part; block.parts) {
19905 					painter.outlineColor = part.color;
19906 					painter.fillColor = part.backgroundColor;
19907 
19908 					auto pos = part.boundingBox.upperLeft;
19909 					auto size = part.boundingBox.size;
19910 
19911 					painter.drawText(pos, part.text);
19912 					if(part.styles & TextFormat.underline)
19913 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
19914 					if(part.styles & TextFormat.strikethrough)
19915 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
19916 				}
19917 			}
19918 
19919 			// on every redraw, I will force the caret to be
19920 			// redrawn too, in order to eliminate perceived lag
19921 			// when moving around with the mouse.
19922 			eraseCaret(painter);
19923 
19924 			if(focused) {
19925 				highlightSelection(painter);
19926 				drawCaret(painter);
19927 			}
19928 		}
19929 
19930 		Color selectionXorColor = Color(255, 255, 127);
19931 
19932 		void highlightSelection(ScreenPainter painter) {
19933 			if(selectionStart is selectionEnd)
19934 				return; // no selection
19935 
19936 			if(selectionStart.inlineElement is null) return;
19937 			if(selectionEnd.inlineElement is null) return;
19938 
19939 			assert(selectionStart.inlineElement !is null);
19940 			assert(selectionEnd.inlineElement !is null);
19941 
19942 			painter.rasterOp = RasterOp.xor;
19943 			painter.outlineColor = Color.transparent;
19944 			painter.fillColor = selectionXorColor;
19945 
19946 			auto at = selectionStart.inlineElement;
19947 			auto atOffset = selectionStart.offset;
19948 			bool done;
19949 			while(at) {
19950 				auto box = at.boundingBox;
19951 				if(atOffset < at.letterXs.length)
19952 					box.left = at.letterXs[atOffset];
19953 
19954 				if(at is selectionEnd.inlineElement) {
19955 					if(selectionEnd.offset < at.letterXs.length)
19956 						box.right = at.letterXs[selectionEnd.offset];
19957 					done = true;
19958 				}
19959 
19960 				painter.drawRectangle(box.upperLeft, box.width, box.height);
19961 
19962 				if(done)
19963 					break;
19964 
19965 				at = at.getNextInlineElement();
19966 				atOffset = 0;
19967 			}
19968 		}
19969 
19970 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
19971 		bool caretShowingOnScreen = false;
19972 		void drawCaret(ScreenPainter painter) {
19973 			//painter.setClipRectangle(boundingBox);
19974 			int x, y1, y2;
19975 			if(caret.inlineElement is null) {
19976 				x = boundingBox.left;
19977 				y1 = boundingBox.top + 2;
19978 				y2 = boundingBox.top + painter.fontHeight;
19979 			} else {
19980 				x = caret.inlineElement.xOfIndex(caret.offset);
19981 				y1 = caret.inlineElement.boundingBox.top + 2;
19982 				y2 = caret.inlineElement.boundingBox.bottom - 2;
19983 			}
19984 
19985 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
19986 				eraseCaret(painter);
19987 
19988 			painter.pen = Pen(Color.white, 1);
19989 			painter.rasterOp = RasterOp.xor;
19990 			painter.drawLine(
19991 				Point(x, y1),
19992 				Point(x, y2)
19993 			);
19994 			painter.rasterOp = RasterOp.normal;
19995 			caretShowingOnScreen = !caretShowingOnScreen;
19996 
19997 			if(caretShowingOnScreen) {
19998 				caretLastDrawnX = x;
19999 				caretLastDrawnY1 = y1;
20000 				caretLastDrawnY2 = y2;
20001 			}
20002 		}
20003 
20004 		Rectangle caretBoundingBox() {
20005 			int x, y1, y2;
20006 			if(caret.inlineElement is null) {
20007 				x = boundingBox.left;
20008 				y1 = boundingBox.top + 2;
20009 				y2 = boundingBox.top + 16;
20010 			} else {
20011 				x = caret.inlineElement.xOfIndex(caret.offset);
20012 				y1 = caret.inlineElement.boundingBox.top + 2;
20013 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20014 			}
20015 
20016 			return Rectangle(x, y1, x + 1, y2);
20017 		}
20018 
20019 		void eraseCaret(ScreenPainter painter) {
20020 			//painter.setClipRectangle(boundingBox);
20021 			if(!caretShowingOnScreen) return;
20022 			painter.pen = Pen(Color.white, 1);
20023 			painter.rasterOp = RasterOp.xor;
20024 			painter.drawLine(
20025 				Point(caretLastDrawnX, caretLastDrawnY1),
20026 				Point(caretLastDrawnX, caretLastDrawnY2)
20027 			);
20028 
20029 			caretShowingOnScreen = false;
20030 			painter.rasterOp = RasterOp.normal;
20031 		}
20032 
20033 		/// Caret movement api
20034 		/// These should give the user a logical result based on what they see on screen...
20035 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20036 		void moveUp() {
20037 			if(caret.inlineElement is null) return;
20038 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20039 			auto y = caret.inlineElement.boundingBox.top + 2;
20040 
20041 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20042 			if(y < 0)
20043 				return;
20044 
20045 			auto i = identify(x, y);
20046 
20047 			if(i.element) {
20048 				caret.inlineElement = i.element;
20049 				caret.offset = i.offset;
20050 			}
20051 		}
20052 		void moveDown() {
20053 			if(caret.inlineElement is null) return;
20054 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20055 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20056 
20057 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20058 
20059 			auto i = identify(x, y);
20060 			if(i.element) {
20061 				caret.inlineElement = i.element;
20062 				caret.offset = i.offset;
20063 			}
20064 		}
20065 		void moveLeft() {
20066 			if(caret.inlineElement is null) return;
20067 			if(caret.offset)
20068 				caret.offset--;
20069 			else {
20070 				auto p = caret.inlineElement.getPreviousInlineElement();
20071 				if(p) {
20072 					caret.inlineElement = p;
20073 					if(p.text.length && p.text[$-1] == '\n')
20074 						caret.offset = cast(int) p.text.length - 1;
20075 					else
20076 						caret.offset = cast(int) p.text.length;
20077 				}
20078 			}
20079 		}
20080 		void moveRight() {
20081 			if(caret.inlineElement is null) return;
20082 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20083 				caret.offset++;
20084 			} else {
20085 				auto p = caret.inlineElement.getNextInlineElement();
20086 				if(p) {
20087 					caret.inlineElement = p;
20088 					caret.offset = 0;
20089 				}
20090 			}
20091 		}
20092 		void moveHome() {
20093 			if(caret.inlineElement is null) return;
20094 			auto x = 0;
20095 			auto y = caret.inlineElement.boundingBox.top + 2;
20096 
20097 			auto i = identify(x, y);
20098 
20099 			if(i.element) {
20100 				caret.inlineElement = i.element;
20101 				caret.offset = i.offset;
20102 			}
20103 		}
20104 		void moveEnd() {
20105 			if(caret.inlineElement is null) return;
20106 			auto x = int.max;
20107 			auto y = caret.inlineElement.boundingBox.top + 2;
20108 
20109 			auto i = identify(x, y);
20110 
20111 			if(i.element) {
20112 				caret.inlineElement = i.element;
20113 				caret.offset = i.offset;
20114 			}
20115 
20116 		}
20117 		void movePageUp(ref Caret caret) {}
20118 		void movePageDown(ref Caret caret) {}
20119 
20120 		void moveDocumentStart(ref Caret caret) {
20121 			if(blocks.length && blocks[0].parts.length)
20122 				caret = Caret(this, blocks[0].parts[0], 0);
20123 			else
20124 				caret = Caret.init;
20125 		}
20126 
20127 		void moveDocumentEnd(ref Caret caret) {
20128 			if(blocks.length) {
20129 				auto parts = blocks[$-1].parts;
20130 				if(parts.length) {
20131 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20132 				} else {
20133 					caret = Caret.init;
20134 				}
20135 			} else
20136 				caret = Caret.init;
20137 		}
20138 
20139 		void deleteSelection() {
20140 			if(selectionStart is selectionEnd)
20141 				return;
20142 
20143 			if(selectionStart.inlineElement is null) return;
20144 			if(selectionEnd.inlineElement is null) return;
20145 
20146 			assert(selectionStart.inlineElement !is null);
20147 			assert(selectionEnd.inlineElement !is null);
20148 
20149 			auto at = selectionStart.inlineElement;
20150 
20151 			if(selectionEnd.inlineElement is at) {
20152 				// same element, need to chop out
20153 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20154 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20155 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20156 			} else {
20157 				// different elements, we can do it with slicing
20158 				at.text = at.text[0 .. selectionStart.offset];
20159 				if(selectionStart.offset < at.letterXs.length)
20160 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20161 
20162 				at = at.getNextInlineElement();
20163 
20164 				while(at) {
20165 					if(at is selectionEnd.inlineElement) {
20166 						at.text = at.text[selectionEnd.offset .. $];
20167 						if(selectionEnd.offset < at.letterXs.length)
20168 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20169 						selectionEnd.offset = 0;
20170 						break;
20171 					} else {
20172 						auto cfd = at;
20173 						cfd.text = null; // delete the whole thing
20174 
20175 						at = at.getNextInlineElement();
20176 
20177 						if(cfd.text.length == 0) {
20178 							// and remove cfd
20179 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20180 								if(cfd.containingBlock.parts[a] is cfd) {
20181 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20182 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20183 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20184 
20185 								}
20186 							}
20187 						}
20188 					}
20189 				}
20190 			}
20191 
20192 			caret = selectionEnd;
20193 			selectNone();
20194 
20195 			invalidateLayout();
20196 
20197 		}
20198 
20199 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20200 		void insert(in char[] text) {
20201 			foreach(dchar ch; text)
20202 				insert(ch);
20203 		}
20204 		/// ditto
20205 		void insert(dchar ch) {
20206 
20207 			bool selectionDeleted = false;
20208 			if(selectionStart !is selectionEnd) {
20209 				deleteSelection();
20210 				selectionDeleted = true;
20211 			}
20212 
20213 			if(ch == 127) {
20214 				delete_();
20215 				return;
20216 			}
20217 			if(ch == 8) {
20218 				if(!selectionDeleted)
20219 					backspace();
20220 				return;
20221 			}
20222 
20223 			invalidateLayout();
20224 
20225 			if(ch == 13) ch = 10;
20226 			auto e = caret.inlineElement;
20227 			if(e is null) {
20228 				addText("" ~ cast(char) ch) ; // FIXME
20229 				return;
20230 			}
20231 
20232 			if(caret.offset == e.text.length) {
20233 				e.text ~= cast(char) ch; // FIXME
20234 				caret.offset++;
20235 				if(ch == 10) {
20236 					auto c = caret.inlineElement.clone;
20237 					c.text = null;
20238 					c.letterXs = null;
20239 					insertPartAfter(c,e);
20240 					caret = Caret(this, c, 0);
20241 				}
20242 			} else {
20243 				// FIXME cast char sucks
20244 				if(ch == 10) {
20245 					auto c = caret.inlineElement.clone;
20246 					c.text = e.text[caret.offset .. $];
20247 					if(caret.offset < c.letterXs.length)
20248 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20249 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20250 					if(caret.offset <= e.letterXs.length) {
20251 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20252 					}
20253 					insertPartAfter(c,e);
20254 					caret = Caret(this, c, 0);
20255 				} else {
20256 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20257 					caret.offset++;
20258 				}
20259 			}
20260 		}
20261 
20262 		void insertPartAfter(InlineElement what, InlineElement where) {
20263 			foreach(idx, p; where.containingBlock.parts) {
20264 				if(p is where) {
20265 					if(idx + 1 == where.containingBlock.parts.length)
20266 						where.containingBlock.parts ~= what;
20267 					else
20268 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20269 					return;
20270 				}
20271 			}
20272 		}
20273 
20274 		void cleanupStructures() {
20275 			for(size_t i = 0; i < blocks.length; i++) {
20276 				auto block = blocks[i];
20277 				for(size_t a = 0; a < block.parts.length; a++) {
20278 					auto part = block.parts[a];
20279 					if(part.text.length == 0) {
20280 						for(size_t b = a; b < block.parts.length - 1; b++)
20281 							block.parts[b] = block.parts[b+1];
20282 						block.parts = block.parts[0 .. $-1];
20283 					}
20284 				}
20285 				if(block.parts.length == 0) {
20286 					for(size_t a = i; a < blocks.length - 1; a++)
20287 						blocks[a] = blocks[a+1];
20288 					blocks = blocks[0 .. $-1];
20289 				}
20290 			}
20291 		}
20292 
20293 		void backspace() {
20294 			try_again:
20295 			auto e = caret.inlineElement;
20296 			if(e is null)
20297 				return;
20298 			if(caret.offset == 0) {
20299 				auto prev = e.getPreviousInlineElement();
20300 				if(prev is null)
20301 					return;
20302 				auto newOffset = cast(int) prev.text.length;
20303 				tryMerge(prev, e);
20304 				caret.inlineElement = prev;
20305 				caret.offset = prev is null ? 0 : newOffset;
20306 
20307 				goto try_again;
20308 			} else if(caret.offset == e.text.length) {
20309 				e.text = e.text[0 .. $-1];
20310 				caret.offset--;
20311 			} else {
20312 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20313 				caret.offset--;
20314 			}
20315 			//cleanupStructures();
20316 
20317 			invalidateLayout();
20318 		}
20319 		void delete_() {
20320 			if(selectionStart !is selectionEnd)
20321 				deleteSelection();
20322 			else {
20323 				auto before = caret;
20324 				moveRight();
20325 				if(caret != before) {
20326 					backspace();
20327 				}
20328 			}
20329 
20330 			invalidateLayout();
20331 		}
20332 		void overstrike() {}
20333 
20334 		/// Selection API. See also: caret movement.
20335 		void selectAll() {
20336 			moveDocumentStart(selectionStart);
20337 			moveDocumentEnd(selectionEnd);
20338 		}
20339 		bool selectNone() {
20340 			if(selectionStart != selectionEnd) {
20341 				selectionStart = selectionEnd = Caret.init;
20342 				return true;
20343 			}
20344 			return false;
20345 		}
20346 
20347 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20348 		/// They will modify the current selection if there is one and will splice one in if needed.
20349 		void changeAttributes() {}
20350 
20351 
20352 		/// Text search api. They manipulate the selection and/or caret.
20353 		void findText(string text) {}
20354 		void findIndex(size_t textIndex) {}
20355 
20356 		// sample event handlers
20357 
20358 		void handleEvent(KeyEvent event) {
20359 			//if(event.type == KeyEvent.Type.KeyPressed) {
20360 
20361 			//}
20362 		}
20363 
20364 		void handleEvent(dchar ch) {
20365 
20366 		}
20367 
20368 		void handleEvent(MouseEvent event) {
20369 
20370 		}
20371 
20372 		bool contentEditable; // can it be edited?
20373 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20374 		bool contentSelectable; // selectable?
20375 
20376 		Caret caret;
20377 		Caret selectionStart;
20378 		Caret selectionEnd;
20379 
20380 		bool insertMode;
20381 	}
20382 
20383 	struct Caret {
20384 		TextLayout layout;
20385 		InlineElement inlineElement;
20386 		int offset;
20387 	}
20388 
20389 	enum TextFormat : ushort {
20390 		// decorations
20391 		underline = 1,
20392 		strikethrough = 2,
20393 
20394 		// font selectors
20395 
20396 		bold = 0x4000 | 1, // weight 700
20397 		light = 0x4000 | 2, // weight 300
20398 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20399 		// bold | light is really invalid but should give weight 500
20400 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20401 
20402 		italic = 0x4000 | 8,
20403 		smallcaps = 0x4000 | 16,
20404 	}
20405 
20406 	void* findFont(string family, int weight, TextFormat formats) {
20407 		return null;
20408 	}
20409 
20410 }
20411 
20412 /++
20413 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20414 
20415 	History:
20416 		Added February 19, 2021
20417 +/
20418 /// Group: drag_and_drop
20419 interface DropHandler {
20420 	/++
20421 		Called when the drag enters the handler's area.
20422 	+/
20423 	DragAndDropAction dragEnter(DropPackage*);
20424 	/++
20425 		Called when the drag leaves the handler's area or is
20426 		cancelled. You should free your resources when this is called.
20427 	+/
20428 	void dragLeave();
20429 	/++
20430 		Called continually as the drag moves over the handler's area.
20431 
20432 		Returns: feedback to the dragger
20433 	+/
20434 	DropParameters dragOver(Point pt);
20435 	/++
20436 		The user dropped the data and you should process it now. You can
20437 		access the data through the given [DropPackage].
20438 	+/
20439 	void drop(scope DropPackage*);
20440 	/++
20441 		Called when the drop is complete. You should free whatever temporary
20442 		resources you were using. It is often reasonable to simply forward
20443 		this call to [dragLeave].
20444 	+/
20445 	void finish();
20446 
20447 	/++
20448 		Parameters returned by [DropHandler.drop].
20449 	+/
20450 	static struct DropParameters {
20451 		/++
20452 			Acceptable action over this area.
20453 		+/
20454 		DragAndDropAction action;
20455 		/++
20456 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20457 
20458 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20459 		+/
20460 		Rectangle consistentWithin;
20461 	}
20462 }
20463 
20464 /++
20465 	History:
20466 		Added February 19, 2021
20467 +/
20468 /// Group: drag_and_drop
20469 enum DragAndDropAction {
20470 	none = 0,
20471 	copy,
20472 	move,
20473 	link,
20474 	ask,
20475 	custom
20476 }
20477 
20478 /++
20479 	An opaque structure representing dropped data. It contains
20480 	private, platform-specific data that your `drop` function
20481 	should simply forward.
20482 
20483 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20484 
20485 	History:
20486 		Added February 19, 2021
20487 +/
20488 /// Group: drag_and_drop
20489 struct DropPackage {
20490 	/++
20491 		Lists the available formats as magic numbers. You should compare these
20492 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20493 		understand the passed data.
20494 	+/
20495 	DraggableData.FormatId[] availableFormats() {
20496 		version(X11) {
20497 			return xFormats;
20498 		} else version(Windows) {
20499 			if(pDataObj is null)
20500 				return null;
20501 
20502 			typeof(return) ret;
20503 
20504 			IEnumFORMATETC ef;
20505 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20506 				FORMATETC fmt;
20507 				ULONG fetched;
20508 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20509 					if(fetched == 0)
20510 						break;
20511 
20512 					if(fmt.lindex != -1)
20513 						continue;
20514 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20515 						continue;
20516 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20517 						continue;
20518 
20519 					ret ~= fmt.cfFormat;
20520 				}
20521 			}
20522 
20523 			return ret;
20524 		}
20525 	}
20526 
20527 	/++
20528 		Gets data from the drop and optionally accepts it.
20529 
20530 		Returns:
20531 			void because the data is fed asynchronously through the `dg` parameter.
20532 
20533 		Params:
20534 			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.
20535 
20536 			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.
20537 
20538 			Calling `getData` again after accepting a drop is not permitted.
20539 
20540 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20541 
20542 			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.
20543 
20544 		Throws:
20545 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20546 
20547 		History:
20548 			Included in first release of [DropPackage].
20549 	+/
20550 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20551 		version(X11) {
20552 
20553 			auto display = XDisplayConnection.get();
20554 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20555 			auto best = format;
20556 
20557 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20558 
20559 				XDisplay* display;
20560 				Atom selectionAtom;
20561 				DraggableData.FormatId best;
20562 				DraggableData.FormatId format;
20563 				void delegate(scope ubyte[] data) dg;
20564 				DragAndDropAction acceptedAction;
20565 				Window sourceWindow;
20566 				SimpleWindow win;
20567 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20568 					this.display = display;
20569 					this.win = win;
20570 					this.sourceWindow = sourceWindow;
20571 					this.format = format;
20572 					this.selectionAtom = selectionAtom;
20573 					this.best = best;
20574 					this.dg = dg;
20575 					this.acceptedAction = acceptedAction;
20576 				}
20577 
20578 
20579 				mixin X11GetSelectionHandler_Basics;
20580 
20581 				void handleData(Atom target, in ubyte[] data) {
20582 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20583 
20584 					dg(cast(ubyte[]) data);
20585 
20586 					if(acceptedAction != DragAndDropAction.none) {
20587 						auto display = XDisplayConnection.get;
20588 
20589 						XClientMessageEvent xclient;
20590 
20591 						xclient.type = EventType.ClientMessage;
20592 						xclient.window = sourceWindow;
20593 						xclient.message_type = GetAtom!"XdndFinished"(display);
20594 						xclient.format = 32;
20595 						xclient.data.l[0] = win.impl.window;
20596 						xclient.data.l[1] = 1; // drop successful
20597 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20598 
20599 						XSendEvent(
20600 							display,
20601 							sourceWindow,
20602 							false,
20603 							EventMask.NoEventMask,
20604 							cast(XEvent*) &xclient
20605 						);
20606 
20607 						XFlush(display);
20608 					}
20609 				}
20610 
20611 				Atom findBestFormat(Atom[] answer) {
20612 					Atom best = None;
20613 					foreach(option; answer) {
20614 						if(option == format) {
20615 							best = option;
20616 							break;
20617 						}
20618 						/*
20619 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20620 							best = option;
20621 							break;
20622 						} else if(option == XA_STRING) {
20623 							best = option;
20624 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20625 							best = option;
20626 						}
20627 						*/
20628 					}
20629 					return best;
20630 				}
20631 			}
20632 
20633 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20634 
20635 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20636 
20637 		} else version(Windows) {
20638 
20639 			// clean up like DragLeave
20640 			// pass effect back up
20641 
20642 			FORMATETC t;
20643 			assert(format >= 0 && format <= ushort.max);
20644 			t.cfFormat = cast(ushort) format;
20645 			t.lindex = -1;
20646 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20647 			t.tymed = TYMED.TYMED_HGLOBAL;
20648 
20649 			STGMEDIUM m;
20650 
20651 			if(pDataObj.GetData(&t, &m) != S_OK) {
20652 				// fail
20653 			} else {
20654 				// succeed, take the data and clean up
20655 
20656 				// FIXME: ensure it is legit HGLOBAL
20657 				auto handle = m.hGlobal;
20658 
20659 				if(handle) {
20660 					auto sz = GlobalSize(handle);
20661 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20662 						scope(exit) GlobalUnlock(handle);
20663 						scope(exit) GlobalFree(handle);
20664 
20665 						auto data = ptr[0 .. sz];
20666 
20667 						dg(data);
20668 					}
20669 				}
20670 			}
20671 		}
20672 	}
20673 
20674 	private:
20675 
20676 	version(X11) {
20677 		SimpleWindow win;
20678 		Window sourceWindow;
20679 		Time dataTimestamp;
20680 
20681 		Atom[] xFormats;
20682 	}
20683 	version(Windows) {
20684 		IDataObject pDataObj;
20685 	}
20686 }
20687 
20688 /++
20689 	A generic helper base class for making a drop handler with a preference list of custom types.
20690 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20691 	droppers too.
20692 
20693 	It assumes the whole window it used, but you can subclass to change that.
20694 
20695 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20696 
20697 	History:
20698 		Added February 19, 2021
20699 +/
20700 /// Group: drag_and_drop
20701 class GenericDropHandlerBase : DropHandler {
20702 	// no fancy state here so no need to do anything here
20703 	void finish() { }
20704 	void dragLeave() { }
20705 
20706 	private DragAndDropAction acceptedAction;
20707 	private DraggableData.FormatId acceptedFormat;
20708 	private void delegate(scope ubyte[]) acceptedHandler;
20709 
20710 	struct FormatHandler {
20711 		DraggableData.FormatId format;
20712 		void delegate(scope ubyte[]) handler;
20713 	}
20714 
20715 	protected abstract FormatHandler[] formatHandlers();
20716 
20717 	DragAndDropAction dragEnter(DropPackage* pkg) {
20718 		debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
20719 		foreach(fmt; formatHandlers())
20720 		foreach(f; pkg.availableFormats())
20721 			if(f == fmt.format) {
20722 				acceptedFormat = f;
20723 				acceptedHandler = fmt.handler;
20724 				return acceptedAction = DragAndDropAction.copy;
20725 			}
20726 		return acceptedAction = DragAndDropAction.none;
20727 	}
20728 	DropParameters dragOver(Point pt) {
20729 		return DropParameters(acceptedAction);
20730 	}
20731 
20732 	void drop(scope DropPackage* dropPackage) {
20733 		if(!acceptedFormat || acceptedHandler is null) {
20734 			debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
20735 			return; // prolly shouldn't happen anyway...
20736 		}
20737 
20738 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
20739 	}
20740 }
20741 
20742 /++
20743 	A simple handler for making your window accept drops of plain text.
20744 
20745 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20746 
20747 	History:
20748 		Added February 22, 2021
20749 +/
20750 /// Group: drag_and_drop
20751 class TextDropHandler : GenericDropHandlerBase {
20752 	private void delegate(in char[] text) dg;
20753 
20754 	/++
20755 
20756 	+/
20757 	this(void delegate(in char[] text) dg) {
20758 		this.dg = dg;
20759 	}
20760 
20761 	protected override FormatHandler[] formatHandlers() {
20762 		version(X11)
20763 			return [
20764 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
20765 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
20766 			];
20767 		else version(Windows)
20768 			return [
20769 				FormatHandler(CF_UNICODETEXT, &translator),
20770 			];
20771 	}
20772 
20773 	private void translator(scope ubyte[] data) {
20774 		version(X11)
20775 			dg(cast(char[]) data);
20776 		else version(Windows)
20777 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
20778 	}
20779 }
20780 
20781 /++
20782 	A simple handler for making your window accept drops of files, issued to you as file names.
20783 
20784 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20785 
20786 	History:
20787 		Added February 22, 2021
20788 +/
20789 /// Group: drag_and_drop
20790 
20791 class FilesDropHandler : GenericDropHandlerBase {
20792 	private void delegate(in char[][]) dg;
20793 
20794 	/++
20795 
20796 	+/
20797 	this(void delegate(in char[][] fileNames) dg) {
20798 		this.dg = dg;
20799 	}
20800 
20801 	protected override FormatHandler[] formatHandlers() {
20802 		version(X11)
20803 			return [
20804 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
20805 			];
20806 		else version(Windows)
20807 			return [
20808 				FormatHandler(CF_HDROP, &translator),
20809 			];
20810 	}
20811 
20812 	private void translator(scope ubyte[] data) {
20813 		version(X11) {
20814 			char[] listString = cast(char[]) data;
20815 			char[][16] buffer;
20816 			int count;
20817 			char[][] result = buffer[];
20818 
20819 			void commit(char[] s) {
20820 				if(count == result.length)
20821 					result.length += 16;
20822 				if(s.length > 7 && s[0 ..7] == "file://")
20823 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
20824 				result[count++] = s;
20825 			}
20826 
20827 			size_t last;
20828 			foreach(idx, char c; listString) {
20829 				if(c == '\n') {
20830 					commit(listString[last .. idx - 1]); // a \r
20831 					last = idx + 1; // a \n
20832 				}
20833 			}
20834 
20835 			if(last < listString.length) {
20836 				commit(listString[last .. $]);
20837 			}
20838 
20839 			// FIXME: they are uris now, should I translate it to local file names?
20840 			// of course the host name is supposed to be there cuz of X rokking...
20841 
20842 			dg(result[0 .. count]);
20843 		} else version(Windows) {
20844 
20845 			static struct DROPFILES {
20846 				DWORD pFiles;
20847 				POINT pt;
20848 				BOOL  fNC;
20849 				BOOL  fWide;
20850 			}
20851 
20852 
20853 			const(char)[][16] buffer;
20854 			int count;
20855 			const(char)[][] result = buffer[];
20856 			size_t last;
20857 
20858 			void commitA(in char[] stuff) {
20859 				if(count == result.length)
20860 					result.length += 16;
20861 				result[count++] = stuff;
20862 			}
20863 
20864 			void commitW(in wchar[] stuff) {
20865 				commitA(makeUtf8StringFromWindowsString(stuff));
20866 			}
20867 
20868 			void magic(T)(T chars) {
20869 				size_t idx;
20870 				while(chars[idx]) {
20871 					last = idx;
20872 					while(chars[idx]) {
20873 						idx++;
20874 					}
20875 					static if(is(T == char*))
20876 						commitA(chars[last .. idx]);
20877 					else
20878 						commitW(chars[last .. idx]);
20879 					idx++;
20880 				}
20881 			}
20882 
20883 			auto df = cast(DROPFILES*) data.ptr;
20884 			if(df.fWide) {
20885 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
20886 				magic(chars);
20887 			} else {
20888 				char* chars = cast(char*) (data.ptr + df.pFiles);
20889 				magic(chars);
20890 			}
20891 			dg(result[0 .. count]);
20892 		}
20893 	}
20894 }
20895 
20896 /++
20897 	Interface to describe data being dragged. See also [draggable] helper function.
20898 
20899 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20900 
20901 	History:
20902 		Added February 19, 2021
20903 +/
20904 interface DraggableData {
20905 	version(X11)
20906 		alias FormatId = Atom;
20907 	else
20908 		alias FormatId = uint;
20909 	/++
20910 		Gets the platform-specific FormatId associated with the given named format.
20911 
20912 		This may be a MIME type, but may also be other various strings defined by the
20913 		programs you want to interoperate with.
20914 
20915 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
20916 		and convert it to some particular type for you.
20917 	+/
20918 	static FormatId getFormatId(string name)() {
20919 		version(X11)
20920 			return GetAtom!name(XDisplayConnection.get);
20921 		else version(Windows) {
20922 			static UINT cache;
20923 			if(!cache)
20924 				cache = RegisterClipboardFormatA(name);
20925 			return cache;
20926 		} else
20927 			throw new NotYetImplementedException();
20928 	}
20929 
20930 	/++
20931 		Looks up a string to represent the name for the given format, if there is one.
20932 
20933 		You should avoid using this function because it is slow. It is provided more for
20934 		debugging than for primary use.
20935 	+/
20936 	static string getFormatName(FormatId format) {
20937 		version(X11) {
20938 			if(format == 0)
20939 				return "None";
20940 			else
20941 				return getAtomName(format, XDisplayConnection.get);
20942 		} else version(Windows) {
20943 			switch(format) {
20944 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
20945 				case CF_DIBV5: return "CF_DIBV5";
20946 				case CF_RIFF: return "CF_RIFF";
20947 				case CF_WAVE: return "CF_WAVE";
20948 				case CF_HDROP: return "CF_HDROP";
20949 				default:
20950 					char[1024] name;
20951 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
20952 					return name[0 .. count].idup;
20953 			}
20954 		}
20955 	}
20956 
20957 	FormatId[] availableFormats();
20958 	// Return the slice of data you filled, empty slice if done.
20959 	// this is to support the incremental thing
20960 	ubyte[] getData(FormatId format, return scope ubyte[] data);
20961 
20962 	size_t dataLength(FormatId format);
20963 }
20964 
20965 /++
20966 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20967 
20968 	History:
20969 		Added February 19, 2021
20970 +/
20971 DraggableData draggable(string s) {
20972 	version(X11)
20973 	return new class X11SetSelectionHandler_Text, DraggableData {
20974 		this() {
20975 			super(s);
20976 		}
20977 
20978 		override FormatId[] availableFormats() {
20979 			return X11SetSelectionHandler_Text.availableFormats();
20980 		}
20981 
20982 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
20983 			return X11SetSelectionHandler_Text.getData(format, data);
20984 		}
20985 
20986 		size_t dataLength(FormatId format) {
20987 			return s.length;
20988 		}
20989 	};
20990 	version(Windows)
20991 	return new class DraggableData {
20992 		FormatId[] availableFormats() {
20993 			return [CF_UNICODETEXT];
20994 		}
20995 
20996 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
20997 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
20998 		}
20999 
21000 		size_t dataLength(FormatId format) {
21001 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21002 		}
21003 	};
21004 }
21005 
21006 /++
21007 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21008 
21009 	History:
21010 		Added February 19, 2021
21011 +/
21012 /// Group: drag_and_drop
21013 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21014 in {
21015 	assert(window !is null);
21016 	assert(handler !is null);
21017 }
21018 do
21019 {
21020 	version(X11) {
21021 		auto sh = cast(X11SetSelectionHandler) handler;
21022 		if(sh is null) {
21023 			// gotta make my own adapter.
21024 			sh = new class X11SetSelectionHandler {
21025 				mixin X11SetSelectionHandler_Basics;
21026 
21027 				Atom[] availableFormats() { return handler.availableFormats(); }
21028 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21029 					return handler.getData(format, data);
21030 				}
21031 
21032 				// since the drop selection is only ever used once it isn't important
21033 				// to reset it.
21034 				void done() {}
21035 			};
21036 		}
21037 		return doDragDropX11(window, sh, action);
21038 	} else version(Windows) {
21039 		return doDragDropWindows(window, handler, action);
21040 	} else throw new NotYetImplementedException();
21041 }
21042 
21043 version(Windows)
21044 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21045 	IDataObject obj = new class IDataObject {
21046 		ULONG refCount;
21047 		ULONG AddRef() {
21048 			return ++refCount;
21049 		}
21050 		ULONG Release() {
21051 			return --refCount;
21052 		}
21053 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21054 			if (IID_IUnknown == *riid) {
21055 				*ppv = cast(void*) cast(IUnknown) this;
21056 			}
21057 			else if (IID_IDataObject == *riid) {
21058 				*ppv = cast(void*) cast(IDataObject) this;
21059 			}
21060 			else {
21061 				*ppv = null;
21062 				return E_NOINTERFACE;
21063 			}
21064 
21065 			AddRef();
21066 			return NOERROR;
21067 		}
21068 
21069 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21070 			// import std.stdio; writeln("Advise");
21071 			return E_NOTIMPL;
21072 		}
21073 		HRESULT DUnadvise(DWORD dwConnection) {
21074 			return E_NOTIMPL;
21075 		}
21076 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21077 			// import std.stdio; writeln("EnumDAdvise");
21078 			return OLE_E_ADVISENOTSUPPORTED;
21079 		}
21080 		// tell what formats it supports
21081 
21082 		FORMATETC[] types;
21083 		this() {
21084 			FORMATETC t;
21085 			foreach(ty; handler.availableFormats()) {
21086 				assert(ty <= ushort.max && ty >= 0);
21087 				t.cfFormat = cast(ushort) ty;
21088 				t.lindex = -1;
21089 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21090 				t.tymed = TYMED.TYMED_HGLOBAL;
21091 			}
21092 			types ~= t;
21093 		}
21094 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21095 			if(dwDirection == DATADIR.DATADIR_GET) {
21096 				*ppenumFormatEtc = new class IEnumFORMATETC {
21097 					ULONG refCount;
21098 					ULONG AddRef() {
21099 						return ++refCount;
21100 					}
21101 					ULONG Release() {
21102 						return --refCount;
21103 					}
21104 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21105 						if (IID_IUnknown == *riid) {
21106 							*ppv = cast(void*) cast(IUnknown) this;
21107 						}
21108 						else if (IID_IEnumFORMATETC == *riid) {
21109 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21110 						}
21111 						else {
21112 							*ppv = null;
21113 							return E_NOINTERFACE;
21114 						}
21115 
21116 						AddRef();
21117 						return NOERROR;
21118 					}
21119 
21120 
21121 					int pos;
21122 					this() {
21123 						pos = 0;
21124 					}
21125 
21126 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21127 						// import std.stdio; writeln("clone");
21128 						return E_NOTIMPL; // FIXME
21129 					}
21130 
21131 					// Caller is responsible for freeing memory
21132 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21133 						// fetched may be null if celt is one
21134 						if(celt != 1)
21135 							return E_NOTIMPL; // FIXME
21136 
21137 						if(celt + pos > types.length)
21138 							return S_FALSE;
21139 
21140 						*rgelt = types[pos++];
21141 
21142 						if(pceltFetched !is null)
21143 							*pceltFetched = 1;
21144 
21145 						// import std.stdio; writeln("ok celt ", celt);
21146 						return S_OK;
21147 					}
21148 
21149 					HRESULT Reset() {
21150 						pos = 0;
21151 						return S_OK;
21152 					}
21153 
21154 					HRESULT Skip(ULONG celt) {
21155 						if(celt + pos <= types.length) {
21156 							pos += celt;
21157 							return S_OK;
21158 						}
21159 						return S_FALSE;
21160 					}
21161 				};
21162 
21163 				return S_OK;
21164 			} else
21165 				return E_NOTIMPL;
21166 		}
21167 		// given a format, return the format you'd prefer to use cuz it is identical
21168 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21169 			// FIXME: prolly could be better but meh
21170 			// import std.stdio; writeln("gcf: ", *pformatectIn);
21171 			*pformatetcOut = *pformatectIn;
21172 			return S_OK;
21173 		}
21174 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21175 			foreach(ty; types) {
21176 				if(ty == *pformatetcIn) {
21177 					auto format = ty.cfFormat;
21178 					// import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty);
21179 					STGMEDIUM medium;
21180 					medium.tymed = TYMED.TYMED_HGLOBAL;
21181 
21182 					auto sz = handler.dataLength(format);
21183 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21184 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21185 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21186 						auto slice = data[0 .. sz];
21187 						scope(exit)
21188 							GlobalUnlock(handle);
21189 
21190 						handler.getData(format, cast(ubyte[]) slice[]);
21191 					}
21192 
21193 
21194 					medium.hGlobal = handle; // FIXME
21195 					*pmedium = medium;
21196 					return S_OK;
21197 				}
21198 			}
21199 			return DV_E_FORMATETC;
21200 		}
21201 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21202 			// import std.stdio; writeln("GDH: ", *pformatetcIn);
21203 			return E_NOTIMPL; // FIXME
21204 		}
21205 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21206 			auto search = *pformatetc;
21207 			search.tymed &= TYMED.TYMED_HGLOBAL;
21208 			foreach(ty; types)
21209 				if(ty == search) {
21210 					// import std.stdio; writeln("QueryGetData ", search, " ", types[0]);
21211 					return S_OK;
21212 				}
21213 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21214 				//import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]);
21215 			}
21216 			return S_FALSE;
21217 		}
21218 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21219 			// import std.stdio; writeln("SetData: ");
21220 			return E_NOTIMPL;
21221 		}
21222 	};
21223 
21224 
21225 	IDropSource src = new class IDropSource {
21226 		ULONG refCount;
21227 		ULONG AddRef() {
21228 			return ++refCount;
21229 		}
21230 		ULONG Release() {
21231 			return --refCount;
21232 		}
21233 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21234 			if (IID_IUnknown == *riid) {
21235 				*ppv = cast(void*) cast(IUnknown) this;
21236 			}
21237 			else if (IID_IDropSource == *riid) {
21238 				*ppv = cast(void*) cast(IDropSource) this;
21239 			}
21240 			else {
21241 				*ppv = null;
21242 				return E_NOINTERFACE;
21243 			}
21244 
21245 			AddRef();
21246 			return NOERROR;
21247 		}
21248 
21249 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21250 			if(fEscapePressed)
21251 				return DRAGDROP_S_CANCEL;
21252 			if(!(grfKeyState & MK_LBUTTON))
21253 				return DRAGDROP_S_DROP;
21254 			return S_OK;
21255 		}
21256 
21257 		int GiveFeedback(uint dwEffect) {
21258 			return DRAGDROP_S_USEDEFAULTCURSORS;
21259 		}
21260 	};
21261 
21262 	DWORD effect;
21263 
21264 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21265 
21266 	DROPEFFECT de = win32DragAndDropAction(action);
21267 
21268 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21269 	// but still prolly a FIXME
21270 
21271 	auto ret = DoDragDrop(obj, src, de, &effect);
21272 	/+
21273 	import std.stdio;
21274 	if(ret == DRAGDROP_S_DROP)
21275 		writeln("drop ", effect);
21276 	else if(ret == DRAGDROP_S_CANCEL)
21277 		writeln("cancel");
21278 	else if(ret == S_OK)
21279 		writeln("ok");
21280 	else writeln(ret);
21281 	+/
21282 
21283 	return ret;
21284 }
21285 
21286 version(Windows)
21287 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21288 	DROPEFFECT de;
21289 
21290 	with(DragAndDropAction)
21291 	with(DROPEFFECT)
21292 	final switch(action) {
21293 		case none: de = DROPEFFECT_NONE; break;
21294 		case copy: de = DROPEFFECT_COPY; break;
21295 		case move: de = DROPEFFECT_MOVE; break;
21296 		case link: de = DROPEFFECT_LINK; break;
21297 		case ask: throw new Exception("ask not implemented yet");
21298 		case custom: throw new Exception("custom not implemented yet");
21299 	}
21300 
21301 	return de;
21302 }
21303 
21304 
21305 /++
21306 	History:
21307 		Added February 19, 2021
21308 +/
21309 /// Group: drag_and_drop
21310 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21311 	version(X11) {
21312 		auto display = XDisplayConnection.get;
21313 
21314 		Atom atom = 5; // right???
21315 
21316 		XChangeProperty(
21317 			display,
21318 			window.impl.window,
21319 			GetAtom!"XdndAware"(display),
21320 			XA_ATOM,
21321 			32 /* bits */,
21322 			PropModeReplace,
21323 			&atom,
21324 			1);
21325 
21326 		window.dropHandler = handler;
21327 	} else version(Windows) {
21328 
21329 		initDnd();
21330 
21331 		auto dropTarget = new class (handler) IDropTarget {
21332 			DropHandler handler;
21333 			this(DropHandler handler) {
21334 				this.handler = handler;
21335 			}
21336 			ULONG refCount;
21337 			ULONG AddRef() {
21338 				return ++refCount;
21339 			}
21340 			ULONG Release() {
21341 				return --refCount;
21342 			}
21343 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21344 				if (IID_IUnknown == *riid) {
21345 					*ppv = cast(void*) cast(IUnknown) this;
21346 				}
21347 				else if (IID_IDropTarget == *riid) {
21348 					*ppv = cast(void*) cast(IDropTarget) this;
21349 				}
21350 				else {
21351 					*ppv = null;
21352 					return E_NOINTERFACE;
21353 				}
21354 
21355 				AddRef();
21356 				return NOERROR;
21357 			}
21358 
21359 
21360 			// ///////////////////
21361 
21362 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21363 				DropPackage dropPackage = DropPackage(pDataObj);
21364 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21365 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21366 			}
21367 
21368 			HRESULT DragLeave() {
21369 				handler.dragLeave();
21370 				// release the IDataObject if needed
21371 				return S_OK;
21372 			}
21373 
21374 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21375 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21376 
21377 				*pdwEffect = win32DragAndDropAction(res.action);
21378 				// same as DragEnter basically
21379 				return S_OK;
21380 			}
21381 
21382 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21383 				DropPackage pkg = DropPackage(pDataObj);
21384 				handler.drop(&pkg);
21385 
21386 				return S_OK;
21387 			}
21388 		};
21389 		// Windows can hold on to the handler and try to call it
21390 		// during which time the GC can't see it. so important to
21391 		// manually manage this. At some point i'll FIXME and make
21392 		// all my com instances manually managed since they supposed
21393 		// to respect the refcount.
21394 		import core.memory;
21395 		GC.addRoot(cast(void*) dropTarget);
21396 
21397 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21398 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21399 
21400 		window.dropHandler = handler;
21401 	} else throw new NotYetImplementedException();
21402 }
21403 
21404 
21405 
21406 static if(UsingSimpledisplayX11) {
21407 
21408 enum _NET_WM_STATE_ADD = 1;
21409 enum _NET_WM_STATE_REMOVE = 0;
21410 enum _NET_WM_STATE_TOGGLE = 2;
21411 
21412 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21413 void demandAttention(SimpleWindow window, bool needs = true) {
21414 	demandAttention(window.impl.window, needs);
21415 }
21416 
21417 /// ditto
21418 void demandAttention(Window window, bool needs = true) {
21419 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21420 }
21421 
21422 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21423 	auto display = XDisplayConnection.get();
21424 	if(atom == None)
21425 		return; // non-failure error
21426 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21427 
21428 	XClientMessageEvent xclient;
21429 
21430 	xclient.type = EventType.ClientMessage;
21431 	xclient.window = window;
21432 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21433 	xclient.format = 32;
21434 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21435 	xclient.data.l[1] = atom;
21436 	xclient.data.l[2] = atom2;
21437 	xclient.data.l[3] = 1;
21438 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21439 
21440 	XSendEvent(
21441 		display,
21442 		RootWindow(display, DefaultScreen(display)),
21443 		false,
21444 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21445 		cast(XEvent*) &xclient
21446 	);
21447 
21448 	/+
21449 	XChangeProperty(
21450 		display,
21451 		window.impl.window,
21452 		GetAtom!"_NET_WM_STATE"(display),
21453 		XA_ATOM,
21454 		32 /* bits */,
21455 		PropModeAppend,
21456 		&atom,
21457 		1);
21458 	+/
21459 }
21460 
21461 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21462 	Atom actionAtom;
21463 	with(DragAndDropAction)
21464 	final switch(action) {
21465 		case none: actionAtom = None; break;
21466 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21467 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21468 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21469 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21470 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21471 	}
21472 
21473 	return actionAtom;
21474 }
21475 
21476 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21477 	// FIXME: I need to show user feedback somehow.
21478 	auto display = XDisplayConnection.get;
21479 
21480 	auto actionAtom = dndActionAtom(display, action);
21481 	assert(actionAtom, "Don't use action none to accept a drop");
21482 
21483 	setX11Selection!"XdndSelection"(window, handler, null);
21484 
21485 	auto oldKeyHandler = window.handleKeyEvent;
21486 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21487 
21488 	auto oldCharHandler = window.handleCharEvent;
21489 	scope(exit) window.handleCharEvent = oldCharHandler;
21490 
21491 	auto oldMouseHandler = window.handleMouseEvent;
21492 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21493 
21494 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21495 
21496 	import core.sys.posix.sys.time;
21497 	timeval tv;
21498 	gettimeofday(&tv, null);
21499 
21500 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21501 
21502 	Time lastMouseTimestamp;
21503 
21504 	bool dnding = true;
21505 	Window lastIn = None;
21506 
21507 	void leave() {
21508 		if(lastIn == None)
21509 			return;
21510 
21511 		XEvent ev;
21512 		ev.xclient.type = EventType.ClientMessage;
21513 		ev.xclient.window = lastIn;
21514 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21515 		ev.xclient.format = 32;
21516 		ev.xclient.data.l[0] = window.impl.window;
21517 
21518 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21519 		XFlush(display);
21520 
21521 		lastIn = None;
21522 	}
21523 
21524 	void enter(Window w) {
21525 		assert(lastIn == None);
21526 
21527 		lastIn = w;
21528 
21529 		XEvent ev;
21530 		ev.xclient.type = EventType.ClientMessage;
21531 		ev.xclient.window = lastIn;
21532 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21533 		ev.xclient.format = 32;
21534 		ev.xclient.data.l[0] = window.impl.window;
21535 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21536 
21537 		auto types = handler.availableFormats();
21538 		assert(types.length > 0);
21539 
21540 		ev.xclient.data.l[2] = types[0];
21541 		if(types.length > 1)
21542 			ev.xclient.data.l[3] = types[1];
21543 		if(types.length > 2)
21544 			ev.xclient.data.l[4] = types[2];
21545 
21546 		// FIXME: other types?!?!? and make sure we skip TARGETS
21547 
21548 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21549 		XFlush(display);
21550 	}
21551 
21552 	void position(int rootX, int rootY) {
21553 		assert(lastIn != None);
21554 
21555 		XEvent ev;
21556 		ev.xclient.type = EventType.ClientMessage;
21557 		ev.xclient.window = lastIn;
21558 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21559 		ev.xclient.format = 32;
21560 		ev.xclient.data.l[0] = window.impl.window;
21561 		ev.xclient.data.l[1] = 0; // reserved
21562 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21563 		ev.xclient.data.l[3] = dataTimestamp;
21564 		ev.xclient.data.l[4] = actionAtom;
21565 
21566 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21567 		XFlush(display);
21568 
21569 	}
21570 
21571 	void drop() {
21572 		XEvent ev;
21573 		ev.xclient.type = EventType.ClientMessage;
21574 		ev.xclient.window = lastIn;
21575 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21576 		ev.xclient.format = 32;
21577 		ev.xclient.data.l[0] = window.impl.window;
21578 		ev.xclient.data.l[1] = 0; // reserved
21579 		ev.xclient.data.l[2] = dataTimestamp;
21580 
21581 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21582 		XFlush(display);
21583 
21584 		lastIn = None;
21585 		dnding = false;
21586 	}
21587 
21588 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21589 	// but idk if i should...
21590 
21591 	window.setEventHandlers(
21592 		delegate(KeyEvent ev) {
21593 			if(ev.pressed == true && ev.key == Key.Escape) {
21594 				// cancel
21595 				dnding = false;
21596 			}
21597 		},
21598 		delegate(MouseEvent ev) {
21599 			if(ev.timestamp < lastMouseTimestamp)
21600 				return;
21601 
21602 			lastMouseTimestamp = ev.timestamp;
21603 
21604 			if(ev.type == MouseEventType.motion) {
21605 				auto display = XDisplayConnection.get;
21606 				auto root = RootWindow(display, DefaultScreen(display));
21607 
21608 				Window topWindow;
21609 				int rootX, rootY;
21610 
21611 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21612 
21613 				if(topWindow == None)
21614 					return;
21615 
21616 				top:
21617 				if(auto result = topWindow in eligibility) {
21618 					auto dropWindow = *result;
21619 					if(dropWindow == None) {
21620 						leave();
21621 						return;
21622 					}
21623 
21624 					if(dropWindow != lastIn) {
21625 						leave();
21626 						enter(dropWindow);
21627 						position(rootX, rootY);
21628 					} else {
21629 						position(rootX, rootY);
21630 					}
21631 				} else {
21632 					// determine eligibility
21633 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21634 					if(data.length == 1) {
21635 						// in case there is no WM or it isn't reparenting
21636 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21637 					} else {
21638 
21639 						Window tryScanChildren(Window search, int maxRecurse) {
21640 							// could be reparenting window manager, so gotta check the next few children too
21641 							Window child;
21642 							int x;
21643 							int y;
21644 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21645 
21646 							if(child == None)
21647 								return None;
21648 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21649 							if(data.length == 1) {
21650 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21651 							} else {
21652 								if(maxRecurse)
21653 									return tryScanChildren(child, maxRecurse - 1);
21654 								else
21655 									return None;
21656 							}
21657 
21658 						}
21659 
21660 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21661 						auto topResult = tryScanChildren(topWindow, 3);
21662 						// it is easy to have a false negative due to the mouse going over a WM
21663 						// child window like the close button if separate from the frame... so I
21664 						// can't really cache negatives, :(
21665 						if(topResult != None) {
21666 							eligibility[topWindow] = topResult;
21667 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21668 						}
21669 					}
21670 
21671 				}
21672 
21673 			} else if(ev.type == MouseEventType.buttonReleased) {
21674 				drop();
21675 				dnding = false;
21676 			}
21677 		}
21678 	);
21679 
21680 	window.grabInput();
21681 	scope(exit)
21682 		window.releaseInputGrab();
21683 
21684 
21685 	EventLoop.get.run(() => dnding);
21686 
21687 	return 0;
21688 }
21689 
21690 /// X-specific
21691 TrueColorImage getWindowNetWmIcon(Window window) {
21692 	try {
21693 		auto display = XDisplayConnection.get;
21694 
21695 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21696 
21697 		if (data.length > arch_ulong.sizeof * 2) {
21698 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21699 			// these are an array of rgba images that we have to convert into pixmaps ourself
21700 
21701 			int width = cast(int) meta[0];
21702 			int height = cast(int) meta[1];
21703 
21704 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21705 
21706 			static if(arch_ulong.sizeof == 4) {
21707 				bytes = bytes[0 .. width * height * 4];
21708 				alias imageData = bytes;
21709 			} else static if(arch_ulong.sizeof == 8) {
21710 				bytes = bytes[0 .. width * height * 8];
21711 				auto imageData = new ubyte[](4 * width * height);
21712 			} else static assert(0);
21713 
21714 
21715 
21716 			// this returns ARGB. Remember it is little-endian so
21717 			//                                         we have BGRA
21718 			// our thing uses RGBA, which in little endian, is ABGR
21719 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
21720 				auto r = bytes[idx + 2];
21721 				auto g = bytes[idx + 1];
21722 				auto b = bytes[idx + 0];
21723 				auto a = bytes[idx + 3];
21724 
21725 				imageData[idx2 + 0] = r;
21726 				imageData[idx2 + 1] = g;
21727 				imageData[idx2 + 2] = b;
21728 				imageData[idx2 + 3] = a;
21729 			}
21730 
21731 			return new TrueColorImage(width, height, imageData);
21732 		}
21733 
21734 		return null;
21735 	} catch(Exception e) {
21736 		return null;
21737 	}
21738 }
21739 
21740 } /* UsingSimpledisplayX11 */
21741 
21742 
21743 void loadBinNameToWindowClassName () {
21744 	import core.stdc.stdlib : realloc;
21745 	version(linux) {
21746 		// args[0] MAY be empty, so we'll just use this
21747 		import core.sys.posix.unistd : readlink;
21748 		char[1024] ebuf = void; // 1KB should be enough for everyone!
21749 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
21750 		if (len < 1) return;
21751 	} else /*version(Windows)*/ {
21752 		import core.runtime : Runtime;
21753 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
21754 		auto ebuf = Runtime.args[0];
21755 		auto len = ebuf.length;
21756 	}
21757 	auto pos = len;
21758 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
21759 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
21760 	if (sdpyWindowClassStr is null) return; // oops
21761 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
21762 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
21763 }
21764 
21765 /++
21766 	An interface representing a font that is drawn with custom facilities.
21767 
21768 	You might want [OperatingSystemFont] instead, which represents
21769 	a font loaded and drawn by functions native to the operating system.
21770 
21771 	WARNING: I might still change this.
21772 +/
21773 interface DrawableFont : MeasurableFont {
21774 	/++
21775 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
21776 
21777 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
21778 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
21779 		fill color, but that's up to the implementation.
21780 	+/
21781 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
21782 
21783 	/++
21784 		Requests that the given string is added to the image cache. You should only do this rarely, but
21785 		if you have a string that you know will be used over and over again, adding it to a cache can
21786 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
21787 		to implement this as a do-nothing method).
21788 	+/
21789 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
21790 }
21791 
21792 /++
21793 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
21794 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
21795 
21796 	You should also consider [OperatingSystemFont], which loads and draws a font with
21797 	facilities native to the user's operating system. You might also consider
21798 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
21799 	of game, as they have their own ways to draw text too.
21800 
21801 	Be warned: this can be slow, especially on remote connections to the X server, since
21802 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
21803 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
21804 	experiment in your specific case.
21805 
21806 	Please note that the return type of [DrawableFont] also includes an implementation of
21807 	[MeasurableFont].
21808 +/
21809 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
21810 	import arsd.ttf;
21811 	static class ArsdTtfFont : DrawableFont {
21812 		TtfFont font;
21813 		int size;
21814 		this(in ubyte[] data, int size) {
21815 			font = TtfFont(data);
21816 			this.size = size;
21817 
21818 
21819 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
21820 			int ascent_, descent_, line_gap;
21821 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
21822 
21823 			int advance, lsb;
21824 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
21825 			xWidth = cast(int) (advance * scale);
21826 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
21827 			MWidth = cast(int) (advance * scale);
21828 		}
21829 
21830 		private int ascent_;
21831 		private int descent_;
21832 		private int xWidth;
21833 		private int MWidth;
21834 
21835 		bool isMonospace() {
21836 			return xWidth == MWidth;
21837 		}
21838 		int averageWidth() {
21839 			return xWidth;
21840 		}
21841 		int height() {
21842 			return size;
21843 		}
21844 		int ascent() {
21845 			return ascent_;
21846 		}
21847 		int descent() {
21848 			return descent_;
21849 		}
21850 
21851 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
21852 			int width, height;
21853 			font.getStringSize(s, size, width, height);
21854 			return width;
21855 		}
21856 
21857 
21858 
21859 		Sprite[string] cache;
21860 
21861 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
21862 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
21863 			cache[text] = sprite;
21864 		}
21865 
21866 		Image stringToImage(Color fg, Color bg, in char[] text) {
21867 			int width, height;
21868 			auto data = font.renderString(text, size, width, height);
21869 			auto image = new TrueColorImage(width, height);
21870 			int pos = 0;
21871 			foreach(y; 0 .. height)
21872 			foreach(x; 0 .. width) {
21873 				fg.a = data[0];
21874 				bg.a = 255;
21875 				auto color = alphaBlend(fg, bg);
21876 				image.imageData.bytes[pos++] = color.r;
21877 				image.imageData.bytes[pos++] = color.g;
21878 				image.imageData.bytes[pos++] = color.b;
21879 				image.imageData.bytes[pos++] = data[0];
21880 				data = data[1 .. $];
21881 			}
21882 			assert(data.length == 0);
21883 
21884 			return Image.fromMemoryImage(image);
21885 		}
21886 
21887 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
21888 			Sprite sprite = (text in cache) ? *(text in cache) : null;
21889 
21890 			auto fg = painter.impl._outlineColor;
21891 			auto bg = painter.impl._fillColor;
21892 
21893 			if(sprite !is null) {
21894 				auto w = cast(SimpleWindow) painter.window;
21895 				assert(w !is null);
21896 
21897 				sprite.drawAt(painter, upperLeft);
21898 			} else {
21899 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
21900 			}
21901 		}
21902 	}
21903 
21904 	return new ArsdTtfFont(data, size);
21905 }
21906 
21907 class NotYetImplementedException : Exception {
21908 	this(string file = __FILE__, size_t line = __LINE__) {
21909 		super("Not yet implemented", file, line);
21910 	}
21911 }
21912 
21913 ///
21914 __gshared bool librariesSuccessfullyLoaded = true;
21915 ///
21916 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
21917 
21918 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
21919 	mixin(staticForeachReplacement!Iface);
21920 
21921 	void loadDynamicLibrary() @nogc {
21922 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
21923 	}
21924 
21925         void loadDynamicLibraryForReal() {
21926                 foreach(name; __traits(derivedMembers, Iface)) {
21927                         mixin("alias tmp = " ~ name ~ ";");
21928                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
21929                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
21930                 }
21931         }
21932 }
21933 
21934 private const(char)[] staticForeachReplacement(Iface)() pure {
21935 /*
21936 	// just this for gdc 9....
21937 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
21938 
21939         static foreach(name; __traits(derivedMembers, Iface))
21940                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
21941 */
21942 
21943 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
21944 	size_t pos;
21945 
21946 	void append(in char[] what) {
21947 		if(pos + what.length > code.length)
21948 			code.length = (code.length * 3) / 2;
21949 		code[pos .. pos + what.length] = what[];
21950 		pos += what.length;
21951 	}
21952 
21953         foreach(name; __traits(derivedMembers, Iface)) {
21954                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
21955 		append(name);
21956 		append(`")) `);
21957 		append(name);
21958 		append(";");
21959 	}
21960 
21961 	return code[0 .. pos];
21962 }
21963 
21964 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
21965 	mixin(staticForeachReplacement!Iface);
21966 
21967 	private __gshared void* libHandle;
21968 	private __gshared bool attempted;
21969 
21970         void loadDynamicLibrary() @nogc {
21971 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
21972 	}
21973 
21974 	bool loadAttempted() {
21975 		return attempted;
21976 	}
21977 	bool loadSuccessful() {
21978 		return libHandle !is null;
21979 	}
21980 
21981         void loadDynamicLibraryForReal() {
21982 		attempted = true;
21983                 version(Posix) {
21984                         import core.sys.posix.dlfcn;
21985 			version(OSX) {
21986 				version(X11)
21987                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
21988 				else
21989                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
21990 			} else {
21991                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
21992 				if(libHandle is null)
21993                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
21994 			}
21995 
21996 			static void* loadsym(void* l, const char* name) {
21997 				import core.stdc.stdlib;
21998 				if(l is null)
21999 					return &abort;
22000 				return dlsym(l, name);
22001 			}
22002                 } else version(Windows) {
22003                         import core.sys.windows.winbase;
22004                         libHandle = LoadLibrary(library ~ ".dll");
22005 			static void* loadsym(void* l, const char* name) {
22006 				import core.stdc.stdlib;
22007 				if(l is null)
22008 					return &abort;
22009 				return GetProcAddress(l, name);
22010 			}
22011                 }
22012                 if(libHandle is null) {
22013 			success = false;
22014                         //throw new Exception("load failure of library " ~ library);
22015 		}
22016                 foreach(name; __traits(derivedMembers, Iface)) {
22017                         mixin("alias tmp = " ~ name ~ ";");
22018                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22019                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22020                 }
22021         }
22022 
22023         void unloadDynamicLibrary() {
22024                 version(Posix) {
22025                         import core.sys.posix.dlfcn;
22026                         dlclose(libHandle);
22027                 } else version(Windows) {
22028                         import core.sys.windows.winbase;
22029                         FreeLibrary(libHandle);
22030                 }
22031                 foreach(name; __traits(derivedMembers, Iface))
22032                         mixin(name ~ " = null;");
22033         }
22034 }
22035 
22036 /+
22037 	The GC can be called from any thread, and a lot of cleanup must be done
22038 	on the gui thread. Since the GC can interrupt any locks - including being
22039 	triggered inside a critical section - it is vital to avoid deadlocks to get
22040 	these functions called from the right place.
22041 
22042 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22043 	right now.
22044 
22045 	The cleanup function is run when the event loop gets around to it, which is just
22046 	whenever there's something there after it has been woken up for other work. It does
22047 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22048 	(Well actually it might be ok but i don't wanna mess with it right now.)
22049 +/
22050 private struct CleanupQueue {
22051 	import core.stdc.stdlib;
22052 
22053 	void queue(alias func, T...)(T args) {
22054 		static struct Args {
22055 			T args;
22056 		}
22057 		static struct RealJob {
22058 			Job j;
22059 			Args a;
22060 		}
22061 		static void call(Job* data) {
22062 			auto rj = cast(RealJob*) data;
22063 			func(rj.a.args);
22064 		}
22065 
22066 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22067 		thing.j.call = &call;
22068 		thing.a.args = args;
22069 
22070 		buffer[tail++] = cast(Job*) thing;
22071 
22072 		// FIXME: set overflowed
22073 	}
22074 
22075 	void process() {
22076 		const tail = this.tail;
22077 
22078 		while(tail != head) {
22079 			Job* job = cast(Job*) buffer[head++];
22080 			job.call(job);
22081 			free(job);
22082 		}
22083 
22084 		if(overflowed)
22085 			throw new Exception("cleanup overflowed");
22086 	}
22087 
22088 	private:
22089 
22090 	ubyte tail; // must ONLY be written by queue
22091 	ubyte head; // must ONLY be written by process
22092 	bool overflowed;
22093 
22094 	static struct Job {
22095 		void function(Job*) call;
22096 	}
22097 
22098 	void*[256] buffer;
22099 }
22100 private __gshared CleanupQueue cleanupQueue;
22101 
22102 version(X11)
22103 /++
22104 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22105 
22106 	$(WARNING
22107 		This function is exempted from stability guarantees.
22108 	)
22109 +/
22110 float customScalingFactorForMonitor(int monitorNumber) {
22111 	import core.stdc.stdlib;
22112 	auto val = getenv("ARSD_SCALING_FACTOR");
22113 
22114 	if(val is null)
22115 		return 1.0;
22116 
22117 	char[16] buffer = 0;
22118 	int pos;
22119 
22120 	const(char)* at = val;
22121 
22122 	foreach(item; 0 .. monitorNumber + 1) {
22123 		if(*at == 0)
22124 			break; // reuse the last number when we at the end of the string
22125 		pos = 0;
22126 		while(pos + 1 < buffer.length && *at && *at != ';') {
22127 			buffer[pos++] = *at;
22128 			at++;
22129 		}
22130 		if(*at)
22131 			at++; // skip the semicolon
22132 		buffer[pos] = 0;
22133 	}
22134 
22135 	//sdpyPrintDebugString(buffer[0 .. pos]);
22136 
22137 	import core.stdc.math;
22138 	auto f = atof(buffer.ptr);
22139 
22140 	if(f <= 0.0 || isnan(f) || isinf(f))
22141 		return 1.0;
22142 
22143 	return f;
22144 }
22145 
22146 void guiAbortProcess(string msg) {
22147 	import core.stdc.stdlib;
22148 	version(Windows) {
22149 		WCharzBuffer t = WCharzBuffer(msg);
22150 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22151 	} else {
22152 		import core.stdc.stdio;
22153 		fwrite(msg.ptr, 1, msg.length, stderr);
22154 		msg = "\n";
22155 		fwrite(msg.ptr, 1, msg.length, stderr);
22156 		fflush(stderr);
22157 	}
22158 
22159 	abort();
22160 }
22161 
22162 private int minInternal(int a, int b) {
22163 	return (a < b) ? a : b;
22164 }
22165 
22166 private alias scriptable = arsd_jsvar_compatible;