The OpenD Programming Language

1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 /+
4 	To share some stuff between two opengl threads:
5 	windows
6 	https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading
7 	linux
8 	https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux
9 +/
10 
11 
12 // Search for: FIXME: leaks if multithreaded gc
13 
14 // https://freedesktop.org/wiki/Specifications/XDND/
15 
16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
17 
18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
20 
21 
22 // on Mac with X11: -L-L/usr/X11/lib
23 
24 /+
25 
26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
27 
28 	Progress bar in taskbar
29 		- i can probably just set a property on the window...
30 		  it sets that prop to an integer 0 .. 100. Taskbar
31 		  deletes it or window deletes it when it is handled.
32 		- prolly display it as a nice little line at the bottom.
33 
34 
35 from gtk:
36 
37 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
38 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
39 
40 >+  if (cardinal > 0)
41 >+  {
42 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
45 >+                     XA_CARDINAL, 32,
46 >+                     PropModeReplace,
47 >+                     (guchar *) &cardinal, 1);
48 >+  }
49 >+  else
50 >+  {
51 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
52 >+                     xid,
53 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
54 >+  }
55 
56 from Windows:
57 
58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
59 
60 interface
61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
63 listen for msg, return TRUE
64 interface->SetProgressState(hwnd, TBPF_NORMAL);
65 interface->SetProgressValue(hwnd, 40, 100);
66 
67 
68 	My new notification system.
69 		- use a unix socket? or a x property? or a udp port?
70 		- could of course also get on the dbus train but ugh.
71 		- it could also reply with the info as a string for easy remote examination.
72 
73 +/
74 
75 /*
76 	Event Loop would be nices:
77 
78 	* add on idle - runs when nothing else happens
79 		* which can specify how long to yield for
80 	* send messages without a recipient window
81 	* setTimeout
82 	* setInterval
83 */
84 
85 /*
86 	Classic games I want to add:
87 		* my tetris clone
88 		* pac man
89 */
90 
91 /*
92 	Text layout needs a lot of work. Plain drawText is useful but too
93 	limited. It will need some kind of text context thing which it will
94 	update and you can pass it on and get more details out of it.
95 
96 	It will need a bounding box, a current cursor location that is updated
97 	as drawing continues, and various changable facts (which can also be
98 	changed on the painter i guess) like font, color, size, background,
99 	etc.
100 
101 	We can also fetch the caret location from it somehow.
102 
103 	Should prolly be an overload of drawText
104 
105 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
106 
107 		WS_EX_NOACTIVATE
108 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
109 		full screen windows. Can just set the atom on X. Windows will be harder.
110 
111 		moving windows. resizing windows.
112 
113 		hide cursor, capture cursor, change cursor.
114 
115 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
116 	sure the pieces are there to do its job easily and make other jobs possible.
117 */
118 
119 /++
120 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
121 	including creating windows, drawing on them, working with the clipboard,
122 	timers, OpenGL, and more. However, it does NOT provide high level GUI
123 	widgets. See my minigui.d, an extension to this module, for that
124 	functionality.
125 
126 	simpledisplay provides cross-platform wrapping for Windows and Linux
127 	(and perhaps other OSes that use X11), but also does not prevent you
128 	from using the underlying facilities if you need them. It has a goal
129 	of working efficiently over a remote X link (at least as far as Xlib
130 	reasonably allows.)
131 
132 	simpledisplay depends on [arsd.color|color.d], which should be available from the
133 	same place where you got this file. Other than that, however, it has
134 	very few dependencies and ones that don't come with the OS and/or the
135 	compiler are all opt-in.
136 
137 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
138 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
139 
140 	simpledisplay is basically stable. I plan to refactor the internals,
141 	and may add new features and fix bugs, but It do not expect to
142 	significantly change the API. It has been stable a few years already now.
143 
144 	Installation_instructions:
145 
146 	`simpledisplay.d` does not have any dependencies outside the
147 	operating system and `color.d`, so it should just work most the
148 	time, but there are a few caveats on some systems:
149 
150 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
151 	console to be automatically allocated.
152 
153 	Please note when compiling on Win64, you need to explicitly list
154 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
155 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
156 
157 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
158 	note the "w".
159 
160 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
161 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
162 	See [EnableWindowsSubsystem] for more information.
163 
164 	$(PITFALL
165 		With the Windows subsystem, there is no console, so standard writeln will throw!
166 		You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
167 		create a console as needed.
168 	)
169 
170 	On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command.
171 
172 	On Ubuntu, you might need to install X11 development libraries to
173 	successfully link.
174 
175 	$(CONSOLE
176 		$ sudo apt-get install libglc-dev
177 		$ sudo apt-get install libx11-dev
178 	)
179 
180 
181 	Jump_list:
182 
183 	Don't worry, you don't have to read this whole documentation file!
184 
185 	Check out the [#event-example] and [#Pong-example] to get started quickly.
186 
187 	The main classes you may want to create are [SimpleWindow], [Timer],
188 	[Image], and [Sprite].
189 
190 	The main functions you'll want are [setClipboardText] and [getClipboardText].
191 
192 	There are also platform-specific functions available such as [XDisplayConnection]
193 	and [GetAtom] for X11, among others.
194 
195 	See the examples and topics list below to learn more.
196 
197 	$(WARNING
198 		There should only be one GUI thread per application,
199 		and all windows should be created in it and your
200 		event loop should run there.
201 
202 		To do otherwise is undefined behavior and has no
203 		cross platform guarantees.
204 	)
205 
206 	$(H2 About this documentation)
207 
208 	The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow.
209 
210 	Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples!
211 
212 	All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them.
213 
214 	To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example.
215 
216 	If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file.
217 
218 	At points, I will talk about implementation details in the documentation. These are sometimes
219 	subject to change, but nevertheless useful to understand what is really going on. You can learn
220 	more about some of the referenced things by searching the web for info about using them from C.
221 	You can always look at the source of simpledisplay.d too for the most authoritative source on
222 	its specific implementation. If you disagree with how I did something, please contact me so we
223 	can discuss it!
224 
225 	$(H2 Using with fibers)
226 
227 	simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor).
228 
229 	$(H2 Topics)
230 
231 	$(H3 $(ID topic-windows) Windows)
232 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
233 		window on the user's screen.
234 
235 		You may create multiple windows, if the underlying platform supports it. You may check
236 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
237 		SimpleWindow's constructor at runtime to handle those cases.
238 
239 		A single running event loop will handle as many windows as needed.
240 
241 	$(H3 $(ID topic-event-loops) Event loops)
242 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
243 
244 		The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there:
245 
246 		---
247 		// dmd example.d simpledisplay.d color.d
248 		import arsd.simpledisplay;
249 		void main() {
250 			auto window = new SimpleWindow(200, 200);
251 			window.eventLoop(0,
252 			  delegate (dchar) { /* got a character key press */ }
253 			);
254 		}
255 		---
256 
257 		$(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.)
258 
259 		On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run.
260 
261 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
262 
263 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
264 
265 		You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway.
266 
267 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
268 		Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like.
269 
270 		See the [NotificationAreaIcon] class.
271 
272 	$(H3 $(ID topic-input-handling) Input handling)
273 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
274 
275 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
276 
277 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
278 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
279 
280 		Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example:
281 
282 		---
283 		// dmd example.d simpledisplay.d color.d
284 		import arsd.simpledisplay;
285 		void main() {
286 			auto window = new SimpleWindow(200, 200);
287 			{ // introduce sub-scope
288 				auto painter = window.draw(); // begin drawing
289 				/* draw here */
290 				painter.outlineColor = Color.red;
291 				painter.fillColor = Color.black;
292 				painter.drawRectangle(Point(0, 0), 200, 200);
293 			} // end scope, calling `painter`'s destructor, drawing to the screen.
294 			window.eventLoop(0); // handle events
295 		}
296 		---
297 
298 		Painting is done based on two color properties, a pen and a brush.
299 
300 		At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead.
301 
302 		FIXME Add example of 2d opengl drawing here.
303 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
304 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
305 
306 		Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it.
307 
308 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
309 
310 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
311 
312 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon].
313 
314 		simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program.
315 
316 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
317 
318 		---
319 		import arsd.simpledisplay;
320 
321 		void main() {
322 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
323 
324 			float otherColor = 0.0;
325 			float colorDelta = 0.05;
326 
327 			window.redrawOpenGlScene = delegate() {
328 				glLoadIdentity();
329 				glBegin(GL_QUADS);
330 
331 				glColor3f(1.0, otherColor, 0);
332 				glVertex3f(-0.8, -0.8, 0);
333 
334 				glColor3f(1.0, otherColor, 1.0);
335 				glVertex3f(0.8, -0.8, 0);
336 
337 				glColor3f(0, 1.0, otherColor);
338 				glVertex3f(0.8, 0.8, 0);
339 
340 				glColor3f(otherColor, 0, 1.0);
341 				glVertex3f(-0.8, 0.8, 0);
342 
343 				glEnd();
344 			};
345 
346 			window.eventLoop(50, () {
347 				otherColor += colorDelta;
348 				if(otherColor > 1.0) {
349 					otherColor = 1.0;
350 					colorDelta = -0.05;
351 				}
352 				if(otherColor < 0) {
353 					otherColor = 0;
354 					colorDelta = 0.05;
355 				}
356 				// at the end of the timer, we have to request a redraw
357 				// or we won't see the changes.
358 				window.redrawOpenGlSceneSoon();
359 			});
360 		}
361 		---
362 
363 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
364 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
365 		simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too.
366 
367 		This example program shows how you can set up a shader to draw a rectangle:
368 
369 		---
370 		import arsd.simpledisplay;
371 
372 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
373 
374 		void main() {
375 			// First thing we do, before creating the window, is declare what version we want.
376 			setOpenGLContextVersion(3, 3);
377 			// turning off legacy compat is required to use version 3.3 and newer
378 			openGLContextCompatible = false;
379 
380 			uint VAO;
381 			OpenGlShader shader;
382 
383 			// then we can create the window.
384 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
385 
386 			// additional setup needs to be done when it is visible, simpledisplay offers a property
387 			// for exactly that:
388 			window.visibleForTheFirstTime = delegate() {
389 				// now with the window loaded, we can start loading the modern opengl functions.
390 
391 				// you MUST set the context first.
392 				window.setAsCurrentOpenGlContext;
393 				// then load the remainder of the library
394 				gl3.loadDynamicLibrary();
395 
396 				// now you can create the shaders, etc.
397 				shader = new OpenGlShader(
398 					OpenGlShader.Source(GL_VERTEX_SHADER, `
399 						#version 330 core
400 						layout (location = 0) in vec3 aPos;
401 						void main() {
402 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
403 						}
404 					`),
405 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
406 						#version 330 core
407 						out vec4 FragColor;
408 						uniform vec4 mycolor;
409 						void main() {
410 							FragColor = mycolor;
411 						}
412 					`),
413 				);
414 
415 				// and do whatever other setup you want.
416 
417 				float[] vertices = [
418 					0.5f,  0.5f, 0.0f,  // top right
419 					0.5f, -0.5f, 0.0f,  // bottom right
420 					-0.5f, -0.5f, 0.0f,  // bottom left
421 					-0.5f,  0.5f, 0.0f   // top left
422 				];
423 				uint[] indices = [  // note that we start from 0!
424 					0, 1, 3,  // first Triangle
425 					1, 2, 3   // second Triangle
426 				];
427 				uint VBO, EBO;
428 				glGenVertexArrays(1, &VAO);
429 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
430 				glBindVertexArray(VAO);
431 
432 				glGenBuffers(1, &VBO);
433 				glGenBuffers(1, &EBO);
434 
435 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
436 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
437 
438 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
439 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
440 
441 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
442 				glEnableVertexAttribArray(0);
443 
444 				// the library will set the initial viewport and trigger our first draw,
445 				// so these next two lines are NOT needed. they are just here as comments
446 				// to show what would happen next.
447 
448 				// glViewport(0, 0, window.width, window.height);
449 				// window.redrawOpenGlSceneNow();
450 			};
451 
452 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
453 			// it is our render method.
454 			window.redrawOpenGlScene = delegate() {
455 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
456 				glClear(GL_COLOR_BUFFER_BIT);
457 
458 				glUseProgram(shader.shaderProgram);
459 
460 				// the shader helper class has methods to set uniforms too
461 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
462 
463 				glBindVertexArray(VAO);
464 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
465 			};
466 
467 			window.eventLoop(0);
468 		}
469 		---
470 
471 	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.
472 
473 	$(H3 $(ID vulkan) Vulkan)
474 
475 	See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings:
476 
477 	https://github.com/adamdruppe/VulkanizeDSdpy
478 
479 	https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo
480 
481 	$(H3 $(ID topic-images) Displaying images)
482 		You can also load PNG images using [arsd.png].
483 
484 		---
485 		// dmd example.d simpledisplay.d color.d png.d
486 		import arsd.simpledisplay;
487 		import arsd.png;
488 
489 		void main() {
490 			auto image = Image.fromMemoryImage(readPng("image.png"));
491 			displayImage(image);
492 		}
493 		---
494 
495 		Compile with `dmd example.d simpledisplay.d png.d`.
496 
497 		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.
498 
499 	$(H3 $(ID topic-sprites) Sprites)
500 		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.
501 
502 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
503 
504 	$(H3 $(ID topic-clipboard) Clipboard)
505 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
506 
507 		It also has helpers for handling X-specific events.
508 
509 	$(H3 $(ID topic-dnd) Drag and Drop)
510 		See [enableDragAndDrop] and [draggable].
511 
512 	$(H3 $(ID topic-timers) Timers)
513 		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].
514 
515 		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.
516 
517 		---
518 			import arsd.simpledisplay;
519 
520 			void main() {
521 				auto window = new SimpleWindow(400, 400);
522 				// every 100 ms, it will draw a random line
523 				// on the window.
524 				window.eventLoop(100, {
525 					auto painter = window.draw();
526 
527 					import std.random;
528 					// random color
529 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
530 					// random line
531 					painter.drawLine(
532 						Point(uniform(0, window.width), uniform(0, window.height)),
533 						Point(uniform(0, window.width), uniform(0, window.height)));
534 
535 				});
536 			}
537 		---
538 
539 		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.
540 
541 		The pulse timer and instances of the [Timer] class may be combined at will.
542 
543 		---
544 			import arsd.simpledisplay;
545 
546 			void main() {
547 				auto window = new SimpleWindow(400, 400);
548 				auto timer = new Timer(1000, delegate {
549 					auto painter = window.draw();
550 					painter.clear();
551 				});
552 
553 				window.eventLoop(0);
554 			}
555 		---
556 
557 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
558 
559 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
560 		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.
561 
562 		See also: `xwindows.d` from my github.
563 
564 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
565 		`handleNativeEvent` and `handleNativeGlobalEvent`.
566 
567 	$(H3 $(ID topic-integration) Integration with other libraries)
568 		Integration with a third-party event loop is possible.
569 
570 		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.
571 
572 	$(H3 $(ID topic-guis) GUI widgets)
573 		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!
574 
575 		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.
576 
577 		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.)
578 
579 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
580 
581 	$(H2 Platform-specific tips and tricks)
582 
583 	X_tips:
584 
585 	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.
586 
587 	Windows_tips:
588 
589 	You can add icons or manifest files to your exe using a resource file.
590 
591 	To create a Windows .ico file, use the gimp or something. I'll write a helper
592 	program later.
593 
594 	Create `yourapp.rc`:
595 
596 	```rc
597 		1 ICON filename.ico
598 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
599 	```
600 
601 	And `yourapp.exe.manifest`:
602 
603 	```xml
604 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
605 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
606 		<assemblyIdentity
607 		    version="1.0.0.0"
608 		    processorArchitecture="*"
609 		    name="CompanyName.ProductName.YourApplication"
610 		    type="win32"
611 		/>
612 		<description>Your application description here.</description>
613 		<dependency>
614 		    <dependentAssembly>
615 			<assemblyIdentity
616 			    type="win32"
617 			    name="Microsoft.Windows.Common-Controls"
618 			    version="6.0.0.0"
619 			    processorArchitecture="*"
620 			    publicKeyToken="6595b64144ccf1df"
621 			    language="*"
622 			/>
623 		    </dependentAssembly>
624 		</dependency>
625 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
626 			<windowsSettings>
627 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
628 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
629 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
630 				<!-- to render crisply in DPI-unaware contexts -->
631 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
632 			</windowsSettings>
633 		</application>
634 		</assembly>
635 	```
636 
637 	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`.
638 
639 	Doing this lets you opt into various new things since Windows XP.
640 
641 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
642 
643 	$(H2 Tips)
644 
645 	$(H3 Name conflicts)
646 
647 	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:
648 
649 	---
650 	static import sdpy = arsd.simpledisplay;
651 	import arsd.simpledisplay : SimpleWindow;
652 
653 	void main() {
654 		auto window = new SimpleWindow();
655 		sdpy.EventLoop.get.run();
656 	}
657 	---
658 
659 	$(H2 $(ID developer-notes) Developer notes)
660 
661 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
662 	implementation though.
663 
664 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
665 	suck. If I was rewriting it, I wouldn't do it that way again.
666 
667 	This file must not have any more required dependencies. If you need bindings, add
668 	them right to this file. Once it gets into druntime and is there for a while, remove
669 	bindings from here to avoid conflicts (or put them in an appropriate version block
670 	so it continues to just work on old dmd), but wait a couple releases before making the
671 	transition so this module remains usable with older versions of dmd.
672 
673 	You may have optional dependencies if needed by putting them in version blocks or
674 	template functions. You may also extend the module with other modules with UFCS without
675 	actually editing this - that is nice to do if you can.
676 
677 	Try to make functions work the same way across operating systems. I typically make
678 	it thinly wrap Windows, then emulate that on Linux.
679 
680 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
681 	Phobos! So try to avoid it.
682 
683 	See more comments throughout the source.
684 
685 	I realize this file is fairly large, but over half that is just bindings at the bottom
686 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
687 	to understand. I suggest you jump around the source by looking for a particular
688 	declaration you're interested in, like `class SimpleWindow` using your editor's search
689 	function, then look at one piece at a time.
690 
691 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
692 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
693 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
694 
695 	I live in the eastern United States, so I will most likely not be around at night in
696 	that US east timezone.
697 
698 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
699 
700 	Building documentation: use my adrdox generator, `dub run adrdox`.
701 
702 	Examples:
703 
704 	$(DIV $(ID Event-example))
705 	$(H3 $(ID event-example) Event example)
706 	This program creates a window and draws events inside them as they
707 	happen, scrolling the text in the window as needed. Run this program
708 	and experiment to get a feel for where basic input events take place
709 	in the library.
710 
711 	---
712 	// dmd example.d simpledisplay.d color.d
713 	import arsd.simpledisplay;
714 	import std.conv;
715 
716 	void main() {
717 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
718 
719 		int y = 0;
720 
721 		void addLine(string text) {
722 			auto painter = window.draw();
723 
724 			if(y + painter.fontHeight >= window.height) {
725 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
726 				y -= painter.fontHeight;
727 			}
728 
729 			painter.outlineColor = Color.red;
730 			painter.fillColor = Color.black;
731 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
732 
733 			painter.outlineColor = Color.white;
734 
735 			painter.drawText(Point(10, y), text);
736 
737 			y += painter.fontHeight;
738 		}
739 
740 		window.eventLoop(1000,
741 		  () {
742 			addLine("Timer went off!");
743 		  },
744 		  (KeyEvent event) {
745 			addLine(to!string(event));
746 		  },
747 		  (MouseEvent event) {
748 			addLine(to!string(event));
749 		  },
750 		  (dchar ch) {
751 			addLine(to!string(ch));
752 		  }
753 		);
754 	}
755 	---
756 
757 	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.
758 
759 	$(COMMENT
760 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
761 
762 	---
763 
764 	---
765 	)
766 
767 	History:
768 		Initial release in April 2011.
769 
770 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
771 
772 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
773 +/
774 module arsd.simpledisplay;
775 
776 import arsd.core;
777 
778 // FIXME: tetris demo
779 // FIXME: space invaders demo
780 // FIXME: asteroids demo
781 
782 /++ $(ID Pong-example)
783 	$(H3 Pong)
784 
785 	This program creates a little Pong-like game. Player one is controlled
786 	with the keyboard.  Player two is controlled with the mouse. It demos
787 	the pulse timer, event handling, and some basic drawing.
788 +/
789 version(demos)
790 unittest {
791 	// dmd example.d simpledisplay.d color.d
792 	import arsd.simpledisplay;
793 
794 	enum paddleMovementSpeed = 8;
795 	enum paddleHeight = 48;
796 
797 	void main() {
798 		auto window = new SimpleWindow(600, 400, "Pong game!");
799 
800 		int playerOnePosition, playerTwoPosition;
801 		int playerOneMovement, playerTwoMovement;
802 		int playerOneScore, playerTwoScore;
803 
804 		int ballX, ballY;
805 		int ballDx, ballDy;
806 
807 		void serve() {
808 			import std.random;
809 
810 			ballX = window.width / 2;
811 			ballY = window.height / 2;
812 			ballDx = uniform(-4, 4) * 3;
813 			ballDy = uniform(-4, 4) * 3;
814 			if(ballDx == 0)
815 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
816 		}
817 
818 		serve();
819 
820 		window.eventLoop(50, // set a 50 ms timer pulls
821 			// This runs once per timer pulse
822 			delegate () {
823 				auto painter = window.draw();
824 
825 				painter.clear();
826 
827 				// Update everyone's motion
828 				playerOnePosition += playerOneMovement;
829 				playerTwoPosition += playerTwoMovement;
830 
831 				ballX += ballDx;
832 				ballY += ballDy;
833 
834 				// Bounce off the top and bottom edges of the window
835 				if(ballY + 7 >= window.height)
836 					ballDy = -ballDy;
837 				if(ballY - 8 <= 0)
838 					ballDy = -ballDy;
839 
840 				// Bounce off the paddle, if it is in position
841 				if(ballX - 8 <= 16) {
842 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
843 						ballDx = -ballDx + 1; // add some speed to keep it interesting
844 						ballDy += playerOneMovement; // and y movement based on your controls too
845 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
846 					} else {
847 						// Missed it
848 						playerTwoScore ++;
849 						serve();
850 					}
851 				}
852 
853 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
854 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
855 						ballDx = -ballDx - 1;
856 						ballDy += playerTwoMovement;
857 						ballX = window.width - 24;
858 					} else {
859 						// Missed it
860 						playerOneScore ++;
861 						serve();
862 					}
863 				}
864 
865 				// Draw the paddles
866 				painter.outlineColor = Color.black;
867 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
868 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
869 
870 				// Draw the ball
871 				painter.fillColor = Color.red;
872 				painter.outlineColor = Color.yellow;
873 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
874 
875 				// Draw the score
876 				painter.outlineColor = Color.blue;
877 				import std.conv;
878 				painter.drawText(Point(64, 4), to!string(playerOneScore));
879 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
880 
881 			},
882 			delegate (KeyEvent event) {
883 				// Player 1's controls are the arrow keys on the keyboard
884 				if(event.key == Key.Down)
885 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
886 				if(event.key == Key.Up)
887 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
888 
889 			},
890 			delegate (MouseEvent event) {
891 				// Player 2's controls are mouse movement while the left button is held down
892 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
893 					if(event.dy > 0)
894 						playerTwoMovement = paddleMovementSpeed;
895 					else if(event.dy < 0)
896 						playerTwoMovement = -paddleMovementSpeed;
897 				} else {
898 					playerTwoMovement = 0;
899 				}
900 			}
901 		);
902 	}
903 }
904 
905 /++ $(H3 $(ID example-minesweeper) Minesweeper)
906 
907 	This minesweeper demo shows how we can implement another classic
908 	game with simpledisplay and shows some mouse input and basic output
909 	code.
910 +/
911 version(demos)
912 unittest {
913 	import arsd.simpledisplay;
914 
915 	enum GameSquare {
916 		mine = 0,
917 		clear,
918 		m1, m2, m3, m4, m5, m6, m7, m8
919 	}
920 
921 	enum UserSquare {
922 		unknown,
923 		revealed,
924 		flagged,
925 		questioned
926 	}
927 
928 	enum GameState {
929 		inProgress,
930 		lose,
931 		win
932 	}
933 
934 	GameSquare[] board;
935 	UserSquare[] userState;
936 	GameState gameState;
937 	int boardWidth;
938 	int boardHeight;
939 
940 	bool isMine(int x, int y) {
941 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
942 			return false;
943 		return board[y * boardWidth + x] == GameSquare.mine;
944 	}
945 
946 	GameState reveal(int x, int y) {
947 		if(board[y * boardWidth + x] == GameSquare.clear) {
948 			floodFill(userState, boardWidth, boardHeight,
949 				UserSquare.unknown, UserSquare.revealed,
950 				x, y,
951 				(x, y) {
952 					if(board[y * boardWidth + x] == GameSquare.clear)
953 						return true;
954 					else {
955 						userState[y * boardWidth + x] = UserSquare.revealed;
956 						return false;
957 					}
958 				});
959 		} else {
960 			userState[y * boardWidth + x] = UserSquare.revealed;
961 			if(isMine(x, y))
962 				return GameState.lose;
963 		}
964 
965 		foreach(state; userState) {
966 			if(state == UserSquare.unknown || state == UserSquare.questioned)
967 				return GameState.inProgress;
968 		}
969 
970 		return GameState.win;
971 	}
972 
973 	void initializeBoard(int width, int height, int numberOfMines) {
974 		boardWidth = width;
975 		boardHeight = height;
976 		board.length = width * height;
977 
978 		userState.length = width * height;
979 		userState[] = UserSquare.unknown;
980 
981 		import std.algorithm, std.random, std.range;
982 
983 		board[] = GameSquare.clear;
984 
985 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
986 			board[minePosition] = GameSquare.mine;
987 
988 		int x;
989 		int y;
990 		foreach(idx, ref square; board) {
991 			if(square == GameSquare.clear) {
992 				int danger = 0;
993 				danger += isMine(x-1, y-1)?1:0;
994 				danger += isMine(x-1, y)?1:0;
995 				danger += isMine(x-1, y+1)?1:0;
996 				danger += isMine(x, y-1)?1:0;
997 				danger += isMine(x, y+1)?1:0;
998 				danger += isMine(x+1, y-1)?1:0;
999 				danger += isMine(x+1, y)?1:0;
1000 				danger += isMine(x+1, y+1)?1:0;
1001 
1002 				square = cast(GameSquare) (danger + 1);
1003 			}
1004 
1005 			x++;
1006 			if(x == width) {
1007 				x = 0;
1008 				y++;
1009 			}
1010 		}
1011 	}
1012 
1013 	void redraw(SimpleWindow window) {
1014 		import std.conv;
1015 
1016 		auto painter = window.draw();
1017 
1018 		painter.clear();
1019 
1020 		final switch(gameState) with(GameState) {
1021 			case inProgress:
1022 				break;
1023 			case win:
1024 				painter.fillColor = Color.green;
1025 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1026 				return;
1027 			case lose:
1028 				painter.fillColor = Color.red;
1029 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1030 				return;
1031 		}
1032 
1033 		int x = 0;
1034 		int y = 0;
1035 
1036 		foreach(idx, square; board) {
1037 			auto state = userState[idx];
1038 
1039 			final switch(state) with(UserSquare) {
1040 				case unknown:
1041 					painter.outlineColor = Color.black;
1042 					painter.fillColor = Color(128,128,128);
1043 
1044 					painter.drawRectangle(
1045 						Point(x * 20, y * 20),
1046 						20, 20
1047 					);
1048 				break;
1049 				case revealed:
1050 					if(square == GameSquare.clear) {
1051 						painter.outlineColor = Color.white;
1052 						painter.fillColor = Color.white;
1053 
1054 						painter.drawRectangle(
1055 							Point(x * 20, y * 20),
1056 							20, 20
1057 						);
1058 					} else {
1059 						painter.outlineColor = Color.black;
1060 						painter.fillColor = Color.white;
1061 
1062 						painter.drawText(
1063 							Point(x * 20, y * 20),
1064 							to!string(square)[1..2],
1065 							Point(x * 20 + 20, y * 20 + 20),
1066 							TextAlignment.Center | TextAlignment.VerticalCenter);
1067 					}
1068 				break;
1069 				case flagged:
1070 					painter.outlineColor = Color.black;
1071 					painter.fillColor = Color.red;
1072 					painter.drawRectangle(
1073 						Point(x * 20, y * 20),
1074 						20, 20
1075 					);
1076 				break;
1077 				case questioned:
1078 					painter.outlineColor = Color.black;
1079 					painter.fillColor = Color.yellow;
1080 					painter.drawRectangle(
1081 						Point(x * 20, y * 20),
1082 						20, 20
1083 					);
1084 				break;
1085 			}
1086 
1087 			x++;
1088 			if(x == boardWidth) {
1089 				x = 0;
1090 				y++;
1091 			}
1092 		}
1093 
1094 	}
1095 
1096 	void main() {
1097 		auto window = new SimpleWindow(200, 200);
1098 
1099 		initializeBoard(10, 10, 10);
1100 
1101 		redraw(window);
1102 		window.eventLoop(0,
1103 			delegate (MouseEvent me) {
1104 				if(me.type != MouseEventType.buttonPressed)
1105 					return;
1106 				auto x = me.x / 20;
1107 				auto y = me.y / 20;
1108 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1109 					if(me.button == MouseButton.left) {
1110 						gameState = reveal(x, y);
1111 					} else {
1112 						userState[y*boardWidth+x] = UserSquare.flagged;
1113 					}
1114 					redraw(window);
1115 				}
1116 			}
1117 		);
1118 	}
1119 }
1120 
1121 // FIXME: tetris demo
1122 // FIXME: space invaders demo
1123 // FIXME: asteroids demo
1124 
1125 version(OSX) version(DigitalMars) version=OSXCocoa;
1126 
1127 
1128 version(OSXCocoa) {
1129 	version=without_opengl;
1130 	version=allow_unimplemented_features;
1131 	// version=OSXCocoa;
1132 	// pragma(linkerDirective, "-framework Cocoa");
1133 }
1134 
1135 version(without_opengl) {
1136 	enum SdpyIsUsingIVGLBinds = false;
1137 } else /*version(Posix)*/ {
1138 	static if (__traits(compiles, (){import iv.glbinds;})) {
1139 		enum SdpyIsUsingIVGLBinds = true;
1140 		public import iv.glbinds;
1141 		//pragma(msg, "SDPY: using iv.glbinds");
1142 	} else {
1143 		enum SdpyIsUsingIVGLBinds = false;
1144 	}
1145 //} else {
1146 //	enum SdpyIsUsingIVGLBinds = false;
1147 }
1148 
1149 
1150 version(Windows) {
1151 	//import core.sys.windows.windows;
1152 	import core.sys.windows.winnls;
1153 	import core.sys.windows.windef;
1154 	import core.sys.windows.basetyps;
1155 	import core.sys.windows.winbase;
1156 	import core.sys.windows.winuser;
1157 	import core.sys.windows.shellapi;
1158 	import core.sys.windows.wingdi;
1159 	static import gdi = core.sys.windows.wingdi; // so i
1160 
1161 	pragma(lib, "gdi32");
1162 	pragma(lib, "user32");
1163 
1164 	// for AlphaBlend... a breaking change....
1165 	version(CRuntime_DigitalMars) { } else
1166 		pragma(lib, "msimg32");
1167 
1168 	// core.sys.windows.dwmapi
1169 	private {
1170 		/++
1171 			See_also:
1172 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute
1173 		 +/
1174 		extern extern(Windows) HRESULT DwmGetWindowAttribute(
1175 			HWND hwnd,
1176 			DWORD dwAttribute,
1177 			PVOID pvAttribute,
1178 			DWORD cbAttribute
1179 		) nothrow @nogc;
1180 
1181 		/++
1182 			See_also:
1183 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
1184 		 +/
1185 		extern extern(Windows) HRESULT DwmSetWindowAttribute(
1186 			HWND hwnd,
1187 			DWORD dwAttribute,
1188 			LPCVOID pvAttribute,
1189 			DWORD cbAttribute
1190 		) nothrow @nogc;
1191 
1192 		/++
1193 			See_also:
1194 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
1195 		 +/
1196 		enum DWMWINDOWATTRIBUTE {
1197 			// incomplete, only declare what we need
1198 
1199 			/++
1200 				Usage:
1201 					pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*`
1202 
1203 				$(NOTE
1204 					Requires Windows 11 or later.
1205 				)
1206 			 +/
1207 			DWMWA_WINDOW_CORNER_PREFERENCE = 33,
1208 		}
1209 
1210 		/++
1211 			See_also:
1212 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
1213 		 +/
1214 		enum DWM_WINDOW_CORNER_PREFERENCE {
1215 			/// System decision
1216 			DWMWCP_DEFAULT = 0,
1217 
1218 			/// Never
1219 			DWMWCP_DONOTROUND = 1,
1220 
1221 			// If "appropriate"
1222 			DWMWCP_ROUND = 2,
1223 
1224 			// If "appropriate", but smaller radius
1225 			DWMWCP_ROUNDSMALL = 3
1226 		}
1227 
1228 		bool fromDWM(
1229 			DWM_WINDOW_CORNER_PREFERENCE value,
1230 			out CornerStyle result,
1231 		) @safe pure nothrow @nogc {
1232 			switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1233 				case DWMWCP_DEFAULT:
1234 					result = CornerStyle.automatic;
1235 					return true;
1236 				case DWMWCP_DONOTROUND:
1237 					result = CornerStyle.rectangular;
1238 					return true;
1239 				case DWMWCP_ROUND:
1240 					result = CornerStyle.rounded;
1241 					return true;
1242 				case DWMWCP_ROUNDSMALL:
1243 					result = CornerStyle.roundedSlightly;
1244 					return true;
1245 				default:
1246 					return false;
1247 			}
1248 		}
1249 
1250 		bool toDWM(
1251 			CornerStyle value,
1252 			out DWM_WINDOW_CORNER_PREFERENCE result,
1253 		) @safe pure nothrow @nogc {
1254 			final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1255 				case CornerStyle.automatic:
1256 					result = DWMWCP_DEFAULT;
1257 					return true;
1258 				case CornerStyle.rectangular:
1259 					result = DWMWCP_DONOTROUND;
1260 					return true;
1261 				case CornerStyle.rounded:
1262 					result = DWMWCP_ROUND;
1263 					return true;
1264 				case CornerStyle.roundedSlightly:
1265 					result = DWMWCP_ROUNDSMALL;
1266 					return true;
1267 			}
1268 		}
1269 
1270 		pragma(lib, "dwmapi");
1271 	}
1272 } else version (linux) {
1273 	//k8: this is hack for rdmd. sorry.
1274 	static import core.sys.linux.epoll;
1275 	static import core.sys.linux.timerfd;
1276 }
1277 
1278 
1279 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1280 
1281 // http://wiki.dlang.org/Simpledisplay.d
1282 
1283 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1284 
1285 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1286 // but can i control the scroll lock led
1287 
1288 
1289 // Note: if you are using Image on X, you might want to do:
1290 /*
1291 	static if(UsingSimpledisplayX11) {
1292 		if(!Image.impl.xshmAvailable) {
1293 			// the images will use the slower XPutImage, you might
1294 			// want to consider an alternative method to get better speed
1295 		}
1296 	}
1297 
1298 	If the shared memory extension is available though, simpledisplay uses it
1299 	for a significant speed boost whenever you draw large Images.
1300 */
1301 
1302 // 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.
1303 
1304 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1305 
1306 /*
1307 	Biggest FIXME:
1308 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1309 
1310 		clean up opengl contexts when their windows close
1311 
1312 		fix resizing the bitmaps/pixmaps
1313 */
1314 
1315 // BTW on Windows:
1316 // -L/SUBSYSTEM:WINDOWS:5.0
1317 // to dmd will make a nice windows binary w/o a console if you want that.
1318 
1319 /*
1320 	Stuff to add:
1321 
1322 	use multibyte functions everywhere we can
1323 
1324 	OpenGL windows
1325 	more event stuff
1326 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1327 
1328 
1329 	resizeEvent
1330 		and make the windows non-resizable by default,
1331 		or perhaps stretched (if I can find something in X like StretchBlt)
1332 
1333 	take a screenshot function!
1334 
1335 	Pens and brushes?
1336 	Maybe a global event loop?
1337 
1338 	Mouse deltas
1339 	Key items
1340 */
1341 
1342 /*
1343 From MSDN:
1344 
1345 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1346 
1347 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.
1348 
1349 */
1350 
1351 version(linux) {
1352 	version = X11;
1353 	version(without_libnotify) {
1354 		// we cool
1355 	}
1356 	else
1357 		version = libnotify;
1358 }
1359 
1360 version(libnotify) {
1361 	pragma(lib, "dl");
1362 	import core.sys.posix.dlfcn;
1363 
1364 	void delegate()[int] libnotify_action_delegates;
1365 	int libnotify_action_delegates_count;
1366 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1367 		auto idx = cast(int) user_data;
1368 		if(auto dgptr = idx in libnotify_action_delegates) {
1369 			(*dgptr)();
1370 			libnotify_action_delegates.remove(idx);
1371 		}
1372 	}
1373 
1374 	struct C_DynamicLibrary {
1375 		void* handle;
1376 		this(string name) {
1377 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1378 			if(handle is null)
1379 				throw new Exception("dlopen");
1380 		}
1381 
1382 		void close() {
1383 			dlclose(handle);
1384 		}
1385 
1386 		~this() {
1387 			// close
1388 		}
1389 
1390 		// FIXME: this looks up by name every time....
1391 		template call(string func, Ret, Args...) {
1392 			extern(C) Ret function(Args) fptr;
1393 			typeof(fptr) call() {
1394 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1395 				return fptr;
1396 			}
1397 		}
1398 	}
1399 
1400 	C_DynamicLibrary* libnotify;
1401 }
1402 
1403 version(OSX) {
1404 	version(OSXCocoa) {}
1405 	else { version = X11; }
1406 }
1407 	//version = OSXCocoa; // this was written by KennyTM
1408 version(FreeBSD)
1409 	version = X11;
1410 version(Solaris)
1411 	version = X11;
1412 
1413 version(X11) {
1414 	version(without_xft) {}
1415 	else version=with_xft;
1416 }
1417 
1418 void featureNotImplemented()() {
1419 	version(allow_unimplemented_features)
1420 		throw new NotYetImplementedException();
1421 	else
1422 		static assert(0);
1423 }
1424 
1425 // these are so the static asserts don't trigger unless you want to
1426 // add support to it for an OS
1427 version(Windows)
1428 	version = with_timer;
1429 version(linux)
1430 	version = with_timer;
1431 version(OSXCocoa)
1432 	version = with_timer;
1433 
1434 version(with_timer)
1435 	enum bool SimpledisplayTimerAvailable = true;
1436 else
1437 	enum bool SimpledisplayTimerAvailable = false;
1438 
1439 /// 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.
1440 version(Windows)
1441 	enum bool UsingSimpledisplayWindows = true;
1442 else
1443 	enum bool UsingSimpledisplayWindows = false;
1444 
1445 /// 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.
1446 version(X11)
1447 	enum bool UsingSimpledisplayX11 = true;
1448 else
1449 	enum bool UsingSimpledisplayX11 = false;
1450 
1451 /// 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.
1452 version(OSXCocoa)
1453 	enum bool UsingSimpledisplayCocoa = true;
1454 else
1455 	enum bool UsingSimpledisplayCocoa = false;
1456 
1457 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1458 version(Windows)
1459 	enum multipleWindowsSupported = true;
1460 else version(X11)
1461 	enum multipleWindowsSupported = true;
1462 else version(OSXCocoa)
1463 	enum multipleWindowsSupported = true;
1464 else
1465 	static assert(0);
1466 
1467 version(without_opengl)
1468 	enum bool OpenGlEnabled = false;
1469 else
1470 	enum bool OpenGlEnabled = true;
1471 
1472 /++
1473 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1474 	If you mix this in above your `main` function, you no longer need to use the linker
1475 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1476 
1477 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1478 
1479 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1480 	stderr writeln. It will fail and throw an exception.
1481 
1482 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1483 
1484 	History:
1485 		Added November 24, 2021 (dub v10.4)
1486 +/
1487 mixin template EnableWindowsSubsystem() {
1488 	version(Windows)
1489 	version(CRuntime_Microsoft) {
1490 		pragma(linkerDirective, "/subsystem:windows");
1491 		version(LDC)
1492 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1493 		else
1494 			pragma(linkerDirective, "/entry:mainCRTStartup");
1495 	}
1496 }
1497 
1498 
1499 /++
1500 	After selecting a type from [WindowTypes], you may further customize
1501 	its behavior by setting one or more of these flags.
1502 
1503 
1504 	The different window types have different meanings of `normal`. If the
1505 	window type already is a good match for what you want to do, you should
1506 	just use [WindowFlags.normal], the default, which will do the right thing
1507 	for your users.
1508 
1509 	The window flags will not always be honored by the operating system
1510 	and window managers; they are hints, not commands.
1511 +/
1512 enum WindowFlags : int {
1513 	normal = 0, ///
1514 	skipTaskbar = 1, ///
1515 	alwaysOnTop = 2, ///
1516 	alwaysOnBottom = 4, ///
1517 	cannotBeActivated = 8, ///
1518 	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.
1519 	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.
1520 	/++
1521 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1522 		it is still a top-level window. This should NOT be set separately for most window types.
1523 
1524 		A transient window will not keep the application open if its main window closes.
1525 
1526 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1527 
1528 
1529 		From the ICCM:
1530 
1531 		$(BLOCKQUOTE
1532 			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.
1533 
1534 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1535 		)
1536 
1537 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1538 
1539 		History:
1540 			Added February 23, 2021 but not yet stabilized.
1541 	+/
1542 	transient = 64,
1543 	/++
1544 		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.
1545 
1546 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1547 
1548 		History:
1549 			Added April 1, 2022
1550 	+/
1551 	managesChildWindowFocus = 128,
1552 
1553 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1554 }
1555 
1556 /++
1557 	When creating a window, you can pass a type to SimpleWindow's constructor,
1558 	then further customize the window by changing `WindowFlags`.
1559 
1560 
1561 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1562 	use. The others are there to build a foundation for a higher level GUI toolkit,
1563 	but are themselves not as high level as you might think from their names.
1564 
1565 	This list is based on the EMWH spec for X11.
1566 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1567 +/
1568 enum WindowTypes : int {
1569 	/// An ordinary application window.
1570 	normal,
1571 	/// 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.
1572 	undecorated,
1573 	/// 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.
1574 	eventOnly,
1575 	/// A drop down menu, such as from a menu bar
1576 	dropdownMenu,
1577 	/// A popup menu, such as from a right click
1578 	popupMenu,
1579 	/// A popup bubble notification
1580 	notification,
1581 	/*
1582 	menu, /// a tearable menu bar
1583 	splashScreen, /// a loading splash screen for your application
1584 	tooltip, /// A tiny window showing temporary help text or something.
1585 	comboBoxDropdown,
1586 	dialog,
1587 	toolbar
1588 	*/
1589 	/// a child nested inside the parent. You must pass a parent window to the ctor
1590 	nestedChild,
1591 
1592 	/++
1593 		The type you get when you pass in an existing browser handle, which means most
1594 		of simpledisplay's fancy things will not be done since they were never set up.
1595 
1596 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1597 		failure; you should use the existing handle constructor.
1598 
1599 		History:
1600 			Added November 17, 2022 (previously it would have type `normal`)
1601 	+/
1602 	minimallyWrapped
1603 }
1604 
1605 
1606 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1607 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1608 private __gshared char* sdpyWindowClassStr = null;
1609 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1610 
1611 /**
1612 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1613 	You may want to change context version if you want to use advanced shaders or
1614 	other modern OpenGL techinques. This setting doesn't affect already created
1615 	windows. You may use version 2.1 as your default, which should be supported
1616 	by any box since 2006, so seems to be a reasonable choice.
1617 
1618 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1619 	old context creation code without any version specified. This is the safest
1620 	way to init OpenGL, but it may not give you access to advanced features.
1621 
1622 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1623 */
1624 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1625 
1626 /**
1627 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1628 	pipeline functions, and without "compatible" mode you won't be able to use
1629 	your old non-shader-based code with such contexts. By default SimpleDisplay
1630 	creates compatible context, so you can gradually upgrade your OpenGL code if
1631 	you want to (or leave it as is, as it should "just work").
1632 */
1633 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1634 
1635 /**
1636 	Set to `true` to allow creating OpenGL context with lower version than requested
1637 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1638 	`openGLContextFallbackActivated()` will return `true`.
1639 	*/
1640 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1641 
1642 /**
1643 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1644 	*/
1645 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1646 
1647 /++
1648 	History:
1649 		Added April 24, 2023  (dub v11.0)
1650 +/
1651 version(without_opengl) {} else
1652 auto openGLCurrentContext() {
1653 	version(Windows)
1654 		return wglGetCurrentContext();
1655 	else
1656 		return glXGetCurrentContext();
1657 }
1658 
1659 
1660 /**
1661 	Set window class name for all following `new SimpleWindow()` calls.
1662 
1663 	WARNING! For Windows, you should set your class name before creating any
1664 	window, and NEVER change it after that!
1665 */
1666 void sdpyWindowClass (const(char)[] v) {
1667 	import core.stdc.stdlib : realloc;
1668 	if (v.length == 0) v = "SimpleDisplayWindow";
1669 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1670 	if (sdpyWindowClassStr is null) return; // oops
1671 	sdpyWindowClassStr[0..v.length+1] = 0;
1672 	sdpyWindowClassStr[0..v.length] = v[];
1673 }
1674 
1675 /**
1676 	Get current window class name.
1677 */
1678 string sdpyWindowClass () @trusted {
1679 	if (sdpyWindowClassStr is null) return null;
1680 	foreach (immutable idx; 0..size_t.max-1) {
1681 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1682 	}
1683 	return null;
1684 }
1685 
1686 /++
1687 	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.
1688 
1689 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1690 +/
1691 float[2] getDpi() {
1692 	float[2] dpi;
1693 	version(Windows) {
1694 		HDC screen = GetDC(null);
1695 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1696 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1697 	} else version(X11) {
1698 		auto display = XDisplayConnection.get;
1699 		auto screen = DefaultScreen(display);
1700 
1701 		void fallback() {
1702 			/+
1703 			// 25.4 millimeters in an inch...
1704 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1705 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1706 			+/
1707 
1708 			// the physical size isn't actually as important as the logical size since this is
1709 			// all about scaling really
1710 			dpi[0] = 96;
1711 			dpi[1] = 96;
1712 		}
1713 
1714 		auto xft = getXftDpi();
1715 		if(xft is float.init)
1716 			fallback();
1717 		else {
1718 			dpi[0] = xft;
1719 			dpi[1] = xft;
1720 		}
1721 	}
1722 
1723 	return dpi;
1724 }
1725 
1726 version(X11)
1727 float getXftDpi() {
1728 	auto display = XDisplayConnection.get;
1729 
1730 	char* resourceString = XResourceManagerString(display);
1731 	XrmInitialize();
1732 
1733 	if (resourceString) {
1734 		auto db = XrmGetStringDatabase(resourceString);
1735 		XrmValue value;
1736 		char* type;
1737 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1738 			if (value.addr) {
1739 				import core.stdc.stdlib;
1740 				return atof(cast(char*) value.addr);
1741 			}
1742 		}
1743 	}
1744 
1745 	return float.init;
1746 }
1747 
1748 /++
1749 	Implementation used by [SimpleWindow.takeScreenshot].
1750 
1751 	Params:
1752 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1753 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1754 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1755 		x = the x-offset of the image to capture, from the left.
1756 		y = the y-offset of the image to capture, from the top.
1757 
1758 	History:
1759 		Added on March 14, 2021
1760 
1761 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1762 
1763 +/
1764 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1765 	TrueColorImage got;
1766 	version(X11) {
1767 		auto display = XDisplayConnection.get;
1768 		if(handle == 0)
1769 			handle = RootWindow(display, DefaultScreen(display));
1770 
1771 		if(width == 0 || height == 0) {
1772 			Window root;
1773 			int xpos, ypos;
1774 			uint widthret, heightret, borderret, depthret;
1775 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1776 
1777 			if(width == 0)
1778 				width = widthret;
1779 			if(height == 0)
1780 				height = heightret;
1781 		}
1782 
1783 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1784 
1785 		// https://github.com/adamdruppe/arsd/issues/98
1786 
1787 		auto i = new Image(image);
1788 		got = i.toTrueColorImage();
1789 
1790 		XDestroyImage(image);
1791 	} else version(Windows) {
1792 		auto hdc = GetDC(handle);
1793 		scope(exit) ReleaseDC(handle, hdc);
1794 
1795 		if(width == 0 || height == 0) {
1796 			BITMAP bmHeader;
1797 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1798 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1799 			if(width == 0)
1800 				width = bmHeader.bmWidth;
1801 			if(height == 0)
1802 				height = bmHeader.bmHeight;
1803 		}
1804 
1805 		auto i = new Image(width, height);
1806 		HDC hdcMem = CreateCompatibleDC(hdc);
1807 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1808 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1809 		SelectObject(hdcMem, hbmOld);
1810 		DeleteDC(hdcMem);
1811 
1812 		got = i.toTrueColorImage();
1813 	} else featureNotImplemented();
1814 
1815 	return got;
1816 }
1817 
1818 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1819 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1820 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1821 
1822 version(Windows)
1823 shared static this() {
1824 	auto lib = LoadLibrary("User32.dll");
1825 	if(lib is null)
1826 		return;
1827 	//scope(exit)
1828 		//FreeLibrary(lib);
1829 
1830 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1831 
1832 	if(SetProcessDpiAwarenessContext is null)
1833 		return;
1834 
1835 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1836 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1837 		//writeln(GetLastError());
1838 	}
1839 
1840 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1841 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1842 }
1843 
1844 /++
1845 	Blocking mode for event loop calls associated with a window instance.
1846 
1847 	History:
1848 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1849 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1850 		is, all would block until the application quit.
1851 
1852 		That behavior can still be achieved here with `untilApplicationQuits`,
1853 		or explicitly calling the top-level `EventLoop.get.run` function.
1854 +/
1855 enum BlockingMode {
1856 	/++
1857 		The event loop call will block until the whole application is ready
1858 		to quit if it is the only one running, but if it is nested inside
1859 		another one, it will only block until the window you're calling it on
1860 		closes.
1861 	+/
1862 	automatic             = 0x00,
1863 	/++
1864 		The event loop call will only return when the whole application
1865 		is ready to quit. This usually means all windows have been closed.
1866 
1867 		This is appropriate for your main application event loop.
1868 	+/
1869 	untilApplicationQuits = 0x01,
1870 	/++
1871 		The event loop will return when the window you're calling it on
1872 		closes. If there are other windows still open, they may be destroyed
1873 		unless you have another event loop running later.
1874 
1875 		This might be appropriate for a modal dialog box loop. Remember that
1876 		other windows are still processing input though, so you can end up
1877 		with a lengthy call stack if this happens in a loop, similar to a
1878 		recursive function (well, it literally is a recursive function, just
1879 		not an obvious looking one).
1880 	+/
1881 	untilWindowCloses     = 0x02,
1882 	/++
1883 		If an event loop is already running, this call will immediately
1884 		return, allowing the existing loop to handle it. If not, this call
1885 		will block until the condition you bitwise-or into the flag.
1886 
1887 		The default is to block until the application quits, same as with
1888 		the `automatic` setting (since if it were nested, which triggers until
1889 		window closes in automatic, this flag would instead not block at all),
1890 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1891 		it will only nest until the window closes. You might want that if you are
1892 		going to open two windows simultaneously and want closing just one of them
1893 		to trigger the event loop return.
1894 	+/
1895 	onlyIfNotNested       = 0x10,
1896 }
1897 
1898 /++
1899 	Window corner visuals preference
1900  +/
1901 enum CornerStyle {
1902 	/++
1903 		Use the default style automatically applied by the system or its window manager/compositor.
1904 	 +/
1905 	automatic,
1906 
1907 	/++
1908 		Prefer rectangular window corners
1909 	 +/
1910 	rectangular,
1911 
1912 	/++
1913 		Prefer rounded window corners
1914 	 +/
1915 	rounded,
1916 
1917 	/++
1918 		Prefer slightly-rounded window corners
1919 	 +/
1920 	roundedSlightly,
1921 }
1922 
1923 /++
1924 	The flagship window class.
1925 
1926 
1927 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1928 	out of more advanced or complex features of the underlying windowing system.
1929 
1930 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1931 	and get a suitable window to work with.
1932 
1933 	From there, you can opt into additional features, like custom resizability and OpenGL support
1934 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1935 	and customization flags with the final two constructor arguments.
1936 
1937 	If none of that works for you, you can also create a window using native function calls, then
1938 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1939 	though, if you do this, managing the window is still your own responsibility! Notably, you
1940 	will need to destroy it yourself.
1941 +/
1942 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1943 
1944 	/++
1945 		Copies the window's current state into a [TrueColorImage].
1946 
1947 		Be warned: this can be a very slow operation
1948 
1949 		History:
1950 			Actually implemented on March 14, 2021
1951 	+/
1952 	TrueColorImage takeScreenshot() {
1953 		version(Windows)
1954 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1955 		else version(OSXCocoa)
1956 			throw new NotYetImplementedException();
1957 		else
1958 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
1959 	}
1960 
1961 	/++
1962 		Returns the actual logical DPI for the window on its current display monitor. If the window
1963 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1964 
1965 		Please note this function may return zero if it doesn't know the answer!
1966 
1967 
1968 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1969 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1970 
1971 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1972 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1973 		window primarily resides on by checking the center point of the window against the monitor map.
1974 
1975 		Returns:
1976 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1977 			assumes the X and Y dpi are the same.
1978 
1979 		History:
1980 			Added November 26, 2021 (dub v10.4)
1981 
1982 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1983 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1984 			that.
1985 
1986 		Bugs:
1987 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1988 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1989 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1990 			and 1.5 on the secondary monitor.
1991 
1992 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1993 			is a historical misnomer - the real thing of interest is the scale factor and due to
1994 			compatibility concerns the scale would modify dpi values to trick applications. But since
1995 			that's the terminology common out there, I used it too.
1996 
1997 		See_Also:
1998 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1999 			as this since the window many be on a different monitor, but it is a reasonable fallback
2000 			to use if `actualDpi` returns 0.
2001 
2002 			[onDpiChanged] is changed when `actualDpi` has changed.
2003 	+/
2004 	int actualDpi() {
2005 		version(X11) bool useFallbackDpi = false;
2006 		if(!actualDpiLoadAttempted) {
2007 			// FIXME: do the actual monitor we are on
2008 			// and on X this is a good chance to load the monitor map.
2009 			version(Windows) {
2010 				if(GetDpiForWindow)
2011 					actualDpi_ = GetDpiForWindow(impl.hwnd);
2012 			} else version(X11) {
2013 				if(!xRandrInfoLoadAttemped) {
2014 					xRandrInfoLoadAttemped = true;
2015 					if(!XRandrLibrary.attempted) {
2016 						XRandrLibrary.loadDynamicLibrary();
2017 					}
2018 
2019 					if(XRandrLibrary.loadSuccessful) {
2020 						auto display = XDisplayConnection.get;
2021 						int scratch;
2022 						int major, minor;
2023 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
2024 							goto fallback;
2025 
2026 						XRRQueryVersion(display, &major, &minor);
2027 						if(major <= 1 && minor < 5)
2028 							goto fallback;
2029 
2030 						int count;
2031 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
2032 						if(monitors is null)
2033 							goto fallback;
2034 						scope(exit) XRRFreeMonitors(monitors);
2035 
2036 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
2037 						MonitorInfo.info.assumeSafeAppend();
2038 						foreach(idx, monitor; monitors[0 .. count]) {
2039 							MonitorInfo.info ~= MonitorInfo(
2040 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2041 								Size(monitor.mwidth, monitor.mheight),
2042 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
2043 							);
2044 
2045 							/+
2046 							if(monitor.mwidth == 0 || monitor.mheight == 0)
2047 							// unknown physical size, just guess 96 to avoid divide by zero
2048 							MonitorInfo.info ~= MonitorInfo(
2049 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2050 								Size(monitor.mwidth, monitor.mheight),
2051 								96
2052 							);
2053 							else
2054 							// and actual thing
2055 							MonitorInfo.info ~= MonitorInfo(
2056 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2057 								Size(monitor.mwidth, monitor.mheight),
2058 								minInternal(
2059 									// millimeter to int then rounding up.
2060 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
2061 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
2062 								)
2063 							);
2064 							+/
2065 						}
2066 					// writeln("Here", MonitorInfo.info);
2067 					}
2068 				}
2069 
2070 				if(XRandrLibrary.loadSuccessful) {
2071 					updateActualDpi(true);
2072 					// writeln("updated");
2073 
2074 					if(!requestedInput) {
2075 						// this is what requests live updates should the configuration change
2076 						// each time you select input, it sends an initial event, so very important
2077 						// to not get into a loop of selecting input, getting event, updating data,
2078 						// and reselecting input...
2079 						requestedInput = true;
2080 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
2081 						// writeln("requested input");
2082 					}
2083 				} else {
2084 					fallback:
2085 					// make sure we disable events that aren't coming
2086 					xrrEventBase = -1;
2087 					// best guess... respect the custom scaling user command to some extent at least though
2088 					useFallbackDpi = true;
2089 				}
2090 			} else version(OSXCocoa) {
2091 				actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME
2092 			}
2093 			actualDpiLoadAttempted = true;
2094 		} else version(X11) if(MonitorInfo.info.length == 0) {
2095 			useFallbackDpi = true;
2096 		}
2097 
2098 		version(X11)
2099 		if(useFallbackDpi)
2100 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
2101 
2102 		return actualDpi_;
2103 	}
2104 
2105 	private int actualDpi_;
2106 	private bool actualDpiLoadAttempted;
2107 
2108 	version(X11) private {
2109 		bool requestedInput;
2110 		static bool xRandrInfoLoadAttemped;
2111 		struct MonitorInfo {
2112 			Rectangle position;
2113 			Size size;
2114 			int dpi;
2115 
2116 			static MonitorInfo[] info;
2117 		}
2118 		bool screenPositionKnown;
2119 		int screenPositionX;
2120 		int screenPositionY;
2121 		void updateActualDpi(bool loadingNow = false) {
2122 			if(!loadingNow && !actualDpiLoadAttempted)
2123 				actualDpi(); // just to make it do the load
2124 			foreach(idx, m; MonitorInfo.info) {
2125 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
2126 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
2127 					actualDpi_ = m.dpi;
2128 					// writeln("monitor ", idx);
2129 					if(changed && onDpiChanged)
2130 						onDpiChanged();
2131 					break;
2132 				}
2133 			}
2134 		}
2135 	}
2136 
2137 	/++
2138 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2139 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2140 
2141 		History:
2142 			Added November 26, 2021 (dub v10.4)
2143 
2144 		See_Also:
2145 			[actualDpi]
2146 	+/
2147 	void delegate() onDpiChanged;
2148 
2149 	version(X11) {
2150 		void recreateAfterDisconnect() {
2151 			if(!stateDiscarded) return;
2152 
2153 			if(_parent !is null && _parent.stateDiscarded)
2154 				_parent.recreateAfterDisconnect();
2155 
2156 			bool wasHidden = hidden;
2157 
2158 			activeScreenPainter = null; // should already be done but just to confirm
2159 
2160 			actualDpi_ = 0;
2161 			actualDpiLoadAttempted = false;
2162 			xRandrInfoLoadAttemped = false;
2163 
2164 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2165 
2166 			if(auto dh = dropHandler) {
2167 				dropHandler = null;
2168 				enableDragAndDrop(this, dh);
2169 			}
2170 
2171 			if(recreateAdditionalConnectionState)
2172 				recreateAdditionalConnectionState();
2173 
2174 			hidden = wasHidden;
2175 			stateDiscarded = false;
2176 		}
2177 
2178 		bool stateDiscarded;
2179 		void discardConnectionState() {
2180 			if(XDisplayConnection.display)
2181 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2182 			if(discardAdditionalConnectionState)
2183 				discardAdditionalConnectionState();
2184 			stateDiscarded = true;
2185 		}
2186 
2187 		void delegate() discardAdditionalConnectionState;
2188 		void delegate() recreateAdditionalConnectionState;
2189 
2190 	}
2191 
2192 	private DropHandler dropHandler;
2193 
2194 	SimpleWindow _parent;
2195 	bool beingOpenKeepsAppOpen = true;
2196 	/++
2197 		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.
2198 
2199 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2200 
2201 		Params:
2202 
2203 		width = the width of the window's client area, in pixels
2204 		height = the height of the window's client area, in pixels
2205 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2206 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2207 		resizable = [Resizability] has three options:
2208 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2209 			$(P `fixedSize` will not allow the user to resize the window.)
2210 			$(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.)
2211 		windowType = The type of window you want to make.
2212 		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.
2213 		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".
2214 	+/
2215 	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) {
2216 		claimGuiThread();
2217 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2218 		this._width = this._virtualWidth = width;
2219 		this._height = this._virtualHeight = height;
2220 		this.openglMode = opengl;
2221 		version(X11) {
2222 			// auto scale not implemented except with opengl and even there it is kinda weird
2223 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2224 				resizable = Resizability.fixedSize;
2225 		}
2226 		this.resizability = resizable;
2227 		this.windowType = windowType;
2228 		this.customizationFlags = customizationFlags;
2229 		this._title = (title is null ? "D Application" : title);
2230 		this._parent = parent;
2231 		impl.createWindow(width, height, this._title, opengl, parent);
2232 
2233 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2234 			beingOpenKeepsAppOpen = false;
2235 	}
2236 
2237 	/// ditto
2238 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2239 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2240 	}
2241 
2242 	/// Same as above, except using the `Size` struct instead of separate width and height.
2243 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2244 		this(size.width, size.height, title, opengl, resizable);
2245 	}
2246 
2247 	/// ditto
2248 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2249 		this(size, title, opengl, resizable);
2250 	}
2251 
2252 
2253 	/++
2254 		Creates a window based on the given [Image]. It's client area
2255 		width and height is equal to the image. (A window's client area
2256 		is the drawable space inside; it excludes the title bar, etc.)
2257 
2258 		Windows based on images will not be resizable and do not use OpenGL.
2259 
2260 		It will draw the image in upon creation, but this will be overwritten
2261 		upon any draws, including the initial window visible event.
2262 
2263 		You probably do not want to use this and it may be removed from
2264 		the library eventually, or I might change it to be a "permanent"
2265 		background image; one that is automatically drawn on it before any
2266 		other drawing event. idk.
2267 	+/
2268 	this(Image image, string title = null) {
2269 		this(image.width, image.height, title);
2270 		this.image = image;
2271 	}
2272 
2273 	/++
2274 		Wraps a native window handle with very little additional processing - notably no destruction
2275 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2276 		windows created through the low level API (so you can use platform-specific options and
2277 		other details SimpleWindow does not expose) available to the event loop wrappers.
2278 	+/
2279 	this(NativeWindowHandle nativeWindow) {
2280 		windowType = WindowTypes.minimallyWrapped;
2281 		version(Windows)
2282 			impl.hwnd = nativeWindow;
2283 		else version(X11) {
2284 			impl.window = nativeWindow;
2285 			if(nativeWindow)
2286 				display = XDisplayConnection.get(); // get initial display to not segfault
2287 		} else version(OSXCocoa) {
2288 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2289 		} else featureNotImplemented();
2290 		// FIXME: set the size correctly
2291 		_width = 1;
2292 		_height = 1;
2293 		if(nativeWindow)
2294 			nativeMapping[cast(void*) nativeWindow] = this;
2295 
2296 		beingOpenKeepsAppOpen = false;
2297 
2298 		if(nativeWindow)
2299 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2300 		_suppressDestruction = true; // so it doesn't try to close
2301 	}
2302 
2303 	/++
2304 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2305 		The delegate will be called when the window manager asks you to take focus.
2306 
2307 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2308 
2309 		History:
2310 			Added April 1, 2022 (dub v10.8)
2311 	+/
2312 	SimpleWindow delegate() setRequestedInputFocus;
2313 
2314 	/// Experimental, do not use yet
2315 	/++
2316 		Grabs exclusive input from the user until you release it with
2317 		[releaseInputGrab].
2318 
2319 
2320 		Note: it is extremely rude to do this without good reason.
2321 		Reasons may include doing some kind of mouse drag operation
2322 		or popping up a temporary menu that should get events and will
2323 		be dismissed at ease by the user clicking away.
2324 
2325 		Params:
2326 			keyboard = do you want to grab keyboard input?
2327 			mouse = grab mouse input?
2328 			confine = confine the mouse cursor to inside this window?
2329 
2330 		History:
2331 			Prior to March 11, 2021, grabbing the keyboard would always also
2332 			set the X input focus. Now, it only focuses if it is a non-transient
2333 			window and otherwise manages the input direction internally.
2334 
2335 			This means spurious focus/blur events will no longer be sent and the
2336 			application will not steal focus from other applications (which the
2337 			window manager may have rejected anyway).
2338 	+/
2339 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2340 		static if(UsingSimpledisplayX11) {
2341 			XSync(XDisplayConnection.get, 0);
2342 			if(keyboard) {
2343 				if(isTransient && _parent) {
2344 					/*
2345 					FIXME:
2346 						setting the keyboard focus is not actually that helpful, what I more likely want
2347 						is the events from the parent window to be sent over here if we're transient.
2348 					*/
2349 
2350 					_parent.inputProxy = this;
2351 				} else {
2352 
2353 					SimpleWindow setTo;
2354 					if(setRequestedInputFocus !is null)
2355 						setTo = setRequestedInputFocus();
2356 					if(setTo is null)
2357 						setTo = this;
2358 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2359 				}
2360 			}
2361 			if(mouse) {
2362 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2363 				EventMask.PointerMotionMask // FIXME: not efficient
2364 				| EventMask.ButtonPressMask
2365 				| EventMask.ButtonReleaseMask
2366 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2367 				)
2368 			{
2369 				XSync(XDisplayConnection.get, 0);
2370 				import core.stdc.stdio;
2371 				printf("Grab input failed %d\n", res);
2372 				//throw new Exception("Grab input failed");
2373 			} else {
2374 				// cool
2375 			}
2376 			}
2377 
2378 		} else version(Windows) {
2379 			// FIXME: keyboard?
2380 			SetCapture(impl.hwnd);
2381 			if(confine) {
2382 				RECT rcClip;
2383 				//RECT rcOldClip;
2384 				//GetClipCursor(&rcOldClip);
2385 				GetWindowRect(hwnd, &rcClip);
2386 				ClipCursor(&rcClip);
2387 			}
2388 		} else version(OSXCocoa) {
2389 			// throw new NotYetImplementedException();
2390 		} else static assert(0);
2391 	}
2392 
2393 	private Point imePopupLocation = Point(0, 0);
2394 
2395 	/++
2396 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2397 
2398 		Bugs:
2399 			Not implemented outside X11.
2400 	+/
2401 	void setIMEPopupLocation(Point location) {
2402 		static if(UsingSimpledisplayX11) {
2403 			imePopupLocation = location;
2404 			updateIMEPopupLocation();
2405 		} else {
2406 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2407 			// throw new NotYetImplementedException();
2408 		}
2409 	}
2410 
2411 	/// ditto
2412 	void setIMEPopupLocation(int x, int y) {
2413 		return setIMEPopupLocation(Point(x, y));
2414 	}
2415 
2416 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2417 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2418 	// receives a ConfigureNotify event
2419 	private void updateIMEPopupLocation() {
2420 		static if(UsingSimpledisplayX11) {
2421 			if (xic is null) {
2422 				return;
2423 			}
2424 
2425 			XPoint nspot;
2426 			nspot.x = cast(short) imePopupLocation.x;
2427 			nspot.y = cast(short) imePopupLocation.y;
2428 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2429 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2430 			XFree(preeditAttr);
2431 		}
2432 	}
2433 
2434 	private bool imeFocused = true;
2435 
2436 	/++
2437 		Tells the IME whether or not an input field is currently focused in the window.
2438 
2439 		Bugs:
2440 			Not implemented outside X11.
2441 	+/
2442 	void setIMEFocused(bool value) {
2443 		imeFocused = value;
2444 		updateIMEFocused();
2445 	}
2446 
2447 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2448 	private void updateIMEFocused() {
2449 		static if(UsingSimpledisplayX11) {
2450 			if (xic is null) {
2451 				return;
2452 			}
2453 
2454 			if (focused && imeFocused) {
2455 				XSetICFocus(xic);
2456 			} else {
2457 				XUnsetICFocus(xic);
2458 			}
2459 		}
2460 	}
2461 
2462 	/++
2463 		Returns the native window.
2464 
2465 		History:
2466 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2467 			to access it through the `impl` member (which is semi-supported
2468 			but platform specific and here it is simple enough to offer an accessor).
2469 
2470 		Bugs:
2471 			Not implemented outside Windows or X11.
2472 	+/
2473 	NativeWindowHandle nativeWindowHandle() {
2474 		version(X11)
2475 			return impl.window;
2476 		else version(Windows)
2477 			return impl.hwnd;
2478 		else
2479 			throw new NotYetImplementedException();
2480 	}
2481 
2482 	private bool isTransient() {
2483 		with(WindowTypes)
2484 		final switch(windowType) {
2485 			case normal, undecorated, eventOnly:
2486 			case nestedChild, minimallyWrapped:
2487 				return (customizationFlags & WindowFlags.transient) ? true : false;
2488 			case dropdownMenu, popupMenu, notification:
2489 				return true;
2490 		}
2491 	}
2492 
2493 	private SimpleWindow inputProxy;
2494 
2495 	/++
2496 		Releases the grab acquired by [grabInput].
2497 	+/
2498 	void releaseInputGrab() {
2499 		static if(UsingSimpledisplayX11) {
2500 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2501 			if(_parent)
2502 				_parent.inputProxy = null;
2503 		} else version(Windows) {
2504 			ReleaseCapture();
2505 			ClipCursor(null);
2506 		} else version(OSXCocoa) {
2507 			// throw new NotYetImplementedException();
2508 		} else static assert(0);
2509 	}
2510 
2511 	/++
2512 		Sets the input focus to this window.
2513 
2514 		You shouldn't call this very often - please let the user control the input focus.
2515 	+/
2516 	void focus() {
2517 		static if(UsingSimpledisplayX11) {
2518 			SimpleWindow setTo;
2519 			if(setRequestedInputFocus !is null)
2520 				setTo = setRequestedInputFocus();
2521 			if(setTo is null)
2522 				setTo = this;
2523 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2524 		} else version(Windows) {
2525 			SetFocus(this.impl.hwnd);
2526 		} else version(OSXCocoa) {
2527 			throw new NotYetImplementedException();
2528 		} else static assert(0);
2529 	}
2530 
2531 	/++
2532 		Requests attention from the user for this window.
2533 
2534 
2535 		The typical result of this function is to change the color
2536 		of the taskbar icon, though it may be tweaked on specific
2537 		platforms.
2538 
2539 		It is meant to unobtrusively tell the user that something
2540 		relevant to them happened in the background and they should
2541 		check the window when they get a chance. Upon receiving the
2542 		keyboard focus, the window will automatically return to its
2543 		natural state.
2544 
2545 		If the window already has the keyboard focus, this function
2546 		may do nothing, because the user is presumed to already be
2547 		giving the window attention.
2548 
2549 		Implementation_note:
2550 
2551 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2552 		atom on X11 and the FlashWindow function on Windows.
2553 	+/
2554 	void requestAttention() {
2555 		if(_focused)
2556 			return;
2557 
2558 		version(Windows) {
2559 			FLASHWINFO info;
2560 			info.cbSize = info.sizeof;
2561 			info.hwnd = impl.hwnd;
2562 			info.dwFlags = FLASHW_TRAY;
2563 			info.uCount = 1;
2564 
2565 			FlashWindowEx(&info);
2566 
2567 		} else version(X11) {
2568 			demandingAttention = true;
2569 			demandAttention(this, true);
2570 		} else version(OSXCocoa) {
2571 			throw new NotYetImplementedException();
2572 		} else static assert(0);
2573 	}
2574 
2575 	private bool _focused;
2576 
2577 	version(X11) private bool demandingAttention;
2578 
2579 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2580 	/// You'll have to call `close()` manually if you set this delegate.
2581 	void delegate () closeQuery;
2582 
2583 	/// This will be called when window visibility was changed.
2584 	void delegate (bool becomesVisible) visibilityChanged;
2585 
2586 	/// This will be called when window becomes visible for the first time.
2587 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2588 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2589 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2590 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2591 	private bool _visibleForTheFirstTimeCalled;
2592 	void delegate () visibleForTheFirstTime;
2593 
2594 	/// Returns true if the window has been closed.
2595 	final @property bool closed() { return _closed; }
2596 
2597 	private final @property bool notClosed() { return !_closed; }
2598 
2599 	/// Returns true if the window is focused.
2600 	final @property bool focused() { return _focused; }
2601 
2602 	private bool _visible;
2603 	/// Returns true if the window is visible (mapped).
2604 	final @property bool visible() { return _visible; }
2605 
2606 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2607 	void close() {
2608 		if (!_closed) {
2609 			runInGuiThread( {
2610 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2611 				if (onClosing !is null) onClosing();
2612 				impl.closeWindow();
2613 				_closed = true;
2614 			} );
2615 		}
2616 	}
2617 
2618 	/++
2619 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2620 
2621 		History:
2622 			Overload added on March 7, 2021.
2623 	+/
2624 	void close() shared {
2625 		(cast() this).close();
2626 	}
2627 
2628 	/++
2629 
2630 	+/
2631 	void maximize() {
2632 		version(Windows)
2633 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2634 		else version(X11) {
2635 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2636 
2637 			// also note _NET_WM_STATE_FULLSCREEN
2638 		}
2639 
2640 	}
2641 
2642 	private bool _fullscreen;
2643 	version(Windows)
2644 	private WINDOWPLACEMENT g_wpPrev;
2645 
2646 	/// not fully implemented but planned for a future release
2647 	void fullscreen(bool yes) {
2648 		version(Windows) {
2649 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2650 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2651 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2652 				MONITORINFO mi;
2653 				mi.cbSize = MONITORINFO.sizeof;
2654 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2655 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2656 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2657 					SetWindowLong(hwnd, GWL_STYLE,
2658 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2659 					SetWindowPos(hwnd, HWND_TOP,
2660 						     mi.rcMonitor.left, mi.rcMonitor.top,
2661 						     mi.rcMonitor.right - mi.rcMonitor.left,
2662 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2663 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2664 				}
2665 			} else {
2666 				SetWindowLong(hwnd, GWL_STYLE,
2667 					      dwStyle | WS_OVERLAPPEDWINDOW);
2668 				SetWindowPlacement(hwnd, &g_wpPrev);
2669 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2670 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2671 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2672 			}
2673 
2674 		} else version(X11) {
2675 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2676 		}
2677 
2678 		_fullscreen = yes;
2679 
2680 	}
2681 
2682 	bool fullscreen() {
2683 		return _fullscreen;
2684 	}
2685 
2686 	/++
2687 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2688 
2689 	+/
2690 	void minimize() {
2691 		version(Windows)
2692 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2693 		//else version(X11)
2694 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2695 	}
2696 
2697 	/// Alias for `hidden = false`
2698 	void show() {
2699 		hidden = false;
2700 	}
2701 
2702 	/// Alias for `hidden = true`
2703 	void hide() {
2704 		hidden = true;
2705 	}
2706 
2707 	/// Hide cursor when it enters the window.
2708 	void hideCursor() {
2709 		version(OSXCocoa) throw new NotYetImplementedException(); else
2710 		if (!_closed) impl.hideCursor();
2711 	}
2712 
2713 	/// Don't hide cursor when it enters the window.
2714 	void showCursor() {
2715 		version(OSXCocoa) throw new NotYetImplementedException(); else
2716 		if (!_closed) impl.showCursor();
2717 	}
2718 
2719 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2720 	 *
2721 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2722 	 * control. Try to think for other approaches before using this function.
2723 	 *
2724 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2725 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2726 	 *       receive "mouse moved here" event.
2727 	 */
2728 	bool warpMouse (int x, int y) {
2729 		version(X11) {
2730 			if (!_closed) { impl.warpMouse(x, y); return true; }
2731 		} else version(Windows) {
2732 			if (!_closed) {
2733 				POINT point;
2734 				point.x = x;
2735 				point.y = y;
2736 				if(ClientToScreen(impl.hwnd, &point)) {
2737 					SetCursorPos(point.x, point.y);
2738 					return true;
2739 				}
2740 			}
2741 		}
2742 		return false;
2743 	}
2744 
2745 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2746 	void sendDummyEvent () {
2747 		version(X11) {
2748 			if (!_closed) { impl.sendDummyEvent(); }
2749 		}
2750 	}
2751 
2752 	/// Set window minimal size.
2753 	void setMinSize (int minwidth, int minheight) {
2754 		version(OSXCocoa) throw new NotYetImplementedException(); else
2755 		if (!_closed) impl.setMinSize(minwidth, minheight);
2756 	}
2757 
2758 	/// Set window maximal size.
2759 	void setMaxSize (int maxwidth, int maxheight) {
2760 		version(OSXCocoa) throw new NotYetImplementedException(); else
2761 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2762 	}
2763 
2764 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2765 	/// Currently only supported on X11.
2766 	void setResizeGranularity (int granx, int grany) {
2767 		version(OSXCocoa) throw new NotYetImplementedException(); else
2768 		if (!_closed) impl.setResizeGranularity(granx, grany);
2769 	}
2770 
2771 	/// Move window.
2772 	void move(int x, int y) {
2773 		version(OSXCocoa) throw new NotYetImplementedException(); else
2774 		if (!_closed) impl.move(x, y);
2775 	}
2776 
2777 	/// ditto
2778 	void move(Point p) {
2779 		version(OSXCocoa) throw new NotYetImplementedException(); else
2780 		if (!_closed) impl.move(p.x, p.y);
2781 	}
2782 
2783 	/++
2784 		Resize window.
2785 
2786 		Note that the width and height of the window are NOT instantly
2787 		updated - it waits for the window manager to approve the resize
2788 		request, which means you must return to the event loop before the
2789 		width and height are actually changed.
2790 	+/
2791 	void resize(int w, int h) {
2792 		if(!_closed && _fullscreen) fullscreen = false;
2793 		version(OSXCocoa) throw new NotYetImplementedException(); else
2794 		if (!_closed) impl.resize(w, h);
2795 	}
2796 
2797 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2798 	void moveResize (int x, int y, int w, int h) {
2799 		if(!_closed && _fullscreen) fullscreen = false;
2800 		version(OSXCocoa) throw new NotYetImplementedException(); else
2801 		if (!_closed) impl.moveResize(x, y, w, h);
2802 	}
2803 
2804 	private bool _hidden;
2805 
2806 	/// Returns true if the window is hidden.
2807 	final @property bool hidden() {
2808 		return _hidden;
2809 	}
2810 
2811 	/// Shows or hides the window based on the bool argument.
2812 	final @property void hidden(bool b) {
2813 		_hidden = b;
2814 		version(Windows) {
2815 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2816 		} else version(X11) {
2817 			if(b)
2818 				//XUnmapWindow(impl.display, impl.window);
2819 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2820 			else
2821 				XMapWindow(impl.display, impl.window);
2822 		} else version(OSXCocoa) {
2823 			// throw new NotYetImplementedException();
2824 		} else static assert(0);
2825 	}
2826 
2827 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2828 	void opacity(double opacity) @property
2829 	in {
2830 		assert(opacity >= 0 && opacity <= 1);
2831 	} do {
2832 		version (Windows) {
2833 			impl.setOpacity(cast(ubyte)(255 * opacity));
2834 		} else version (X11) {
2835 			impl.setOpacity(cast(uint)(uint.max * opacity));
2836 		} else throw new NotYetImplementedException();
2837 	}
2838 
2839 	/++
2840 		Sets your event handlers, without entering the event loop. Useful if you
2841 		have multiple windows - set the handlers on each window, then only do
2842 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2843 
2844 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2845 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2846 		delegate signatures.
2847 	+/
2848 	void setEventHandlers(T...)(T eventHandlers) {
2849 		// FIXME: add more events
2850 		foreach(handler; eventHandlers) {
2851 			static if(__traits(compiles, handleKeyEvent = handler)) {
2852 				handleKeyEvent = handler;
2853 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2854 				handleCharEvent = handler;
2855 			} else static if(__traits(compiles, handlePulse = handler)) {
2856 				handlePulse = handler;
2857 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2858 				handleMouseEvent = handler;
2859 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2860 		}
2861 	}
2862 
2863 	/++
2864 		The event loop automatically returns when the window is closed
2865 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2866 		pulse timer is created. The event loop will block until an event
2867 		arrives or the pulse timer goes off.
2868 
2869 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2870 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2871 		[handleMouseEvent], based on the signature of delegates you provide.
2872 
2873 		Give one with no parameters to set a timer pulse handler. Give one that
2874 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2875 		and one that takes `dchar` for a char event handler. You can use as many
2876 		or as few handlers as you need for your application.
2877 
2878 		Bugs:
2879 
2880 		$(PITFALL
2881 			You should always have one event loop live for your application.
2882 			If you make two windows in sequence, the second call to eventLoop
2883 			might fail:
2884 
2885 			---
2886 			// don't do this!
2887 			auto window = new SimpleWindow();
2888 			window.eventLoop(0);
2889 
2890 			auto window2 = new SimpleWindow();
2891 			window2.eventLoop(0); // problematic! might crash
2892 			---
2893 
2894 			simpledisplay's current implementation assumes that final cleanup is
2895 			done when the event loop refcount reaches zero. So after the first
2896 			eventLoop returns, when there isn't already another one active, it assumes
2897 			the program will exit soon and cleans up.
2898 
2899 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2900 			it eventually, but in the mean time, there's an easy solution:
2901 
2902 			---
2903 			// do this
2904 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2905 
2906 			auto window = new SimpleWindow();
2907 			window.eventLoop(0);
2908 
2909 			auto window2 = new SimpleWindow();
2910 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2911 			---
2912 
2913 			By adding a top-level reference to the event loop, it ensures the final cleanup
2914 			is not performed until it goes out of scope too, letting the individual window loops
2915 			work without trouble despite the bug.
2916 		)
2917 
2918 		History:
2919 			The overload without `pulseTimeout` was added on December 8, 2021.
2920 
2921 			On December 9, 2021, the default blocking mode (which is now configurable
2922 			because [eventLoopWithBlockingMode] was added) switched from
2923 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2924 			should almost never be noticeable to you since the typical simpledisplay
2925 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2926 
2927 		See_Also:
2928 			[eventLoopWithBlockingMode]
2929 	+/
2930 	final int eventLoop(T...)(
2931 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2932 		T eventHandlers) /// delegate list like std.concurrency.receive
2933 	{
2934 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2935 	}
2936 
2937 	/// ditto
2938 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2939 	{
2940 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2941 	}
2942 
2943 	/++
2944 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2945 
2946 		History:
2947 			Added December 8, 2021 (dub v10.5)
2948 
2949 			Previously, this implementation was right inside [eventLoop], but when I wanted
2950 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2951 			just renamed it instead of adding as an overload. Besides, the new name makes it
2952 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2953 
2954 		See_Also:
2955 			[SimpleWindow.eventLoop], [EventLoop]
2956 
2957 		Bugs:
2958 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2959 	+/
2960 	final int eventLoopWithBlockingMode(T...)(
2961 		BlockingMode blockingMode, /// when you want this function to block until
2962 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2963 		T eventHandlers) /// delegate list like std.concurrency.receive
2964 	{
2965 		setEventHandlers(eventHandlers);
2966 
2967 		version(with_eventloop) {
2968 			// delegates event loop to my other module
2969 			version(X11)
2970 				XFlush(display);
2971 
2972 			import arsd.eventloop;
2973 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2974 			scope(exit) clearInterval(handle);
2975 
2976 			loop();
2977 			return 0;
2978 		} else version(OSXCocoa) {
2979 			// FIXME
2980 			if (handlePulse !is null && pulseTimeout != 0) {
2981 				timer = NSTimer.schedule(pulseTimeout*1e-3,
2982 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
2983 					null, true);
2984 			}
2985 
2986 			view.setNeedsDisplay(true);
2987 
2988 			NSApp.run();
2989             		return 0;
2990         	} else {
2991 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2992 
2993 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2994 				return 0;
2995 
2996 			return el.run(
2997 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2998 					null :
2999 					&this.notClosed
3000 			);
3001 		}
3002 	}
3003 
3004 	/++
3005 		This lets you draw on the window (or its backing buffer) using basic
3006 		2D primitives.
3007 
3008 		Be sure to call this in a limited scope because your changes will not
3009 		actually appear on the window until ScreenPainter's destructor runs.
3010 
3011 		Returns: an instance of [ScreenPainter], which has the drawing methods
3012 		on it to draw on this window.
3013 
3014 		Params:
3015 			manualInvalidations = if you set this to true, you will need to
3016 			set the invalid rectangle on the painter yourself. If false, it
3017 			assumes the whole window has been redrawn each time you draw.
3018 
3019 			Only invalidated rectangles are blitted back to the window when
3020 			the destructor runs. Doing this yourself can reduce flickering
3021 			of child windows.
3022 
3023 		History:
3024 			The `manualInvalidations` parameter overload was added on
3025 			December 30, 2021 (dub v10.5)
3026 	+/
3027 	ScreenPainter draw() {
3028 		return draw(false);
3029 	}
3030 	/// ditto
3031 	ScreenPainter draw(bool manualInvalidations) {
3032 		return impl.getPainter(manualInvalidations);
3033 	}
3034 
3035 	// This is here to implement the interface we use for various native handlers.
3036 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
3037 
3038 	// maps native window handles to SimpleWindow instances, if there are any
3039 	// you shouldn't need this, but it is public in case you do in a native event handler or something
3040 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
3041 	version(OSXCocoa)
3042 	public __gshared SimpleWindow[void*] nativeMapping;
3043 	else
3044 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
3045 
3046 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
3047 	private int _virtualWidth;
3048 	private int _virtualHeight;
3049 
3050 	/// Width of the window's drawable client area, in pixels.
3051 	@scriptable
3052 	final @property int width() const pure nothrow @safe @nogc {
3053 		if(resizability == Resizability.automaticallyScaleIfPossible)
3054 			return _virtualWidth;
3055 		else
3056 			return _width;
3057 	}
3058 
3059 	/// Height of the window's drawable client area, in pixels.
3060 	@scriptable
3061 	final @property int height() const pure nothrow @safe @nogc {
3062 		if(resizability == Resizability.automaticallyScaleIfPossible)
3063 			return _virtualHeight;
3064 		else
3065 			return _height;
3066 	}
3067 
3068 	/++
3069 		Returns the actual size of the window, bypassing the logical
3070 		illusions of [Resizability.automaticallyScaleIfPossible].
3071 
3072 		History:
3073 			Added November 11, 2022 (dub v10.10)
3074 	+/
3075 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
3076 		return Size(_width, _height);
3077 	}
3078 
3079 
3080 	private int _width;
3081 	private int _height;
3082 
3083 	// HACK: making the best of some copy constructor woes with refcounting
3084 	private ScreenPainterImplementation* activeScreenPainter_;
3085 
3086 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
3087 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
3088 
3089 	private OpenGlOptions openglMode;
3090 	private Resizability resizability;
3091 	private WindowTypes windowType;
3092 	private int customizationFlags;
3093 
3094 	/// `true` if OpenGL was initialized for this window.
3095 	@property bool isOpenGL () const pure nothrow @safe @nogc {
3096 		version(without_opengl)
3097 			return false;
3098 		else
3099 			return (openglMode == OpenGlOptions.yes);
3100 	}
3101 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
3102 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
3103 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
3104 
3105 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3106 	/// to call this, as it's not recommended to share window between threads.
3107 	void mtLock () {
3108 		version(X11) {
3109 			XLockDisplay(this.display);
3110 		}
3111 	}
3112 
3113 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3114 	/// to call this, as it's not recommended to share window between threads.
3115 	void mtUnlock () {
3116 		version(X11) {
3117 			XUnlockDisplay(this.display);
3118 		}
3119 	}
3120 
3121 	/// Emit a beep to get user's attention.
3122 	void beep () {
3123 		version(X11) {
3124 			XBell(this.display, 100);
3125 		} else version(Windows) {
3126 			MessageBeep(0xFFFFFFFF);
3127 		}
3128 	}
3129 
3130 
3131 
3132 	version(without_opengl) {} else {
3133 
3134 		/// 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`.
3135 		void delegate() redrawOpenGlScene;
3136 
3137 		/// This will allow you to change OpenGL vsync state.
3138 		final @property void vsync (bool wait) {
3139 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3140 		  version(X11) {
3141 		    setAsCurrentOpenGlContext();
3142 		    glxSetVSync(display, impl.window, wait);
3143 		  } else version(Windows) {
3144 		    setAsCurrentOpenGlContext();
3145                     wglSetVSync(wait);
3146 		  }
3147 		}
3148 
3149 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3150 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3151 		/// enough without waiting 'em to finish their frame business.
3152 		bool useGLFinish = true;
3153 
3154 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3155 		/// 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.
3156 		void redrawOpenGlSceneNow() {
3157 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3158 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3159 			if(redrawOpenGlScene is null)
3160 				return;
3161 
3162 			this.mtLock();
3163 			scope(exit) this.mtUnlock();
3164 
3165 			this.setAsCurrentOpenGlContext();
3166 
3167 			redrawOpenGlScene();
3168 
3169 			this.swapOpenGlBuffers();
3170 			// 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.
3171 			if (useGLFinish) glFinish();
3172 		}
3173 
3174 		private bool redrawOpenGlSceneSoonSet = false;
3175 		private static class RedrawOpenGlSceneEvent {
3176 			SimpleWindow w;
3177 			this(SimpleWindow w) { this.w = w; }
3178 		}
3179 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3180 		/++
3181 			Queues an opengl redraw as soon as the other pending events are cleared.
3182 		+/
3183 		void redrawOpenGlSceneSoon() {
3184 			if(redrawOpenGlScene is null)
3185 				return;
3186 
3187 			if(!redrawOpenGlSceneSoonSet) {
3188 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3189 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3190 				redrawOpenGlSceneSoonSet = true;
3191 			}
3192 			this.postEvent(redrawOpenGlSceneEvent, true);
3193 		}
3194 
3195 
3196 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3197 		void setAsCurrentOpenGlContext() {
3198 			assert(openglMode == OpenGlOptions.yes);
3199 			version(X11) {
3200 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3201 					throw new Exception("glXMakeCurrent");
3202 			} else version(Windows) {
3203 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3204 				if (!wglMakeCurrent(ghDC, ghRC))
3205 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3206 			}
3207 		}
3208 
3209 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3210 		/// This doesn't throw, returning success flag instead.
3211 		bool setAsCurrentOpenGlContextNT() nothrow {
3212 			assert(openglMode == OpenGlOptions.yes);
3213 			version(X11) {
3214 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3215 			} else version(Windows) {
3216 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3217 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3218 			}
3219 		}
3220 
3221 		/// 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.
3222 		/// This doesn't throw, returning success flag instead.
3223 		bool releaseCurrentOpenGlContext() nothrow {
3224 			assert(openglMode == OpenGlOptions.yes);
3225 			version(X11) {
3226 				return (glXMakeCurrent(display, 0, null) != 0);
3227 			} else version(Windows) {
3228 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3229 				return wglMakeCurrent(ghDC, null) ? true : false;
3230 			}
3231 		}
3232 
3233 		/++
3234 			simpledisplay always uses double buffering, usually automatically. This
3235 			manually swaps the OpenGL buffers.
3236 
3237 
3238 			You should not need to call this yourself because simpledisplay will do it
3239 			for you after calling your `redrawOpenGlScene`.
3240 
3241 			Remember that this may throw an exception, which you can catch in a multithreaded
3242 			application to keep your thread from dying from an unhandled exception.
3243 		+/
3244 		void swapOpenGlBuffers() {
3245 			assert(openglMode == OpenGlOptions.yes);
3246 			version(X11) {
3247 				if (!this._visible) return; // no need to do this if window is invisible
3248 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3249 				glXSwapBuffers(display, impl.window);
3250 			} else version(Windows) {
3251 				SwapBuffers(ghDC);
3252 			}
3253 		}
3254 	}
3255 
3256 	/++
3257 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3258 
3259 
3260 		---
3261 			auto window = new SimpleWindow(100, 100, "First title");
3262 			window.title = "A new title";
3263 		---
3264 
3265 		You may call this function at any time.
3266 	+/
3267 	@property void title(string title) {
3268 		_title = title;
3269 		version(OSXCocoa) throw new NotYetImplementedException(); else
3270 		impl.setTitle(title);
3271 	}
3272 
3273 	private string _title;
3274 
3275 	/// Gets the title
3276 	@property string title() {
3277 		if(_title is null)
3278 			_title = getRealTitle();
3279 		return _title;
3280 	}
3281 
3282 	/++
3283 		Get the title as set by the window manager.
3284 		May not match what you attempted to set.
3285 	+/
3286 	string getRealTitle() {
3287 		static if(is(typeof(impl.getTitle())))
3288 			return impl.getTitle();
3289 		else
3290 			return null;
3291 	}
3292 
3293 	// don't use this generally it is not yet really released
3294 	version(X11)
3295 	@property Image secret_icon() {
3296 		return secret_icon_inner;
3297 	}
3298 	private Image secret_icon_inner;
3299 
3300 
3301 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3302 	@property void icon(MemoryImage icon) {
3303 		if(icon is null)
3304 			return;
3305 		auto tci = icon.getAsTrueColorImage();
3306 		version(Windows) {
3307 			winIcon = new WindowsIcon(icon);
3308 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3309 		} else version(X11) {
3310 			secret_icon_inner = Image.fromMemoryImage(icon);
3311 			// FIXME: ensure this is correct
3312 			auto display = XDisplayConnection.get;
3313 			arch_ulong[] buffer;
3314 			buffer ~= icon.width;
3315 			buffer ~= icon.height;
3316 			foreach(c; tci.imageData.colors) {
3317 				arch_ulong b;
3318 				b |= c.a << 24;
3319 				b |= c.r << 16;
3320 				b |= c.g << 8;
3321 				b |= c.b;
3322 				buffer ~= b;
3323 			}
3324 
3325 			XChangeProperty(
3326 				display,
3327 				impl.window,
3328 				GetAtom!("_NET_WM_ICON", true)(display),
3329 				GetAtom!"CARDINAL"(display),
3330 				32 /* bits */,
3331 				0 /*PropModeReplace*/,
3332 				buffer.ptr,
3333 				cast(int) buffer.length);
3334 		} else version(OSXCocoa) {
3335 			throw new NotYetImplementedException();
3336 		} else static assert(0);
3337 	}
3338 
3339 	version(Windows)
3340 		private WindowsIcon winIcon;
3341 
3342 	bool _suppressDestruction;
3343 
3344 	~this() {
3345 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3346 		if(_suppressDestruction)
3347 			return;
3348 		impl.dispose();
3349 	}
3350 
3351 	private bool _closed;
3352 
3353 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3354 	/*
3355 	ScreenPainter drawTransiently() {
3356 		return impl.getPainter();
3357 	}
3358 	*/
3359 
3360 	/// Draws an image on the window. This is meant to provide quick look
3361 	/// of a static image generated elsewhere.
3362 	@property void image(Image i) {
3363 	/+
3364 		version(Windows) {
3365 			BITMAP bm;
3366 			HDC hdc = GetDC(hwnd);
3367 			HDC hdcMem = CreateCompatibleDC(hdc);
3368 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3369 
3370 			GetObject(i.handle, bm.sizeof, &bm);
3371 
3372 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3373 
3374 			SelectObject(hdcMem, hbmOld);
3375 			DeleteDC(hdcMem);
3376 			ReleaseDC(hwnd, hdc);
3377 
3378 			/*
3379 			RECT r;
3380 			r.right = i.width;
3381 			r.bottom = i.height;
3382 			InvalidateRect(hwnd, &r, false);
3383 			*/
3384 		} else
3385 		version(X11) {
3386 			if(!destroyed) {
3387 				if(i.usingXshm)
3388 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3389 				else
3390 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3391 			}
3392 		} else
3393 		version(OSXCocoa) {
3394 			draw().drawImage(Point(0, 0), i);
3395 			setNeedsDisplay(view, true);
3396 		} else static assert(0);
3397 	+/
3398 		auto painter = this.draw;
3399 		painter.drawImage(Point(0, 0), i);
3400 	}
3401 
3402 	/++
3403 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3404 
3405 		---
3406 		window.cursor = GenericCursor.Help;
3407 		// now the window mouse cursor is set to a generic help
3408 		---
3409 
3410 	+/
3411 	@property void cursor(MouseCursor cursor) {
3412 		version(OSXCocoa)
3413 			{} // featureNotImplemented();
3414 		else
3415 		if(this.impl.curHidden <= 0) {
3416 			static if(UsingSimpledisplayX11) {
3417 				auto ch = cursor.cursorHandle;
3418 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3419 			} else version(Windows) {
3420 				auto ch = cursor.cursorHandle;
3421 				impl.currentCursor = ch;
3422 				SetCursor(ch); // redraw without waiting for mouse movement to update
3423 			} else featureNotImplemented();
3424 		}
3425 
3426 	}
3427 
3428 	/// What follows are the event handlers. These are set automatically
3429 	/// by the eventLoop function, but are still public so you can change
3430 	/// them later. wasPressed == true means key down. false == key up.
3431 
3432 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3433 	void delegate(KeyEvent ke) handleKeyEvent;
3434 
3435 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3436 	void delegate(dchar c) handleCharEvent;
3437 
3438 	/// Handles a timer pulse. Settable through setEventHandlers.
3439 	void delegate() handlePulse;
3440 
3441 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3442 	void delegate(bool) onFocusChange;
3443 
3444 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3445 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3446 	void delegate() onClosing;
3447 
3448 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3449 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3450 	 * last minute cleanup. */
3451 	void delegate() onDestroyed;
3452 
3453 	static if (UsingSimpledisplayX11)
3454 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3455 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3456 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3457 	 *
3458 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3459 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3460 
3461 	//version(Windows)
3462 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3463 
3464 	private {
3465 		int lastMouseX = int.min;
3466 		int lastMouseY = int.min;
3467 		void mdx(ref MouseEvent ev) {
3468 			if(lastMouseX == int.min || lastMouseY == int.min) {
3469 				ev.dx = 0;
3470 				ev.dy = 0;
3471 			} else {
3472 				ev.dx = ev.x - lastMouseX;
3473 				ev.dy = ev.y - lastMouseY;
3474 			}
3475 
3476 			lastMouseX = ev.x;
3477 			lastMouseY = ev.y;
3478 		}
3479 	}
3480 
3481 	/// Mouse event handler. Settable through setEventHandlers.
3482 	void delegate(MouseEvent) handleMouseEvent;
3483 
3484 	/// use to redraw child widgets if you use system apis to add stuff
3485 	void delegate() paintingFinished;
3486 
3487 	void delegate() paintingFinishedDg() {
3488 		return paintingFinished;
3489 	}
3490 
3491 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3492 	/// for this to ever happen.
3493 	void delegate(int width, int height) windowResized;
3494 
3495 	/++
3496 		Platform specific - handle any native message this window gets.
3497 
3498 		Note: this is called *in addition to* other event handlers, unless you either:
3499 
3500 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3501 
3502 		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.
3503 
3504 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3505 
3506 		On X, it takes the form of `int delegate(XEvent)`.
3507 
3508 		History:
3509 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3510 
3511 			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.
3512 	+/
3513 	NativeEventHandler handleNativeEvent_;
3514 
3515 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3516 		return handleNativeEvent_;
3517 	}
3518 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3519 		handleNativeEvent_ = neh;
3520 	}
3521 
3522 	version(Windows)
3523 	// compatibility shim with the old deprecated way
3524 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3525 	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) {
3526 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3527 			auto ret = dg(h, m, w, l);
3528 			if(ret == 0)
3529 				r = 1;
3530 			return ret;
3531 		};
3532 	}
3533 
3534 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3535 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3536 	/// this instead and it will work the same way.
3537 	__gshared NativeEventHandler handleNativeGlobalEvent;
3538 
3539 //  private:
3540 	/// The native implementation is available, but you shouldn't use it unless you are
3541 	/// familiar with the underlying operating system, don't mind depending on it, and
3542 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3543 	/// do what you need to do with handleNativeEvent instead.
3544 	///
3545 	/// This is likely to eventually change to be just a struct holding platform-specific
3546 	/// handles instead of a template mixin at some point because I'm not happy with the
3547 	/// code duplication here (ironically).
3548 	mixin NativeSimpleWindowImplementation!() impl;
3549 
3550 	/**
3551 		This is in-process one-way (from anything to window) event sending mechanics.
3552 		It is thread-safe, so it can be used in multi-threaded applications to send,
3553 		for example, "wake up and repaint" events when thread completed some operation.
3554 		This will allow to avoid using timer pulse to check events with synchronization,
3555 		'cause event handler will be called in UI thread. You can stop guessing which
3556 		pulse frequency will be enough for your app.
3557 		Note that events handlers may be called in arbitrary order, i.e. last registered
3558 		handler can be called first, and vice versa.
3559 	*/
3560 public:
3561 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3562 	 * "spamming" window with events it can't cope with.
3563 	 * It is safe to call this from non-UI threads.
3564 	 */
3565 	@property bool eventQueueEmpty() () {
3566 		synchronized(this) {
3567 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3568 		}
3569 		return true;
3570 	}
3571 
3572 	/** Does our custom event queue contains at least one with the given type?
3573 	 * Can be used in simple cases to prevent "spamming" window with events
3574 	 * it can't cope with.
3575 	 * It is safe to call this from non-UI threads.
3576 	 */
3577 	@property bool eventQueued(ET:Object) () {
3578 		synchronized(this) {
3579 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3580 				if (!o.doProcess) {
3581 					if (cast(ET)(o.evt)) return true;
3582 				}
3583 			}
3584 		}
3585 		return false;
3586 	}
3587 
3588 	/++
3589 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3590 
3591 		History:
3592 			Added May 12, 2021
3593 	+/
3594 	void delegate(Exception e) nothrow eventUncaughtException;
3595 
3596 	/** Add listener for custom event. Can be used like this:
3597 	 *
3598 	 * ---------------------
3599 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3600 	 *   ...
3601 	 *   win.removeEventListener(eid);
3602 	 * ---------------------
3603 	 *
3604 	 * Returns: 0 on failure (should never happen, so ignore it)
3605 	 *
3606 	 * $(WARNING Don't use this method in object destructors!)
3607 	 *
3608 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3609 	 *           'cause if event handler id counter will overflow, you won't be able
3610 	 *           to register any more events.)
3611 	 */
3612 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3613 		if (dg is null) return 0; // ignore empty handlers
3614 		synchronized(this) {
3615 			//FIXME: abort on overflow?
3616 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3617 			EventHandlerEntry e;
3618 			e.dg = delegate (Object o) {
3619 				if (auto co = cast(ET)o) {
3620 					try {
3621 						dg(co);
3622 					} catch (Exception e) {
3623 						// sorry!
3624 						if(eventUncaughtException)
3625 							eventUncaughtException(e);
3626 					}
3627 					return true;
3628 				}
3629 				return false;
3630 			};
3631 			e.id = lastUsedHandlerId;
3632 			auto optr = eventHandlers.ptr;
3633 			eventHandlers ~= e;
3634 			if (eventHandlers.ptr !is optr) {
3635 				import core.memory : GC;
3636 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3637 			}
3638 			return lastUsedHandlerId;
3639 		}
3640 	}
3641 
3642 	/// Remove event listener. It is safe to pass invalid event id here.
3643 	/// $(WARNING Don't use this method in object destructors!)
3644 	void removeEventListener() (uint id) {
3645 		if (id == 0 || id > lastUsedHandlerId) return;
3646 		synchronized(this) {
3647 			foreach (immutable idx; 0..eventHandlers.length) {
3648 				if (eventHandlers[idx].id == id) {
3649 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3650 					eventHandlers[$-1].dg = null;
3651 					eventHandlers.length -= 1;
3652 					eventHandlers.assumeSafeAppend;
3653 					return;
3654 				}
3655 			}
3656 		}
3657 	}
3658 
3659 	/// Post event to queue. It is safe to call this from non-UI threads.
3660 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3661 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3662 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3663 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3664 		if (this.closed) return false; // closed windows can't handle events
3665 
3666 		// remove all events of type `ET`
3667 		void removeAllET () {
3668 			uint eidx = 0, ec = eventQueueUsed;
3669 			auto eptr = eventQueue.ptr;
3670 			while (eidx < ec) {
3671 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3672 				if (cast(ET)eptr.evt !is null) {
3673 					// i found her!
3674 					if (inCustomEventProcessor) {
3675 						// if we're in custom event processing loop, processor will clear it for us
3676 						eptr.evt = null;
3677 						++eidx;
3678 						++eptr;
3679 					} else {
3680 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3681 						ec = --eventQueueUsed;
3682 						// clear last event (it is already copied)
3683 						eventQueue.ptr[ec].evt = null;
3684 					}
3685 				} else {
3686 					++eidx;
3687 					++eptr;
3688 				}
3689 			}
3690 		}
3691 
3692 		if (evt is null) {
3693 			if (replace) { synchronized(this) removeAllET(); }
3694 			// ignore empty events, they can't be handled anyway
3695 			return false;
3696 		}
3697 
3698 		// add events even if no event FD/event object created yet
3699 		synchronized(this) {
3700 			if (replace) removeAllET();
3701 			if (eventQueueUsed == uint.max) return false; // just in case
3702 			if (eventQueueUsed < eventQueue.length) {
3703 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3704 			} else {
3705 				if (eventQueue.capacity == eventQueue.length) {
3706 					// need to reallocate; do a trick to ensure that old array is cleared
3707 					auto oarr = eventQueue;
3708 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3709 					// just in case, do yet another check
3710 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3711 					import core.memory : GC;
3712 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3713 				} else {
3714 					auto optr = eventQueue.ptr;
3715 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3716 					assert(eventQueue.ptr is optr);
3717 				}
3718 				++eventQueueUsed;
3719 				assert(eventQueueUsed == eventQueue.length);
3720 			}
3721 			if (!eventWakeUp()) {
3722 				// can't wake up event processor, so there is no reason to keep the event
3723 				assert(eventQueueUsed > 0);
3724 				eventQueue[--eventQueueUsed].evt = null;
3725 				return false;
3726 			}
3727 			return true;
3728 		}
3729 	}
3730 
3731 	/// Post event to queue. It is safe to call this from non-UI threads.
3732 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3733 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3734 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3735 		return postTimeout!ET(evt, 0, replace);
3736 	}
3737 
3738 private:
3739 	private import core.time : MonoTime;
3740 
3741 	version(Posix) {
3742 		__gshared int customEventFDRead = -1;
3743 		__gshared int customEventFDWrite = -1;
3744 		__gshared int customSignalFD = -1;
3745 	} else version(Windows) {
3746 		__gshared HANDLE customEventH = null;
3747 	}
3748 
3749 	// wake up event processor
3750 	static bool eventWakeUp () {
3751 		version(X11) {
3752 			import core.sys.posix.unistd : write;
3753 			ulong n = 1;
3754 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3755 			return true;
3756 		} else version(Windows) {
3757 			if (customEventH !is null) SetEvent(customEventH);
3758 			return true;
3759 		} else version(OSXCocoa) {
3760 			if(globalAppDelegate)
3761 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3762 			return true;
3763 		} else {
3764 			// not implemented for other OSes
3765 			return false;
3766 		}
3767 	}
3768 
3769 	static struct QueuedEvent {
3770 		Object evt;
3771 		bool timed = false;
3772 		MonoTime hittime = MonoTime.zero;
3773 		bool doProcess = false; // process event at the current iteration (internal flag)
3774 
3775 		this (Object aevt, uint toutmsecs) {
3776 			evt = aevt;
3777 			if (toutmsecs > 0) {
3778 				import core.time : msecs;
3779 				timed = true;
3780 				hittime = MonoTime.currTime+toutmsecs.msecs;
3781 			}
3782 		}
3783 	}
3784 
3785 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3786 	static struct EventHandlerEntry {
3787 		CustomEventHandler dg;
3788 		uint id;
3789 	}
3790 
3791 	uint lastUsedHandlerId;
3792 	EventHandlerEntry[] eventHandlers;
3793 	QueuedEvent[] eventQueue = null;
3794 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3795 	bool inCustomEventProcessor = false; // required to properly remove events
3796 
3797 	// process queued events and call custom event handlers
3798 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3799 	void processCustomEvents () @system {
3800 		bool hasSomethingToDo = false;
3801 		uint ecount;
3802 		bool ocep;
3803 		synchronized(this) {
3804 			ocep = inCustomEventProcessor;
3805 			inCustomEventProcessor = true;
3806 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3807 			auto ctt = MonoTime.currTime;
3808 			bool hasEmpty = false;
3809 			// mark events to process (this is required for `eventQueued()`)
3810 			foreach (ref qe; eventQueue[0..ecount]) {
3811 				if (qe.evt is null) { hasEmpty = true; continue; }
3812 				if (qe.timed) {
3813 					qe.doProcess = (qe.hittime <= ctt);
3814 				} else {
3815 					qe.doProcess = true;
3816 				}
3817 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3818 			}
3819 			if (!hasSomethingToDo) {
3820 				// remove empty events
3821 				if (hasEmpty) {
3822 					uint eidx = 0, ec = eventQueueUsed;
3823 					auto eptr = eventQueue.ptr;
3824 					while (eidx < ec) {
3825 						if (eptr.evt is null) {
3826 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3827 							ec = --eventQueueUsed;
3828 							eventQueue.ptr[ec].evt = null; // make GC life easier
3829 						} else {
3830 							++eidx;
3831 							++eptr;
3832 						}
3833 					}
3834 				}
3835 				inCustomEventProcessor = ocep;
3836 				return;
3837 			}
3838 		}
3839 		// process marked events
3840 		uint efree = 0; // non-processed events will be put at this index
3841 		EventHandlerEntry[] eh;
3842 		Object evt;
3843 		foreach (immutable eidx; 0..ecount) {
3844 			synchronized(this) {
3845 				if (!eventQueue[eidx].doProcess) {
3846 					// skip this event
3847 					assert(efree <= eidx);
3848 					if (efree != eidx) {
3849 						// copy this event to queue start
3850 						eventQueue[efree] = eventQueue[eidx];
3851 						eventQueue[eidx].evt = null; // just in case
3852 					}
3853 					++efree;
3854 					continue;
3855 				}
3856 				evt = eventQueue[eidx].evt;
3857 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3858 				if (evt is null) continue; // just in case
3859 				// try all handlers; this can be slow, but meh...
3860 				eh = eventHandlers;
3861 			}
3862 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3863 			evt = null;
3864 			eh = null;
3865 		}
3866 		synchronized(this) {
3867 			// move all unprocessed events to queue top; efree holds first "free index"
3868 			foreach (immutable eidx; ecount..eventQueueUsed) {
3869 				assert(efree <= eidx);
3870 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3871 				++efree;
3872 			}
3873 			eventQueueUsed = efree;
3874 			// wake up event processor on next event loop iteration if we have more queued events
3875 			// also, remove empty events
3876 			bool awaken = false;
3877 			uint eidx = 0, ec = eventQueueUsed;
3878 			auto eptr = eventQueue.ptr;
3879 			while (eidx < ec) {
3880 				if (eptr.evt is null) {
3881 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3882 					ec = --eventQueueUsed;
3883 					eventQueue.ptr[ec].evt = null; // make GC life easier
3884 				} else {
3885 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3886 					++eidx;
3887 					++eptr;
3888 				}
3889 			}
3890 			inCustomEventProcessor = ocep;
3891 		}
3892 	}
3893 
3894 	// for all windows in nativeMapping
3895 	package static void processAllCustomEvents () @system {
3896 
3897 		cleanupQueue.process();
3898 
3899 		justCommunication.processCustomEvents();
3900 
3901 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3902 			if (sw is null || sw.closed) continue;
3903 			sw.processCustomEvents();
3904 		}
3905 
3906 		runPendingRunInGuiThreadDelegates();
3907 	}
3908 
3909 	// 0: infinite (i.e. no scheduled events in queue)
3910 	uint eventQueueTimeoutMSecs () {
3911 		synchronized(this) {
3912 			if (eventQueueUsed == 0) return 0;
3913 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3914 			uint res = int.max;
3915 			auto ctt = MonoTime.currTime;
3916 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3917 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3918 				if (qe.doProcess) continue; // just in case
3919 				if (!qe.timed) return 1; // minimal
3920 				if (qe.hittime <= ctt) return 1; // minimal
3921 				auto tms = (qe.hittime-ctt).total!"msecs";
3922 				if (tms < 1) tms = 1; // safety net
3923 				if (tms >= int.max) tms = int.max-1; // and another safety net
3924 				if (res > tms) res = cast(uint)tms;
3925 			}
3926 			return (res >= int.max ? 0 : res);
3927 		}
3928 	}
3929 
3930 	// for all windows in nativeMapping
3931 	static uint eventAllQueueTimeoutMSecs () {
3932 		uint res = uint.max;
3933 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3934 			if (sw is null || sw.closed) continue;
3935 			uint to = sw.eventQueueTimeoutMSecs();
3936 			if (to && to < res) {
3937 				res = to;
3938 				if (to == 1) break; // can't have less than this
3939 			}
3940 		}
3941 		return (res >= int.max ? 0 : res);
3942 	}
3943 
3944 	version(X11) {
3945 		ResizeEvent pendingResizeEvent;
3946 	}
3947 
3948 	/++
3949 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3950 
3951 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3952 		worth so you can disable it by setting this to `true`.
3953 
3954 		History:
3955 			Added November 13, 2022.
3956 	+/
3957 	public bool suppressAutoOpenglViewport = false;
3958 	private void updateOpenglViewportIfNeeded(int width, int height) {
3959 		if(suppressAutoOpenglViewport) return;
3960 
3961 		version(without_opengl) {} else
3962 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
3963 		// writeln(width, " ", height);
3964 			setAsCurrentOpenGlContextNT();
3965 			glViewport(0, 0, width, height);
3966 		}
3967 	}
3968 
3969 	// TODO: Implement on non-Windows platforms (where available).
3970 	private CornerStyle _fauxCornerStyle = CornerStyle.automatic;
3971 
3972 	/++
3973 		Style of the window's corners
3974 
3975 		$(WARNING
3976 			Currently only implemented on Windows targets.
3977 			Has no visual effect elsewhere.
3978 
3979 			Windows: Requires Windows 11 or later.
3980 		)
3981 
3982 		History:
3983 			Added September 09, 2024.
3984 	 +/
3985 	public CornerStyle cornerStyle() @trusted {
3986 		version(Windows) {
3987 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
3988 			const apiResult = DwmGetWindowAttribute(
3989 				this.hwnd,
3990 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
3991 				&dwmCorner,
3992 				typeof(dwmCorner).sizeof
3993 			);
3994 
3995 			if (apiResult != S_OK) {
3996 				// Unsupported?
3997 				if (apiResult == E_INVALIDARG) {
3998 					// Feature unsupported; Windows version probably too old.
3999 					// Requires Windows 11 (build 22000) or later.
4000 					return _fauxCornerStyle;
4001 				}
4002 
4003 				throw new WindowsApiException("DwmGetWindowAttribute", apiResult);
4004 			}
4005 
4006 			CornerStyle corner;
4007 			if (!dwmCorner.fromDWM(corner)) {
4008 				throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner);
4009 			}
4010 			return corner;
4011 		} else {
4012 			return _fauxCornerStyle;
4013 		}
4014 	}
4015 
4016 	/// ditto
4017 	public void cornerStyle(const CornerStyle corner) @trusted {
4018 		version(Windows) {
4019 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4020 			if (!corner.toDWM(dwmCorner)) {
4021 				assert(false, "This should have been impossible because of a final switch.");
4022 			}
4023 
4024 			const apiResult = DwmSetWindowAttribute(
4025 				this.hwnd,
4026 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4027 				&dwmCorner,
4028 				typeof(dwmCorner).sizeof
4029 			);
4030 
4031 			if (apiResult != S_OK) {
4032 				// Unsupported?
4033 				if (apiResult == E_INVALIDARG) {
4034 					// Feature unsupported; Windows version probably too old.
4035 					// Requires Windows 11 (build 22000) or later.
4036 					_fauxCornerStyle = corner;
4037 					return;
4038 				}
4039 
4040 				throw new WindowsApiException("DwmSetWindowAttribute", apiResult);
4041 			}
4042 		} else {
4043 			_fauxCornerStyle = corner;
4044 		}
4045 	}
4046 }
4047 
4048 version(OSXCocoa)
4049 	enum NSWindow NullWindow = null;
4050 else
4051 	enum NullWindow = NativeWindowHandle.init;
4052 
4053 /++
4054 	Magic pseudo-window for just posting events to a global queue.
4055 
4056 	Not entirely supported, I might delete it at any time.
4057 
4058 	Added Nov 5, 2021.
4059 +/
4060 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
4061 
4062 /* Drag and drop support { */
4063 version(X11) {
4064 
4065 } else version(Windows) {
4066 	import core.sys.windows.uuid;
4067 	import core.sys.windows.ole2;
4068 	import core.sys.windows.oleidl;
4069 	import core.sys.windows.objidl;
4070 	import core.sys.windows.wtypes;
4071 
4072 	pragma(lib, "ole32");
4073 	void initDnd() {
4074 		auto err = OleInitialize(null);
4075 		if(err != S_OK && err != S_FALSE)
4076 			throw new Exception("init");//err);
4077 	}
4078 }
4079 /* } End drag and drop support */
4080 
4081 
4082 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
4083 /// See [GenericCursor].
4084 class MouseCursor {
4085 	int osId;
4086 	bool isStockCursor;
4087 	private this(int osId) {
4088 		this.osId = osId;
4089 		this.isStockCursor = true;
4090 	}
4091 
4092 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
4093 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
4094 
4095 	version(Windows) {
4096 		HCURSOR cursor_;
4097 		HCURSOR cursorHandle() {
4098 			if(cursor_ is null)
4099 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
4100 			return cursor_;
4101 		}
4102 
4103 	} else static if(UsingSimpledisplayX11) {
4104 		Cursor cursor_ = None;
4105 		int xDisplaySequence;
4106 
4107 		Cursor cursorHandle() {
4108 			if(this.osId == None)
4109 				return None;
4110 
4111 			// we need to reload if we on a new X connection
4112 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
4113 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
4114 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
4115 			}
4116 			return cursor_;
4117 		}
4118 	}
4119 }
4120 
4121 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
4122 // https://tronche.com/gui/x/xlib/appendix/b/
4123 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
4124 /// 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.
4125 enum GenericCursorType {
4126 	Default, /// The default arrow pointer.
4127 	Wait, /// A cursor indicating something is loading and the user must wait.
4128 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
4129 	Help, /// A cursor indicating the user can get help about the pointer location.
4130 	Cross, /// A crosshair.
4131 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
4132 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
4133 	UpArrow, /// An arrow pointing straight up.
4134 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
4135 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
4136 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
4137 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
4138 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
4139 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
4140 
4141 }
4142 
4143 /*
4144 	X_plus == css cell == Windows ?
4145 */
4146 
4147 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
4148 static struct GenericCursor {
4149 	static:
4150 	///
4151 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
4152 		static MouseCursor mc;
4153 
4154 		auto type = __traits(getMember, GenericCursorType, str);
4155 
4156 		if(mc is null) {
4157 
4158 			version(Windows) {
4159 				int osId;
4160 				final switch(type) {
4161 					case GenericCursorType.Default: osId = IDC_ARROW; break;
4162 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
4163 					case GenericCursorType.Hand: osId = IDC_HAND; break;
4164 					case GenericCursorType.Help: osId = IDC_HELP; break;
4165 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
4166 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
4167 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
4168 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
4169 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
4170 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
4171 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
4172 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
4173 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
4174 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
4175 				}
4176 			} else static if(UsingSimpledisplayX11) {
4177 				int osId;
4178 				final switch(type) {
4179 					case GenericCursorType.Default: osId = None; break;
4180 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
4181 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
4182 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
4183 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
4184 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
4185 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
4186 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
4187 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
4188 
4189 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
4190 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
4191 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
4192 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
4193 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
4194 				}
4195 
4196 			} else {
4197 				int osId;
4198 				// featureNotImplemented();
4199 			}
4200 
4201 			mc = new MouseCursor(osId);
4202 		}
4203 		return mc;
4204 	}
4205 }
4206 
4207 
4208 /++
4209 	If you want to get more control over the event loop, you can use this.
4210 
4211 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4212 	to `EventLoop.get.run`.
4213 +/
4214 struct EventLoop {
4215 	@disable this();
4216 
4217 	/// Gets a reference to an existing event loop
4218 	static EventLoop get() {
4219 		return EventLoop(0, null);
4220 	}
4221 
4222 	static void quitApplication() {
4223 		version(use_arsd_core) {
4224 			import arsd.core;
4225 			ICoreEventLoop.exitApplication();
4226 		}
4227 		EventLoop.get().exit();
4228 	}
4229 
4230 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4231 
4232 	/// Construct an application-global event loop for yourself
4233 	/// See_Also: [SimpleWindow.setEventHandlers]
4234 	this(long pulseTimeout, void delegate() handlePulse) {
4235 		synchronized(monitor) {
4236 			if(impl is null) {
4237 				claimGuiThread();
4238 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4239 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4240 			} else {
4241 				if(pulseTimeout) {
4242 					impl.pulseTimeout = pulseTimeout;
4243 					impl.handlePulse = handlePulse;
4244 				}
4245 			}
4246 			impl.refcount++;
4247 		}
4248 	}
4249 
4250 	~this() {
4251 		if(impl is null)
4252 			return;
4253 		impl.refcount--;
4254 		if(impl.refcount == 0) {
4255 			impl.dispose();
4256 			if(thisIsGuiThread)
4257 				guiThreadFinalize();
4258 		}
4259 
4260 	}
4261 
4262 	this(this) {
4263 		if(impl is null)
4264 			return;
4265 		impl.refcount++;
4266 	}
4267 
4268 	/// Runs the event loop until the whileCondition, if present, returns false
4269 	int run(bool delegate() whileCondition = null) {
4270 		assert(impl !is null);
4271 		impl.notExited = true;
4272 		return impl.run(whileCondition);
4273 	}
4274 
4275 	/// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program)
4276 	void exit() {
4277 		assert(impl !is null);
4278 		impl.notExited = false;
4279 
4280 		version(use_arsd_core) {
4281 			import arsd.core;
4282 			ICoreEventLoop.exitApplication();
4283 		}
4284 	}
4285 
4286 	version(linux)
4287 	ref void delegate(int) signalHandler() {
4288 		assert(impl !is null);
4289 		return impl.signalHandler;
4290 	}
4291 
4292 	__gshared static EventLoopImpl* impl;
4293 }
4294 
4295 version(linux)
4296 	void delegate(int, int) globalHupHandler;
4297 
4298 version(Posix)
4299 	void makeNonBlocking(int fd) {
4300 		import fcntl = core.sys.posix.fcntl;
4301 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4302 		if(flags == -1)
4303 			throw new Exception("fcntl get");
4304 		flags |= fcntl.O_NONBLOCK;
4305 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4306 		if(s == -1)
4307 			throw new Exception("fcntl set");
4308 	}
4309 
4310 struct EventLoopImpl {
4311 	int refcount;
4312 
4313 	bool notExited = true;
4314 
4315 	version(linux) {
4316 		static import ep = core.sys.linux.epoll;
4317 		static import unix = core.sys.posix.unistd;
4318 		static import err = core.stdc.errno;
4319 		import core.sys.linux.timerfd;
4320 
4321 		void delegate(int) signalHandler;
4322 	}
4323 
4324 	version(X11) {
4325 		int pulseFd = -1;
4326 		version(linux) ep.epoll_event[16] events = void;
4327 	} else version(Windows) {
4328 		Timer pulser;
4329 		HANDLE[] handles;
4330 	}
4331 
4332 
4333 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4334 	/// to call this, as it's not recommended to share window between threads.
4335 	void mtLock () {
4336 		version(X11) {
4337 			XLockDisplay(this.display);
4338 		}
4339 	}
4340 
4341 	version(X11)
4342 	auto display() { return XDisplayConnection.get; }
4343 
4344 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4345 	/// to call this, as it's not recommended to share window between threads.
4346 	void mtUnlock () {
4347 		version(X11) {
4348 			XUnlockDisplay(this.display);
4349 		}
4350 	}
4351 
4352 	version(with_eventloop)
4353 	void initialize(long pulseTimeout) {}
4354 	else
4355 	void initialize(long pulseTimeout) @system {
4356 		version(Windows) {
4357 			if(pulseTimeout && handlePulse !is null)
4358 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4359 
4360 			if (customEventH is null) {
4361 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4362 				if (customEventH !is null) {
4363 					handles ~= customEventH;
4364 				} else {
4365 					// this is something that should not be; better be safe than sorry
4366 					throw new Exception("can't create eventfd for custom event processing");
4367 				}
4368 			}
4369 
4370 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4371 		}
4372 
4373 		version(linux) {
4374 			prepareEventLoop();
4375 			{
4376 				auto display = XDisplayConnection.get;
4377 				// adding Xlib file
4378 				ep.epoll_event ev = void;
4379 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4380 				ev.events = ep.EPOLLIN;
4381 				ev.data.fd = display.fd;
4382 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4383 					throw new Exception("add x fd");// ~ to!string(epollFd));
4384 				displayFd = display.fd;
4385 			}
4386 
4387 			if(pulseTimeout && handlePulse !is null) {
4388 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4389 				if(pulseFd == -1)
4390 					throw new Exception("pulse timer create failed");
4391 
4392 				itimerspec value;
4393 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4394 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4395 
4396 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4397 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4398 
4399 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4400 					throw new Exception("couldn't make pulse timer");
4401 
4402 				ep.epoll_event ev = void;
4403 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4404 				ev.events = ep.EPOLLIN;
4405 				ev.data.fd = pulseFd;
4406 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4407 			}
4408 
4409 			// eventfd for custom events
4410 			if (customEventFDWrite == -1) {
4411 				customEventFDWrite = eventfd(0, 0);
4412 				customEventFDRead = customEventFDWrite;
4413 				if (customEventFDRead >= 0) {
4414 					ep.epoll_event ev = void;
4415 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4416 					ev.events = ep.EPOLLIN;
4417 					ev.data.fd = customEventFDRead;
4418 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4419 				} else {
4420 					// this is something that should not be; better be safe than sorry
4421 					throw new Exception("can't create eventfd for custom event processing");
4422 				}
4423 			}
4424 
4425 			if (customSignalFD == -1) {
4426 				import core.sys.linux.sys.signalfd;
4427 
4428 				sigset_t sigset;
4429 				auto err = sigemptyset(&sigset);
4430 				assert(!err);
4431 				err = sigaddset(&sigset, SIGINT);
4432 				assert(!err);
4433 				err = sigaddset(&sigset, SIGHUP);
4434 				assert(!err);
4435 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4436 				assert(!err);
4437 
4438 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4439 				assert(customSignalFD != -1);
4440 
4441 				ep.epoll_event ev = void;
4442 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4443 				ev.events = ep.EPOLLIN;
4444 				ev.data.fd = customSignalFD;
4445 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4446 			}
4447 		} else version(Posix) {
4448 			prepareEventLoop();
4449 			if (customEventFDRead == -1) {
4450 				int[2] bfr;
4451 				import core.sys.posix.unistd;
4452 				auto ret = pipe(bfr);
4453 				if(ret == -1) throw new Exception("pipe");
4454 				customEventFDRead = bfr[0];
4455 				customEventFDWrite = bfr[1];
4456 			}
4457 
4458 		}
4459 
4460 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4461 
4462 		version(linux) {
4463 			this.mtLock();
4464 			scope(exit) this.mtUnlock();
4465 			XPending(display); // no, really
4466 		}
4467 
4468 		disposed = false;
4469 	}
4470 
4471 	bool disposed = true;
4472 	version(X11)
4473 		int displayFd = -1;
4474 
4475 	version(with_eventloop)
4476 	void dispose() {}
4477 	else
4478 	void dispose() @system {
4479 		disposed = true;
4480 		version(X11) {
4481 			if(pulseFd != -1) {
4482 				import unix = core.sys.posix.unistd;
4483 				unix.close(pulseFd);
4484 				pulseFd = -1;
4485 			}
4486 
4487 				version(linux)
4488 				if(displayFd != -1) {
4489 					// 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
4490 					ep.epoll_event ev = void;
4491 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4492 					ev.events = ep.EPOLLIN;
4493 					ev.data.fd = displayFd;
4494 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4495 					displayFd = -1;
4496 				}
4497 
4498 		} else version(Windows) {
4499 			if(pulser !is null) {
4500 				pulser.destroy();
4501 				pulser = null;
4502 			}
4503 			if (customEventH !is null) {
4504 				CloseHandle(customEventH);
4505 				customEventH = null;
4506 			}
4507 		}
4508 	}
4509 
4510 	this(long pulseTimeout, void delegate() handlePulse) {
4511 		this.pulseTimeout = pulseTimeout;
4512 		this.handlePulse = handlePulse;
4513 		initialize(pulseTimeout);
4514 	}
4515 
4516 	private long pulseTimeout;
4517 	void delegate() handlePulse;
4518 
4519 	~this() {
4520 		dispose();
4521 	}
4522 
4523 	version(Posix)
4524 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4525 	version(Posix)
4526 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4527 	version(linux)
4528 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4529 	version(Windows)
4530 	ref auto customEventH() { return SimpleWindow.customEventH; }
4531 
4532 	version(X11) {
4533 		bool doXPending() {
4534 			bool done = false;
4535 
4536 			this.mtLock();
4537 			scope(exit) this.mtUnlock();
4538 			//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4539 			while(!done && XPending(display)) {
4540 				done = doXNextEvent(this.display);
4541 			}
4542 
4543 			return done;
4544 		}
4545 		void doXNextEventVoid() {
4546 			doXPending();
4547 		}
4548 	}
4549 
4550 	version(with_eventloop) {
4551 		int loopHelper(bool delegate() whileCondition) {
4552 			// FIXME: whileCondition
4553 			import arsd.eventloop;
4554 			loop();
4555 			return 0;
4556 		}
4557 	} else
4558 	int loopHelper(bool delegate() whileCondition) {
4559 		version(X11) {
4560 			bool done = false;
4561 
4562 			XFlush(display);
4563 			insideXEventLoop = true;
4564 			scope(exit) insideXEventLoop = false;
4565 
4566 			version(use_arsd_core) {
4567 				import arsd.core;
4568 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4569 
4570 				static bool loopInitialized = false;
4571 				if(!loopInitialized) {
4572 					el.addDelegateOnLoopIteration(&doXNextEventVoid, 0);
4573 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4574 
4575 					if(customSignalFD != -1)
4576 					cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() {
4577 						version(linux) {
4578 							import core.sys.linux.sys.signalfd;
4579 							import core.sys.posix.unistd : read;
4580 							signalfd_siginfo info;
4581 							read(customSignalFD, &info, info.sizeof);
4582 
4583 							auto sig = info.ssi_signo;
4584 
4585 							if(EventLoop.get.signalHandler !is null) {
4586 								EventLoop.get.signalHandler()(sig);
4587 							} else {
4588 								EventLoop.get.exit();
4589 							}
4590 						}
4591 					}));
4592 
4593 					if(display.fd != -1)
4594 					cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() {
4595 						this.mtLock();
4596 						scope(exit) this.mtUnlock();
4597 						while(!done && XPending(display)) {
4598 							done = doXNextEvent(this.display);
4599 						}
4600 					}));
4601 
4602 					if(pulseFd != -1)
4603 					cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() {
4604 						long expirationCount;
4605 						// 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...
4606 
4607 						handlePulse();
4608 
4609 						// read just to clear the buffer so poll doesn't trigger again
4610 						// BTW I read AFTER the pulse because if the pulse handler takes
4611 						// a lot of time to execute, we don't want the app to get stuck
4612 						// in a loop of timer hits without a chance to do anything else
4613 						//
4614 						// IOW handlePulse happens at most once per pulse interval.
4615 						unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4616 					}));
4617 
4618 					if(customEventFDRead != -1)
4619 					cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() {
4620 						// we have some custom events; process 'em
4621 						import core.sys.posix.unistd : read;
4622 						ulong n;
4623 						read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4624 						//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4625 						//SimpleWindow.processAllCustomEvents();
4626 					}));
4627 
4628 					// FIXME: posix fds
4629 					// FIXME up?
4630 
4631 
4632 					loopInitialized = true;
4633 				}
4634 
4635 				el.run(() => !whileCondition());
4636 			} else version(linux) {
4637 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4638 					bool forceXPending = false;
4639 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4640 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4641 					{
4642 						this.mtLock();
4643 						scope(exit) this.mtUnlock();
4644 						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
4645 					}
4646 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4647 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4648 					if(nfds == -1) {
4649 						if(err.errno == err.EINTR) {
4650 							//if(forceXPending) goto xpending;
4651 							continue; // interrupted by signal, just try again
4652 						}
4653 						throw new Exception("epoll wait failure");
4654 					}
4655 					// writeln(nfds, " ", events[0].data.fd);
4656 
4657 					SimpleWindow.processAllCustomEvents(); // anyway
4658 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4659 					foreach(idx; 0 .. nfds) {
4660 						if(done) break;
4661 						auto fd = events[idx].data.fd;
4662 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4663 						auto flags = events[idx].events;
4664 						if(flags & ep.EPOLLIN) {
4665 							if (fd == customSignalFD) {
4666 								version(linux) {
4667 									import core.sys.linux.sys.signalfd;
4668 									import core.sys.posix.unistd : read;
4669 									signalfd_siginfo info;
4670 									read(customSignalFD, &info, info.sizeof);
4671 
4672 									auto sig = info.ssi_signo;
4673 
4674 									if(EventLoop.get.signalHandler !is null) {
4675 										EventLoop.get.signalHandler()(sig);
4676 									} else {
4677 										EventLoop.get.exit();
4678 									}
4679 								}
4680 							} else if(fd == display.fd) {
4681 								version(sdddd) { writeln("X EVENT PENDING!"); }
4682 								this.mtLock();
4683 								scope(exit) this.mtUnlock();
4684 								while(!done && XPending(display)) {
4685 									done = doXNextEvent(this.display);
4686 								}
4687 								forceXPending = false;
4688 							} else if(fd == pulseFd) {
4689 								long expirationCount;
4690 								// 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...
4691 
4692 								handlePulse();
4693 
4694 								// read just to clear the buffer so poll doesn't trigger again
4695 								// BTW I read AFTER the pulse because if the pulse handler takes
4696 								// a lot of time to execute, we don't want the app to get stuck
4697 								// in a loop of timer hits without a chance to do anything else
4698 								//
4699 								// IOW handlePulse happens at most once per pulse interval.
4700 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4701 								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
4702 							} else if (fd == customEventFDRead) {
4703 								// we have some custom events; process 'em
4704 								import core.sys.posix.unistd : read;
4705 								ulong n;
4706 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4707 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4708 								//SimpleWindow.processAllCustomEvents();
4709 
4710 								forceXPending = true;
4711 							} else {
4712 								// some other timer
4713 								version(sdddd) { writeln("unknown fd: ", fd); }
4714 
4715 								if(Timer* t = fd in Timer.mapping)
4716 									(*t).trigger();
4717 
4718 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4719 									(*pfr).ready(flags);
4720 
4721 								// we don't know what the user did in this timer, so we need to assume that
4722 								// there's X data to be flushed and potentially processed
4723 								forceXPending = true;
4724 
4725 								// or i might add support for other FDs too
4726 								// but for now it is just timer
4727 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4728 							}
4729 						}
4730 						if(flags & ep.EPOLLHUP) {
4731 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4732 								(*pfr).hup(flags);
4733 							if(globalHupHandler)
4734 								globalHupHandler(fd, flags);
4735 						}
4736 						/+
4737 						} else {
4738 							// not interested in OUT, we are just reading here.
4739 							//
4740 							// error or hup might also be reported
4741 							// but it shouldn't here since we are only
4742 							// using a few types of FD and Xlib will report
4743 							// if it dies.
4744 							// so instead of thoughtfully handling it, I'll
4745 							// just throw. for now at least
4746 
4747 							throw new Exception("epoll did something else");
4748 						}
4749 						+/
4750 					}
4751 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4752 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4753 					xpending:
4754 					if (!done && forceXPending) {
4755 						done = doXPending();
4756 					}
4757 				}
4758 			} else {
4759 				// Generic fallback: yes to simple pulse support,
4760 				// but NO timer support!
4761 
4762 				// FIXME: we could probably support the POSIX timer_create
4763 				// signal-based option, but I'm in no rush to write it since
4764 				// I prefer the fd-based functions.
4765 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4766 
4767 					import core.sys.posix.poll;
4768 
4769 					pollfd[] pfds;
4770 					pollfd[32] pfdsBuffer;
4771 					auto len = PosixFdReader.mapping.length + 2;
4772 					// FIXME: i should just reuse the buffer
4773 					if(len < pfdsBuffer.length)
4774 						pfds = pfdsBuffer[0 .. len];
4775 					else
4776 						pfds = new pollfd[](len);
4777 
4778 					pfds[0].fd = display.fd;
4779 					pfds[0].events = POLLIN;
4780 					pfds[0].revents = 0;
4781 
4782 					int slot = 1;
4783 
4784 					if(customEventFDRead != -1) {
4785 						pfds[slot].fd = customEventFDRead;
4786 						pfds[slot].events = POLLIN;
4787 						pfds[slot].revents = 0;
4788 
4789 						slot++;
4790 					}
4791 
4792 					foreach(fd, obj; PosixFdReader.mapping) {
4793 						if(!obj.enabled) continue;
4794 						pfds[slot].fd = fd;
4795 						pfds[slot].events = POLLIN;
4796 						pfds[slot].revents = 0;
4797 
4798 						slot++;
4799 					}
4800 
4801 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4802 					if(ret == -1) throw new Exception("poll");
4803 
4804 					if(ret == 0) {
4805 						// FIXME it may not necessarily time out if events keep coming
4806 						if(handlePulse !is null)
4807 							handlePulse();
4808 					} else {
4809 						foreach(s; 0 .. slot) {
4810 							if(pfds[s].revents == 0) continue;
4811 
4812 							if(pfds[s].fd == display.fd) {
4813 								while(!done && XPending(display)) {
4814 									this.mtLock();
4815 									scope(exit) this.mtUnlock();
4816 									done = doXNextEvent(this.display);
4817 								}
4818 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4819 
4820 								import core.sys.posix.unistd : read;
4821 								ulong n;
4822 								read(customEventFDRead, &n, n.sizeof);
4823 								SimpleWindow.processAllCustomEvents();
4824 							} else {
4825 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4826 								if(pfds[s].revents & POLLNVAL) {
4827 									obj.dispose();
4828 								} else {
4829 									obj.ready(pfds[s].revents);
4830 								}
4831 							}
4832 
4833 							ret--;
4834 							if(ret == 0) break;
4835 						}
4836 					}
4837 				}
4838 			}
4839 		}
4840 
4841 		version(Windows) {
4842 
4843 			version(use_arsd_core) {
4844 				import arsd.core;
4845 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4846 				static bool loopInitialized = false;
4847 				if(!loopInitialized) {
4848 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0);
4849 					el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0);
4850 					loopInitialized = true;
4851 				}
4852 				el.run(() => !whileCondition());
4853 			} else {
4854 				int ret = -1;
4855 				MSG message;
4856 				while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4857 					eventLoopRound++;
4858 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4859 					auto waitResult = MsgWaitForMultipleObjectsEx(
4860 						cast(int) handles.length, handles.ptr,
4861 						(wto == 0 ? INFINITE : wto), /* timeout */
4862 						0x04FF, /* QS_ALLINPUT */
4863 						0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4864 
4865 					SimpleWindow.processAllCustomEvents(); // anyway
4866 					enum WAIT_OBJECT_0 = 0;
4867 					if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4868 						auto h = handles[waitResult - WAIT_OBJECT_0];
4869 						if(auto e = h in WindowsHandleReader.mapping) {
4870 							(*e).ready();
4871 						}
4872 					} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4873 						// message ready
4874 						int count;
4875 						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
4876 							ret = GetMessage(&message, null, 0, 0);
4877 							if(ret == -1)
4878 								throw new WindowsApiException("GetMessage", GetLastError());
4879 							TranslateMessage(&message);
4880 							DispatchMessage(&message);
4881 
4882 							count++;
4883 							if(count > 10)
4884 								break; // take the opportunity to catch up on other events
4885 
4886 							if(ret == 0) { // WM_QUIT
4887 								EventLoop.quitApplication();
4888 								break;
4889 							}
4890 						}
4891 					} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4892 						SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4893 					} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4894 						// timeout, should never happen since we aren't using it
4895 					} else if(waitResult == 0xFFFFFFFF) {
4896 							// failed
4897 							throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4898 					} else {
4899 						// idk....
4900 					}
4901 				}
4902 			}
4903 
4904 			// return message.wParam;
4905 			return 0;
4906 		} else {
4907 			return 0;
4908 		}
4909 	}
4910 
4911 	int run(bool delegate() whileCondition = null) {
4912 		if(disposed)
4913 			initialize(this.pulseTimeout);
4914 
4915 		version(X11) {
4916 			try {
4917 				return loopHelper(whileCondition);
4918 			} catch(XDisconnectException e) {
4919 				if(e.userRequested) {
4920 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4921 						item.discardConnectionState();
4922 					XCloseDisplay(XDisplayConnection.display);
4923 				}
4924 
4925 				XDisplayConnection.display = null;
4926 
4927 				this.dispose();
4928 
4929 				throw e;
4930 			}
4931 		} else {
4932 			return loopHelper(whileCondition);
4933 		}
4934 	}
4935 }
4936 
4937 
4938 /++
4939 	Provides an icon on the system notification area (also known as the system tray).
4940 
4941 
4942 	If a notification area is not available with the NotificationIcon object is created,
4943 	it will silently succeed and simply attempt to create one when an area becomes available.
4944 
4945 
4946 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
4947 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
4948 	with true color was added at that time. I was just too lazy to write the fallback.
4949 
4950 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
4951 	you use arsd 10.x when targeting Windows XP.
4952 +/
4953 version(OSXCocoa) {} else // NotYetImplementedException
4954 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4955 
4956 	version(X11) {
4957 		void recreateAfterDisconnect() {
4958 			stateDiscarded = false;
4959 			clippixmap = None;
4960 			throw new Exception("NOT IMPLEMENTED");
4961 		}
4962 
4963 		bool stateDiscarded;
4964 		void discardConnectionState() {
4965 			stateDiscarded = true;
4966 		}
4967 	}
4968 
4969 
4970 	version(X11) {
4971 		Image img;
4972 
4973 		NativeEventHandler getNativeEventHandler() {
4974 			return delegate int(XEvent e) {
4975 				switch(e.type) {
4976 					case EventType.Expose:
4977 					//case EventType.VisibilityNotify:
4978 						redraw();
4979 					break;
4980 					case EventType.ClientMessage:
4981 						version(sddddd) {
4982 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4983 						writeln("\t", e.xclient.format);
4984 						writeln("\t", e.xclient.data.l);
4985 						}
4986 					break;
4987 					case EventType.ButtonPress:
4988 						auto event = e.xbutton;
4989 						if (onClick !is null || onClickEx !is null) {
4990 							MouseButton mb = cast(MouseButton)0;
4991 							switch (event.button) {
4992 								case 1: mb = MouseButton.left; break; // left
4993 								case 2: mb = MouseButton.middle; break; // middle
4994 								case 3: mb = MouseButton.right; break; // right
4995 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4996 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4997 								case 6: break; // scroll left...
4998 								case 7: break; // scroll right...
4999 								case 8: mb = MouseButton.backButton; break;
5000 								case 9: mb = MouseButton.forwardButton; break;
5001 								default:
5002 							}
5003 							if (mb) {
5004 								try { onClick()(mb); } catch (Exception) {}
5005 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
5006 							}
5007 						}
5008 					break;
5009 					case EventType.EnterNotify:
5010 						if (onEnter !is null) {
5011 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
5012 						}
5013 						break;
5014 					case EventType.LeaveNotify:
5015 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
5016 						break;
5017 					case EventType.DestroyNotify:
5018 						active = false;
5019 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
5020 					break;
5021 					case EventType.ConfigureNotify:
5022 						auto event = e.xconfigure;
5023 						this.width = event.width;
5024 						this.height = event.height;
5025 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
5026 						redraw();
5027 					break;
5028 					default: return 1;
5029 				}
5030 				return 1;
5031 			};
5032 		}
5033 
5034 		/* private */ void hideBalloon() {
5035 			balloon.close();
5036 			version(with_timer)
5037 				timer.destroy();
5038 			balloon = null;
5039 			version(with_timer)
5040 				timer = null;
5041 		}
5042 
5043 		void redraw() {
5044 			if (!active) return;
5045 
5046 			auto display = XDisplayConnection.get;
5047 			GC gc;
5048 
5049 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
5050 
5051 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
5052 				Visual *visual;
5053 				XVisualInfo vis_info;
5054 				XSetWindowAttributes win_attr;
5055 				c_ulong win_mask;
5056 
5057 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
5058 					assert(0);
5059 					// return 1;
5060 				}
5061 
5062 				visual = vis_info.visual;
5063 
5064 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
5065 				win_attr.background_pixel = 0;
5066 				win_attr.border_pixel = 0;
5067 
5068 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
5069 
5070 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
5071 
5072 				return 0;
5073 			}
5074 
5075 			if(useAlpha)
5076 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
5077 			else
5078 				gc = DefaultGC(display, DefaultScreen(display));
5079 
5080 			XClearWindow(display, nativeHandle);
5081 
5082 			if(!useAlpha && img !is null)
5083 				XSetClipMask(display, gc, clippixmap);
5084 
5085 			/+
5086 			XSetForeground(display, gc,
5087 				cast(uint) 0 << 16 |
5088 				cast(uint) 0 << 8 |
5089 				cast(uint) 0);
5090 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
5091 			+/
5092 
5093 			if (img is null) {
5094 				XSetForeground(display, gc,
5095 					cast(uint) 0 << 16 |
5096 					cast(uint) 127 << 8 |
5097 					cast(uint) 0);
5098 				XFillArc(display, nativeHandle,
5099 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
5100 			} else {
5101 				int dx = 0;
5102 				int dy = 0;
5103 				if(width > img.width)
5104 					dx = (width - img.width) / 2;
5105 				if(height > img.height)
5106 					dy = (height - img.height) / 2;
5107 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
5108 				XSetClipOrigin(display, gc, dx, dy);
5109 
5110 				int max(int a, int b) {
5111 					if(a > b) return a; else return b;
5112 				}
5113 
5114 				if (img.usingXshm)
5115 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
5116 				else
5117 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
5118 			}
5119 			XSetClipMask(display, gc, None);
5120 			flushGui();
5121 		}
5122 
5123 		static Window getTrayOwner() {
5124 			auto display = XDisplayConnection.get;
5125 			auto i = cast(int) DefaultScreen(display);
5126 			if(i < 10 && i >= 0) {
5127 				static Atom atom;
5128 				if(atom == None)
5129 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
5130 				return XGetSelectionOwner(display, atom);
5131 			}
5132 			return None;
5133 		}
5134 
5135 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
5136 			auto to = getTrayOwner();
5137 			auto display = XDisplayConnection.get;
5138 			XEvent ev;
5139 			ev.xclient.type = EventType.ClientMessage;
5140 			ev.xclient.window = to;
5141 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
5142 			ev.xclient.format = 32;
5143 			ev.xclient.data.l[0] = CurrentTime;
5144 			ev.xclient.data.l[1] = message;
5145 			ev.xclient.data.l[2] = d1;
5146 			ev.xclient.data.l[3] = d2;
5147 			ev.xclient.data.l[4] = d3;
5148 
5149 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
5150 		}
5151 
5152 		private static NotificationAreaIcon[] activeIcons;
5153 
5154 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
5155 		private void newManager() {
5156 			close();
5157 			createXWin();
5158 
5159 			if(this.clippixmap)
5160 				XFreePixmap(XDisplayConnection.get, clippixmap);
5161 			if(this.originalMemoryImage)
5162 				this.icon = this.originalMemoryImage;
5163 			else if(this.img)
5164 				this.icon = this.img;
5165 		}
5166 
5167 		private bool useAlpha = false;
5168 
5169 		private void createXWin () {
5170 			// create window
5171 			auto display = XDisplayConnection.get;
5172 
5173 			// to check for MANAGER on root window to catch new/changed tray owners
5174 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
5175 			// so if a thing does appear, we can handle it
5176 			foreach(ai; activeIcons)
5177 				if(ai is this)
5178 					goto alreadythere;
5179 			activeIcons ~= this;
5180 			alreadythere:
5181 
5182 			// and check for an existing tray
5183 			auto trayOwner = getTrayOwner();
5184 			if(trayOwner == None)
5185 				return;
5186 				//throw new Exception("No notification area found");
5187 
5188 			Visual* v = cast(Visual*) CopyFromParent;
5189 
5190 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
5191 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
5192 			// a resize event later.
5193 			width = 22;
5194 			height = 22;
5195 
5196 			// if they system gave us a 32 bit visual we need to switch to it too
5197 			int depth = 24;
5198 
5199 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
5200 			if(visualProp !is null) {
5201 				c_ulong[] info = cast(c_ulong[]) visualProp;
5202 				if(info.length == 1) {
5203 					auto vid = info[0];
5204 					int returned;
5205 					XVisualInfo t;
5206 					t.visualid = vid;
5207 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
5208 					if(got !is null) {
5209 						if(returned == 1) {
5210 							v = got.visual;
5211 							depth = got.depth;
5212 							// writeln("using special visual ", got.depth);
5213 							// writeln(depth);
5214 						}
5215 						XFree(got);
5216 					}
5217 				}
5218 			}
5219 
5220 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
5221 			XSetWindowAttributes attr;
5222 			attr.background_pixel = 0;
5223 			attr.border_pixel = 0;
5224 			attr.override_redirect = 0;
5225 			if(v !is cast(Visual*) CopyFromParent) {
5226 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
5227 				CWFlags |= CWColormap;
5228 				if(depth == 32)
5229 					useAlpha = true;
5230 				else
5231 					goto plain;
5232 			} else {
5233 				plain:
5234 				attr.background_pixmap = 1 /* ParentRelative */;
5235 				CWFlags |= CWBackPixmap;
5236 			}
5237 
5238 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
5239 
5240 			assert(nativeWindow);
5241 
5242 			if(!useAlpha)
5243 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
5244 
5245 			nativeHandle = nativeWindow;
5246 
5247 			///+
5248 			arch_ulong[2] info;
5249 			info[0] = 0;
5250 			info[1] = 1;
5251 
5252 			string title = this.name is null ? "simpledisplay.d program" : this.name;
5253 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
5254 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
5255 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
5256 
5257 			XChangeProperty(
5258 				display,
5259 				nativeWindow,
5260 				GetAtom!("_XEMBED_INFO", true)(display),
5261 				GetAtom!("_XEMBED_INFO", true)(display),
5262 				32 /* bits */,
5263 				0 /*PropModeReplace*/,
5264 				info.ptr,
5265 				2);
5266 
5267 			import core.sys.posix.unistd;
5268 			arch_ulong pid = getpid();
5269 
5270 			XChangeProperty(
5271 				display,
5272 				nativeWindow,
5273 				GetAtom!("_NET_WM_PID", true)(display),
5274 				XA_CARDINAL,
5275 				32 /* bits */,
5276 				0 /*PropModeReplace*/,
5277 				&pid,
5278 				1);
5279 
5280 			updateNetWmIcon();
5281 
5282 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
5283 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
5284 				XClassHint klass;
5285 				XWMHints wh;
5286 				XSizeHints size;
5287 				klass.res_name = sdpyWindowClassStr;
5288 				klass.res_class = sdpyWindowClassStr;
5289 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
5290 			}
5291 
5292 				// believe it or not, THIS is what xfce needed for the 9999 issue
5293 				XSizeHints sh;
5294 				c_long spr;
5295 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
5296 				sh.flags |= PMaxSize | PMinSize;
5297 				// FIXME maybe nicer resizing
5298 				sh.min_width = 16;
5299 				sh.min_height = 16;
5300 				sh.max_width = 22;
5301 				sh.max_height = 22;
5302 				XSetWMNormalHints(display, nativeWindow, &sh);
5303 
5304 
5305 			//+/
5306 
5307 
5308 			XSelectInput(display, nativeWindow,
5309 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
5310 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
5311 
5312 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5313 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5314 
5315 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5316 			active = true;
5317 		}
5318 
5319 		void updateNetWmIcon() {
5320 			if(img is null) return;
5321 			auto display = XDisplayConnection.get;
5322 			// FIXME: ensure this is correct
5323 			arch_ulong[] buffer;
5324 			auto imgMi = img.toTrueColorImage;
5325 			buffer ~= imgMi.width;
5326 			buffer ~= imgMi.height;
5327 			foreach(c; imgMi.imageData.colors) {
5328 				arch_ulong b;
5329 				b |= c.a << 24;
5330 				b |= c.r << 16;
5331 				b |= c.g << 8;
5332 				b |= c.b;
5333 				buffer ~= b;
5334 			}
5335 
5336 			XChangeProperty(
5337 				display,
5338 				nativeHandle,
5339 				GetAtom!"_NET_WM_ICON"(display),
5340 				GetAtom!"CARDINAL"(display),
5341 				32 /* bits */,
5342 				0 /*PropModeReplace*/,
5343 				buffer.ptr,
5344 				cast(int) buffer.length);
5345 		}
5346 
5347 
5348 
5349 		private SimpleWindow balloon;
5350 		version(with_timer)
5351 		private Timer timer;
5352 
5353 		private Window nativeHandle;
5354 		private Pixmap clippixmap = None;
5355 		private int width = 16;
5356 		private int height = 16;
5357 		private bool active = false;
5358 
5359 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5360 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5361 		void delegate () onLeave; /// X11 only.
5362 
5363 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5364 
5365 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5366 		void getWindowRect (out int x, out int y, out int width, out int height) {
5367 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5368 			Window dummyw;
5369 			auto dpy = XDisplayConnection.get;
5370 			//XWindowAttributes xwa;
5371 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5372 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5373 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5374 			width = this.width;
5375 			height = this.height;
5376 		}
5377 	}
5378 
5379 	/+
5380 		What I actually want from this:
5381 
5382 		* set / change: icon, tooltip
5383 		* handle: mouse click, right click
5384 		* show: notification bubble.
5385 	+/
5386 
5387 	version(Windows) {
5388 		WindowsIcon win32Icon;
5389 		HWND hwnd;
5390 
5391 		NOTIFYICONDATAW data;
5392 
5393 		NativeEventHandler getNativeEventHandler() {
5394 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5395 				if(msg == WM_USER) {
5396 					auto event = LOWORD(lParam);
5397 					auto iconId = HIWORD(lParam);
5398 					//auto x = GET_X_LPARAM(wParam);
5399 					//auto y = GET_Y_LPARAM(wParam);
5400 					switch(event) {
5401 						case WM_LBUTTONDOWN:
5402 							onClick()(MouseButton.left);
5403 						break;
5404 						case WM_RBUTTONDOWN:
5405 							onClick()(MouseButton.right);
5406 						break;
5407 						case WM_MBUTTONDOWN:
5408 							onClick()(MouseButton.middle);
5409 						break;
5410 						case WM_MOUSEMOVE:
5411 							// sent, we could use it.
5412 						break;
5413 						case WM_MOUSEWHEEL:
5414 							// NOT SENT
5415 						break;
5416 						//case NIN_KEYSELECT:
5417 						//case NIN_SELECT:
5418 						//break;
5419 						default: {}
5420 					}
5421 				}
5422 				return 0;
5423 			};
5424 		}
5425 
5426 		enum NIF_SHOWTIP = 0x00000080;
5427 
5428 		private static struct NOTIFYICONDATAW {
5429 			DWORD cbSize;
5430 			HWND  hWnd;
5431 			UINT  uID;
5432 			UINT  uFlags;
5433 			UINT  uCallbackMessage;
5434 			HICON hIcon;
5435 			WCHAR[128] szTip;
5436 			DWORD dwState;
5437 			DWORD dwStateMask;
5438 			WCHAR[256] szInfo;
5439 			union {
5440 				UINT uTimeout;
5441 				UINT uVersion;
5442 			}
5443 			WCHAR[64] szInfoTitle;
5444 			DWORD dwInfoFlags;
5445 			GUID  guidItem;
5446 			HICON hBalloonIcon;
5447 		}
5448 
5449 	}
5450 
5451 	/++
5452 		Note that on Windows, only left, right, and middle buttons are sent.
5453 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5454 		program is meant to be used on Windows too.
5455 	+/
5456 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5457 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5458 		// but on X, we need an Image, so its canonical ctor is there. They should
5459 		// forward to each other though.
5460 		version(X11) {
5461 			this.name = name;
5462 			this.onClick = onClick;
5463 			createXWin();
5464 			this.icon = icon;
5465 		} else version(Windows) {
5466 			this.onClick = onClick;
5467 			this.win32Icon = new WindowsIcon(icon);
5468 
5469 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5470 
5471 			static bool registered = false;
5472 			if(!registered) {
5473 				WNDCLASSEX wc;
5474 				wc.cbSize = wc.sizeof;
5475 				wc.hInstance = hInstance;
5476 				wc.lpfnWndProc = &WndProc;
5477 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5478 				if(!RegisterClassExW(&wc))
5479 					throw new WindowsApiException("RegisterClass", GetLastError());
5480 				registered = true;
5481 			}
5482 
5483 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5484 			if(hwnd is null)
5485 				throw new WindowsApiException("CreateWindow", GetLastError());
5486 
5487 			data.cbSize = data.sizeof;
5488 			data.hWnd = hwnd;
5489 			data.uID = cast(uint) cast(void*) this;
5490 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5491 				// NIF_INFO means show balloon
5492 			data.uCallbackMessage = WM_USER;
5493 			data.hIcon = this.win32Icon.hIcon;
5494 			data.szTip = ""; // FIXME
5495 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5496 			data.dwStateMask = NIS_HIDDEN; // windows vista
5497 
5498 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5499 
5500 
5501 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5502 
5503 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5504 		} else version(OSXCocoa) {
5505 			throw new NotYetImplementedException();
5506 		} else static assert(0);
5507 	}
5508 
5509 	/// ditto
5510 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5511 		version(X11) {
5512 			this.onClick = onClick;
5513 			this.name = name;
5514 			createXWin();
5515 			this.icon = icon;
5516 		} else version(Windows) {
5517 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5518 		} else version(OSXCocoa) {
5519 			throw new NotYetImplementedException();
5520 		} else static assert(0);
5521 	}
5522 
5523 	version(X11) {
5524 		/++
5525 			X-specific extension (for now at least)
5526 		+/
5527 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5528 			this.onClickEx = onClickEx;
5529 			createXWin();
5530 			if (icon !is null) this.icon = icon;
5531 		}
5532 
5533 		/// ditto
5534 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5535 			this.onClickEx = onClickEx;
5536 			createXWin();
5537 			this.icon = icon;
5538 		}
5539 	}
5540 
5541 	private void delegate (MouseButton button) onClick_;
5542 
5543 	///
5544 	@property final void delegate(MouseButton) onClick() {
5545 		if(onClick_ is null)
5546 			onClick_ = delegate void(MouseButton) {};
5547 		return onClick_;
5548 	}
5549 
5550 	/// ditto
5551 	@property final void onClick(void delegate(MouseButton) handler) {
5552 		// I made this a property setter so we can wrap smaller arg
5553 		// delegates and just forward all to onClickEx or something.
5554 		onClick_ = handler;
5555 	}
5556 
5557 
5558 	string name_;
5559 	@property void name(string n) {
5560 		name_ = n;
5561 	}
5562 
5563 	@property string name() {
5564 		return name_;
5565 	}
5566 
5567 	private MemoryImage originalMemoryImage;
5568 
5569 	///
5570 	@property void icon(MemoryImage i) {
5571 		version(X11) {
5572 			this.originalMemoryImage = i;
5573 			if (!active) return;
5574 			if (i !is null) {
5575 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5576 				if(!useAlpha)
5577 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5578 				// writeln("using pixmap ", clippixmap);
5579 				updateNetWmIcon();
5580 				redraw();
5581 			} else {
5582 				if (this.img !is null) {
5583 					this.img = null;
5584 					redraw();
5585 				}
5586 			}
5587 		} else version(Windows) {
5588 			this.win32Icon = new WindowsIcon(i);
5589 
5590 			data.uFlags = NIF_ICON;
5591 			data.hIcon = this.win32Icon.hIcon;
5592 
5593 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5594 		} else version(OSXCocoa) {
5595 			throw new NotYetImplementedException();
5596 		} else static assert(0);
5597 	}
5598 
5599 	/// ditto
5600 	@property void icon (Image i) {
5601 		version(X11) {
5602 			if (!active) return;
5603 			if (i !is img) {
5604 				originalMemoryImage = null;
5605 				img = i;
5606 				redraw();
5607 			}
5608 		} else version(Windows) {
5609 			this.icon(i is null ? null : i.toTrueColorImage());
5610 		} else version(OSXCocoa) {
5611 			throw new NotYetImplementedException();
5612 		} else static assert(0);
5613 	}
5614 
5615 	/++
5616 		Shows a balloon notification. You can only show one balloon at a time, if you call
5617 		it twice while one is already up, the first balloon will be replaced.
5618 
5619 
5620 		The user is free to block notifications and they will automatically disappear after
5621 		a timeout period.
5622 
5623 		Params:
5624 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5625 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5626 			icon = the icon to display with the notification. If null, it uses your existing icon.
5627 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5628 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5629 	+/
5630 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5631 		bool useCustom = true;
5632 		version(libnotify) {
5633 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5634 			try {
5635 				if(!active) return;
5636 
5637 				if(libnotify is null) {
5638 					libnotify = new C_DynamicLibrary("libnotify.so");
5639 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5640 				}
5641 
5642 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5643 
5644 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5645 
5646 				if(onclick) {
5647 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5648 					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);
5649 					libnotify_action_delegates_count++;
5650 				}
5651 
5652 				// FIXME icon
5653 
5654 				// set hint image-data
5655 				// set default action for onclick
5656 
5657 				void* error;
5658 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5659 
5660 				useCustom = false;
5661 			} catch(Exception e) {
5662 
5663 			}
5664 		}
5665 
5666 		version(X11) {
5667 		if(useCustom) {
5668 			if(!active) return;
5669 			if(balloon) {
5670 				hideBalloon();
5671 			}
5672 			// I know there are two specs for this, but one is never
5673 			// implemented by any window manager I have ever seen, and
5674 			// the other is a bloated mess and too complicated for simpledisplay...
5675 			// so doing my own little window instead.
5676 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5677 
5678 			int x, y, width, height;
5679 			getWindowRect(x, y, width, height);
5680 
5681 			int bx = x - balloon.width;
5682 			int by = y - balloon.height;
5683 			if(bx < 0)
5684 				bx = x + width + balloon.width;
5685 			if(by < 0)
5686 				by = y + height;
5687 
5688 			// just in case, make sure it is actually on scren
5689 			if(bx < 0)
5690 				bx = 0;
5691 			if(by < 0)
5692 				by = 0;
5693 
5694 			balloon.move(bx, by);
5695 			auto painter = balloon.draw();
5696 			painter.fillColor = Color(220, 220, 220);
5697 			painter.outlineColor = Color.black;
5698 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5699 			auto iconWidth = icon is null ? 0 : icon.width;
5700 			if(icon)
5701 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5702 			iconWidth += 6; // margin around the icon
5703 
5704 			// draw a close button
5705 			painter.outlineColor = Color(44, 44, 44);
5706 			painter.fillColor = Color(255, 255, 255);
5707 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5708 			painter.pen = Pen(Color.black, 3);
5709 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5710 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5711 			painter.pen = Pen(Color.black, 1);
5712 			painter.fillColor = Color(220, 220, 220);
5713 
5714 			// Draw the title and message
5715 			painter.drawText(Point(4 + iconWidth, 4), title);
5716 			painter.drawLine(
5717 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5718 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5719 			);
5720 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5721 
5722 			balloon.setEventHandlers(
5723 				(MouseEvent ev) {
5724 					if(ev.type == MouseEventType.buttonPressed) {
5725 						if(ev.x > balloon.width - 16 && ev.y < 16)
5726 							hideBalloon();
5727 						else if(onclick)
5728 							onclick();
5729 					}
5730 				}
5731 			);
5732 			balloon.show();
5733 
5734 			version(with_timer)
5735 			timer = new Timer(timeout, &hideBalloon);
5736 			else {} // FIXME
5737 		}
5738 		} else version(Windows) {
5739 			enum NIF_INFO = 0x00000010;
5740 
5741 			data.uFlags = NIF_INFO;
5742 
5743 			// FIXME: go back to the last valid unicode code point
5744 			if(title.length > 40)
5745 				title = title[0 .. 40];
5746 			if(message.length > 220)
5747 				message = message[0 .. 220];
5748 
5749 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5750 			enum NIIF_LARGE_ICON  = 0x00000020;
5751 			enum NIIF_NOSOUND = 0x00000010;
5752 			enum NIIF_USER = 0x00000004;
5753 			enum NIIF_ERROR = 0x00000003;
5754 			enum NIIF_WARNING = 0x00000002;
5755 			enum NIIF_INFO = 0x00000001;
5756 			enum NIIF_NONE = 0;
5757 
5758 			WCharzBuffer t = WCharzBuffer(title);
5759 			WCharzBuffer m = WCharzBuffer(message);
5760 
5761 			t.copyInto(data.szInfoTitle);
5762 			m.copyInto(data.szInfo);
5763 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5764 
5765 			if(icon !is null) {
5766 				auto i = new WindowsIcon(icon);
5767 				data.hBalloonIcon = i.hIcon;
5768 				data.dwInfoFlags |= NIIF_USER;
5769 			}
5770 
5771 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5772 		} else version(OSXCocoa) {
5773 			throw new NotYetImplementedException();
5774 		} else static assert(0);
5775 	}
5776 
5777 	///
5778 	//version(Windows)
5779 	void show() {
5780 		version(X11) {
5781 			if(!hidden)
5782 				return;
5783 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5784 			hidden = false;
5785 		} else version(Windows) {
5786 			data.uFlags = NIF_STATE;
5787 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5788 			data.dwStateMask = NIS_HIDDEN; // windows vista
5789 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5790 		} else version(OSXCocoa) {
5791 			throw new NotYetImplementedException();
5792 		} else static assert(0);
5793 	}
5794 
5795 	version(X11)
5796 		bool hidden = false;
5797 
5798 	///
5799 	//version(Windows)
5800 	void hide() {
5801 		version(X11) {
5802 			if(hidden)
5803 				return;
5804 			hidden = true;
5805 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5806 		} else version(Windows) {
5807 			data.uFlags = NIF_STATE;
5808 			data.dwState = NIS_HIDDEN; // windows vista
5809 			data.dwStateMask = NIS_HIDDEN; // windows vista
5810 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5811 		} else version(OSXCocoa) {
5812 			throw new NotYetImplementedException();
5813 		} else static assert(0);
5814 	}
5815 
5816 	///
5817 	void close () {
5818 		version(X11) {
5819 			if (active) {
5820 				active = false; // event handler will set this too, but meh
5821 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5822 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5823 				flushGui();
5824 			}
5825 		} else version(Windows) {
5826 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5827 		} else version(OSXCocoa) {
5828 			throw new NotYetImplementedException();
5829 		} else static assert(0);
5830 	}
5831 
5832 	~this() {
5833 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5834 		version(X11)
5835 			if(clippixmap != None)
5836 				XFreePixmap(XDisplayConnection.get, clippixmap);
5837 		close();
5838 	}
5839 }
5840 
5841 version(X11)
5842 /// Call `XFreePixmap` on the return value.
5843 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5844 	char[] data = new char[](i.width * i.height / 8 + 2);
5845 	data[] = 0;
5846 
5847 	int bitOffset = 0;
5848 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5849 		ubyte v = c.a > 128 ? 1 : 0;
5850 		data[bitOffset / 8] |= v << (bitOffset%8);
5851 		bitOffset++;
5852 	}
5853 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5854 	return handle;
5855 }
5856 
5857 
5858 // basic functions to make timers
5859 /**
5860 	A timer that will trigger your function on a given interval.
5861 
5862 
5863 	You create a timer with an interval and a callback. It will continue
5864 	to fire on the interval until it is destroyed.
5865 
5866 	There are currently no one-off timers (instead, just create one and
5867 	destroy it when it is triggered) nor are there pause/resume functions -
5868 	the timer must again be destroyed and recreated if you want to pause it.
5869 
5870 	---
5871 	auto timer = new Timer(50, { it happened!; });
5872 	timer.destroy();
5873 	---
5874 
5875 	Timers can only be expected to fire when the event loop is running and only
5876 	once per iteration through the event loop.
5877 
5878 	History:
5879 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5880 		slow could lock up the event loop. It now guarantees other things will
5881 		get a chance to run between timer calls, even if that means not keeping up
5882 		with the requested interval.
5883 */
5884 version(with_timer) {
5885 version(use_arsd_core)
5886 	alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api
5887 else
5888 class Timer {
5889 // FIXME: needs pause and unpause
5890 	// FIXME: I might add overloads for ones that take a count of
5891 	// how many elapsed since last time (on Windows, it will divide
5892 	// the ticks thing given, on Linux it is just available) and
5893 	// maybe one that takes an instance of the Timer itself too
5894 	/// Create a timer with a callback when it triggers.
5895 	this(int intervalInMilliseconds, void delegate() onPulse) @trusted {
5896 		assert(onPulse !is null);
5897 
5898 		this.intervalInMilliseconds = intervalInMilliseconds;
5899 		this.onPulse = onPulse;
5900 
5901 		version(Windows) {
5902 			/*
5903 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5904 			if(handle == 0)
5905 				throw new WindowsApiException("SetTimer", GetLastError());
5906 			*/
5907 
5908 			// thanks to Archival 998 for the WaitableTimer blocks
5909 			handle = CreateWaitableTimer(null, false, null);
5910 			long initialTime = -intervalInMilliseconds;
5911 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5912 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5913 
5914 			mapping[handle] = this;
5915 
5916 		} else version(linux) {
5917 			static import ep = core.sys.linux.epoll;
5918 
5919 			import core.sys.linux.timerfd;
5920 
5921 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5922 			if(fd == -1)
5923 				throw new Exception("timer create failed");
5924 
5925 			mapping[fd] = this;
5926 
5927 			itimerspec value = makeItimerspec(intervalInMilliseconds);
5928 
5929 			if(timerfd_settime(fd, 0, &value, null) == -1)
5930 				throw new Exception("couldn't make pulse timer");
5931 
5932 			version(with_eventloop) {
5933 				import arsd.eventloop;
5934 				addFileEventListeners(fd, &trigger, null, null);
5935 			} else {
5936 				prepareEventLoop();
5937 
5938 				ep.epoll_event ev = void;
5939 				ev.events = ep.EPOLLIN;
5940 				ev.data.fd = fd;
5941 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5942 			}
5943 		} else featureNotImplemented();
5944 	}
5945 
5946 	private int intervalInMilliseconds;
5947 
5948 	// just cuz I sometimes call it this.
5949 	alias dispose = destroy;
5950 
5951 	/// Stop and destroy the timer object.
5952 	void destroy() {
5953 		version(Windows) {
5954 			staticDestroy(handle);
5955 			handle = null;
5956 		} else version(linux) {
5957 			staticDestroy(fd);
5958 			fd = -1;
5959 		} else featureNotImplemented();
5960 	}
5961 
5962 	version(Windows)
5963 	static void staticDestroy(HANDLE handle) {
5964 		if(handle) {
5965 			// KillTimer(null, handle);
5966 			CancelWaitableTimer(cast(void*)handle);
5967 			mapping.remove(handle);
5968 			CloseHandle(handle);
5969 		}
5970 	}
5971 	else version(linux)
5972 	static void staticDestroy(int fd) @system {
5973 		if(fd != -1) {
5974 			import unix = core.sys.posix.unistd;
5975 			static import ep = core.sys.linux.epoll;
5976 
5977 			version(with_eventloop) {
5978 				import arsd.eventloop;
5979 				removeFileEventListeners(fd);
5980 			} else {
5981 				ep.epoll_event ev = void;
5982 				ev.events = ep.EPOLLIN;
5983 				ev.data.fd = fd;
5984 
5985 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5986 			}
5987 			unix.close(fd);
5988 			mapping.remove(fd);
5989 		}
5990 	}
5991 
5992 	~this() {
5993 		version(Windows) { if(handle)
5994 			cleanupQueue.queue!staticDestroy(handle);
5995 		} else version(linux) { if(fd != -1)
5996 			cleanupQueue.queue!staticDestroy(fd);
5997 		}
5998 	}
5999 
6000 	void changeTime(int intervalInMilliseconds)
6001 	{
6002 		this.intervalInMilliseconds = intervalInMilliseconds;
6003 		version(Windows)
6004 		{
6005 			if(handle)
6006 			{
6007 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6008 				long initialTime = -intervalInMilliseconds;
6009 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6010 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
6011 			}
6012 		} else version(linux) {
6013 			import core.sys.linux.timerfd;
6014 
6015 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6016 			if(timerfd_settime(fd, 0, &value, null) == -1) {
6017 				throw new Exception("couldn't change pulse timer");
6018 			}
6019 		} else {
6020 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
6021 		}
6022 	}
6023 
6024 
6025 	private:
6026 
6027 	void delegate() onPulse;
6028 
6029 	int lastEventLoopRoundTriggered;
6030 
6031 	version(linux) {
6032 		static auto makeItimerspec(int intervalInMilliseconds) {
6033 			import core.sys.linux.timerfd;
6034 
6035 			itimerspec value;
6036 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6037 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6038 
6039 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6040 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6041 
6042 			return value;
6043 		}
6044 	}
6045 
6046 	void trigger() {
6047 		version(linux) {
6048 			import unix = core.sys.posix.unistd;
6049 			long val;
6050 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
6051 		} else version(Windows) {
6052 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
6053 				return; // never try to actually run faster than the event loop
6054 			lastEventLoopRoundTriggered = eventLoopRound;
6055 		} else featureNotImplemented();
6056 
6057 		onPulse();
6058 	}
6059 
6060 	version(Windows)
6061 	void rearm() {
6062 
6063 	}
6064 
6065 	version(Windows)
6066 		extern(Windows)
6067 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
6068 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
6069 			if(Timer* t = timer in mapping) {
6070 				try
6071 				(*t).trigger();
6072 				catch(Exception e) { sdpy_abort(e); assert(0); }
6073 			}
6074 		}
6075 
6076 	version(Windows) {
6077 		//UINT_PTR handle;
6078 		//static Timer[UINT_PTR] mapping;
6079 		HANDLE handle;
6080 		__gshared Timer[HANDLE] mapping;
6081 	} else version(linux) {
6082 		int fd = -1;
6083 		__gshared Timer[int] mapping;
6084 	} else version(OSXCocoa) {
6085 	} else static assert(0, "timer not supported");
6086 }
6087 }
6088 
6089 version(Windows)
6090 private int eventLoopRound;
6091 
6092 version(Windows)
6093 /// 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
6094 class WindowsHandleReader {
6095 	///
6096 	this(void delegate() onReady, HANDLE handle) {
6097 		this.onReady = onReady;
6098 		this.handle = handle;
6099 
6100 		mapping[handle] = this;
6101 
6102 		enable();
6103 	}
6104 
6105 	version(use_arsd_core)
6106 		ICoreEventLoop.UnregisterToken unregisterToken;
6107 
6108 	///
6109 	void enable() {
6110 		version(use_arsd_core) {
6111 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready));
6112 		} else {
6113 			auto el = EventLoop.get().impl;
6114 			el.handles ~= handle;
6115 		}
6116 	}
6117 
6118 	///
6119 	void disable() {
6120 		version(use_arsd_core) {
6121 			unregisterToken.unregister();
6122 		} else {
6123 			auto el = EventLoop.get().impl;
6124 			for(int i = 0; i < el.handles.length; i++) {
6125 				if(el.handles[i] is handle) {
6126 					el.handles[i] = el.handles[$-1];
6127 					el.handles = el.handles[0 .. $-1];
6128 					return;
6129 				}
6130 			}
6131 		}
6132 	}
6133 
6134 	void dispose() {
6135 		disable();
6136 		if(handle)
6137 			mapping.remove(handle);
6138 		handle = null;
6139 	}
6140 
6141 	void ready() {
6142 		if(onReady)
6143 			onReady();
6144 	}
6145 
6146 	HANDLE handle;
6147 	void delegate() onReady;
6148 
6149 	__gshared WindowsHandleReader[HANDLE] mapping;
6150 }
6151 
6152 version(Posix)
6153 /// Lets you add files to the event loop for reading. Use at your own risk.
6154 class PosixFdReader {
6155 	///
6156 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6157 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
6158 	}
6159 
6160 	///
6161 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6162 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
6163 	}
6164 
6165 	///
6166 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6167 		this.onReady = onReady;
6168 		this.fd = fd;
6169 		this.captureWrites = captureWrites;
6170 		this.captureReads = captureReads;
6171 
6172 		mapping[fd] = this;
6173 
6174 		version(with_eventloop) {
6175 			import arsd.eventloop;
6176 			addFileEventListeners(fd, &readyel);
6177 		} else {
6178 			enable();
6179 		}
6180 	}
6181 
6182 	bool captureReads;
6183 	bool captureWrites;
6184 
6185 	version(use_arsd_core) {
6186 		import arsd.core;
6187 		ICoreEventLoop.UnregisterToken unregisterToken;
6188 	}
6189 
6190 	version(with_eventloop) {} else
6191 	///
6192 	void enable() @system {
6193 		enabled = true;
6194 
6195 		version(use_arsd_core) {
6196 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper(
6197 				() { onReady(fd, true, false); }
6198 			));
6199 			// FIXME: what if it is writeable?
6200 
6201 		} else version(linux) {
6202 			prepareEventLoop();
6203 			static import ep = core.sys.linux.epoll;
6204 			ep.epoll_event ev = void;
6205 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6206 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
6207 			ev.data.fd = fd;
6208 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6209 		} else {
6210 
6211 		}
6212 	}
6213 
6214 	version(with_eventloop) {} else
6215 	///
6216 	void disable() @system {
6217 		enabled = false;
6218 
6219 		version(use_arsd_core) {
6220 			unregisterToken.unregister();
6221 		} else
6222 		version(linux) {
6223 			prepareEventLoop();
6224 			static import ep = core.sys.linux.epoll;
6225 			ep.epoll_event ev = void;
6226 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6227 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
6228 			ev.data.fd = fd;
6229 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6230 		}
6231 	}
6232 
6233 	version(with_eventloop) {} else
6234 	///
6235 	void dispose() {
6236 		if(enabled)
6237 			disable();
6238 		if(fd != -1)
6239 			mapping.remove(fd);
6240 		fd = -1;
6241 	}
6242 
6243 	void delegate(int, bool, bool) onReady;
6244 
6245 	version(with_eventloop)
6246 	void readyel() {
6247 		onReady(fd, true, true);
6248 	}
6249 
6250 	void ready(uint flags) {
6251 		version(linux) {
6252 			static import ep = core.sys.linux.epoll;
6253 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
6254 		} else {
6255 			import core.sys.posix.poll;
6256 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
6257 		}
6258 	}
6259 
6260 	void hup(uint flags) {
6261 		if(onHup)
6262 			onHup();
6263 	}
6264 
6265 	void delegate() onHup;
6266 
6267 	int fd = -1;
6268 	private bool enabled;
6269 	__gshared PosixFdReader[int] mapping;
6270 }
6271 
6272 // basic functions to access the clipboard
6273 /+
6274 
6275 
6276 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
6277 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
6278 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6279 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
6280 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
6281 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6282 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
6283 
6284 +/
6285 
6286 /++
6287 	this does a delegate because it is actually an async call on X...
6288 	the receiver may never be called if the clipboard is empty or unavailable
6289 	gets plain text from the clipboard.
6290 +/
6291 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system {
6292 	version(Windows) {
6293 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6294 		if(OpenClipboard(hwndOwner) == 0)
6295 			throw new WindowsApiException("OpenClipboard", GetLastError());
6296 		scope(exit)
6297 			CloseClipboard();
6298 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
6299 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
6300 
6301 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
6302 				scope(exit)
6303 					GlobalUnlock(dataHandle);
6304 
6305 				// FIXME: CR/LF conversions
6306 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
6307 				int len = 0;
6308 				auto d = data;
6309 				while(*d) {
6310 					d++;
6311 					len++;
6312 				}
6313 				string s;
6314 				s.reserve(len);
6315 				foreach(dchar ch; data[0 .. len]) {
6316 					s ~= ch;
6317 				}
6318 				receiver(s);
6319 			}
6320 		}
6321 	} else version(X11) {
6322 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6323 	} else version(OSXCocoa) {
6324 		throw new NotYetImplementedException();
6325 	} else static assert(0);
6326 }
6327 
6328 // FIXME: a clipboard listener might be cool btw
6329 
6330 /++
6331 	this does a delegate because it is actually an async call on X...
6332 	the receiver may never be called if the clipboard is empty or unavailable
6333 	gets image from the clipboard.
6334 
6335 	templated because it introduces an optional dependency on arsd.bmp
6336 +/
6337 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6338 	version(Windows) {
6339 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6340 		if(OpenClipboard(hwndOwner) == 0)
6341 			throw new WindowsApiException("OpenClipboard", GetLastError());
6342 		scope(exit)
6343 			CloseClipboard();
6344 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6345 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6346 				scope(exit)
6347 					GlobalUnlock(dataHandle);
6348 
6349 				auto len = GlobalSize(dataHandle);
6350 
6351 				import arsd.bmp;
6352 				auto img = readBmp(data[0 .. len], false);
6353 				receiver(img);
6354 			}
6355 		}
6356 	} else version(X11) {
6357 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6358 	} else version(OSXCocoa) {
6359 		throw new NotYetImplementedException();
6360 	} else static assert(0);
6361 }
6362 
6363 /// Copies some text to the clipboard.
6364 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6365 	assert(clipboardOwner !is null);
6366 	version(Windows) {
6367 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6368 			throw new WindowsApiException("OpenClipboard", GetLastError());
6369 		scope(exit)
6370 			CloseClipboard();
6371 		EmptyClipboard();
6372 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6373 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6374 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6375 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6376 			auto slice = data[0 .. sz];
6377 			scope(failure)
6378 				GlobalUnlock(handle);
6379 
6380 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6381 
6382 			GlobalUnlock(handle);
6383 			SetClipboardData(CF_UNICODETEXT, handle);
6384 		}
6385 	} else version(X11) {
6386 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6387 	} else version(OSXCocoa) {
6388 		throw new NotYetImplementedException();
6389 	} else static assert(0);
6390 }
6391 
6392 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6393 	assert(clipboardOwner !is null);
6394 	version(Windows) {
6395 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6396 			throw new WindowsApiException("OpenClipboard", GetLastError());
6397 		scope(exit)
6398 			CloseClipboard();
6399 		EmptyClipboard();
6400 
6401 
6402 		import arsd.bmp;
6403 		ubyte[] mdata;
6404 		mdata.reserve(img.width * img.height);
6405 		void sink(ubyte b) {
6406 			mdata ~= b;
6407 		}
6408 		writeBmpIndirect(img, &sink, false);
6409 
6410 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6411 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6412 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6413 			auto slice = data[0 .. mdata.length];
6414 			scope(failure)
6415 				GlobalUnlock(handle);
6416 
6417 			slice[] = mdata[];
6418 
6419 			GlobalUnlock(handle);
6420 			SetClipboardData(CF_DIB, handle);
6421 		}
6422 	} else version(X11) {
6423 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6424 			mixin X11SetSelectionHandler_Basics;
6425 			private const(ubyte)[] mdata;
6426 			private const(ubyte)[] mdata_original;
6427 			this(MemoryImage img) {
6428 				import arsd.bmp;
6429 
6430 				mdata.reserve(img.width * img.height);
6431 				void sink(ubyte b) {
6432 					mdata ~= b;
6433 				}
6434 				writeBmpIndirect(img, &sink, true);
6435 
6436 				mdata_original = mdata;
6437 			}
6438 
6439 			Atom[] availableFormats() {
6440 				auto display = XDisplayConnection.get;
6441 				return [
6442 					GetAtom!"image/bmp"(display),
6443 					GetAtom!"TARGETS"(display)
6444 				];
6445 			}
6446 
6447 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6448 				if(mdata.length < data.length) {
6449 					data[0 .. mdata.length] = mdata[];
6450 					auto ret = data[0 .. mdata.length];
6451 					mdata = mdata[$..$];
6452 					return ret;
6453 				} else {
6454 					data[] = mdata[0 .. data.length];
6455 					mdata = mdata[data.length .. $];
6456 					return data[];
6457 				}
6458 			}
6459 
6460 			void done() {
6461 				mdata = mdata_original;
6462 			}
6463 		}
6464 
6465 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6466 	} else version(OSXCocoa) {
6467 		throw new NotYetImplementedException();
6468 	} else static assert(0);
6469 }
6470 
6471 
6472 version(X11) {
6473 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6474 
6475 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6476 
6477 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6478 	/// Platform-specific for X11.
6479 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6480 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6481 		__gshared static Atom a;
6482 		if(!a) {
6483 			a = XInternAtom(display, name, !create);
6484 			// FIXME: might need to synchronize this and attach it to the actual object
6485 			interredAtoms ~= &a;
6486 		}
6487 		if(a == None)
6488 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6489 		return a;
6490 	}
6491 
6492 	/// Platform-specific for X11 - gets atom names as a string.
6493 	string getAtomName(Atom atom, Display* display) {
6494 		auto got = XGetAtomName(display, atom);
6495 		scope(exit) XFree(got);
6496 		import core.stdc.string;
6497 		string s = got[0 .. strlen(got)].idup;
6498 		return s;
6499 	}
6500 
6501 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6502 	void setPrimarySelection(SimpleWindow window, string text) {
6503 		setX11Selection!"PRIMARY"(window, text);
6504 	}
6505 
6506 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6507 	void setSecondarySelection(SimpleWindow window, string text) {
6508 		setX11Selection!"SECONDARY"(window, text);
6509 	}
6510 
6511 	interface X11SetSelectionHandler {
6512 		// should include TARGETS right now
6513 		Atom[] availableFormats();
6514 		// Return the slice of data you filled, empty slice if done.
6515 		// this is to support the incremental thing
6516 		ubyte[] getData(Atom format, return scope ubyte[] data);
6517 
6518 		void done();
6519 
6520 		void handleRequest(XEvent);
6521 
6522 		bool matchesIncr(Window, Atom);
6523 		void sendMoreIncr(XPropertyEvent*);
6524 	}
6525 
6526 	mixin template X11SetSelectionHandler_Basics() {
6527 		Window incrWindow;
6528 		Atom incrAtom;
6529 		Atom selectionAtom;
6530 		Atom formatAtom;
6531 		ubyte[] toSend;
6532 		bool matchesIncr(Window w, Atom a) {
6533 			return incrAtom && incrAtom == a && w == incrWindow;
6534 		}
6535 		void sendMoreIncr(XPropertyEvent* event) {
6536 			auto display = XDisplayConnection.get;
6537 
6538 			XChangeProperty (display,
6539 				incrWindow,
6540 				incrAtom,
6541 				formatAtom,
6542 				8 /* bits */, PropModeReplace,
6543 				toSend.ptr, cast(int) toSend.length);
6544 
6545 			if(toSend.length != 0) {
6546 				toSend = this.getData(formatAtom, toSend[]);
6547 			} else {
6548 				this.done();
6549 				incrWindow = None;
6550 				incrAtom = None;
6551 				selectionAtom = None;
6552 				formatAtom = None;
6553 				toSend = null;
6554 			}
6555 		}
6556 		void handleRequest(XEvent ev) {
6557 
6558 			auto display = XDisplayConnection.get;
6559 
6560 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6561 			XSelectionEvent selectionEvent;
6562 			selectionEvent.type = EventType.SelectionNotify;
6563 			selectionEvent.display = event.display;
6564 			selectionEvent.requestor = event.requestor;
6565 			selectionEvent.selection = event.selection;
6566 			selectionEvent.time = event.time;
6567 			selectionEvent.target = event.target;
6568 
6569 			bool supportedType() {
6570 				foreach(t; this.availableFormats())
6571 					if(t == event.target)
6572 						return true;
6573 				return false;
6574 			}
6575 
6576 			if(event.property == None) {
6577 				selectionEvent.property = event.target;
6578 
6579 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6580 				XFlush(display);
6581 			} if(event.target == GetAtom!"TARGETS"(display)) {
6582 				/* respond with the supported types */
6583 				auto tlist = this.availableFormats();
6584 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6585 				selectionEvent.property = event.property;
6586 
6587 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6588 				XFlush(display);
6589 			} else if(supportedType()) {
6590 				auto buffer = new ubyte[](1024 * 64);
6591 				auto toSend = this.getData(event.target, buffer[]);
6592 
6593 				if(toSend.length < 32 * 1024) {
6594 					// small enough to send directly...
6595 					selectionEvent.property = event.property;
6596 					XChangeProperty (display,
6597 						selectionEvent.requestor,
6598 						selectionEvent.property,
6599 						event.target,
6600 						8 /* bits */, 0 /* PropModeReplace */,
6601 						toSend.ptr, cast(int) toSend.length);
6602 
6603 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6604 					XFlush(display);
6605 				} else {
6606 					// large, let's send incrementally
6607 					arch_ulong l = toSend.length;
6608 
6609 					// if I wanted other events from this window don't want to clear that out....
6610 					XWindowAttributes xwa;
6611 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6612 
6613 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6614 
6615 					incrWindow = event.requestor;
6616 					incrAtom = event.property;
6617 					formatAtom = event.target;
6618 					selectionAtom = event.selection;
6619 					this.toSend = toSend;
6620 
6621 					selectionEvent.property = event.property;
6622 					XChangeProperty (display,
6623 						selectionEvent.requestor,
6624 						selectionEvent.property,
6625 						GetAtom!"INCR"(display),
6626 						32 /* bits */, PropModeReplace,
6627 						&l, 1);
6628 
6629 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6630 					XFlush(display);
6631 				}
6632 				//if(after)
6633 					//after();
6634 			} else {
6635 				debug(sdpy_clip) {
6636 					writeln("Unsupported data ", getAtomName(event.target, display));
6637 				}
6638 				selectionEvent.property = None; // I don't know how to handle this type...
6639 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6640 				XFlush(display);
6641 			}
6642 		}
6643 	}
6644 
6645 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6646 		mixin X11SetSelectionHandler_Basics;
6647 		private const(ubyte)[] text;
6648 		private const(ubyte)[] text_original;
6649 		this(string text) {
6650 			this.text = cast(const ubyte[]) text;
6651 			this.text_original = this.text;
6652 		}
6653 		Atom[] availableFormats() {
6654 			auto display = XDisplayConnection.get;
6655 			return [
6656 				GetAtom!"UTF8_STRING"(display),
6657 				GetAtom!"text/plain"(display),
6658 				XA_STRING,
6659 				GetAtom!"TARGETS"(display)
6660 			];
6661 		}
6662 
6663 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6664 			if(text.length < data.length) {
6665 				data[0 .. text.length] = text[];
6666 				return data[0 .. text.length];
6667 			} else {
6668 				data[] = text[0 .. data.length];
6669 				text = text[data.length .. $];
6670 				return data[];
6671 			}
6672 		}
6673 
6674 		void done() {
6675 			text = text_original;
6676 		}
6677 	}
6678 
6679 	/// 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?!)
6680 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6681 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6682 	}
6683 
6684 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6685 		assert(window !is null);
6686 
6687 		auto display = XDisplayConnection.get();
6688 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6689 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6690 		else Atom a = GetAtom!atomName(display);
6691 
6692 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6693 
6694 		window.impl.setSelectionHandlers[a] = data;
6695 	}
6696 
6697 	///
6698 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6699 		getX11Selection!"PRIMARY"(window, handler);
6700 	}
6701 
6702 	// added July 28, 2020
6703 	// undocumented as experimental tho
6704 	interface X11GetSelectionHandler {
6705 		void handleData(Atom target, in ubyte[] data);
6706 		Atom findBestFormat(Atom[] answer);
6707 
6708 		void prepareIncremental(Window, Atom);
6709 		bool matchesIncr(Window, Atom);
6710 		void handleIncrData(Atom, in ubyte[] data);
6711 	}
6712 
6713 	mixin template X11GetSelectionHandler_Basics() {
6714 		Window incrWindow;
6715 		Atom incrAtom;
6716 
6717 		void prepareIncremental(Window w, Atom a) {
6718 			incrWindow = w;
6719 			incrAtom = a;
6720 		}
6721 		bool matchesIncr(Window w, Atom a) {
6722 			return incrWindow == w && incrAtom == a;
6723 		}
6724 
6725 		Atom incrFormatAtom;
6726 		ubyte[] incrData;
6727 		void handleIncrData(Atom format, in ubyte[] data) {
6728 			incrFormatAtom = format;
6729 
6730 			if(data.length)
6731 				incrData ~= data;
6732 			else
6733 				handleData(incrFormatAtom, incrData);
6734 
6735 		}
6736 	}
6737 
6738 	///
6739 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6740 		assert(window !is null);
6741 
6742 		auto display = XDisplayConnection.get();
6743 		auto atom = GetAtom!atomName(display);
6744 
6745 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6746 			this(void delegate(in char[]) handler) {
6747 				this.handler = handler;
6748 			}
6749 
6750 			mixin X11GetSelectionHandler_Basics;
6751 
6752 			void delegate(in char[]) handler;
6753 
6754 			void handleData(Atom target, in ubyte[] data) {
6755 				// import std.stdio; writeln(target, " ", data);
6756 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6757 					handler(cast(const char[]) data);
6758 				else if(target == None && data is null)
6759 					handler(null); // no suitable selection exists
6760 			}
6761 
6762 			Atom findBestFormat(Atom[] answer) {
6763 				Atom best = None;
6764 				foreach(option; answer) {
6765 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6766 						best = option;
6767 						break;
6768 					} else if(option == XA_STRING) {
6769 						best = option;
6770 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6771 						best = option;
6772 					}
6773 				}
6774 				return best;
6775 			}
6776 		}
6777 
6778 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6779 
6780 		auto target = GetAtom!"TARGETS"(display);
6781 
6782 		// SDD_DATA is "simpledisplay.d data"
6783 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6784 	}
6785 
6786 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6787 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6788 		assert(window !is null);
6789 
6790 		auto display = XDisplayConnection.get();
6791 		auto atom = GetAtom!atomName(display);
6792 
6793 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6794 			this(void delegate(MemoryImage) handler) {
6795 				this.handler = handler;
6796 			}
6797 
6798 			mixin X11GetSelectionHandler_Basics;
6799 
6800 			void delegate(MemoryImage) handler;
6801 
6802 			void handleData(Atom target, in ubyte[] data) {
6803 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6804 					import arsd.bmp;
6805 					handler(readBmp(data));
6806 				}
6807 			}
6808 
6809 			Atom findBestFormat(Atom[] answer) {
6810 				Atom best = None;
6811 				foreach(option; answer) {
6812 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6813 						best = option;
6814 					}
6815 				}
6816 				return best;
6817 			}
6818 
6819 		}
6820 
6821 
6822 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6823 
6824 		auto target = GetAtom!"TARGETS"(display);
6825 
6826 		// SDD_DATA is "simpledisplay.d data"
6827 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6828 	}
6829 
6830 
6831 	///
6832 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6833 		Atom actualType;
6834 		int actualFormat;
6835 		arch_ulong actualItems;
6836 		arch_ulong bytesRemaining;
6837 		void* data;
6838 
6839 		auto display = XDisplayConnection.get();
6840 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6841 			if(actualFormat == 0)
6842 				return null;
6843 			else {
6844 				int byteLength;
6845 				if(actualFormat == 32) {
6846 					// 32 means it is a C long... which is variable length
6847 					actualFormat = cast(int) arch_long.sizeof * 8;
6848 				}
6849 
6850 				// then it is just a bit count
6851 				byteLength = cast(int) (actualItems * actualFormat / 8);
6852 
6853 				auto d = new ubyte[](byteLength);
6854 				d[] = cast(ubyte[]) data[0 .. byteLength];
6855 				XFree(data);
6856 				return d;
6857 			}
6858 		}
6859 		return null;
6860 	}
6861 
6862 	/* defined in the systray spec */
6863 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6864 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6865 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6866 
6867 
6868 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6869 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6870 	public class GlobalHotkey {
6871 		KeyEvent key;
6872 		void delegate () handler;
6873 
6874 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6875 
6876 		/// Create from initialzed KeyEvent object
6877 		this (KeyEvent akey, void delegate () ahandler=null) {
6878 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6879 			key = akey;
6880 			handler = ahandler;
6881 		}
6882 
6883 		/// Create from emacs-like key name ("C-M-Y", etc.)
6884 		this (const(char)[] akey, void delegate () ahandler=null) {
6885 			key = KeyEvent.parse(akey);
6886 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6887 			handler = ahandler;
6888 		}
6889 
6890 	}
6891 
6892 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6893 		//conwriteln("failed to grab key");
6894 		GlobalHotkeyManager.ghfailed = true;
6895 		return 0;
6896 	}
6897 
6898 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6899 		Image.impl.xshmfailed = true;
6900 		return 0;
6901 	}
6902 
6903 	private __gshared int errorHappened;
6904 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6905 		import core.stdc.stdio;
6906 		char[265] buffer;
6907 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6908 		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);
6909 		errorHappened = true;
6910 		return 0;
6911 	}
6912 
6913 	/++
6914 		Global hotkey manager. It contains static methods to manage global hotkeys.
6915 
6916 		---
6917 		 try {
6918 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6919 		} catch (Exception e) {
6920 			conwriteln("ERROR registering hotkey!");
6921 		}
6922 		EventLoop.get.run();
6923 		---
6924 
6925 		The key strings are based on Emacs. In practical terms,
6926 		`M` means `alt` and `H` means the Windows logo key. `C`
6927 		is `ctrl`.
6928 
6929 		$(WARNING
6930 			This is X-specific right now. If you are on
6931 			Windows, try [registerHotKey] instead.
6932 
6933 			We will probably merge these into a single
6934 			interface later.
6935 		)
6936 	+/
6937 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6938 		version(X11) {
6939 			void recreateAfterDisconnect() {
6940 				throw new Exception("NOT IMPLEMENTED");
6941 			}
6942 			void discardConnectionState() {
6943 				throw new Exception("NOT IMPLEMENTED");
6944 			}
6945 		}
6946 
6947 		private static immutable uint[8] masklist = [ 0,
6948 			KeyOrButtonMask.LockMask,
6949 			KeyOrButtonMask.Mod2Mask,
6950 			KeyOrButtonMask.Mod3Mask,
6951 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6952 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6953 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6954 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6955 		];
6956 		private __gshared GlobalHotkeyManager ghmanager;
6957 		private __gshared bool ghfailed = false;
6958 
6959 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6960 			if (modmask == 0) return false;
6961 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6962 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6963 			return true;
6964 		}
6965 
6966 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6967 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6968 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6969 			return modmask;
6970 		}
6971 
6972 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6973 			uint keycode = cast(uint)ke.key;
6974 			auto dpy = XDisplayConnection.get;
6975 			return XKeysymToKeycode(dpy, keycode);
6976 		}
6977 
6978 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6979 
6980 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6981 
6982 		NativeEventHandler getNativeEventHandler () {
6983 			return delegate int (XEvent e) {
6984 				if (e.type != EventType.KeyPress) return 1;
6985 				auto kev = cast(const(XKeyEvent)*)&e;
6986 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6987 				if (auto ghkp = hash in globalHotkeyList) {
6988 					try {
6989 						ghkp.doHandle();
6990 					} catch (Exception e) {
6991 						import core.stdc.stdio : stderr, fprintf;
6992 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6993 					}
6994 				}
6995 				return 1;
6996 			};
6997 		}
6998 
6999 		private this () {
7000 			auto dpy = XDisplayConnection.get;
7001 			auto root = RootWindow(dpy, DefaultScreen(dpy));
7002 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
7003 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
7004 		}
7005 
7006 		/// Register new global hotkey with initialized `GlobalHotkey` object.
7007 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
7008 		static void register (GlobalHotkey gh) {
7009 			if (gh is null) return;
7010 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
7011 
7012 			auto dpy = XDisplayConnection.get;
7013 			immutable keycode = keyEvent2KeyCode(gh.key);
7014 
7015 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
7016 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
7017 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
7018 			XSync(dpy, 0/*False*/);
7019 
7020 			Window root = RootWindow(dpy, DefaultScreen(dpy));
7021 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7022 			ghfailed = false;
7023 			foreach (immutable uint ormask; masklist[]) {
7024 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
7025 			}
7026 			XSync(dpy, 0/*False*/);
7027 			XSetErrorHandler(savedErrorHandler);
7028 
7029 			if (ghfailed) {
7030 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7031 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
7032 				XSync(dpy, 0/*False*/);
7033 				XSetErrorHandler(savedErrorHandler);
7034 				throw new Exception("cannot register global hotkey");
7035 			}
7036 
7037 			globalHotkeyList[hash] = gh;
7038 		}
7039 
7040 		/// Ditto
7041 		static void register (const(char)[] akey, void delegate () ahandler) {
7042 			register(new GlobalHotkey(akey, ahandler));
7043 		}
7044 
7045 		private static void removeByHash (ulong hash) {
7046 			if (auto ghp = hash in globalHotkeyList) {
7047 				auto dpy = XDisplayConnection.get;
7048 				immutable keycode = keyEvent2KeyCode(ghp.key);
7049 				Window root = RootWindow(dpy, DefaultScreen(dpy));
7050 				XSync(dpy, 0/*False*/);
7051 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7052 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
7053 				XSync(dpy, 0/*False*/);
7054 				XSetErrorHandler(savedErrorHandler);
7055 				globalHotkeyList.remove(hash);
7056 			}
7057 		}
7058 
7059 		/// Register new global hotkey with previously used `GlobalHotkey` object.
7060 		/// It is safe to unregister unknown or invalid hotkey.
7061 		static void unregister (GlobalHotkey gh) {
7062 			//TODO: add second AA for faster search? prolly doesn't worth it.
7063 			if (gh is null) return;
7064 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
7065 				if (kv.value is gh) {
7066 					removeByHash(kv.key);
7067 					return;
7068 				}
7069 			}
7070 		}
7071 
7072 		/// Ditto.
7073 		static void unregister (const(char)[] key) {
7074 			auto kev = KeyEvent.parse(key);
7075 			immutable keycode = keyEvent2KeyCode(kev);
7076 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
7077 		}
7078 	}
7079 }
7080 
7081 version(Windows) {
7082 	/++
7083 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
7084 
7085 		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).
7086 	+/
7087 	void sendSyntheticInput(wstring s) {
7088 			INPUT[] inputs;
7089 			inputs.reserve(s.length * 2);
7090 
7091 			foreach(wchar c; s) {
7092 				INPUT input;
7093 				input.type = INPUT_KEYBOARD;
7094 				input.ki.wScan = c;
7095 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7096 				inputs ~= input;
7097 
7098 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7099 				inputs ~= input;
7100 			}
7101 
7102 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7103 				throw new WindowsApiException("SendInput", GetLastError());
7104 			}
7105 
7106 	}
7107 
7108 
7109 	// global hotkey helper function
7110 
7111 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
7112 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system {
7113 		__gshared int hotkeyId = 0;
7114 		int id = ++hotkeyId;
7115 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
7116 			throw new Exception("RegisterHotKey");
7117 
7118 		__gshared void delegate()[WPARAM][HWND] handlers;
7119 
7120 		handlers[window.impl.hwnd][id] = handler;
7121 
7122 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
7123 
7124 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7125 			switch(msg) {
7126 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
7127 				case WM_HOTKEY:
7128 					if(auto list = hwnd in handlers) {
7129 						if(auto h = wParam in *list) {
7130 							(*h)();
7131 							return 0;
7132 						}
7133 					}
7134 				goto default;
7135 				default:
7136 			}
7137 			if(oldHandler)
7138 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
7139 			return 1; // pass it on
7140 		};
7141 
7142 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
7143 			oldHandler = window.handleNativeEvent;
7144 			window.handleNativeEvent = nativeEventHandler;
7145 		}
7146 
7147 		return id;
7148 	}
7149 
7150 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
7151 	void unregisterHotKey(SimpleWindow window, int id) {
7152 		if(!UnregisterHotKey(window.impl.hwnd, id))
7153 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
7154 	}
7155 }
7156 
7157 version (X11) {
7158 	pragma(lib, "dl");
7159 	import core.sys.posix.dlfcn;
7160 }
7161 
7162 /++
7163 	Allows for sending synthetic input to the X server via the Xtst
7164 	extension or on Windows using SendInput.
7165 
7166 	Please remember user input is meant to be user - don't use this
7167 	if you have some other alternative!
7168 
7169 	History:
7170 		Added May 17, 2020 with the X implementation.
7171 
7172 		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.)
7173 	Bugs:
7174 		All methods on OSX Cocoa will throw not yet implemented exceptions.
7175 +/
7176 struct SyntheticInput {
7177 	@disable this();
7178 
7179 	private int* refcount;
7180 
7181 	version(X11) {
7182 		private void* lib;
7183 
7184 		private extern(C) {
7185 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
7186 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
7187 		}
7188 	}
7189 
7190 	/// The dummy param must be 0.
7191 	this(int dummy) {
7192 		version(X11) {
7193 			lib = dlopen("libXtst.so", RTLD_NOW);
7194 			if(lib is null)
7195 				throw new Exception("cannot load xtest lib extension");
7196 			scope(failure)
7197 				dlclose(lib);
7198 
7199 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
7200 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
7201 
7202 			if(XTestFakeKeyEvent is null)
7203 				throw new Exception("No XTestFakeKeyEvent");
7204 			if(XTestFakeButtonEvent is null)
7205 				throw new Exception("No XTestFakeButtonEvent");
7206 		}
7207 
7208 		refcount = new int;
7209 		*refcount = 1;
7210 	}
7211 
7212 	this(this) {
7213 		if(refcount)
7214 			*refcount += 1;
7215 	}
7216 
7217 	~this() {
7218 		if(refcount) {
7219 			*refcount -= 1;
7220 			if(*refcount == 0)
7221 				// I commented this because if I close the lib before
7222 				// XCloseDisplay, it is liable to segfault... so just
7223 				// gonna keep it loaded if it is loaded, no big deal
7224 				// anyway.
7225 				{} // dlclose(lib);
7226 		}
7227 	}
7228 
7229 	/++
7230 		Simulates typing a string into the keyboard.
7231 
7232 		Bugs:
7233 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
7234 
7235 			Not implemented except on Windows and X11.
7236 	+/
7237 	void sendSyntheticInput(string s) {
7238 		version(Windows) {
7239 			INPUT[] inputs;
7240 			inputs.reserve(s.length * 2);
7241 
7242 			auto ei = GetMessageExtraInfo();
7243 
7244 			foreach(wchar c; s) {
7245 				INPUT input;
7246 				input.type = INPUT_KEYBOARD;
7247 				input.ki.wScan = c;
7248 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7249 				input.ki.dwExtraInfo = ei;
7250 				inputs ~= input;
7251 
7252 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7253 				inputs ~= input;
7254 			}
7255 
7256 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7257 				throw new WindowsApiException("SendInput", GetLastError());
7258 			}
7259 		} else version(X11) {
7260 			int delay = 0;
7261 			foreach(ch; s) {
7262 				pressKey(cast(Key) ch, true, delay);
7263 				pressKey(cast(Key) ch, false, delay);
7264 				delay += 5;
7265 			}
7266 		} else throw new NotYetImplementedException();
7267 	}
7268 
7269 	/++
7270 		Sends a fake press or release key event.
7271 
7272 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7273 
7274 		Bugs:
7275 			The `delay` parameter is not implemented yet on Windows.
7276 
7277 			Not implemented except on Windows and X11.
7278 	+/
7279 	void pressKey(Key key, bool pressed, int delay = 0) {
7280 		version(Windows) {
7281 			INPUT input;
7282 			input.type = INPUT_KEYBOARD;
7283 			input.ki.wVk = cast(ushort) key;
7284 
7285 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
7286 			input.ki.dwExtraInfo = GetMessageExtraInfo();
7287 
7288 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7289 				throw new WindowsApiException("SendInput", GetLastError());
7290 			}
7291 		} else version(X11) {
7292 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
7293 		} else throw new NotYetImplementedException();
7294 	}
7295 
7296 	/++
7297 		Sends a fake mouse button press or release event.
7298 
7299 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7300 
7301 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
7302 
7303 		Bugs:
7304 			The `delay` parameter is not implemented yet on Windows.
7305 
7306 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
7307 
7308 			All arguments will throw NotYetImplementedException on OSX Cocoa.
7309 	+/
7310 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
7311 		version(Windows) {
7312 			INPUT input;
7313 			input.type = INPUT_MOUSE;
7314 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7315 
7316 			// input.mi.mouseData for a wheel event
7317 
7318 			switch(button) {
7319 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
7320 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
7321 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
7322 				case MouseButton.wheelUp:
7323 				case MouseButton.wheelDown:
7324 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
7325 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
7326 				break;
7327 				case MouseButton.backButton: throw new NotYetImplementedException();
7328 				case MouseButton.forwardButton: throw new NotYetImplementedException();
7329 				default:
7330 			}
7331 
7332 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7333 				throw new WindowsApiException("SendInput", GetLastError());
7334 			}
7335 		} else version(X11) {
7336 			int btn;
7337 
7338 			switch(button) {
7339 				case MouseButton.left: btn = 1; break;
7340 				case MouseButton.middle: btn = 2; break;
7341 				case MouseButton.right: btn = 3; break;
7342 				case MouseButton.wheelUp: btn = 4; break;
7343 				case MouseButton.wheelDown: btn = 5; break;
7344 				case MouseButton.backButton: btn = 8; break;
7345 				case MouseButton.forwardButton: btn = 9; break;
7346 				default:
7347 			}
7348 
7349 			assert(btn);
7350 
7351 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7352 		} else throw new NotYetImplementedException();
7353 	}
7354 
7355 	///
7356 	static void moveMouseArrowBy(int dx, int dy) {
7357 		version(Windows) {
7358 			INPUT input;
7359 			input.type = INPUT_MOUSE;
7360 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7361 			input.mi.dx = dx;
7362 			input.mi.dy = dy;
7363 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7364 
7365 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7366 				throw new WindowsApiException("SendInput", GetLastError());
7367 			}
7368 		} else version(X11) {
7369 			auto disp = XDisplayConnection.get();
7370 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7371 			XFlush(disp);
7372 		} else throw new NotYetImplementedException();
7373 	}
7374 
7375 	///
7376 	static void moveMouseArrowTo(int x, int y) {
7377 		version(Windows) {
7378 			INPUT input;
7379 			input.type = INPUT_MOUSE;
7380 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7381 			input.mi.dx = x;
7382 			input.mi.dy = y;
7383 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7384 
7385 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7386 				throw new WindowsApiException("SendInput", GetLastError());
7387 			}
7388 		} else version(X11) {
7389 			auto disp = XDisplayConnection.get();
7390 			auto root = RootWindow(disp, DefaultScreen(disp));
7391 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7392 			XFlush(disp);
7393 		} else throw new NotYetImplementedException();
7394 	}
7395 }
7396 
7397 
7398 
7399 /++
7400 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7401 
7402 	See_Also:
7403 	$(LIST
7404 		*[ScreenPainter]
7405 		*[ScreenPainter.rasterOp]
7406 	)
7407 +/
7408 enum RasterOp {
7409 	normal, /// Replaces the pixel.
7410 	xor, /// Uses bitwise xor to draw.
7411 }
7412 
7413 // being phobos-free keeps the size WAY down
7414 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7415 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7416 package(arsd) const(wchar)* toWStringz(string s) {
7417 	wstring r;
7418 	foreach(dchar c; s)
7419 		r ~= c;
7420 	r ~= '\0';
7421 	return r.ptr;
7422 }
7423 private string[] split(in void[] a, char c) {
7424 		string[] ret;
7425 		size_t previous = 0;
7426 		foreach(i, char ch; cast(ubyte[]) a) {
7427 			if(ch == c) {
7428 				ret ~= cast(string) a[previous .. i];
7429 				previous = i + 1;
7430 			}
7431 		}
7432 		if(previous != a.length)
7433 			ret ~= cast(string) a[previous .. $];
7434 		return ret;
7435 	}
7436 
7437 version(without_opengl) {
7438 	enum OpenGlOptions {
7439 		no,
7440 	}
7441 } else {
7442 	/++
7443 		Determines if you want an OpenGL context created on the new window.
7444 
7445 
7446 		See more: [#topics-3d|in the 3d topic].
7447 
7448 		---
7449 		import arsd.simpledisplay;
7450 		void main() {
7451 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7452 
7453 			// Set up the matrix
7454 			window.setAsCurrentOpenGlContext(); // make this window active
7455 
7456 			// This is called on each frame, we will draw our scene
7457 			window.redrawOpenGlScene = delegate() {
7458 
7459 			};
7460 
7461 			window.eventLoop(0);
7462 		}
7463 		---
7464 	+/
7465 	enum OpenGlOptions {
7466 		no, /// No OpenGL context is created
7467 		yes, /// Yes, create an OpenGL context
7468 	}
7469 
7470 	version(X11) {
7471 		static if (!SdpyIsUsingIVGLBinds) {
7472 
7473 
7474 			struct __GLXFBConfigRec {}
7475 			alias GLXFBConfig = __GLXFBConfigRec*;
7476 
7477 			//pragma(lib, "GL");
7478 			//pragma(lib, "GLU");
7479 			interface GLX {
7480 			extern(C) nothrow @nogc {
7481 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7482 						const int *attrib_list);
7483 
7484 				 void glXCopyContext(Display *dpy, GLXContext src,
7485 						GLXContext dst, arch_ulong mask);
7486 
7487 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7488 						GLXContext share_list, Bool direct);
7489 
7490 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7491 						Pixmap pixmap);
7492 
7493 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7494 
7495 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7496 
7497 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7498 						int attrib, int *value);
7499 
7500 				 GLXContext glXGetCurrentContext();
7501 
7502 				 GLXDrawable glXGetCurrentDrawable();
7503 
7504 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7505 
7506 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7507 						GLXContext ctx);
7508 
7509 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7510 
7511 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7512 
7513 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7514 
7515 				 void glXUseXFont(Font font, int first, int count, int list_base);
7516 
7517 				 void glXWaitGL();
7518 
7519 				 void glXWaitX();
7520 
7521 
7522 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7523 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7524 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7525 
7526 				char* glXQueryExtensionsString (Display*, int);
7527 				void* glXGetProcAddress (const(char)*);
7528 
7529 			}
7530 			}
7531 
7532 			version(OSX)
7533 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7534 			else
7535 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7536 			shared static this() {
7537 				glx.loadDynamicLibrary();
7538 			}
7539 
7540 			alias glbindGetProcAddress = glXGetProcAddress;
7541 		}
7542 	} else version(Windows) {
7543 		/* it is done below by interface GL */
7544 	} else
7545 		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.");
7546 }
7547 
7548 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7549 alias Resizablity = Resizability;
7550 
7551 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7552 enum Resizability {
7553 	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.
7554 	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.
7555 	/++
7556 		$(PITFALL
7557 			Planned for the future but not implemented.
7558 		)
7559 
7560 		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.
7561 
7562 		History:
7563 			Added November 11, 2022, but not yet implemented and may not be for some time.
7564 	+/
7565 	/*@__future*/ allowResizingMaintainingAspectRatio,
7566 	/++
7567 		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.
7568 
7569 		History:
7570 			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.
7571 
7572 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7573 	+/
7574 	automaticallyScaleIfPossible,
7575 }
7576 /// ditto
7577 alias Resizeability = Resizability;
7578 
7579 
7580 /++
7581 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7582 +/
7583 enum TextAlignment : uint {
7584 	Left = 0, ///
7585 	Center = 1, ///
7586 	Right = 2, ///
7587 
7588 	VerticalTop = 0, ///
7589 	VerticalCenter = 4, ///
7590 	VerticalBottom = 8, ///
7591 }
7592 
7593 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7594 alias Rectangle = arsd.color.Rectangle;
7595 
7596 
7597 /++
7598 	Keyboard press and release events.
7599 +/
7600 struct KeyEvent {
7601 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7602 	Key key;
7603 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7604 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7605 
7606 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7607 
7608 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7609 
7610 	SimpleWindow window; /// associated Window
7611 
7612 	/++
7613 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7614 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7615 		to predict if char events are actually coming..
7616 
7617 		Only available on X systems since this information is not given ahead of time elsewhere.
7618 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7619 
7620 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7621 		and potential quirks I'd recommend avoiding it.
7622 
7623 		History:
7624 			Added April 26, 2021 (dub v9.5)
7625 	+/
7626 	version(X11)
7627 		dchar[] charsPossible;
7628 
7629 	// convert key event to simplified string representation a-la emacs
7630 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7631 		uint dpos = 0;
7632 		void put (const(char)[] s...) nothrow @trusted {
7633 			static if (growdest) {
7634 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7635 			} else {
7636 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7637 			}
7638 		}
7639 
7640 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7641 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7642 		}
7643 
7644 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7645 
7646 		// put modifiers
7647 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7648 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7649 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7650 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7651 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7652 
7653 		if (this.key) {
7654 			foreach (string kn; __traits(allMembers, Key)) {
7655 				if (this.key == __traits(getMember, Key, kn)) {
7656 					// HACK!
7657 					static if (kn == "N0") put("0");
7658 					else static if (kn == "N1") put("1");
7659 					else static if (kn == "N2") put("2");
7660 					else static if (kn == "N3") put("3");
7661 					else static if (kn == "N4") put("4");
7662 					else static if (kn == "N5") put("5");
7663 					else static if (kn == "N6") put("6");
7664 					else static if (kn == "N7") put("7");
7665 					else static if (kn == "N8") put("8");
7666 					else static if (kn == "N9") put("9");
7667 					else put(kn);
7668 					return dest[0..dpos];
7669 				}
7670 			}
7671 			put("Unknown");
7672 		} else {
7673 			if (dpos && dest[dpos-1] == '+') --dpos;
7674 		}
7675 		return dest[0..dpos];
7676 	}
7677 
7678 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7679 
7680 	/** Parse string into key name with modifiers. It accepts things like:
7681 	 *
7682 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7683 	 *
7684 	 * Ctrl+Win+1 -- windows style
7685 	 *
7686 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7687 	 *
7688 	 * Ctrl Win 1 -- and space
7689 	 *
7690 	 * and even "Win + 1 + Ctrl".
7691 	 */
7692 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7693 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7694 
7695 		// remove trailing spaces
7696 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7697 
7698 		// tokens delimited by blank, '+', or '-'
7699 		// null on eol
7700 		const(char)[] getToken () nothrow @trusted @nogc {
7701 			// remove leading spaces and delimiters
7702 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7703 			if (name.length == 0) return null; // oops, no more tokens
7704 			// get token
7705 			size_t epos = 0;
7706 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7707 			assert(epos > 0 && epos <= name.length);
7708 			auto res = name[0..epos];
7709 			name = name[epos..$];
7710 			return res;
7711 		}
7712 
7713 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7714 			if (s0.length != s1.length) return false;
7715 			foreach (immutable ci, char c0; s0) {
7716 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7717 				char c1 = s1[ci];
7718 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7719 				if (c0 != c1) return false;
7720 			}
7721 			return true;
7722 		}
7723 
7724 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7725 		if (updown !is null) *updown = -1;
7726 		KeyEvent res;
7727 		res.key = cast(Key)0; // just in case
7728 		const(char)[] tk, tkn; // last token
7729 		bool allowEmascStyle = true;
7730 		bool ignoreModifiers = false;
7731 		tokenloop: for (;;) {
7732 			tk = tkn;
7733 			tkn = getToken();
7734 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7735 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7736 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7737 			if (allowEmascStyle && tkn.length != 0) {
7738 				if (tk.length == 1) {
7739 					char mdc = tk[0];
7740 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7741 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7742 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7743 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7744 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7745 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7746 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7747 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7748 				}
7749 			}
7750 			allowEmascStyle = false;
7751 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7752 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7753 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7754 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7755 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7756 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7757 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7758 			if (tk.length == 0) continue;
7759 			// try key name
7760 			if (res.key == 0) {
7761 				// little hack
7762 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7763 					final switch (tk[0]) {
7764 						case '0': tk = "N0"; break;
7765 						case '1': tk = "N1"; break;
7766 						case '2': tk = "N2"; break;
7767 						case '3': tk = "N3"; break;
7768 						case '4': tk = "N4"; break;
7769 						case '5': tk = "N5"; break;
7770 						case '6': tk = "N6"; break;
7771 						case '7': tk = "N7"; break;
7772 						case '8': tk = "N8"; break;
7773 						case '9': tk = "N9"; break;
7774 					}
7775 				}
7776 				foreach (string kn; __traits(allMembers, Key)) {
7777 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7778 				}
7779 			}
7780 			// unknown or duplicate key name, get out of here
7781 			break;
7782 		}
7783 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7784 		return res; // something
7785 	}
7786 
7787 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7788 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7789 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7790 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7791 		}
7792 		bool ignoreMods;
7793 		int updown;
7794 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7795 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7796 		if (this.key != ke.key) {
7797 			// things like "ctrl+alt" are complicated
7798 			uint tkm = this.modifierState&modmask;
7799 			uint kkm = ke.modifierState&modmask;
7800 			Key tk = this.key;
7801 			// ke
7802 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7803 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7804 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7805 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7806 			// this
7807 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7808 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7809 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7810 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7811 			return (tk == ke.key && tkm == kkm);
7812 		}
7813 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7814 	}
7815 }
7816 
7817 /// Sets the application name.
7818 @property string ApplicationName(string name) {
7819 	return _applicationName = name;
7820 }
7821 
7822 string _applicationName;
7823 
7824 /// ditto
7825 @property string ApplicationName() {
7826 	if(_applicationName is null) {
7827 		import core.runtime;
7828 		return Runtime.args[0];
7829 	}
7830 	return _applicationName;
7831 }
7832 
7833 
7834 /// Type of a [MouseEvent].
7835 enum MouseEventType : int {
7836 	motion = 0, /// The mouse moved inside the window
7837 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7838 	buttonReleased = 2, /// A mouse button was released
7839 }
7840 
7841 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7842 /++
7843 	Listen for this on your event listeners if you are interested in mouse action.
7844 
7845 	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.
7846 
7847 	Examples:
7848 
7849 	This will draw boxes on the window with the mouse as you hold the left button.
7850 	---
7851 	import arsd.simpledisplay;
7852 
7853 	void main() {
7854 		auto window = new SimpleWindow();
7855 
7856 		window.eventLoop(0,
7857 			(MouseEvent ev) {
7858 				if(ev.modifierState & ModifierState.leftButtonDown) {
7859 					auto painter = window.draw();
7860 					painter.fillColor = Color.red;
7861 					painter.outlineColor = Color.black;
7862 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7863 				}
7864 			}
7865 		);
7866 	}
7867 	---
7868 +/
7869 struct MouseEvent {
7870 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7871 
7872 	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.
7873 	int y; /// Current Y position of the cursor when the event fired.
7874 
7875 	int dx; /// Change in X position since last report
7876 	int dy; /// Change in Y position since last report
7877 
7878 	MouseButton button; /// See [MouseButton]
7879 	int modifierState; /// See [ModifierState]
7880 
7881 	version(X11)
7882 		private Time timestamp;
7883 
7884 	/// Returns a linear representation of mouse button,
7885 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7886 	///
7887 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7888 	@property ubyte buttonLinear() const {
7889 		import core.bitop;
7890 		if(button == 0)
7891 			return 0;
7892 		return (bsf(button) + 1) & 0b1111;
7893 	}
7894 
7895 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7896 
7897 	SimpleWindow window; /// The window in which the event happened.
7898 
7899 	Point globalCoordinates() {
7900 		Point p;
7901 		if(window is null)
7902 			throw new Exception("wtf");
7903 		static if(UsingSimpledisplayX11) {
7904 			Window child;
7905 			XTranslateCoordinates(
7906 				XDisplayConnection.get,
7907 				window.impl.window,
7908 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7909 				x, y, &p.x, &p.y, &child);
7910 			return p;
7911 		} else version(Windows) {
7912 			POINT[1] points;
7913 			points[0].x = x;
7914 			points[0].y = y;
7915 			MapWindowPoints(
7916 				window.impl.hwnd,
7917 				null,
7918 				points.ptr,
7919 				points.length
7920 			);
7921 			p.x = points[0].x;
7922 			p.y = points[0].y;
7923 
7924 			return p;
7925 		} else version(OSXCocoa) {
7926 			throw new NotYetImplementedException();
7927 		} else static assert(0);
7928 	}
7929 
7930 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7931 
7932 	/**
7933 	can contain emacs-like modifier prefix
7934 	case-insensitive names:
7935 		lmbX/leftX
7936 		rmbX/rightX
7937 		mmbX/middleX
7938 		wheelX
7939 		motion (no prefix allowed)
7940 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7941 	*/
7942 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7943 		if (str.length == 0) return false; // just in case
7944 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7945 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7946 		auto anchor = str;
7947 		uint mods = 0; // uint.max == any
7948 		// interesting bits in kmod
7949 		uint kmodmask =
7950 			ModifierState.shift|
7951 			ModifierState.ctrl|
7952 			ModifierState.alt|
7953 			ModifierState.windows|
7954 			ModifierState.leftButtonDown|
7955 			ModifierState.middleButtonDown|
7956 			ModifierState.rightButtonDown|
7957 			0;
7958 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7959 		bool wasButtons = false;
7960 		while (str.length) {
7961 			if (str.ptr[0] <= ' ') {
7962 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7963 				continue;
7964 			}
7965 			// one-letter modifier?
7966 			if (str.length >= 2 && str.ptr[1] == '-') {
7967 				switch (str.ptr[0]) {
7968 					case '*': // "any" modifier (cannot be undone)
7969 						mods = mods.max;
7970 						break;
7971 					case 'C': case 'c': // emacs "ctrl"
7972 						if (mods != mods.max) mods |= ModifierState.ctrl;
7973 						break;
7974 					case 'M': case 'm': // emacs "meta"
7975 						if (mods != mods.max) mods |= ModifierState.alt;
7976 						break;
7977 					case 'S': case 's': // emacs "shift"
7978 						if (mods != mods.max) mods |= ModifierState.shift;
7979 						break;
7980 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7981 						if (mods != mods.max) mods |= ModifierState.windows;
7982 						break;
7983 					default:
7984 						return false; // unknown modifier
7985 				}
7986 				str = str[2..$];
7987 				continue;
7988 			}
7989 			// word
7990 			char[16] buf = void; // locased
7991 			auto wep = 0;
7992 			while (str.length) {
7993 				immutable char ch = str.ptr[0];
7994 				if (ch <= ' ' || ch == '-') break;
7995 				str = str[1..$];
7996 				if (wep > buf.length) return false; // too long
7997 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7998 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7999 				else return false; // invalid char
8000 			}
8001 			if (wep == 0) return false; // just in case
8002 			uint bnum;
8003 			enum UpDown { None = -1, Up, Down, Any }
8004 			auto updown = UpDown.None; // 0: up; 1: down
8005 			switch (buf[0..wep]) {
8006 				// left button
8007 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
8008 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
8009 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
8010 				case "lmb": case "left": bnum = 0; break;
8011 				// middle button
8012 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
8013 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
8014 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
8015 				case "mmb": case "middle": bnum = 1; break;
8016 				// right button
8017 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
8018 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
8019 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
8020 				case "rmb": case "right": bnum = 2; break;
8021 				// wheel
8022 				case "wheelup": updown = UpDown.Up; goto case "wheel";
8023 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
8024 				case "wheelany": updown = UpDown.Any; goto case "wheel";
8025 				case "wheel": bnum = 3; break;
8026 				// motion
8027 				case "motion": bnum = 7; break;
8028 				// unknown
8029 				default: return false;
8030 			}
8031 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8032 			// parse possible "-up" or "-down"
8033 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
8034 				wep = 0;
8035 				foreach (immutable idx, immutable char ch; str[1..$]) {
8036 					if (ch <= ' ' || ch == '-') break;
8037 					assert(idx == wep); // for now; trick
8038 					if (wep > buf.length) { wep = 0; break; } // too long
8039 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8040 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8041 					else { wep = 0; break; } // invalid char
8042 				}
8043 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
8044 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
8045 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
8046 				// remove parsed part
8047 				if (updown != UpDown.None) str = str[wep+1..$];
8048 			}
8049 			if (updown == UpDown.None) {
8050 				updown = UpDown.Down;
8051 			}
8052 			wasButtons = wasButtons || (bnum <= 2);
8053 			//assert(updown != UpDown.None);
8054 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8055 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
8056 			if (lastButt != lastButt.max) {
8057 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
8058 				if (mods != mods.max) {
8059 					uint butbit = 0;
8060 					final switch (lastButt&0x03) {
8061 						case 0: butbit = ModifierState.leftButtonDown; break;
8062 						case 1: butbit = ModifierState.middleButtonDown; break;
8063 						case 2: butbit = ModifierState.rightButtonDown; break;
8064 					}
8065 					     if (lastButt&Flag.Down) mods |= butbit;
8066 					else if (lastButt&Flag.Up) mods &= ~butbit;
8067 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
8068 				}
8069 			}
8070 			// remember last button
8071 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
8072 		}
8073 		// no button -- nothing to do
8074 		if (lastButt == lastButt.max) return false;
8075 		// done parsing, check if something's left
8076 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
8077 		// remove action button from mask
8078 		if ((lastButt&0xff) < 3) {
8079 			final switch (lastButt&0x03) {
8080 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
8081 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
8082 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
8083 			}
8084 		}
8085 		// special case: "Motion" means "ignore buttons"
8086 		if ((lastButt&0xff) == 7 && !wasButtons) {
8087 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
8088 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
8089 		}
8090 		uint kmod = event.modifierState&kmodmask;
8091 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
8092 		// check modifier state
8093 		if (mods != mods.max) {
8094 			if (kmod != mods) return false;
8095 		}
8096 		// now check type
8097 		if ((lastButt&0xff) == 7) {
8098 			// motion
8099 			if (event.type != MouseEventType.motion) return false;
8100 		} else if ((lastButt&0xff) == 3) {
8101 			// wheel
8102 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
8103 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
8104 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
8105 			return false;
8106 		} else {
8107 			// buttons
8108 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
8109 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
8110 			{
8111 				return false;
8112 			}
8113 			// button number
8114 			switch (lastButt&0x03) {
8115 				case 0: if (event.button != MouseButton.left) return false; break;
8116 				case 1: if (event.button != MouseButton.middle) return false; break;
8117 				case 2: if (event.button != MouseButton.right) return false; break;
8118 				default: return false;
8119 			}
8120 		}
8121 		return true;
8122 	}
8123 }
8124 
8125 version(arsd_mevent_strcmp_test) unittest {
8126 	MouseEvent event;
8127 	event.type = MouseEventType.buttonPressed;
8128 	event.button = MouseButton.left;
8129 	event.modifierState = ModifierState.ctrl;
8130 	assert(event == "C-LMB");
8131 	assert(event != "C-LMBUP");
8132 	assert(event != "C-LMB-UP");
8133 	assert(event != "C-S-LMB");
8134 	assert(event == "*-LMB");
8135 	assert(event != "*-LMB-UP");
8136 
8137 	event.type = MouseEventType.buttonReleased;
8138 	assert(event != "C-LMB");
8139 	assert(event == "C-LMBUP");
8140 	assert(event == "C-LMB-UP");
8141 	assert(event != "C-S-LMB");
8142 	assert(event != "*-LMB");
8143 	assert(event == "*-LMB-UP");
8144 
8145 	event.button = MouseButton.right;
8146 	event.modifierState |= ModifierState.shift;
8147 	event.type = MouseEventType.buttonPressed;
8148 	assert(event != "C-LMB");
8149 	assert(event != "C-LMBUP");
8150 	assert(event != "C-LMB-UP");
8151 	assert(event != "C-S-LMB");
8152 	assert(event != "*-LMB");
8153 	assert(event != "*-LMB-UP");
8154 
8155 	assert(event != "C-RMB");
8156 	assert(event != "C-RMBUP");
8157 	assert(event != "C-RMB-UP");
8158 	assert(event == "C-S-RMB");
8159 	assert(event == "*-RMB");
8160 	assert(event != "*-RMB-UP");
8161 }
8162 
8163 /// This gives a few more options to drawing lines and such
8164 struct Pen {
8165 	Color color; /// the foreground color
8166 	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.
8167 	Style style; /// See [Style]
8168 /+
8169 // From X.h
8170 
8171 #define LineSolid		0
8172 #define LineOnOffDash		1
8173 #define LineDoubleDash		2
8174        LineDou-        The full path of the line is drawn, but the
8175        bleDash         even dashes are filled differently from the
8176                        odd dashes (see fill-style) with CapButt
8177                        style used where even and odd dashes meet.
8178 
8179 
8180 
8181 /* capStyle */
8182 
8183 #define CapNotLast		0
8184 #define CapButt			1
8185 #define CapRound		2
8186 #define CapProjecting		3
8187 
8188 /* joinStyle */
8189 
8190 #define JoinMiter		0
8191 #define JoinRound		1
8192 #define JoinBevel		2
8193 
8194 /* fillStyle */
8195 
8196 #define FillSolid		0
8197 #define FillTiled		1
8198 #define FillStippled		2
8199 #define FillOpaqueStippled	3
8200 
8201 
8202 +/
8203 	/// Style of lines drawn
8204 	enum Style {
8205 		Solid, /// a solid line
8206 		Dashed, /// a dashed line
8207 		Dotted, /// a dotted line
8208 	}
8209 }
8210 
8211 
8212 /++
8213 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
8214 
8215 
8216 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
8217 
8218 	$(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.)
8219 
8220 	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.
8221 
8222 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
8223 
8224 	$(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.
8225 
8226 	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!
8227 
8228 	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!)
8229 
8230 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
8231 
8232 	---
8233 		auto image = new Image(256, 256);
8234 		scope(exit) destroy(image);
8235 	---
8236 
8237 	As long as you don't hold on to it outside the scope.
8238 
8239 	I might change it to be an owned pointer at some point in the future.
8240 
8241 	)
8242 
8243 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
8244 	you can also often get a fair amount of speedup by getting the raw data format and
8245 	writing some custom code.
8246 
8247 	FIXME INSERT EXAMPLES HERE
8248 
8249 
8250 +/
8251 final class Image {
8252 	///
8253 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
8254 		this.width = width;
8255 		this.height = height;
8256 		this.enableAlpha = enableAlpha;
8257 
8258 		impl.createImage(width, height, forcexshm, enableAlpha);
8259 	}
8260 
8261 	///
8262 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
8263 		this(size.width, size.height, forcexshm, enableAlpha);
8264 	}
8265 
8266 	private bool suppressDestruction;
8267 
8268 	version(X11)
8269 	this(XImage* handle) {
8270 		this.handle = handle;
8271 		this.rawData = cast(ubyte*) handle.data;
8272 		this.width = handle.width;
8273 		this.height = handle.height;
8274 		this.enableAlpha = handle.depth == 32;
8275 		suppressDestruction = true;
8276 	}
8277 
8278 	~this() {
8279 		if(suppressDestruction) return;
8280 		impl.dispose();
8281 	}
8282 
8283 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
8284 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
8285 	pure const @system nothrow {
8286 		/*
8287 			To use these to draw a blue rectangle with size WxH at position X,Y...
8288 
8289 			// make certain that it will fit before we proceed
8290 			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!
8291 
8292 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
8293 			// (though calculating them isn't really that expensive).
8294 			auto nextLineAdjustment = img.adjustmentForNextLine();
8295 			auto offR = img.redByteOffset();
8296 			auto offB = img.blueByteOffset();
8297 			auto offG = img.greenByteOffset();
8298 			auto bpp = img.bytesPerPixel();
8299 
8300 			auto data = img.getDataPointer();
8301 
8302 			// figure out the starting byte offset
8303 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
8304 
8305 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
8306 
8307 			// and now our drawing loop for the rectangle
8308 			foreach(y; 0 .. H) {
8309 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
8310 				foreach(x; 0 .. W) {
8311 					// write our color
8312 					data[offR] = 0;
8313 					data[offG] = 0;
8314 					data[offB] = 255;
8315 
8316 					data += bpp; // moving to the next pixel is just an addition...
8317 				}
8318 				startOfLine += nextLineAdjustment;
8319 			}
8320 
8321 
8322 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
8323 
8324 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
8325 			can be made into a bitmask or something so we can write them as *uint...
8326 		*/
8327 
8328 		///
8329 		int offsetForTopLeftPixel() {
8330 			version(X11) {
8331 				return 0;
8332 			} else version(Windows) {
8333 				if(enableAlpha) {
8334 					return (width * 4) * (height - 1);
8335 				} else {
8336 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
8337 				}
8338 			} else version(OSXCocoa) {
8339 				return 0 ; //throw new NotYetImplementedException();
8340 			} else static assert(0, "fill in this info for other OSes");
8341 		}
8342 
8343 		///
8344 		int offsetForPixel(int x, int y) {
8345 			version(X11) {
8346 				auto offset = (y * width + x) * 4;
8347 				return offset;
8348 			} else version(Windows) {
8349 				if(enableAlpha) {
8350 					auto itemsPerLine = width * 4;
8351 					// remember, bmps are upside down
8352 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8353 					return offset;
8354 				} else {
8355 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8356 					// remember, bmps are upside down
8357 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8358 					return offset;
8359 				}
8360 			} else version(OSXCocoa) {
8361 				return 0 ; //throw new NotYetImplementedException();
8362 			} else static assert(0, "fill in this info for other OSes");
8363 		}
8364 
8365 		///
8366 		int adjustmentForNextLine() {
8367 			version(X11) {
8368 				return width * 4;
8369 			} else version(Windows) {
8370 				// windows bmps are upside down, so the adjustment is actually negative
8371 				if(enableAlpha)
8372 					return - (cast(int) width * 4);
8373 				else
8374 					return -((cast(int) width * 3 + 3) / 4) * 4;
8375 			} else version(OSXCocoa) {
8376 				return 0 ; //throw new NotYetImplementedException();
8377 			} else static assert(0, "fill in this info for other OSes");
8378 		}
8379 
8380 		/// once you have the position of a pixel, use these to get to the proper color
8381 		int redByteOffset() {
8382 			version(X11) {
8383 				return 2;
8384 			} else version(Windows) {
8385 				return 2;
8386 			} else version(OSXCocoa) {
8387 				return 0 ; //throw new NotYetImplementedException();
8388 			} else static assert(0, "fill in this info for other OSes");
8389 		}
8390 
8391 		///
8392 		int greenByteOffset() {
8393 			version(X11) {
8394 				return 1;
8395 			} else version(Windows) {
8396 				return 1;
8397 			} else version(OSXCocoa) {
8398 				return 0 ; //throw new NotYetImplementedException();
8399 			} else static assert(0, "fill in this info for other OSes");
8400 		}
8401 
8402 		///
8403 		int blueByteOffset() {
8404 			version(X11) {
8405 				return 0;
8406 			} else version(Windows) {
8407 				return 0;
8408 			} else version(OSXCocoa) {
8409 				return 0 ; //throw new NotYetImplementedException();
8410 			} else static assert(0, "fill in this info for other OSes");
8411 		}
8412 
8413 		/// Only valid if [enableAlpha] is true
8414 		int alphaByteOffset() {
8415 			version(X11) {
8416 				return 3;
8417 			} else version(Windows) {
8418 				return 3;
8419 			} else version(OSXCocoa) {
8420 				return 3; //throw new NotYetImplementedException();
8421 			} else static assert(0, "fill in this info for other OSes");
8422 		}
8423 	}
8424 
8425 	///
8426 	final void putPixel(int x, int y, Color c) {
8427 		if(x < 0 || x >= width)
8428 			return;
8429 		if(y < 0 || y >= height)
8430 			return;
8431 
8432 		impl.setPixel(x, y, c);
8433 	}
8434 
8435 	///
8436 	final Color getPixel(int x, int y) {
8437 		if(x < 0 || x >= width)
8438 			return Color.transparent;
8439 		if(y < 0 || y >= height)
8440 			return Color.transparent;
8441 
8442 		version(OSXCocoa) throw new NotYetImplementedException(); else
8443 		return impl.getPixel(x, y);
8444 	}
8445 
8446 	///
8447 	final void opIndexAssign(Color c, int x, int y) {
8448 		putPixel(x, y, c);
8449 	}
8450 
8451 	///
8452 	TrueColorImage toTrueColorImage() {
8453 		auto tci = new TrueColorImage(width, height);
8454 		convertToRgbaBytes(tci.imageData.bytes);
8455 		return tci;
8456 	}
8457 
8458 	///
8459 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8460 		auto tci = i.getAsTrueColorImage();
8461 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8462 		static if(UsingSimpledisplayX11)
8463 			img.premultiply = premultiply;
8464 		img.setRgbaBytes(tci.imageData.bytes);
8465 		return img;
8466 	}
8467 
8468 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8469 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8470 	/// if you pass null, it will allocate a new one.
8471 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8472 		if(where is null)
8473 			where = new ubyte[this.width*this.height*4];
8474 		convertToRgbaBytes(where);
8475 		return where;
8476 	}
8477 
8478 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8479 	void setRgbaBytes(in ubyte[] from ) {
8480 		assert(from.length == this.width * this.height * 4);
8481 		setFromRgbaBytes(from);
8482 	}
8483 
8484 	// FIXME: make properly cross platform by getting rgba right
8485 
8486 	/// warning: this is not portable across platforms because the data format can change
8487 	ubyte* getDataPointer() {
8488 		return impl.rawData;
8489 	}
8490 
8491 	/// for use with getDataPointer
8492 	final int bytesPerLine() const pure @safe nothrow {
8493 		version(Windows)
8494 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8495 		else version(X11)
8496 			return 4 * width;
8497 		else version(OSXCocoa)
8498 			return 4 * width;
8499 		else static assert(0);
8500 	}
8501 
8502 	/// for use with getDataPointer
8503 	final int bytesPerPixel() const pure @safe nothrow {
8504 		version(Windows)
8505 			return enableAlpha ? 4 : 3;
8506 		else version(X11)
8507 			return 4;
8508 		else version(OSXCocoa)
8509 			return 4;
8510 		else static assert(0);
8511 	}
8512 
8513 	///
8514 	immutable int width;
8515 
8516 	///
8517 	immutable int height;
8518 
8519 	///
8520 	immutable bool enableAlpha;
8521     //private:
8522 	mixin NativeImageImplementation!() impl;
8523 }
8524 
8525 /++
8526 	A convenience function to pop up a window displaying the image.
8527 	If you pass a win, it will draw the image in it. Otherwise, it will
8528 	create a window with the size of the image and run its event loop, closing
8529 	when a key is pressed.
8530 
8531 	History:
8532 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8533 		always block until the application quit which could cause bizarre behavior
8534 		inside a more complex application. Now, the default is to block until
8535 		this window closes if it is the only event loop running, and otherwise,
8536 		not to block at all and just pop up the display window asynchronously.
8537 +/
8538 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8539 	if(win is null) {
8540 		win = new SimpleWindow(image);
8541 		{
8542 			auto p = win.draw;
8543 			p.drawImage(Point(0, 0), image);
8544 		}
8545 		win.eventLoopWithBlockingMode(
8546 			bm, 0,
8547 			(KeyEvent ev) {
8548 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8549 			} );
8550 	} else {
8551 		win.image = image;
8552 	}
8553 }
8554 
8555 enum FontWeight : int {
8556 	dontcare = 0,
8557 	thin = 100,
8558 	extralight = 200,
8559 	light = 300,
8560 	regular = 400,
8561 	medium = 500,
8562 	semibold = 600,
8563 	bold = 700,
8564 	extrabold = 800,
8565 	heavy = 900
8566 }
8567 
8568 /++
8569 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8570 
8571 	History:
8572 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8573 +/
8574 interface MeasurableFont {
8575 	/++
8576 		Returns true if it is a monospace font, meaning each of the
8577 		glyphs (at least the ascii characters) have matching width
8578 		and no kerning, so you can determine the display width of some
8579 		strings by simply multiplying the string width by [averageWidth].
8580 
8581 		(Please note that multiply doesn't $(I actually) work in general,
8582 		consider characters like tab and newline, but it does sometimes.)
8583 	+/
8584 	bool isMonospace();
8585 
8586 	/++
8587 		The average width of glyphs in the font, traditionally equal to the
8588 		width of the lowercase x. Can be used to estimate bounding boxes,
8589 		especially if the font [isMonospace].
8590 
8591 		Given in pixels.
8592 	+/
8593 	int averageWidth();
8594 	/++
8595 		The height of the bounding box of a line.
8596 	+/
8597 	int height();
8598 	/++
8599 		The maximum ascent of a glyph above the baseline.
8600 
8601 		Given in pixels.
8602 	+/
8603 	int ascent();
8604 	/++
8605 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8606 
8607 		Given in pixels.
8608 	+/
8609 	int descent();
8610 	/++
8611 		The display width of the given string, and if you provide a window, it will use it to
8612 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8613 
8614 		Given in pixels.
8615 	+/
8616 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8617 
8618 }
8619 
8620 // FIXME: i need a font cache and it needs to handle disconnects.
8621 
8622 /++
8623 	Represents a font loaded off the operating system or the X server.
8624 
8625 
8626 	While the api here is unified cross platform, the fonts are not necessarily
8627 	available, even across machines of the same platform, so be sure to always check
8628 	for null (using [isNull]) and have a fallback plan.
8629 
8630 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8631 
8632 	Worst case, a null font will automatically fall back to the default font loaded
8633 	for your system.
8634 +/
8635 class OperatingSystemFont : MeasurableFont {
8636 	// FIXME: when the X Connection is lost, these need to be invalidated!
8637 	// that means I need to store the original stuff again to reconstruct it too.
8638 
8639 	version(X11) {
8640 		XFontStruct* font;
8641 		XFontSet fontset;
8642 
8643 		version(with_xft) {
8644 			XftFont* xftFont;
8645 			bool isXft;
8646 		}
8647 	} else version(Windows) {
8648 		HFONT font;
8649 		int width_;
8650 		int height_;
8651 	} else version(OSXCocoa) {
8652 		NSFont font;
8653 	} else static assert(0);
8654 
8655 	/++
8656 		Constructs the class and immediately calls [load].
8657 	+/
8658 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8659 		load(name, size, weight, italic);
8660 	}
8661 
8662 	/++
8663 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8664 
8665 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8666 
8667 		History:
8668 			Added January 24, 2021.
8669 	+/
8670 	this() {
8671 		// this space intentionally left blank
8672 	}
8673 
8674 	/++
8675 		Constructs a copy of the given font object.
8676 
8677 		History:
8678 			Added January 7, 2023.
8679 	+/
8680 	this(OperatingSystemFont font) {
8681 		if(font is null || font.loadedInfo is LoadedInfo.init)
8682 			loadDefault();
8683 		else
8684 			load(font.loadedInfo.tupleof);
8685 	}
8686 
8687 	/++
8688 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8689 
8690 		History:
8691 			Added November 13, 2020.
8692 	+/
8693 	version(with_xft)
8694 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8695 		unload();
8696 
8697 		if(!XftLibrary.attempted) {
8698 			XftLibrary.loadDynamicLibrary();
8699 		}
8700 
8701 		if(!XftLibrary.loadSuccessful)
8702 			return false;
8703 
8704 		auto display = XDisplayConnection.get;
8705 
8706 		char[256] nameBuffer = void;
8707 		int nbp = 0;
8708 
8709 		void add(in char[] a) {
8710 			nameBuffer[nbp .. nbp + a.length] = a[];
8711 			nbp += a.length;
8712 		}
8713 		add(name);
8714 
8715 		if(size) {
8716 			add(":size=");
8717 			add(toInternal!string(size));
8718 		}
8719 		if(weight != FontWeight.dontcare && weight != 400) {
8720 			if(weight < 400)
8721 				add(":style=Light");
8722 			else
8723 				add(":style=Bold");
8724 			add(":weight=");
8725 			add(weightToString(weight));
8726 		}
8727 		if(italic) {
8728 			if(weight == FontWeight.dontcare)
8729 				add(":style=Italic");
8730 			add(":slant=100");
8731 		}
8732 
8733 		nameBuffer[nbp] = 0;
8734 
8735 		this.xftFont = XftFontOpenName(
8736 			display,
8737 			DefaultScreen(display),
8738 			nameBuffer.ptr
8739 		);
8740 
8741 		this.isXft = true;
8742 
8743 		if(xftFont !is null) {
8744 			isMonospace_ = stringWidth("x") == stringWidth("M");
8745 			ascent_ = xftFont.ascent;
8746 			descent_ = xftFont.descent;
8747 		}
8748 
8749 		return !isNull();
8750 	}
8751 
8752 	/++
8753 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8754 
8755 
8756 		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.
8757 
8758 		If `pattern` is null, it returns all available font families.
8759 
8760 		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.
8761 
8762 		The format of the pattern is platform-specific.
8763 
8764 		History:
8765 			Added May 1, 2021 (dub v9.5)
8766 	+/
8767 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8768 		version(Windows) {
8769 			auto hdc = GetDC(null);
8770 			scope(exit) ReleaseDC(null, hdc);
8771 			LOGFONT logfont;
8772 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8773 				auto localHandler = *(cast(typeof(handler)*) p);
8774 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8775 			}
8776 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8777 		} else version(X11) {
8778 			//import core.stdc.stdio;
8779 			bool done = false;
8780 			version(with_xft) {
8781 				if(!XftLibrary.attempted) {
8782 					XftLibrary.loadDynamicLibrary();
8783 				}
8784 
8785 				if(!XftLibrary.loadSuccessful)
8786 					goto skipXft;
8787 
8788 				if(!FontConfigLibrary.attempted)
8789 					FontConfigLibrary.loadDynamicLibrary();
8790 				if(!FontConfigLibrary.loadSuccessful)
8791 					goto skipXft;
8792 
8793 				{
8794 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8795 					if(got is null)
8796 						goto skipXft;
8797 					scope(exit) FcFontSetDestroy(got);
8798 
8799 					auto fontPatterns = got.fonts[0 .. got.nfont];
8800 					foreach(candidate; fontPatterns) {
8801 						char* where, whereStyle;
8802 
8803 						char* pmg = FcNameUnparse(candidate);
8804 
8805 						//FcPatternGetString(candidate, "family", 0, &where);
8806 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8807 						//if(where && whereStyle) {
8808 						if(pmg) {
8809 							if(!handler(pmg.sliceCString))
8810 								return;
8811 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8812 						}
8813 					}
8814 				}
8815 			}
8816 
8817 			skipXft:
8818 
8819 			if(pattern is null)
8820 				pattern = "*";
8821 
8822 			int count;
8823 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8824 			scope(exit) XFreeFontNames(coreFontsRaw);
8825 
8826 			auto coreFonts = coreFontsRaw[0 .. count];
8827 
8828 			foreach(font; coreFonts) {
8829 				char[128] tmp;
8830 				tmp[0 ..5] = "core:";
8831 				auto cf = font.sliceCString;
8832 				if(5 + cf.length > tmp.length)
8833 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8834 				tmp[5 .. 5 + cf.length] = cf;
8835 				if(!handler(tmp[0 .. 5 + cf.length]))
8836 					return;
8837 			}
8838 		}
8839 	}
8840 
8841 	/++
8842 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8843 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8844 
8845 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8846 		underlying system doesn't support returning the raw bytes.
8847 
8848 		History:
8849 			Added September 10, 2021 (dub v10.3)
8850 	+/
8851 	ubyte[] getTtfBytes() {
8852 		if(isNull)
8853 			return null;
8854 
8855 		version(Windows) {
8856 			auto dc = GetDC(null);
8857 			auto orig = SelectObject(dc, font);
8858 
8859 			scope(exit) {
8860 				SelectObject(dc, orig);
8861 				ReleaseDC(null, dc);
8862 			}
8863 
8864 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8865 			if(res == GDI_ERROR)
8866 				return null;
8867 
8868 			ubyte[] buffer = new ubyte[](res);
8869 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8870 			if(res == GDI_ERROR)
8871 				return null; // wtf really tbh
8872 
8873 			return buffer;
8874 		} else version(with_xft) {
8875 			if(isXft && xftFont) {
8876 				if(!FontConfigLibrary.attempted)
8877 					FontConfigLibrary.loadDynamicLibrary();
8878 				if(!FontConfigLibrary.loadSuccessful)
8879 					return null;
8880 
8881 				char* file;
8882 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8883 					if (file !is null && file[0]) {
8884 						import core.stdc.stdio;
8885 						auto fp = fopen(file, "rb");
8886 						if(fp is null)
8887 							return null;
8888 						scope(exit)
8889 							fclose(fp);
8890 						fseek(fp, 0, SEEK_END);
8891 						ubyte[] buffer = new ubyte[](ftell(fp));
8892 						fseek(fp, 0, SEEK_SET);
8893 
8894 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8895 						if(got != buffer.length)
8896 							return null;
8897 
8898 						return buffer;
8899 					}
8900 				}
8901 			}
8902 			return null;
8903 		} else throw new NotYetImplementedException();
8904 	}
8905 
8906 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8907 
8908 	private string weightToString(FontWeight weight) {
8909 		with(FontWeight)
8910 		final switch(weight) {
8911 			case dontcare: return "*";
8912 			case thin: return "extralight";
8913 			case extralight: return "extralight";
8914 			case light: return "light";
8915 			case regular: return "regular";
8916 			case medium: return "medium";
8917 			case semibold: return "demibold";
8918 			case bold: return "bold";
8919 			case extrabold: return "demibold";
8920 			case heavy: return "black";
8921 		}
8922 	}
8923 
8924 	/++
8925 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8926 
8927 		History:
8928 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8929 	+/
8930 	version(X11)
8931 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8932 		unload();
8933 
8934 		string xfontstr;
8935 
8936 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8937 			// this is kinda a disgusting hack but if the user sends an exact
8938 			// string I'd like to honor it...
8939 			xfontstr = name;
8940 		} else {
8941 			string weightstr = weightToString(weight);
8942 			string sizestr;
8943 			if(size == 0)
8944 				sizestr = "*";
8945 			else
8946 				sizestr = toInternal!string(size);
8947 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8948 		}
8949 
8950 		// writeln(xfontstr);
8951 
8952 		auto display = XDisplayConnection.get;
8953 
8954 		font = XLoadQueryFont(display, xfontstr.ptr);
8955 		if(font is null)
8956 			return false;
8957 
8958 		char** lol;
8959 		int lol2;
8960 		char* lol3;
8961 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8962 
8963 		prepareFontInfo();
8964 
8965 		return !isNull();
8966 	}
8967 
8968 	version(X11)
8969 	private void prepareFontInfo() {
8970 		if(font !is null) {
8971 			isMonospace_ = stringWidth("l") == stringWidth("M");
8972 			ascent_ = font.max_bounds.ascent;
8973 			descent_ = font.max_bounds.descent;
8974 		}
8975 	}
8976 
8977 	version(OSXCocoa)
8978 	private void prepareFontInfo() {
8979 		if(font !is null) {
8980 			isMonospace_ = font.isFixedPitch;
8981 			ascent_ = cast(int) font.ascender;
8982 			descent_ = cast(int) - font.descender;
8983 		}
8984 	}
8985 
8986 
8987 	/++
8988 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8989 
8990 		History:
8991 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8992 	+/
8993 	version(Windows)
8994 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8995 		unload();
8996 
8997 		WCharzBuffer buffer = WCharzBuffer(name);
8998 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8999 
9000 		prepareFontInfo(hdc);
9001 
9002 		return !isNull();
9003 	}
9004 
9005 	version(Windows)
9006 	void prepareFontInfo(HDC hdc = null) {
9007 		if(font is null)
9008 			return;
9009 
9010 		TEXTMETRIC tm;
9011 		auto dc = hdc ? hdc : GetDC(null);
9012 		auto orig = SelectObject(dc, font);
9013 		GetTextMetrics(dc, &tm);
9014 		SelectObject(dc, orig);
9015 		if(hdc is null)
9016 			ReleaseDC(null, dc);
9017 
9018 		width_ = tm.tmAveCharWidth;
9019 		height_ = tm.tmHeight;
9020 		ascent_ = tm.tmAscent;
9021 		descent_ = tm.tmDescent;
9022 		// 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.
9023 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
9024 	}
9025 
9026 
9027 	/++
9028 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
9029 
9030 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
9031 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
9032 
9033 		On Windows, it forwards directly to [loadWin32].
9034 
9035 		Params:
9036 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
9037 			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.
9038 			weight = approximate boldness, results may vary.
9039 			italic = try to get a slanted version of the given font.
9040 
9041 		History:
9042 			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.
9043 	+/
9044 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9045 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
9046 		version(X11) {
9047 			version(with_xft) {
9048 				if(name.length > 5 && name[0 .. 5] == "core:") {
9049 					goto core;
9050 				}
9051 
9052 				if(loadXft(name, size, weight, italic))
9053 					return true;
9054 				// if xft fails, fallback to core to avoid breaking
9055 				// code that already depended on this.
9056 			}
9057 
9058 			core:
9059 
9060 			if(name.length > 5 && name[0 .. 5] == "core:") {
9061 				name = name[5 .. $];
9062 			}
9063 
9064 			return loadCoreX(name, size, weight, italic);
9065 		} else version(Windows) {
9066 			return loadWin32(name, size, weight, italic);
9067 		} else version(OSXCocoa) {
9068 			return loadCocoa(name, size, weight, italic);
9069 		} else static assert(0);
9070 	}
9071 
9072 	version(OSXCocoa)
9073 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
9074 		unload();
9075 
9076 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
9077 		prepareFontInfo();
9078 
9079 		return !isNull();
9080 	}
9081 
9082 	private struct LoadedInfo {
9083 		string name;
9084 		int size;
9085 		FontWeight weight;
9086 		bool italic;
9087 	}
9088 	private LoadedInfo loadedInfo;
9089 
9090 	///
9091 	void unload() {
9092 		if(isNull())
9093 			return;
9094 
9095 		version(X11) {
9096 			auto display = XDisplayConnection.display;
9097 
9098 			if(display is null)
9099 				return;
9100 
9101 			version(with_xft) {
9102 				if(isXft) {
9103 					if(xftFont)
9104 						XftFontClose(display, xftFont);
9105 					isXft = false;
9106 					xftFont = null;
9107 					return;
9108 				}
9109 			}
9110 
9111 			if(font && font !is ScreenPainterImplementation.defaultfont)
9112 				XFreeFont(display, font);
9113 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
9114 				XFreeFontSet(display, fontset);
9115 
9116 			font = null;
9117 			fontset = null;
9118 		} else version(Windows) {
9119 			DeleteObject(font);
9120 			font = null;
9121 		} else version(OSXCocoa) {
9122 			font.release();
9123 			font = null;
9124 		} else static assert(0);
9125 	}
9126 
9127 	private bool isMonospace_;
9128 
9129 	/++
9130 		History:
9131 			Added January 16, 2021
9132 	+/
9133 	bool isMonospace() {
9134 		return isMonospace_;
9135 	}
9136 
9137 	/++
9138 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
9139 
9140 		History:
9141 			Added March 26, 2020
9142 			Documented January 16, 2021
9143 	+/
9144 	int averageWidth() {
9145 		version(X11) {
9146 			return stringWidth("x");
9147 		} version(OSXCocoa) {
9148 			return stringWidth("x");
9149 		} else version(Windows)
9150 			return width_;
9151 		else assert(0);
9152 	}
9153 
9154 	/++
9155 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
9156 
9157 		History:
9158 			Added January 16, 2021
9159 	+/
9160 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
9161 	// FIXME: what about tab?
9162 		if(isNull)
9163 			return 0;
9164 
9165 		version(X11) {
9166 			version(with_xft)
9167 				if(isXft && xftFont !is null) {
9168 					//return xftFont.max_advance_width;
9169 					XGlyphInfo extents;
9170 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
9171 					// writeln(extents);
9172 					return extents.xOff;
9173 				}
9174 			if(font is null)
9175 				return 0;
9176 			else if(fontset) {
9177 				XRectangle rect;
9178 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
9179 
9180 				return rect.width;
9181 			} else {
9182 				return XTextWidth(font, s.ptr, cast(int) s.length);
9183 			}
9184 		} else version(Windows) {
9185 			WCharzBuffer buffer = WCharzBuffer(s);
9186 
9187 			return stringWidth(buffer.slice, window);
9188 		} else version(OSXCocoa) {
9189 			/+
9190 			int charCount = [string length];
9191 			CGGlyph glyphs[charCount];
9192 			CGRect rects[charCount];
9193 
9194 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
9195 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
9196 
9197 			int totalwidth = 0, maxheight = 0;
9198 			for (int i=0; i < charCount; i++)
9199 			{
9200 				totalwidth += rects[i].size.width;
9201 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
9202 			}
9203 
9204 			dim = CGSizeMake(totalwidth, maxheight);
9205 			+/
9206 
9207 			return 16; // FIXME
9208 		}
9209 		else assert(0);
9210 	}
9211 
9212 	version(Windows)
9213 	/// ditto
9214 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
9215 		if(isNull)
9216 			return 0;
9217 		version(Windows) {
9218 			SIZE size;
9219 
9220 			prepareContext(window);
9221 			scope(exit) releaseContext();
9222 
9223 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
9224 
9225 			return size.cx;
9226 		} else {
9227 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
9228 			static assert(0, "not implemented yet");
9229 			//return stringWidth(s, window);
9230 		}
9231 	}
9232 
9233 	private {
9234 		int prepRefcount;
9235 
9236 		version(Windows) {
9237 			HDC dc;
9238 			HANDLE orig;
9239 			HWND hwnd;
9240 		}
9241 	}
9242 	/++
9243 		[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.
9244 
9245 		History:
9246 			Added January 23, 2021
9247 	+/
9248 	void prepareContext(SimpleWindow window = null) {
9249 		prepRefcount++;
9250 		if(prepRefcount == 1) {
9251 			version(Windows) {
9252 				hwnd = window is null ? null : window.impl.hwnd;
9253 				dc = GetDC(hwnd);
9254 				orig = SelectObject(dc, font);
9255 			}
9256 		}
9257 	}
9258 	/// ditto
9259 	void releaseContext() {
9260 		prepRefcount--;
9261 		if(prepRefcount == 0) {
9262 			version(Windows) {
9263 				SelectObject(dc, orig);
9264 				ReleaseDC(hwnd, dc);
9265 				hwnd = null;
9266 				dc = null;
9267 				orig = null;
9268 			}
9269 		}
9270 	}
9271 
9272 	/+
9273 		FIXME: I think I need advance and kerning pair
9274 
9275 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
9276 	+/
9277 
9278 	/++
9279 		Returns the height of the font.
9280 
9281 		History:
9282 			Added March 26, 2020
9283 			Documented January 16, 2021
9284 	+/
9285 	int height() {
9286 		version(X11) {
9287 			version(with_xft)
9288 				if(isXft && xftFont !is null) {
9289 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
9290 				}
9291 			if(font is null)
9292 				return 0;
9293 			return font.max_bounds.ascent + font.max_bounds.descent;
9294 		} else version(Windows) {
9295 			return height_;
9296 		} else version(OSXCocoa) {
9297 			if(font is null)
9298 				return 0;
9299 			return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight
9300 		}
9301 		else assert(0);
9302 	}
9303 
9304 	private int ascent_;
9305 	private int descent_;
9306 
9307 	/++
9308 		Max ascent above the baseline.
9309 
9310 		History:
9311 			Added January 22, 2021
9312 	+/
9313 	int ascent() {
9314 		return ascent_;
9315 	}
9316 
9317 	/++
9318 		Max descent below the baseline.
9319 
9320 		History:
9321 			Added January 22, 2021
9322 	+/
9323 	int descent() {
9324 		return descent_;
9325 	}
9326 
9327 	/++
9328 		Loads the default font used by [ScreenPainter] if none others are loaded.
9329 
9330 		Returns:
9331 			This method mutates the `this` object, but then returns `this` for
9332 			easy chaining like:
9333 
9334 			---
9335 			auto font = foo.isNull ? foo : foo.loadDefault
9336 			---
9337 
9338 		History:
9339 			Added previously, but left unimplemented until January 24, 2021.
9340 	+/
9341 	OperatingSystemFont loadDefault() {
9342 		unload();
9343 
9344 		loadedInfo = LoadedInfo.init;
9345 
9346 		version(X11) {
9347 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9348 			// but meh since sdpy does its own thing, this should be ok too
9349 
9350 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9351 			this.font = ScreenPainterImplementation.defaultfont;
9352 			this.fontset = ScreenPainterImplementation.defaultfontset;
9353 
9354 			prepareFontInfo();
9355 			return this;
9356 		} else version(Windows) {
9357 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9358 			this.font = ScreenPainterImplementation.defaultGuiFont;
9359 
9360 			prepareFontInfo();
9361 			return this;
9362 		} else version(OSXCocoa) {
9363 			this.font = NSFont.systemFontOfSize(15);
9364 
9365 			prepareFontInfo();
9366 
9367 			// import std.stdio; writeln("Load default: ", this.height());
9368 			return this;
9369 		} else throw new NotYetImplementedException();
9370 	}
9371 
9372 	///
9373 	bool isNull() {
9374 		version(with_xft)
9375 			if(isXft)
9376 				return xftFont is null;
9377 		return font is null;
9378 	}
9379 
9380 	/* Metrics */
9381 	/+
9382 		GetABCWidth
9383 		GetKerningPairs
9384 
9385 		if I do it right, I can size it all here, and match
9386 		what happens when I draw the full string with the OS functions.
9387 
9388 		subclasses might do the same thing while getting the glyphs on images
9389 	struct GlyphInfo {
9390 		int glyph;
9391 
9392 		size_t stringIdxStart;
9393 		size_t stringIdxEnd;
9394 
9395 		Rectangle boundingBox;
9396 	}
9397 	GlyphInfo[] getCharBoxes() {
9398 		// XftTextExtentsUtf8
9399 		return null;
9400 
9401 	}
9402 	+/
9403 
9404 	~this() {
9405 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9406 		unload();
9407 	}
9408 }
9409 
9410 version(Windows)
9411 private string sliceCString(const(wchar)[] w) {
9412 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9413 }
9414 
9415 private inout(char)[] sliceCString(inout(char)* s) {
9416 	import core.stdc.string;
9417 	auto len = strlen(s);
9418 	return s[0 .. len];
9419 }
9420 
9421 version(OSXCocoa)
9422 	alias PaintingHandle = NSObject;
9423 else
9424 	alias PaintingHandle = NativeWindowHandle;
9425 
9426 /**
9427 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9428 	than constructing it directly. Then, it is reference counted so you can pass it
9429 	at around and when the last ref goes out of scope, the buffered drawing activities
9430 	are all carried out.
9431 
9432 
9433 	Most functions use the outlineColor instead of taking a color themselves.
9434 	ScreenPainter is reference counted and draws its buffer to the screen when its
9435 	final reference goes out of scope.
9436 */
9437 struct ScreenPainter {
9438 	CapableOfBeingDrawnUpon window;
9439 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9440 		this.window = window;
9441 		if(window.closed)
9442 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9443 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9444 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9445 		if(window.activeScreenPainter !is null) {
9446 			impl = window.activeScreenPainter;
9447 			if(impl.referenceCount == 0) {
9448 				impl.window = window;
9449 				impl.create(handle);
9450 			}
9451 			impl.manualInvalidations = manualInvalidations;
9452 			impl.referenceCount++;
9453 		//	writeln("refcount ++ ", impl.referenceCount);
9454 		} else {
9455 			impl = new ScreenPainterImplementation;
9456 			impl.window = window;
9457 			impl.create(handle);
9458 			impl.referenceCount = 1;
9459 			impl.manualInvalidations = manualInvalidations;
9460 			window.activeScreenPainter = impl;
9461 			// writeln("constructed");
9462 		}
9463 
9464 		copyActiveOriginals();
9465 	}
9466 
9467 	/++
9468 		EXPERIMENTAL. subject to change.
9469 
9470 		When you draw a cursor, you can draw this to notify your window of where it is,
9471 		for IME systems to use.
9472 	+/
9473 	void notifyCursorPosition(int x, int y, int width, int height) {
9474 		if(auto w = cast(SimpleWindow) window) {
9475 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9476 		}
9477 	}
9478 
9479 	/++
9480 		If you are using manual invalidations, this informs the
9481 		window system that a section needs to be redrawn.
9482 
9483 		If you didn't opt into manual invalidation, you don't
9484 		have to call this.
9485 
9486 		History:
9487 			Added December 30, 2021 (dub v10.5)
9488 	+/
9489 	void invalidateRect(Rectangle rect) {
9490 		if(impl is null) return;
9491 
9492 		// transform(rect)
9493 		rect.left += _originX;
9494 		rect.right += _originX;
9495 		rect.top += _originY;
9496 		rect.bottom += _originY;
9497 
9498 		impl.invalidateRect(rect);
9499 	}
9500 
9501 	private Pen originalPen;
9502 	private Color originalFillColor;
9503 	private arsd.color.Rectangle originalClipRectangle;
9504 	private OperatingSystemFont originalFont;
9505 	void copyActiveOriginals() {
9506 		if(impl is null) return;
9507 		originalPen = impl._activePen;
9508 		originalFillColor = impl._fillColor;
9509 		originalClipRectangle = impl._clipRectangle;
9510 		version(OSXCocoa) {} else
9511 		originalFont = impl._activeFont;
9512 	}
9513 
9514 	~this() {
9515 		if(impl is null) return;
9516 		impl.referenceCount--;
9517 		//writeln("refcount -- ", impl.referenceCount);
9518 		if(impl.referenceCount == 0) {
9519 			// writeln("destructed");
9520 			impl.dispose();
9521 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9522 			// writeln("paint finished");
9523 		} else {
9524 			// there is still an active reference, reset stuff so the
9525 			// next user doesn't get weirdness via the reference
9526 			this.rasterOp = RasterOp.normal;
9527 			pen = originalPen;
9528 			fillColor = originalFillColor;
9529 			if(originalFont)
9530 				setFont(originalFont);
9531 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9532 		}
9533 	}
9534 
9535 	this(this) {
9536 		if(impl is null) return;
9537 		impl.referenceCount++;
9538 		//writeln("refcount ++ ", impl.referenceCount);
9539 
9540 		copyActiveOriginals();
9541 	}
9542 
9543 	private int _originX;
9544 	private int _originY;
9545 	@property int originX() { return _originX; }
9546 	@property int originY() { return _originY; }
9547 	@property int originX(int a) {
9548 		_originX = a;
9549 		return _originX;
9550 	}
9551 	@property int originY(int a) {
9552 		_originY = a;
9553 		return _originY;
9554 	}
9555 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9556 	private void transform(ref Point p) {
9557 		if(impl is null) return;
9558 		p.x += _originX;
9559 		p.y += _originY;
9560 	}
9561 
9562 	// this needs to be checked BEFORE the originX/Y transformation
9563 	private bool isClipped(Point p) {
9564 		return !currentClipRectangle.contains(p);
9565 	}
9566 	private bool isClipped(Point p, int width, int height) {
9567 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9568 	}
9569 	private bool isClipped(Point p, Size s) {
9570 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9571 	}
9572 	private bool isClipped(Point p, Point p2) {
9573 		// need to ensure the end points are actually included inside, so the +1 does that
9574 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9575 	}
9576 
9577 
9578 	/++
9579 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9580 
9581 		Returns:
9582 			The old clip rectangle.
9583 
9584 		History:
9585 			Return value was `void` prior to May 10, 2021.
9586 
9587 	+/
9588 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9589 		if(impl is null) return currentClipRectangle;
9590 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9591 			return currentClipRectangle; // no need to do anything
9592 		auto old = currentClipRectangle;
9593 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9594 		transform(pt);
9595 
9596 		impl.setClipRectangle(pt.x, pt.y, width, height);
9597 
9598 		return old;
9599 	}
9600 
9601 	/// ditto
9602 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9603 		if(impl is null) return currentClipRectangle;
9604 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9605 	}
9606 
9607 	///
9608 	void setFont(OperatingSystemFont font) {
9609 		if(impl is null) return;
9610 		impl.setFont(font);
9611 	}
9612 
9613 	///
9614 	int fontHeight() {
9615 		if(impl is null) return 0;
9616 		return impl.fontHeight();
9617 	}
9618 
9619 	private Pen activePen;
9620 
9621 	///
9622 	@property void pen(Pen p) {
9623 		if(impl is null) return;
9624 		activePen = p;
9625 		impl.pen(p);
9626 	}
9627 
9628 	///
9629 	@scriptable
9630 	@property void outlineColor(Color c) {
9631 		if(impl is null) return;
9632 		if(activePen.color == c)
9633 			return;
9634 		activePen.color = c;
9635 		impl.pen(activePen);
9636 	}
9637 
9638 	///
9639 	@scriptable
9640 	@property void fillColor(Color c) {
9641 		if(impl is null) return;
9642 		impl.fillColor(c);
9643 	}
9644 
9645 	///
9646 	@property void rasterOp(RasterOp op) {
9647 		if(impl is null) return;
9648 		impl.rasterOp(op);
9649 	}
9650 
9651 
9652 	void updateDisplay() {
9653 		// FIXME this should do what the dtor does
9654 	}
9655 
9656 	/// 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)
9657 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9658 		if(impl is null) return;
9659 		if(isClipped(upperLeft, width, height)) return;
9660 		transform(upperLeft);
9661 		version(Windows) {
9662 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9663 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9664 			RECT clip = scroll;
9665 			RECT uncovered;
9666 			HRGN hrgn;
9667 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9668 				throw new WindowsApiException("ScrollDC", GetLastError());
9669 
9670 		} else version(X11) {
9671 			// FIXME: clip stuff outside this rectangle
9672 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9673 		} else version(OSXCocoa) {
9674 			throw new NotYetImplementedException();
9675 		} else static assert(0);
9676 	}
9677 
9678 	///
9679 	void clear(Color color = Color.white()) {
9680 		if(impl is null) return;
9681 		fillColor = color;
9682 		outlineColor = color;
9683 		drawRectangle(Point(0, 0), window.width, window.height);
9684 	}
9685 
9686 	/++
9687 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9688 
9689 		Params:
9690 			upperLeft = point on the window where the upper left corner of the image will be drawn
9691 			imageUpperLeft = point on the image to start the slice to draw
9692 			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.
9693 		History:
9694 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9695 	+/
9696 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9697 		if(impl is null) return;
9698 		if(isClipped(upperLeft, s.width, s.height)) return;
9699 		transform(upperLeft);
9700 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9701 	}
9702 
9703 	///
9704 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9705 		if(impl is null) return;
9706 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9707 		transform(upperLeft);
9708 		if(w == 0 || w > i.width)
9709 			w = i.width;
9710 		if(h == 0 || h > i.height)
9711 			h = i.height;
9712 		if(upperLeftOfImage.x < 0)
9713 			upperLeftOfImage.x = 0;
9714 		if(upperLeftOfImage.y < 0)
9715 			upperLeftOfImage.y = 0;
9716 
9717 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9718 	}
9719 
9720 	///
9721 	Size textSize(in char[] text) {
9722 		if(impl is null) return Size(0, 0);
9723 		return impl.textSize(text);
9724 	}
9725 
9726 	/++
9727 		Draws a string in the window with the set font (see [setFont] to change it).
9728 
9729 		Params:
9730 			upperLeft = the upper left point of the bounding box of the text
9731 			text = the string to draw
9732 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9733 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9734 	+/
9735 	@scriptable
9736 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9737 		if(impl is null) return;
9738 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9739 			if(isClipped(upperLeft, lowerRight)) return;
9740 			transform(lowerRight);
9741 		} else {
9742 			if(isClipped(upperLeft, textSize(text))) return;
9743 		}
9744 		transform(upperLeft);
9745 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9746 	}
9747 
9748 	/++
9749 		Draws text using a custom font.
9750 
9751 		This is still MAJOR work in progress.
9752 
9753 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9754 	+/
9755 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9756 		if(impl is null) return;
9757 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9758 		transform(upperLeft);
9759 		font.drawString(this, upperLeft, text);
9760 	}
9761 
9762 	version(Windows)
9763 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9764 		if(impl is null) return;
9765 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9766 		transform(upperLeft);
9767 
9768 		if(text.length && text[$-1] == '\n')
9769 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9770 
9771 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9772 	}
9773 
9774 	static struct TextDrawingContext {
9775 		Point boundingBoxUpperLeft;
9776 		Point boundingBoxLowerRight;
9777 
9778 		Point currentLocation;
9779 
9780 		Point lastDrewUpperLeft;
9781 		Point lastDrewLowerRight;
9782 
9783 		// how do i do right aligned rich text?
9784 		// i kinda want to do a pre-made drawing then right align
9785 		// draw the whole block.
9786 		//
9787 		// That's exactly the diff: inline vs block stuff.
9788 
9789 		// I need to get coordinates of an inline section out too,
9790 		// not just a bounding box, but a series of bounding boxes
9791 		// should be ok. Consider what's needed to detect a click
9792 		// on a link in the middle of a paragraph breaking a line.
9793 		//
9794 		// Generally, we should be able to get the rectangles of
9795 		// any portion we draw.
9796 		//
9797 		// It also needs to tell what text is left if it overflows
9798 		// out of the box, so we can do stuff like float images around
9799 		// it. It should not attempt to draw a letter that would be
9800 		// clipped.
9801 		//
9802 		// I might also turn off word wrap stuff.
9803 	}
9804 
9805 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9806 		if(impl is null) return;
9807 		// FIXME
9808 	}
9809 
9810 	/// Drawing an individual pixel is slow. Avoid it if possible.
9811 	void drawPixel(Point where) {
9812 		if(impl is null) return;
9813 		if(isClipped(where)) return;
9814 		transform(where);
9815 		impl.drawPixel(where.x, where.y);
9816 	}
9817 
9818 
9819 	/// Draws a pen using the current pen / outlineColor
9820 	@scriptable
9821 	void drawLine(Point starting, Point ending) {
9822 		if(impl is null) return;
9823 		if(isClipped(starting, ending)) return;
9824 		transform(starting);
9825 		transform(ending);
9826 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9827 	}
9828 
9829 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9830 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9831 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9832 	@scriptable
9833 	void drawRectangle(Point upperLeft, int width, int height) {
9834 		if(impl is null) return;
9835 		if(isClipped(upperLeft, width, height)) return;
9836 		transform(upperLeft);
9837 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9838 	}
9839 
9840 	/// ditto
9841 	void drawRectangle(Point upperLeft, Size size) {
9842 		if(impl is null) return;
9843 		if(isClipped(upperLeft, size.width, size.height)) return;
9844 		transform(upperLeft);
9845 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9846 	}
9847 
9848 	/// ditto
9849 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9850 		if(impl is null) return;
9851 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9852 		transform(upperLeft);
9853 		transform(lowerRightInclusive);
9854 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9855 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9856 	}
9857 
9858 	// overload added on May 12, 2021
9859 	/// ditto
9860 	void drawRectangle(Rectangle rect) {
9861 		drawRectangle(rect.upperLeft, rect.size);
9862 	}
9863 
9864 	/// Arguments are the points of the bounding rectangle
9865 	void drawEllipse(Point upperLeft, Point lowerRight) {
9866 		if(impl is null) return;
9867 		if(isClipped(upperLeft, lowerRight)) return;
9868 		transform(upperLeft);
9869 		transform(lowerRight);
9870 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9871 	}
9872 
9873 	/++
9874 		Draws an arc inside the bounding box given by `upperLeft`, `width`, and `height`, from the angle (`start` / 64) degrees for (`length` / 64) degrees of rotation.
9875 
9876 
9877 		If `length` is positive, it travels counter-clockwise from `start`. If negative, it goes clockwise.  `start` == 0 at the three o'clock position of the bounding box - the center of the line at the right-hand side of the screen.
9878 
9879 		The arc is outlined with the current pen and filled with the current fill. On Windows, the line segments back to the middle are also drawn unless you have a full length ellipse.
9880 
9881 		Bugs:
9882 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9883 
9884 			The arc outline on Linux sometimes goes over the target.
9885 
9886 			The fill on Windows sometimes stops short.
9887 
9888 		History:
9889 			This function was broken af, totally inconsistent on platforms until September 24, 2021.
9890 
9891 			The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024.
9892 	+/
9893 	void drawArc(Point upperLeft, int width, int height, int start, int length) {
9894 		if(impl is null) return;
9895 		// FIXME: not actually implemented
9896 		if(isClipped(upperLeft, width, height)) return;
9897 		transform(upperLeft);
9898 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length);
9899 	}
9900 
9901 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9902 	void drawCircle(Point upperLeft, int diameter) {
9903 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9904 	}
9905 
9906 	/++
9907 		Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush.
9908 
9909 
9910 		Bugs:
9911 			Not implemented on Mac; it will instead draw a non-rounded rectangle for now.
9912 
9913 		History:
9914 			Added August 3, 2024
9915 	+/
9916 	void drawRectangleRounded(Rectangle rect, int borderRadius) {
9917 		drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius);
9918 	}
9919 
9920 	/// ditto
9921 	void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) {
9922 		drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius);
9923 	}
9924 
9925 	/// ditto
9926 	void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
9927 		if(borderRadius <= 0) {
9928 			drawRectangle(upperLeft, lowerRight);
9929 			return;
9930 		}
9931 
9932 		transform(upperLeft);
9933 		transform(lowerRight);
9934 
9935 		impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius);
9936 	}
9937 
9938 	/// .
9939 	void drawPolygon(Point[] vertexes) {
9940 		if(impl is null) return;
9941 		assert(vertexes.length);
9942 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9943 		foreach(ref vertex; vertexes) {
9944 			if(vertex.x < minX)
9945 				minX = vertex.x;
9946 			if(vertex.y < minY)
9947 				minY = vertex.y;
9948 			if(vertex.x > maxX)
9949 				maxX = vertex.x;
9950 			if(vertex.y > maxY)
9951 				maxY = vertex.y;
9952 			transform(vertex);
9953 		}
9954 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9955 		impl.drawPolygon(vertexes);
9956 	}
9957 
9958 	/// ditto
9959 	void drawPolygon(Point[] vertexes...) {
9960 		if(impl is null) return;
9961 		drawPolygon(vertexes);
9962 	}
9963 
9964 
9965 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9966 
9967 	//mixin NativeScreenPainterImplementation!() impl;
9968 
9969 
9970 	// HACK: if I mixin the impl directly, it won't let me override the copy
9971 	// constructor! The linker complains about there being multiple definitions.
9972 	// I'll make the best of it and reference count it though.
9973 	ScreenPainterImplementation* impl;
9974 }
9975 
9976 	// HACK: I need a pointer to the implementation so it's separate
9977 	struct ScreenPainterImplementation {
9978 		CapableOfBeingDrawnUpon window;
9979 		int referenceCount;
9980 		mixin NativeScreenPainterImplementation!();
9981 	}
9982 
9983 // FIXME: i haven't actually tested the sprite class on MS Windows
9984 
9985 /**
9986 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9987 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9988 
9989 
9990 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9991 	though I'm not sure that's ideal and the implementation might change.
9992 
9993 	You create one by giving a window and an image. It optimizes for that window,
9994 	and copies the image into it to use as the initial picture. Creating a sprite
9995 	can be quite slow (especially over a network connection) so you should do it
9996 	as little as possible and just hold on to your sprite handles after making them.
9997 	simpledisplay does try to do its best though, using the XSHM extension if available,
9998 	but you should still write your code as if it will always be slow.
9999 
10000 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
10001 	a fast operation - much faster than drawing the Image itself every time.
10002 
10003 	`Sprite` represents a scarce resource which should be freed when you
10004 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
10005 	after it has been disposed. If you are unsure about this, don't take chances,
10006 	just let the garbage collector do it for you. But ideally, you can manage its
10007 	lifetime more efficiently.
10008 
10009 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
10010 	support alpha blending in its drawing at this time. That might change in the
10011 	future, but if you need alpha blending right now, use OpenGL instead. See
10012 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
10013 
10014 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
10015 	in by setting the enableAlpha = true in the constructor.
10016 */
10017 class Sprite : CapableOfBeingDrawnUpon {
10018 
10019 	///
10020 	ScreenPainter draw() {
10021 		return ScreenPainter(this, handle, false);
10022 	}
10023 
10024 	/++
10025 		Copies the sprite's current state into a [TrueColorImage].
10026 
10027 		Be warned: this can be a very slow operation
10028 
10029 		History:
10030 			Actually implemented on March 14, 2021
10031 	+/
10032 	TrueColorImage takeScreenshot() {
10033 		return trueColorImageFromNativeHandle(handle, width, height);
10034 	}
10035 
10036 	void delegate() paintingFinishedDg() { return null; }
10037 	bool closed() { return false; }
10038 	ScreenPainterImplementation* activeScreenPainter_;
10039 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
10040 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
10041 
10042 	version(Windows)
10043 		private ubyte* rawData;
10044 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
10045 	// ditto on the XPicture stuff
10046 
10047 	version(X11) {
10048 		private static XRenderPictFormat* RGB24;
10049 		private static XRenderPictFormat* ARGB32;
10050 
10051 		private Picture xrenderPicture;
10052 	}
10053 
10054 	version(X11)
10055 	private static void requireXRender() {
10056 		if(!XRenderLibrary.loadAttempted) {
10057 			XRenderLibrary.loadDynamicLibrary();
10058 		}
10059 
10060 		if(!XRenderLibrary.loadSuccessful)
10061 			throw new Exception("XRender library load failure");
10062 
10063 		auto display = XDisplayConnection.get;
10064 
10065 		// FIXME: if we migrate X displays, these need to be changed
10066 		if(RGB24 is null)
10067 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
10068 		if(ARGB32 is null)
10069 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
10070 	}
10071 
10072 	protected this() {}
10073 
10074 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
10075 		this._width = width;
10076 		this._height = height;
10077 		this.enableAlpha = enableAlpha;
10078 
10079 		version(X11) {
10080 			auto display = XDisplayConnection.get();
10081 
10082 			if(enableAlpha) {
10083 				requireXRender();
10084 			}
10085 
10086 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
10087 
10088 			if(enableAlpha) {
10089 				XRenderPictureAttributes attrs;
10090 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
10091 			}
10092 		} else version(Windows) {
10093 			version(CRuntime_DigitalMars) {
10094 				//if(enableAlpha)
10095 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
10096 			}
10097 
10098 			BITMAPINFO infoheader;
10099 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
10100 			infoheader.bmiHeader.biWidth = width;
10101 			infoheader.bmiHeader.biHeight = height;
10102 			infoheader.bmiHeader.biPlanes = 1;
10103 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
10104 			infoheader.bmiHeader.biCompression = BI_RGB;
10105 
10106 			// FIXME: this should prolly be a device dependent bitmap...
10107 			handle = CreateDIBSection(
10108 				null,
10109 				&infoheader,
10110 				DIB_RGB_COLORS,
10111 				cast(void**) &rawData,
10112 				null,
10113 				0);
10114 
10115 			if(handle is null)
10116 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
10117 		}
10118 	}
10119 
10120 	/// Makes a sprite based on the image with the initial contents from the Image
10121 	this(SimpleWindow win, Image i) {
10122 		this(win, i.width, i.height, i.enableAlpha);
10123 
10124 		version(X11) {
10125 			auto display = XDisplayConnection.get();
10126 			auto gc = XCreateGC(display, this.handle, 0, null);
10127 			scope(exit) XFreeGC(display, gc);
10128 			if(i.usingXshm)
10129 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
10130 			else
10131 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
10132 		} else version(Windows) {
10133 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
10134 			auto arrLength = itemsPerLine * height;
10135 			rawData[0..arrLength] = i.rawData[0..arrLength];
10136 		} else version(OSXCocoa) {
10137 			// FIXME: I have no idea if this is even any good
10138 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
10139 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
10140 				colorSpace,
10141 				kCGImageAlphaPremultipliedLast
10142 				|kCGBitmapByteOrder32Big);
10143 			CGColorSpaceRelease(colorSpace);
10144 			auto rawData = CGBitmapContextGetData(handle);
10145 
10146 			auto rdl = (width * height * 4);
10147 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
10148 		} else static assert(0);
10149 	}
10150 
10151 	/++
10152 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
10153 
10154 		Params:
10155 			where = point on the window where the upper left corner of the image will be drawn
10156 			imageUpperLeft = point on the image to start the slice to draw
10157 			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.
10158 		History:
10159 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
10160 	+/
10161 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
10162 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
10163 	}
10164 
10165 	/// Call this when you're ready to get rid of it
10166 	void dispose() {
10167 		version(X11) {
10168 			staticDispose(xrenderPicture, handle);
10169 			xrenderPicture = None;
10170 			handle = None;
10171 		} else version(Windows) {
10172 			staticDispose(handle);
10173 			handle = null;
10174 		} else version(OSXCocoa) {
10175 			staticDispose(handle);
10176 			handle = null;
10177 		} else static assert(0);
10178 
10179 	}
10180 
10181 	version(X11)
10182 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
10183 		if(xrenderPicture)
10184 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
10185 		if(handle)
10186 			XFreePixmap(XDisplayConnection.get(), handle);
10187 	}
10188 	else version(Windows)
10189 	static void staticDispose(HBITMAP handle) {
10190 		if(handle)
10191 			DeleteObject(handle);
10192 	}
10193 	else version(OSXCocoa)
10194 	static void staticDispose(CGContextRef context) {
10195 		if(context)
10196 			CGContextRelease(context);
10197 	}
10198 
10199 	~this() {
10200 		version(X11) { if(xrenderPicture || handle)
10201 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
10202 		} else version(Windows) { if(handle)
10203 			cleanupQueue.queue!staticDispose(handle);
10204 		} else version(OSXCocoa) { if(handle)
10205 			cleanupQueue.queue!staticDispose(handle);
10206 		} else static assert(0);
10207 	}
10208 
10209 	///
10210 	final @property int width() { return _width; }
10211 
10212 	///
10213 	final @property int height() { return _height; }
10214 
10215 	///
10216 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
10217 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
10218 	}
10219 
10220 	auto nativeHandle() {
10221 		return handle;
10222 	}
10223 
10224 	private:
10225 
10226 	int _width;
10227 	int _height;
10228 	bool enableAlpha;
10229 	version(X11)
10230 		Pixmap handle;
10231 	else version(Windows)
10232 		HBITMAP handle;
10233 	else version(OSXCocoa)
10234 		CGContextRef handle;
10235 	else static assert(0);
10236 }
10237 
10238 /++
10239 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
10240 
10241 	History:
10242 		Added November 20, 2021 (dub v10.4)
10243 +/
10244 version(OSXCocoa) {} else // NotYetImplementedException
10245 abstract class Gradient : Sprite {
10246 	protected this(int w, int h) {
10247 		version(X11) {
10248 			Sprite.requireXRender();
10249 
10250 			super();
10251 			enableAlpha = true;
10252 			_width = w;
10253 			_height = h;
10254 		} else version(Windows) {
10255 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
10256 		}
10257 	}
10258 
10259 	version(Windows)
10260 	final void forEachPixel(scope Color delegate(int x, int y) dg) @system {
10261 		auto ptr = rawData;
10262 		foreach(j; 0 .. _height)
10263 		foreach(i; 0 .. _width) {
10264 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
10265 			*rawData = (color.a * color.b) / 255; rawData++;
10266 			*rawData = (color.a * color.g) / 255; rawData++;
10267 			*rawData = (color.a * color.r) / 255; rawData++;
10268 			*rawData = color.a; rawData++;
10269 		}
10270 	}
10271 
10272 	version(X11)
10273 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
10274 		assert(stops.length > 0);
10275 		assert(stops.length <= 16, "I got lazy with buffers");
10276 
10277 		XFixed[16] stopsPositions = void;
10278 		XRenderColor[16] colors = void;
10279 
10280 		foreach(idx, stop; stops) {
10281 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
10282 			auto c = stop.c;
10283 			colors[idx] = XRenderColor(
10284 				cast(ushort)(c.r * ushort.max / 255),
10285 				cast(ushort)(c.g * ushort.max / 255),
10286 				cast(ushort)(c.b * ushort.max / 255),
10287 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
10288 			);
10289 		}
10290 
10291 		xrenderPicture = dg(stopsPositions, colors);
10292 	}
10293 
10294 	///
10295 	static struct Stop {
10296 		float percentage; /// between 0 and 1.0
10297 		Color c;
10298 	}
10299 }
10300 
10301 /++
10302 	Creates a linear gradient between p1 and p2.
10303 
10304 	X ONLY RIGHT NOW
10305 
10306 	History:
10307 		Added November 20, 2021 (dub v10.4)
10308 
10309 	Bugs:
10310 		Not yet implemented on Windows.
10311 +/
10312 version(OSXCocoa) {} else // NotYetImplementedException
10313 class LinearGradient : Gradient {
10314 	/++
10315 
10316 	+/
10317 	this(Point p1, Point p2, Stop[] stops...) {
10318 		super(p2.x, p2.y);
10319 
10320 		version(X11) {
10321 			XLinearGradient gradient;
10322 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
10323 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
10324 
10325 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10326 				return XRenderCreateLinearGradient(
10327 					XDisplayConnection.get,
10328 					&gradient,
10329 					stopsPositions.ptr,
10330 					colors.ptr,
10331 					cast(int) stops.length);
10332 			});
10333 		} else version(Windows) {
10334 			// FIXME
10335 			forEachPixel((int x, int y) {
10336 				import core.stdc.math;
10337 
10338 				//sqrtf(
10339 
10340 				return Color.transparent;
10341 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10342 			});
10343 		}
10344 	}
10345 }
10346 
10347 /++
10348 	A conical gradient goes from color to color around a circumference from a center point.
10349 
10350 	X ONLY RIGHT NOW
10351 
10352 	History:
10353 		Added November 20, 2021 (dub v10.4)
10354 
10355 	Bugs:
10356 		Not yet implemented on Windows.
10357 +/
10358 version(OSXCocoa) {} else // NotYetImplementedException
10359 class ConicalGradient : Gradient {
10360 	/++
10361 
10362 	+/
10363 	this(Point center, float angleInDegrees, Stop[] stops...) {
10364 		super(center.x * 2, center.y * 2);
10365 
10366 		version(X11) {
10367 			XConicalGradient gradient;
10368 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
10369 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
10370 
10371 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10372 				return XRenderCreateConicalGradient(
10373 					XDisplayConnection.get,
10374 					&gradient,
10375 					stopsPositions.ptr,
10376 					colors.ptr,
10377 					cast(int) stops.length);
10378 			});
10379 		} else version(Windows) {
10380 			// FIXME
10381 			forEachPixel((int x, int y) {
10382 				import core.stdc.math;
10383 
10384 				//sqrtf(
10385 
10386 				return Color.transparent;
10387 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10388 			});
10389 
10390 		}
10391 	}
10392 }
10393 
10394 /++
10395 	A radial gradient goes from color to color based on distance from the center.
10396 	It is like rings of color.
10397 
10398 	X ONLY RIGHT NOW
10399 
10400 
10401 	More specifically, you create two circles: an inner circle and an outer circle.
10402 	The gradient is only drawn in the area outside the inner circle but inside the outer
10403 	circle. The closest line between those two circles forms the line for the gradient
10404 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10405 
10406 	History:
10407 		Added November 20, 2021 (dub v10.4)
10408 
10409 	Bugs:
10410 		Not yet implemented on Windows.
10411 +/
10412 version(OSXCocoa) {} else // NotYetImplementedException
10413 class RadialGradient : Gradient {
10414 	/++
10415 
10416 	+/
10417 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10418 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10419 
10420 		version(X11) {
10421 			XRadialGradient gradient;
10422 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10423 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10424 
10425 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10426 				return XRenderCreateRadialGradient(
10427 					XDisplayConnection.get,
10428 					&gradient,
10429 					stopsPositions.ptr,
10430 					colors.ptr,
10431 					cast(int) stops.length);
10432 			});
10433 		} else version(Windows) {
10434 			// FIXME
10435 			forEachPixel((int x, int y) {
10436 				import core.stdc.math;
10437 
10438 				//sqrtf(
10439 
10440 				return Color.transparent;
10441 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10442 			});
10443 		}
10444 	}
10445 }
10446 
10447 
10448 
10449 /+
10450 	NOT IMPLEMENTED
10451 
10452 	A display-stored image optimized for relatively quick drawing, like
10453 	[Sprite], but this one supports alpha channel blending and does NOT
10454 	support direct drawing upon it with a [ScreenPainter].
10455 
10456 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10457 	plain [ScreenPainter]... sort of.
10458 
10459 	On X11, it requires the Xrender extension and library. This is available
10460 	almost everywhere though.
10461 
10462 	History:
10463 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10464 +/
10465 version(none)
10466 class AlphaSprite {
10467 	/++
10468 		Copies the given image into it.
10469 	+/
10470 	this(MemoryImage img) {
10471 
10472 		if(!XRenderLibrary.loadAttempted) {
10473 			XRenderLibrary.loadDynamicLibrary();
10474 
10475 			// FIXME: this needs to be reconstructed when the X server changes
10476 			repopulateX();
10477 		}
10478 		if(!XRenderLibrary.loadSuccessful)
10479 			throw new Exception("XRender library load failure");
10480 
10481 		// I probably need to put the alpha mask in a separate Picture
10482 		// ugh
10483 		// maybe the Sprite itself can have an alpha bitmask anyway
10484 
10485 
10486 		auto display = XDisplayConnection.get();
10487 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10488 
10489 
10490 		XRenderPictureAttributes attrs;
10491 
10492 		handle = XRenderCreatePicture(
10493 			XDisplayConnection.get,
10494 			pixmap,
10495 			RGBA,
10496 			0,
10497 			&attrs
10498 		);
10499 
10500 	}
10501 
10502 	// maybe i'll use the create gradient functions too with static factories..
10503 
10504 	void drawAt(ScreenPainter painter, Point where) {
10505 		//painter.drawPixmap(this, where);
10506 
10507 		XRenderPictureAttributes attrs;
10508 
10509 		auto pic = XRenderCreatePicture(
10510 			XDisplayConnection.get,
10511 			painter.impl.d,
10512 			RGB,
10513 			0,
10514 			&attrs
10515 		);
10516 
10517 		XRenderComposite(
10518 			XDisplayConnection.get,
10519 			3, // PictOpOver
10520 			handle,
10521 			None,
10522 			pic,
10523 			0, // src
10524 			0,
10525 			0, // mask
10526 			0,
10527 			10, // dest
10528 			10,
10529 			100, // width
10530 			100
10531 		);
10532 
10533 		/+
10534 		XRenderFreePicture(
10535 			XDisplayConnection.get,
10536 			pic
10537 		);
10538 
10539 		XRenderFreePicture(
10540 			XDisplayConnection.get,
10541 			fill
10542 		);
10543 		+/
10544 		// on Windows you can stretch but Xrender still can't :(
10545 	}
10546 
10547 	static XRenderPictFormat* RGB;
10548 	static XRenderPictFormat* RGBA;
10549 	static void repopulateX() {
10550 		auto display = XDisplayConnection.get;
10551 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10552 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10553 	}
10554 
10555 	XPixmap pixmap;
10556 	Picture handle;
10557 }
10558 
10559 ///
10560 interface CapableOfBeingDrawnUpon {
10561 	///
10562 	ScreenPainter draw();
10563 	///
10564 	int width();
10565 	///
10566 	int height();
10567 	protected ScreenPainterImplementation* activeScreenPainter();
10568 	protected void activeScreenPainter(ScreenPainterImplementation*);
10569 	bool closed();
10570 
10571 	void delegate() paintingFinishedDg();
10572 
10573 	/// Be warned: this can be a very slow operation
10574 	TrueColorImage takeScreenshot();
10575 }
10576 
10577 /// 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].
10578 void flushGui() {
10579 	version(X11) {
10580 		auto dpy = XDisplayConnection.get();
10581 		XLockDisplay(dpy);
10582 		scope(exit) XUnlockDisplay(dpy);
10583 		XFlush(dpy);
10584 	}
10585 }
10586 
10587 /++
10588 	Runs the given code in the GUI thread when its event loop
10589 	is available, blocking until it completes. This allows you
10590 	to create and manipulate windows from another thread without
10591 	invoking undefined behavior.
10592 
10593 	If this is the gui thread, it runs the code immediately.
10594 
10595 	If no gui thread exists yet, the current thread is assumed
10596 	to be it. Attempting to create windows or run the event loop
10597 	in any other thread will cause an assertion failure.
10598 
10599 
10600 	$(TIP
10601 		Did you know you can use UFCS on delegate literals?
10602 
10603 		() {
10604 			// code here
10605 		}.runInGuiThread;
10606 	)
10607 
10608 	Returns:
10609 		`true` if the function was called, `false` if it was not.
10610 		The function may not be called because the gui thread had
10611 		already terminated by the time you called this.
10612 
10613 	History:
10614 		Added April 10, 2020 (v7.2.0)
10615 
10616 		Return value added and implementation tweaked to avoid locking
10617 		at program termination on February 24, 2021 (v9.2.1).
10618 +/
10619 bool runInGuiThread(scope void delegate() dg) @trusted {
10620 	claimGuiThread();
10621 
10622 	if(thisIsGuiThread) {
10623 		dg();
10624 		return true;
10625 	}
10626 
10627 	if(guiThreadTerminating)
10628 		return false;
10629 
10630 	import core.sync.semaphore;
10631 	static Semaphore sc;
10632 	if(sc is null)
10633 		sc = new Semaphore();
10634 
10635 	static RunQueueMember* rqm;
10636 	if(rqm is null)
10637 		rqm = new RunQueueMember;
10638 	rqm.dg = cast(typeof(rqm.dg)) dg;
10639 	rqm.signal = sc;
10640 	rqm.thrown = null;
10641 
10642 	synchronized(runInGuiThreadLock) {
10643 		runInGuiThreadQueue ~= rqm;
10644 	}
10645 
10646 	if(!SimpleWindow.eventWakeUp())
10647 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10648 
10649 	rqm.signal.wait();
10650 	auto t = rqm.thrown;
10651 
10652 	if(t)
10653 		throw t;
10654 
10655 	return true;
10656 }
10657 
10658 // note it runs sync if this is the gui thread....
10659 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10660 	claimGuiThread();
10661 
10662 	try {
10663 
10664 		if(thisIsGuiThread) {
10665 			dg();
10666 			return;
10667 		}
10668 
10669 		if(guiThreadTerminating)
10670 			return;
10671 
10672 		RunQueueMember* rqm = new RunQueueMember;
10673 		rqm.dg = cast(typeof(rqm.dg)) dg;
10674 		rqm.signal = null;
10675 		rqm.thrown = null;
10676 
10677 		synchronized(runInGuiThreadLock) {
10678 			runInGuiThreadQueue ~= rqm;
10679 		}
10680 
10681 		if(!SimpleWindow.eventWakeUp())
10682 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10683 	} catch(Exception e) {
10684 		if(handleError)
10685 			handleError(e);
10686 	}
10687 }
10688 
10689 private void runPendingRunInGuiThreadDelegates() {
10690 	more:
10691 	RunQueueMember* next;
10692 	synchronized(runInGuiThreadLock) {
10693 		if(runInGuiThreadQueue.length) {
10694 			next = runInGuiThreadQueue[0];
10695 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10696 		} else {
10697 			next = null;
10698 		}
10699 	}
10700 
10701 	if(next) {
10702 		try {
10703 			next.dg();
10704 			next.thrown = null;
10705 		} catch(Throwable t) {
10706 			next.thrown = t;
10707 		}
10708 
10709 		if(next.signal)
10710 			next.signal.notify();
10711 
10712 		goto more;
10713 	}
10714 }
10715 
10716 private void claimGuiThread() nothrow {
10717 	import core.atomic;
10718 	if(cas(&guiThreadExists_, false, true))
10719 		thisIsGuiThread = true;
10720 }
10721 
10722 private struct RunQueueMember {
10723 	void delegate() dg;
10724 	import core.sync.semaphore;
10725 	Semaphore signal;
10726 	Throwable thrown;
10727 }
10728 
10729 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10730 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10731 private bool thisIsGuiThread = false;
10732 private shared bool guiThreadExists_ = false;
10733 private shared bool guiThreadTerminating = false;
10734 
10735 /++
10736 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10737 	event loop. All windows must be exclusively created and managed by a single thread.
10738 
10739 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10740 	when you call one of its constructors.
10741 
10742 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10743 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10744 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10745 
10746 	The reason this function is available is in case you want to message pass between a gui
10747 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10748 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10749 
10750 	History:
10751 		Added December 3, 2021 (dub v10.5)
10752 +/
10753 public bool guiThreadExists() {
10754 	return guiThreadExists_;
10755 }
10756 
10757 /++
10758 	Returns `true` if this thread is either running or set to be running the
10759 	simpledisplay.d gui core event loop because it owns windows.
10760 
10761 	It is important to keep gui-related functionality in the right thread, so you will
10762 	want to `runInGuiThread` when you call them (with some specific exceptions called
10763 	out in those specific functions' documentation). Notably, all windows must be
10764 	created and managed only from the gui thread.
10765 
10766 	Will return false if simpledisplay's other functions haven't been called
10767 	yet; check [guiThreadExists] in addition to this.
10768 
10769 	History:
10770 		Added December 3, 2021 (dub v10.5)
10771 +/
10772 public bool thisThreadRunningGui() {
10773 	return thisIsGuiThread;
10774 }
10775 
10776 /++
10777 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10778 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10779 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10780 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10781 	file instead if you are in one of those situations).
10782 
10783 	It does not support outputting very many types; just strings and ints are likely to actually work.
10784 
10785 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10786 	is unspecified meaning I can change it at any time. The only point of this function is to help
10787 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10788 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10789 	in those contexts.
10790 
10791 	$(WARNING
10792 		I reserve the right to change this function at any time. You can use it if it helps you
10793 		but do not rely on it for anything permanent.
10794 	)
10795 
10796 	History:
10797 		Added December 3, 2021. Not formally supported under any stable tag.
10798 +/
10799 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10800 	try {
10801 		version(Windows) {
10802 			import core.sys.windows.wincon;
10803 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10804 				AllocConsole();
10805 			const(char)* fn = "CONOUT$";
10806 		} else version(Posix) {
10807 			const(char)* fn = "/dev/tty";
10808 		} else static assert(0, "Function not implemented for your system");
10809 
10810 		if(fileOverride.length)
10811 			fn = fileOverride.ptr;
10812 
10813 		import core.stdc.stdio;
10814 		auto fp = fopen(fn, "wt");
10815 		if(fp is null) return;
10816 		scope(exit) fclose(fp);
10817 
10818 		string str;
10819 		foreach(item; t) {
10820 			static if(is(typeof(item) : const(char)[]))
10821 				str ~= item;
10822 			else
10823 				str ~= toInternal!string(item);
10824 			str ~= " ";
10825 		}
10826 		str ~= "\n";
10827 
10828 		fwrite(str.ptr, 1, str.length, fp);
10829 		fflush(fp);
10830 	} catch(Exception e) {
10831 		// sorry no hope
10832 	}
10833 }
10834 
10835 private void guiThreadFinalize() {
10836 	assert(thisIsGuiThread);
10837 
10838 	guiThreadTerminating = true; // don't add any more from this point on
10839 	runPendingRunInGuiThreadDelegates();
10840 }
10841 
10842 /+
10843 interface IPromise {
10844 	void reportProgress(int current, int max, string message);
10845 
10846 	/+ // not formally in cuz of templates but still
10847 	IPromise Then();
10848 	IPromise Catch();
10849 	IPromise Finally();
10850 	+/
10851 }
10852 
10853 /+
10854 	auto promise = async({ ... });
10855 	promise.Then(whatever).
10856 		Then(whateverelse).
10857 		Catch((exception) { });
10858 
10859 
10860 	A promise is run inside a fiber and it looks something like:
10861 
10862 	try {
10863 		auto res = whatever();
10864 		auto res2 = whateverelse(res);
10865 	} catch(Exception e) {
10866 		{ }(e);
10867 	}
10868 
10869 	When a thing succeeds, it is passed as an arg to the next
10870 +/
10871 class Promise(T) : IPromise {
10872 	auto Then() { return null; }
10873 	auto Catch() { return null; }
10874 	auto Finally() { return null; }
10875 
10876 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10877 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10878 	T await();
10879 }
10880 
10881 interface Task {
10882 }
10883 
10884 interface Resolvable(T) : Task {
10885 	void run();
10886 
10887 	void resolve(T);
10888 
10889 	Resolvable!T then(void delegate(T)); // returns a new promise
10890 	Resolvable!T error(Throwable); // js catch
10891 	Resolvable!T completed(); // js finally
10892 
10893 }
10894 
10895 /++
10896 	Runs `work` in a helper thread and sends its return value back to the main gui
10897 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10898 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10899 	kill the program.
10900 
10901 	You can call reportProgress(position, max, message) to update your parent window
10902 	on your progress.
10903 
10904 	I should also use `shared` methods. FIXME
10905 
10906 	History:
10907 		Added March 6, 2021 (dub version 9.3).
10908 +/
10909 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10910 	uponCompletion(work(null));
10911 }
10912 
10913 +/
10914 
10915 /// Used internal to dispatch events to various classes.
10916 interface CapableOfHandlingNativeEvent {
10917 	NativeEventHandler getNativeEventHandler();
10918 
10919 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10920 
10921 	version(X11) {
10922 		// if this is impossible, you are allowed to just throw from it
10923 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10924 		void recreateAfterDisconnect();
10925 		// discard any *connection specific* state, but keep enough that you
10926 		// can be recreated if possible. discardConnectionState() is always called immediately
10927 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10928 		// you need initialization order
10929 		void discardConnectionState();
10930 	}
10931 }
10932 
10933 version(X11)
10934 /++
10935 	State of keys on mouse events, especially motion.
10936 
10937 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10938 +/
10939 enum ModifierState : uint {
10940 	shift = 1, ///
10941 	capsLock = 2, ///
10942 	ctrl = 4, ///
10943 	alt = 8, /// Not always available on Windows
10944 	windows = 64, /// ditto
10945 	numLock = 16, ///
10946 
10947 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10948 	middleButtonDown = 512, /// ditto
10949 	rightButtonDown = 1024, /// ditto
10950 }
10951 else version(Windows)
10952 /// ditto
10953 enum ModifierState : uint {
10954 	shift = 4, ///
10955 	ctrl = 8, ///
10956 
10957 	// i'm not sure if the next two are available
10958 	alt = 256, /// not always available on Windows
10959 	windows = 512, /// ditto
10960 
10961 	capsLock = 1024, ///
10962 	numLock = 2048, ///
10963 
10964 	leftButtonDown = 1, /// not available on key events
10965 	middleButtonDown = 16, /// ditto
10966 	rightButtonDown = 2, /// ditto
10967 
10968 	backButtonDown = 0x20, /// not available on X
10969 	forwardButtonDown = 0x40, /// ditto
10970 }
10971 else version(OSXCocoa)
10972 // FIXME FIXME NotYetImplementedException
10973 enum ModifierState : uint {
10974 	shift = 1, ///
10975 	capsLock = 2, ///
10976 	ctrl = 4, ///
10977 	alt = 8, /// Not always available on Windows
10978 	windows = 64, /// ditto
10979 	numLock = 16, ///
10980 
10981 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10982 	middleButtonDown = 512, /// ditto
10983 	rightButtonDown = 1024, /// ditto
10984 }
10985 
10986 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10987 enum MouseButton : int {
10988 	none = 0,
10989 	left = 1, ///
10990 	right = 2, ///
10991 	middle = 4, ///
10992 	wheelUp = 8, ///
10993 	wheelDown = 16, ///
10994 	backButton = 32, /// often found on the thumb and used for back in browsers
10995 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10996 }
10997 
10998 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
10999 enum MouseButtonLinear : ubyte {
11000 	left = 1, ///
11001 	right, ///
11002 	middle, ///
11003 	wheelUp, ///
11004 	wheelDown, ///
11005 	backButton, /// often found on the thumb and used for back in browsers
11006 	forwardButton, /// often found on the thumb and used for forward in browsers
11007 }
11008 
11009 version(X11) {
11010 	// FIXME: match ASCII whenever we can. Most of it is already there,
11011 	// but there's a few exceptions and mismatches with Windows
11012 
11013 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11014 	enum Key {
11015 		Escape = 0xff1b, ///
11016 		F1 = 0xffbe, ///
11017 		F2 = 0xffbf, ///
11018 		F3 = 0xffc0, ///
11019 		F4 = 0xffc1, ///
11020 		F5 = 0xffc2, ///
11021 		F6 = 0xffc3, ///
11022 		F7 = 0xffc4, ///
11023 		F8 = 0xffc5, ///
11024 		F9 = 0xffc6, ///
11025 		F10 = 0xffc7, ///
11026 		F11 = 0xffc8, ///
11027 		F12 = 0xffc9, ///
11028 		PrintScreen = 0xff61, ///
11029 		ScrollLock = 0xff14, ///
11030 		Pause = 0xff13, ///
11031 		Grave = 0x60, /// The $(BACKTICK) ~ key
11032 		// number keys across the top of the keyboard
11033 		N1 = 0x31, /// Number key atop the keyboard
11034 		N2 = 0x32, ///
11035 		N3 = 0x33, ///
11036 		N4 = 0x34, ///
11037 		N5 = 0x35, ///
11038 		N6 = 0x36, ///
11039 		N7 = 0x37, ///
11040 		N8 = 0x38, ///
11041 		N9 = 0x39, ///
11042 		N0 = 0x30, ///
11043 		Dash = 0x2d, ///
11044 		Equals = 0x3d, ///
11045 		Backslash = 0x5c, /// The \ | key
11046 		Backspace = 0xff08, ///
11047 		Insert = 0xff63, ///
11048 		Home = 0xff50, ///
11049 		PageUp = 0xff55, ///
11050 		Delete = 0xffff, ///
11051 		End = 0xff57, ///
11052 		PageDown = 0xff56, ///
11053 		Up = 0xff52, ///
11054 		Down = 0xff54, ///
11055 		Left = 0xff51, ///
11056 		Right = 0xff53, ///
11057 
11058 		Tab = 0xff09, ///
11059 		Q = 0x71, ///
11060 		W = 0x77, ///
11061 		E = 0x65, ///
11062 		R = 0x72, ///
11063 		T = 0x74, ///
11064 		Y = 0x79, ///
11065 		U = 0x75, ///
11066 		I = 0x69, ///
11067 		O = 0x6f, ///
11068 		P = 0x70, ///
11069 		LeftBracket = 0x5b, /// the [ { key
11070 		RightBracket = 0x5d, /// the ] } key
11071 		CapsLock = 0xffe5, ///
11072 		A = 0x61, ///
11073 		S = 0x73, ///
11074 		D = 0x64, ///
11075 		F = 0x66, ///
11076 		G = 0x67, ///
11077 		H = 0x68, ///
11078 		J = 0x6a, ///
11079 		K = 0x6b, ///
11080 		L = 0x6c, ///
11081 		Semicolon = 0x3b, ///
11082 		Apostrophe = 0x27, ///
11083 		Enter = 0xff0d, ///
11084 		Shift = 0xffe1, ///
11085 		Z = 0x7a, ///
11086 		X = 0x78, ///
11087 		C = 0x63, ///
11088 		V = 0x76, ///
11089 		B = 0x62, ///
11090 		N = 0x6e, ///
11091 		M = 0x6d, ///
11092 		Comma = 0x2c, ///
11093 		Period = 0x2e, ///
11094 		Slash = 0x2f, /// the / ? key
11095 		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
11096 		Ctrl = 0xffe3, ///
11097 		Windows = 0xffeb, ///
11098 		Alt = 0xffe9, ///
11099 		Space = 0x20, ///
11100 		Alt_r = 0xffea, /// ditto of shift_r
11101 		Windows_r = 0xffec, ///
11102 		Menu = 0xff67, ///
11103 		Ctrl_r = 0xffe4, ///
11104 
11105 		NumLock = 0xff7f, ///
11106 		Divide = 0xffaf, /// The / key on the number pad
11107 		Multiply = 0xffaa, /// The * key on the number pad
11108 		Minus = 0xffad, /// The - key on the number pad
11109 		Plus = 0xffab, /// The + key on the number pad
11110 		PadEnter = 0xff8d, /// Numberpad enter key
11111 		Pad1 = 0xff9c, /// Numberpad keys
11112 		Pad2 = 0xff99, ///
11113 		Pad3 = 0xff9b, ///
11114 		Pad4 = 0xff96, ///
11115 		Pad5 = 0xff9d, ///
11116 		Pad6 = 0xff98, ///
11117 		Pad7 = 0xff95, ///
11118 		Pad8 = 0xff97, ///
11119 		Pad9 = 0xff9a, ///
11120 		Pad0 = 0xff9e, ///
11121 		PadDot = 0xff9f, ///
11122 	}
11123 } else version(Windows) {
11124 	// the character here is for en-us layouts and for illustration only
11125 	// if you actually want to get characters, wait for character events
11126 	// (the argument to your event handler is simply a dchar)
11127 	// those will be converted by the OS for the right locale.
11128 
11129 	enum Key {
11130 		Escape = 0x1b,
11131 		F1 = 0x70,
11132 		F2 = 0x71,
11133 		F3 = 0x72,
11134 		F4 = 0x73,
11135 		F5 = 0x74,
11136 		F6 = 0x75,
11137 		F7 = 0x76,
11138 		F8 = 0x77,
11139 		F9 = 0x78,
11140 		F10 = 0x79,
11141 		F11 = 0x7a,
11142 		F12 = 0x7b,
11143 		PrintScreen = 0x2c,
11144 		ScrollLock = 0x91,
11145 		Pause = 0x13,
11146 		Grave = 0xc0,
11147 		// number keys across the top of the keyboard
11148 		N1 = 0x31,
11149 		N2 = 0x32,
11150 		N3 = 0x33,
11151 		N4 = 0x34,
11152 		N5 = 0x35,
11153 		N6 = 0x36,
11154 		N7 = 0x37,
11155 		N8 = 0x38,
11156 		N9 = 0x39,
11157 		N0 = 0x30,
11158 		Dash = 0xbd,
11159 		Equals = 0xbb,
11160 		Backslash = 0xdc,
11161 		Backspace = 0x08,
11162 		Insert = 0x2d,
11163 		Home = 0x24,
11164 		PageUp = 0x21,
11165 		Delete = 0x2e,
11166 		End = 0x23,
11167 		PageDown = 0x22,
11168 		Up = 0x26,
11169 		Down = 0x28,
11170 		Left = 0x25,
11171 		Right = 0x27,
11172 
11173 		Tab = 0x09,
11174 		Q = 0x51,
11175 		W = 0x57,
11176 		E = 0x45,
11177 		R = 0x52,
11178 		T = 0x54,
11179 		Y = 0x59,
11180 		U = 0x55,
11181 		I = 0x49,
11182 		O = 0x4f,
11183 		P = 0x50,
11184 		LeftBracket = 0xdb,
11185 		RightBracket = 0xdd,
11186 		CapsLock = 0x14,
11187 		A = 0x41,
11188 		S = 0x53,
11189 		D = 0x44,
11190 		F = 0x46,
11191 		G = 0x47,
11192 		H = 0x48,
11193 		J = 0x4a,
11194 		K = 0x4b,
11195 		L = 0x4c,
11196 		Semicolon = 0xba,
11197 		Apostrophe = 0xde,
11198 		Enter = 0x0d,
11199 		Shift = 0x10,
11200 		Z = 0x5a,
11201 		X = 0x58,
11202 		C = 0x43,
11203 		V = 0x56,
11204 		B = 0x42,
11205 		N = 0x4e,
11206 		M = 0x4d,
11207 		Comma = 0xbc,
11208 		Period = 0xbe,
11209 		Slash = 0xbf,
11210 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11211 		Ctrl = 0x11,
11212 		Windows = 0x5b,
11213 		Alt = -5, // FIXME
11214 		Space = 0x20,
11215 		Alt_r = 0xffea, // ditto of shift_r
11216 		Windows_r = 0x5c, // ditto of shift_r
11217 		Menu = 0x5d,
11218 		Ctrl_r = 0xa3, // ditto of shift_r
11219 
11220 		NumLock = 0x90,
11221 		Divide = 0x6f,
11222 		Multiply = 0x6a,
11223 		Minus = 0x6d,
11224 		Plus = 0x6b,
11225 		PadEnter = -8, // FIXME
11226 		Pad1 = 0x61,
11227 		Pad2 = 0x62,
11228 		Pad3 = 0x63,
11229 		Pad4 = 0x64,
11230 		Pad5 = 0x65,
11231 		Pad6 = 0x66,
11232 		Pad7 = 0x67,
11233 		Pad8 = 0x68,
11234 		Pad9 = 0x69,
11235 		Pad0 = 0x60,
11236 		PadDot = 0x6e,
11237 	}
11238 
11239 	// I'm keeping this around for reference purposes
11240 	// ideally all these buttons will be listed for all platforms,
11241 	// but now now I'm just focusing on my US keyboard
11242 	version(none)
11243 	enum Key {
11244 		LBUTTON = 0x01,
11245 		RBUTTON = 0x02,
11246 		CANCEL = 0x03,
11247 		MBUTTON = 0x04,
11248 		//static if (_WIN32_WINNT > =  0x500) {
11249 		XBUTTON1 = 0x05,
11250 		XBUTTON2 = 0x06,
11251 		//}
11252 		BACK = 0x08,
11253 		TAB = 0x09,
11254 		CLEAR = 0x0C,
11255 		RETURN = 0x0D,
11256 		SHIFT = 0x10,
11257 		CONTROL = 0x11,
11258 		MENU = 0x12,
11259 		PAUSE = 0x13,
11260 		CAPITAL = 0x14,
11261 		KANA = 0x15,
11262 		HANGEUL = 0x15,
11263 		HANGUL = 0x15,
11264 		JUNJA = 0x17,
11265 		FINAL = 0x18,
11266 		HANJA = 0x19,
11267 		KANJI = 0x19,
11268 		ESCAPE = 0x1B,
11269 		CONVERT = 0x1C,
11270 		NONCONVERT = 0x1D,
11271 		ACCEPT = 0x1E,
11272 		MODECHANGE = 0x1F,
11273 		SPACE = 0x20,
11274 		PRIOR = 0x21,
11275 		NEXT = 0x22,
11276 		END = 0x23,
11277 		HOME = 0x24,
11278 		LEFT = 0x25,
11279 		UP = 0x26,
11280 		RIGHT = 0x27,
11281 		DOWN = 0x28,
11282 		SELECT = 0x29,
11283 		PRINT = 0x2A,
11284 		EXECUTE = 0x2B,
11285 		SNAPSHOT = 0x2C,
11286 		INSERT = 0x2D,
11287 		DELETE = 0x2E,
11288 		HELP = 0x2F,
11289 		LWIN = 0x5B,
11290 		RWIN = 0x5C,
11291 		APPS = 0x5D,
11292 		SLEEP = 0x5F,
11293 		NUMPAD0 = 0x60,
11294 		NUMPAD1 = 0x61,
11295 		NUMPAD2 = 0x62,
11296 		NUMPAD3 = 0x63,
11297 		NUMPAD4 = 0x64,
11298 		NUMPAD5 = 0x65,
11299 		NUMPAD6 = 0x66,
11300 		NUMPAD7 = 0x67,
11301 		NUMPAD8 = 0x68,
11302 		NUMPAD9 = 0x69,
11303 		MULTIPLY = 0x6A,
11304 		ADD = 0x6B,
11305 		SEPARATOR = 0x6C,
11306 		SUBTRACT = 0x6D,
11307 		DECIMAL = 0x6E,
11308 		DIVIDE = 0x6F,
11309 		F1 = 0x70,
11310 		F2 = 0x71,
11311 		F3 = 0x72,
11312 		F4 = 0x73,
11313 		F5 = 0x74,
11314 		F6 = 0x75,
11315 		F7 = 0x76,
11316 		F8 = 0x77,
11317 		F9 = 0x78,
11318 		F10 = 0x79,
11319 		F11 = 0x7A,
11320 		F12 = 0x7B,
11321 		F13 = 0x7C,
11322 		F14 = 0x7D,
11323 		F15 = 0x7E,
11324 		F16 = 0x7F,
11325 		F17 = 0x80,
11326 		F18 = 0x81,
11327 		F19 = 0x82,
11328 		F20 = 0x83,
11329 		F21 = 0x84,
11330 		F22 = 0x85,
11331 		F23 = 0x86,
11332 		F24 = 0x87,
11333 		NUMLOCK = 0x90,
11334 		SCROLL = 0x91,
11335 		LSHIFT = 0xA0,
11336 		RSHIFT = 0xA1,
11337 		LCONTROL = 0xA2,
11338 		RCONTROL = 0xA3,
11339 		LMENU = 0xA4,
11340 		RMENU = 0xA5,
11341 		//static if (_WIN32_WINNT > =  0x500) {
11342 		BROWSER_BACK = 0xA6,
11343 		BROWSER_FORWARD = 0xA7,
11344 		BROWSER_REFRESH = 0xA8,
11345 		BROWSER_STOP = 0xA9,
11346 		BROWSER_SEARCH = 0xAA,
11347 		BROWSER_FAVORITES = 0xAB,
11348 		BROWSER_HOME = 0xAC,
11349 		VOLUME_MUTE = 0xAD,
11350 		VOLUME_DOWN = 0xAE,
11351 		VOLUME_UP = 0xAF,
11352 		MEDIA_NEXT_TRACK = 0xB0,
11353 		MEDIA_PREV_TRACK = 0xB1,
11354 		MEDIA_STOP = 0xB2,
11355 		MEDIA_PLAY_PAUSE = 0xB3,
11356 		LAUNCH_MAIL = 0xB4,
11357 		LAUNCH_MEDIA_SELECT = 0xB5,
11358 		LAUNCH_APP1 = 0xB6,
11359 		LAUNCH_APP2 = 0xB7,
11360 		//}
11361 		OEM_1 = 0xBA,
11362 		//static if (_WIN32_WINNT > =  0x500) {
11363 		OEM_PLUS = 0xBB,
11364 		OEM_COMMA = 0xBC,
11365 		OEM_MINUS = 0xBD,
11366 		OEM_PERIOD = 0xBE,
11367 		//}
11368 		OEM_2 = 0xBF,
11369 		OEM_3 = 0xC0,
11370 		OEM_4 = 0xDB,
11371 		OEM_5 = 0xDC,
11372 		OEM_6 = 0xDD,
11373 		OEM_7 = 0xDE,
11374 		OEM_8 = 0xDF,
11375 		//static if (_WIN32_WINNT > =  0x500) {
11376 		OEM_102 = 0xE2,
11377 		//}
11378 		PROCESSKEY = 0xE5,
11379 		//static if (_WIN32_WINNT > =  0x500) {
11380 		PACKET = 0xE7,
11381 		//}
11382 		ATTN = 0xF6,
11383 		CRSEL = 0xF7,
11384 		EXSEL = 0xF8,
11385 		EREOF = 0xF9,
11386 		PLAY = 0xFA,
11387 		ZOOM = 0xFB,
11388 		NONAME = 0xFC,
11389 		PA1 = 0xFD,
11390 		OEM_CLEAR = 0xFE,
11391 	}
11392 
11393 } else version(OSXCocoa) {
11394 	enum Key {
11395 		Escape = 53,
11396 		F1 = 122,
11397 		F2 = 120,
11398 		F3 = 99,
11399 		F4 = 118,
11400 		F5 = 96,
11401 		F6 = 97,
11402 		F7 = 98,
11403 		F8 = 100,
11404 		F9 = 101,
11405 		F10 = 109,
11406 		F11 = 103,
11407 		F12 = 111,
11408 		PrintScreen = 105,
11409 		ScrollLock = 107,
11410 		Pause = 113,
11411 		Grave = 50,
11412 		// number keys across the top of the keyboard
11413 		N1 = 18,
11414 		N2 = 19,
11415 		N3 = 20,
11416 		N4 = 21,
11417 		N5 = 23,
11418 		N6 = 22,
11419 		N7 = 26,
11420 		N8 = 28,
11421 		N9 = 25,
11422 		N0 = 29,
11423 		Dash = 27,
11424 		Equals = 24,
11425 		Backslash = 42,
11426 		Backspace = 51,
11427 		Insert = 114,
11428 		Home = 115,
11429 		PageUp = 116,
11430 		Delete = 117,
11431 		End = 119,
11432 		PageDown = 121,
11433 		Up = 126,
11434 		Down = 125,
11435 		Left = 123,
11436 		Right = 124,
11437 
11438 		Tab = 48,
11439 		Q = 12,
11440 		W = 13,
11441 		E = 14,
11442 		R = 15,
11443 		T = 17,
11444 		Y = 16,
11445 		U = 32,
11446 		I = 34,
11447 		O = 31,
11448 		P = 35,
11449 		LeftBracket = 33,
11450 		RightBracket = 30,
11451 		CapsLock = 57,
11452 		A = 0,
11453 		S = 1,
11454 		D = 2,
11455 		F = 3,
11456 		G = 5,
11457 		H = 4,
11458 		J = 38,
11459 		K = 40,
11460 		L = 37,
11461 		Semicolon = 41,
11462 		Apostrophe = 39,
11463 		Enter = 36,
11464 		Shift = 56,
11465 		Z = 6,
11466 		X = 7,
11467 		C = 8,
11468 		V = 9,
11469 		B = 11,
11470 		N = 45,
11471 		M = 46,
11472 		Comma = 43,
11473 		Period = 47,
11474 		Slash = 44,
11475 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11476 		Ctrl = 59,
11477 		Windows = 55,
11478 		Alt = 58,
11479 		Space = 49,
11480 		Alt_r = -3, // ditto of shift_r
11481 		Windows_r = -2,
11482 		Menu = 110,
11483 		Ctrl_r = -1,
11484 
11485 		NumLock = 1,
11486 		Divide = 75,
11487 		Multiply = 67,
11488 		Minus = 78,
11489 		Plus = 69,
11490 		PadEnter = 76,
11491 		Pad1 = 83,
11492 		Pad2 = 84,
11493 		Pad3 = 85,
11494 		Pad4 = 86,
11495 		Pad5 = 87,
11496 		Pad6 = 88,
11497 		Pad7 = 89,
11498 		Pad8 = 91,
11499 		Pad9 = 92,
11500 		Pad0 = 82,
11501 		PadDot = 65,
11502 	}
11503 
11504 }
11505 
11506 /* Additional utilities */
11507 
11508 
11509 Color fromHsl(real h, real s, real l) {
11510 	return arsd.color.fromHsl([h,s,l]);
11511 }
11512 
11513 
11514 
11515 /* ********** What follows is the system-specific implementations *********/
11516 version(Windows) {
11517 
11518 
11519 	// helpers for making HICONs from MemoryImages
11520 	class WindowsIcon {
11521 		struct Win32Icon {
11522 			align(1):
11523 			uint biSize;
11524 			int biWidth;
11525 			int biHeight;
11526 			ushort biPlanes;
11527 			ushort biBitCount;
11528 			uint biCompression;
11529 			uint biSizeImage;
11530 			int biXPelsPerMeter;
11531 			int biYPelsPerMeter;
11532 			uint biClrUsed;
11533 			uint biClrImportant;
11534 			// RGBQUAD[colorCount] biColors;
11535 			/* Pixels:
11536 			Uint8 pixels[]
11537 			*/
11538 			/* Mask:
11539 			Uint8 mask[]
11540 			*/
11541 		}
11542 
11543 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11544 
11545 			assert(mi.width <= 256, "image too wide");
11546 			assert(mi.height <= 256, "image too tall");
11547 			assert(mi.width % 8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy
11548 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11549 
11550 			int icon_plen = mi.width * mi.height * 4;
11551 			int icon_mlen = mi.width * mi.height / 8;
11552 
11553 			int colorCount = 0;
11554 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11555 
11556 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11557 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11558 
11559 			auto data = memory[Win32Icon.sizeof .. $];
11560 
11561 			width = mi.width;
11562 			height = mi.height;
11563 
11564 			auto trueColorImage = mi.getAsTrueColorImage();
11565 
11566 			icon_win32.biSize = 40;
11567 			icon_win32.biWidth = mi.width;
11568 			icon_win32.biHeight = mi.height*2;
11569 			icon_win32.biPlanes = 1;
11570 			icon_win32.biBitCount = 32;
11571 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11572 
11573 			int offset = 0;
11574 			int andOff = icon_plen * 8; // the and offset is in bits
11575 
11576 			// leaving the and mask as the default 0 so the rgba alpha blend
11577 			// does its thing instead
11578 			for(int y = height - 1; y >= 0; y--) {
11579 				int off2 = y * width * 4;
11580 				foreach(x; 0 .. width) {
11581 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11582 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11583 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11584 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11585 
11586 					offset += 4;
11587 					off2 += 4;
11588 				}
11589 			}
11590 
11591 			return memory;
11592 		}
11593 
11594 		this(MemoryImage mi) {
11595 			int icon_len, width, height;
11596 
11597 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11598 
11599 			/*
11600 			PNG* png = readPnpngData);
11601 			PNGHeader pngh = getHeader(png);
11602 			void* icon_win32;
11603 			if(pngh.depth == 4) {
11604 				auto i = new Win32Icon!(16);
11605 				i.fromPNG(png, pngh, icon_len, width, height);
11606 				icon_win32 = i;
11607 			}
11608 			else if(pngh.depth == 8) {
11609 				auto i = new Win32Icon!(256);
11610 				i.fromPNG(png, pngh, icon_len, width, height);
11611 				icon_win32 = i;
11612 			} else assert(0);
11613 			*/
11614 
11615 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11616 
11617 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11618 		}
11619 
11620 		~this() {
11621 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11622 			DestroyIcon(hIcon);
11623 		}
11624 
11625 		HICON hIcon;
11626 	}
11627 
11628 
11629 
11630 
11631 
11632 
11633 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11634 	alias HWND NativeWindowHandle;
11635 
11636 	extern(Windows)
11637 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11638 		try {
11639 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11640 				// it returns zero if the message is handled, so we won't do anything more there
11641 				// do I like that though?
11642 				int mustReturn;
11643 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11644 				if(mustReturn)
11645 					return ret;
11646 			}
11647 
11648 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11649 				if(window.getNativeEventHandler !is null) {
11650 					int mustReturn;
11651 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11652 					if(mustReturn)
11653 						return ret;
11654 				}
11655 				if(auto w = cast(SimpleWindow) (*window))
11656 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11657 				else
11658 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11659 			} else {
11660 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11661 			}
11662 		} catch (Exception e) {
11663 			try {
11664 				sdpy_abort(e);
11665 				return 0;
11666 			} catch(Exception e) { assert(0); }
11667 		}
11668 	}
11669 
11670 	void sdpy_abort(Throwable e) nothrow {
11671 		try
11672 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11673 		catch(Exception e)
11674 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11675 		ExitProcess(1);
11676 	}
11677 
11678 	mixin template NativeScreenPainterImplementation() {
11679 		HDC hdc;
11680 		HWND hwnd;
11681 		//HDC windowHdc;
11682 		HBITMAP oldBmp;
11683 
11684 		void create(PaintingHandle window) {
11685 			hwnd = window;
11686 
11687 			if(auto sw = cast(SimpleWindow) this.window) {
11688 				// drawing on a window, double buffer
11689 				auto windowHdc = GetDC(hwnd);
11690 
11691 				auto buffer = sw.impl.buffer;
11692 				if(buffer is null) {
11693 					hdc = windowHdc;
11694 					windowDc = true;
11695 				} else {
11696 					hdc = CreateCompatibleDC(windowHdc);
11697 
11698 					ReleaseDC(hwnd, windowHdc);
11699 
11700 					oldBmp = SelectObject(hdc, buffer);
11701 				}
11702 			} else {
11703 				// drawing on something else, draw directly
11704 				hdc = CreateCompatibleDC(null);
11705 				SelectObject(hdc, window);
11706 			}
11707 
11708 			// X doesn't draw a text background, so neither should we
11709 			SetBkMode(hdc, TRANSPARENT);
11710 
11711 			ensureDefaultFontLoaded();
11712 
11713 			if(defaultGuiFont) {
11714 				SelectObject(hdc, defaultGuiFont);
11715 				// DeleteObject(defaultGuiFont);
11716 			}
11717 		}
11718 
11719 		static HFONT defaultGuiFont;
11720 		static void ensureDefaultFontLoaded() {
11721 			static bool triedDefaultGuiFont = false;
11722 			if(!triedDefaultGuiFont) {
11723 				NONCLIENTMETRICS params;
11724 				params.cbSize = params.sizeof;
11725 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11726 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11727 				}
11728 				triedDefaultGuiFont = true;
11729 			}
11730 		}
11731 
11732 		private OperatingSystemFont _activeFont;
11733 
11734 		void setFont(OperatingSystemFont font) {
11735 			_activeFont = font;
11736 			if(font && font.font) {
11737 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11738 					// error... how to handle tho?
11739 				} else {
11740 
11741 				}
11742 			}
11743 			else if(defaultGuiFont)
11744 				SelectObject(hdc, defaultGuiFont);
11745 		}
11746 
11747 		arsd.color.Rectangle _clipRectangle;
11748 
11749 		void setClipRectangle(int x, int y, int width, int height) {
11750 			auto old = _clipRectangle;
11751 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11752 			if(old == _clipRectangle)
11753 				return;
11754 
11755 			if(width == 0 || height == 0) {
11756 				SelectClipRgn(hdc, null);
11757 			} else {
11758 				auto region = CreateRectRgn(x, y, x + width, y + height);
11759 				SelectClipRgn(hdc, region);
11760 				DeleteObject(region);
11761 			}
11762 		}
11763 
11764 
11765 		// just because we can on Windows...
11766 		//void create(Image image);
11767 
11768 		void invalidateRect(Rectangle invalidRect) {
11769 			RECT rect;
11770 			rect.left = invalidRect.left;
11771 			rect.right = invalidRect.right;
11772 			rect.top = invalidRect.top;
11773 			rect.bottom = invalidRect.bottom;
11774 			InvalidateRect(hwnd, &rect, false);
11775 		}
11776 		bool manualInvalidations;
11777 
11778 		void dispose() {
11779 			// FIXME: this.window.width/height is probably wrong
11780 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11781 			// ReleaseDC(hwnd, windowHdc);
11782 
11783 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11784 			if(cast(SimpleWindow) this.window) {
11785 				if(!manualInvalidations)
11786 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11787 			}
11788 
11789 			if(originalPen !is null)
11790 				SelectObject(hdc, originalPen);
11791 			if(currentPen !is null)
11792 				DeleteObject(currentPen);
11793 			if(originalBrush !is null)
11794 				SelectObject(hdc, originalBrush);
11795 			if(currentBrush !is null)
11796 				DeleteObject(currentBrush);
11797 
11798 			SelectObject(hdc, oldBmp);
11799 
11800 			if(windowDc)
11801 				ReleaseDC(hwnd, hdc);
11802 			else
11803 				DeleteDC(hdc);
11804 
11805 			if(window.paintingFinishedDg !is null)
11806 				window.paintingFinishedDg()();
11807 		}
11808 
11809 		bool windowDc;
11810 		HPEN originalPen;
11811 		HPEN currentPen;
11812 
11813 		Pen _activePen;
11814 
11815 		Color _outlineColor;
11816 
11817 		@property void pen(Pen p) {
11818 			_activePen = p;
11819 			_outlineColor = p.color;
11820 
11821 			HPEN pen;
11822 			if(p.color.a == 0) {
11823 				pen = GetStockObject(NULL_PEN);
11824 			} else {
11825 				int style = PS_SOLID;
11826 				final switch(p.style) {
11827 					case Pen.Style.Solid:
11828 						style = PS_SOLID;
11829 					break;
11830 					case Pen.Style.Dashed:
11831 						style = PS_DASH;
11832 					break;
11833 					case Pen.Style.Dotted:
11834 						style = PS_DOT;
11835 					break;
11836 				}
11837 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11838 			}
11839 			auto orig = SelectObject(hdc, pen);
11840 			if(originalPen is null)
11841 				originalPen = orig;
11842 
11843 			if(currentPen !is null)
11844 				DeleteObject(currentPen);
11845 
11846 			currentPen = pen;
11847 
11848 			// the outline is like a foreground since it's done that way on X
11849 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11850 
11851 		}
11852 
11853 		@property void rasterOp(RasterOp op) {
11854 			int mode;
11855 			final switch(op) {
11856 				case RasterOp.normal:
11857 					mode = R2_COPYPEN;
11858 				break;
11859 				case RasterOp.xor:
11860 					mode = R2_XORPEN;
11861 				break;
11862 			}
11863 			SetROP2(hdc, mode);
11864 		}
11865 
11866 		HBRUSH originalBrush;
11867 		HBRUSH currentBrush;
11868 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11869 		@property void fillColor(Color c) {
11870 			if(c == _fillColor)
11871 				return;
11872 			_fillColor = c;
11873 			HBRUSH brush;
11874 			if(c.a == 0) {
11875 				brush = GetStockObject(HOLLOW_BRUSH);
11876 			} else {
11877 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11878 			}
11879 			auto orig = SelectObject(hdc, brush);
11880 			if(originalBrush is null)
11881 				originalBrush = orig;
11882 
11883 			if(currentBrush !is null)
11884 				DeleteObject(currentBrush);
11885 
11886 			currentBrush = brush;
11887 
11888 			// background color is NOT set because X doesn't draw text backgrounds
11889 			//   SetBkColor(hdc, RGB(255, 255, 255));
11890 		}
11891 
11892 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11893 			BITMAP bm;
11894 
11895 			HDC hdcMem = CreateCompatibleDC(hdc);
11896 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11897 
11898 			GetObject(i.handle, bm.sizeof, &bm);
11899 
11900 			// or should I AlphaBlend!??!?!
11901 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11902 
11903 			SelectObject(hdcMem, hbmOld);
11904 			DeleteDC(hdcMem);
11905 		}
11906 
11907 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11908 			BITMAP bm;
11909 
11910 			HDC hdcMem = CreateCompatibleDC(hdc);
11911 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11912 
11913 			GetObject(s.handle, bm.sizeof, &bm);
11914 
11915 			version(CRuntime_DigitalMars) goto noalpha;
11916 
11917 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11918 			if(s.enableAlpha) {
11919 				auto dw = w ? w : bm.bmWidth;
11920 				auto dh = h ? h : bm.bmHeight;
11921 				BLENDFUNCTION bf;
11922 				bf.BlendOp = AC_SRC_OVER;
11923 				bf.SourceConstantAlpha = 255;
11924 				bf.AlphaFormat = AC_SRC_ALPHA;
11925 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11926 			} else {
11927 				noalpha:
11928 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11929 			}
11930 
11931 			SelectObject(hdcMem, hbmOld);
11932 			DeleteDC(hdcMem);
11933 		}
11934 
11935 		Size textSize(scope const(char)[] text) {
11936 			bool dummyX;
11937 			if(text.length == 0) {
11938 				text = " ";
11939 				dummyX = true;
11940 			}
11941 			RECT rect;
11942 			WCharzBuffer buffer = WCharzBuffer(text);
11943 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11944 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11945 		}
11946 
11947 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11948 			if(text.length && text[$-1] == '\n')
11949 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11950 			if(text.length && text[$-1] == '\r')
11951 				text = text[0 .. $-1];
11952 
11953 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11954 			if(x2 == 0 && y2 == 0) {
11955 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11956 			} else {
11957 				RECT rect;
11958 				rect.left = x;
11959 				rect.top = y;
11960 				rect.right = x2;
11961 				rect.bottom = y2;
11962 
11963 				uint mode = DT_LEFT;
11964 				if(alignment & TextAlignment.Right)
11965 					mode = DT_RIGHT;
11966 				else if(alignment & TextAlignment.Center)
11967 					mode = DT_CENTER;
11968 
11969 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11970 				if(alignment & TextAlignment.VerticalCenter)
11971 					mode |= DT_VCENTER | DT_SINGLELINE;
11972 
11973 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11974 			}
11975 
11976 			/*
11977 			uint mode;
11978 
11979 			if(alignment & TextAlignment.Center)
11980 				mode = TA_CENTER;
11981 
11982 			SetTextAlign(hdc, mode);
11983 			*/
11984 		}
11985 
11986 		int fontHeight() {
11987 			TEXTMETRIC metric;
11988 			if(GetTextMetricsW(hdc, &metric)) {
11989 				return metric.tmHeight;
11990 			}
11991 
11992 			return 16; // idk just guessing here, maybe we should throw
11993 		}
11994 
11995 		void drawPixel(int x, int y) {
11996 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11997 		}
11998 
11999 		// The basic shapes, outlined
12000 
12001 		void drawLine(int x1, int y1, int x2, int y2) {
12002 			MoveToEx(hdc, x1, y1, null);
12003 			LineTo(hdc, x2, y2);
12004 		}
12005 
12006 		void drawRectangle(int x, int y, int width, int height) {
12007 			// FIXME: with a wider pen this might not draw quite right. im not sure.
12008 			gdi.Rectangle(hdc, x, y, x + width, y + height);
12009 		}
12010 
12011 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
12012 			RoundRect(
12013 				hdc,
12014 				upperLeft.x, upperLeft.y,
12015 				lowerRight.x, lowerRight.y,
12016 				borderRadius, borderRadius
12017 			);
12018 		}
12019 
12020 		/// Arguments are the points of the bounding rectangle
12021 		void drawEllipse(int x1, int y1, int x2, int y2) {
12022 			Ellipse(hdc, x1, y1, x2, y2);
12023 		}
12024 
12025 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
12026 			//if(length > 360*64)
12027 				//length = 360*64;
12028 
12029 			if((start == 0 && length == 360*64)) {
12030 				drawEllipse(x1, y1, x1 + width, y1 + height);
12031 			} else {
12032 				import core.stdc.math;
12033 
12034 				bool clockwise = false;
12035 				if(length < 0) {
12036 					clockwise = true;
12037 					length = -length;
12038 				}
12039 
12040 				double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323;
12041 				double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323;
12042 
12043 				auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12044 				auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12045 				auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12046 				auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12047 
12048 				if(clockwise) {
12049 					auto t1 = c1;
12050 					auto t2 = c2;
12051 					c1 = c3;
12052 					c2 = c4;
12053 					c3 = t1;
12054 					c4 = t2;
12055 				}
12056 
12057 				//if(_activePen.color.a)
12058 					//Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12059 				//if(_fillColor.a)
12060 
12061 				Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12062 			}
12063 		}
12064 
12065 		void drawPolygon(Point[] vertexes) {
12066 			POINT[] points;
12067 			points.length = vertexes.length;
12068 
12069 			foreach(i, p; vertexes) {
12070 				points[i].x = p.x;
12071 				points[i].y = p.y;
12072 			}
12073 
12074 			Polygon(hdc, points.ptr, cast(int) points.length);
12075 		}
12076 	}
12077 
12078 
12079 	// Mix this into the SimpleWindow class
12080 	mixin template NativeSimpleWindowImplementation() {
12081 		int curHidden = 0; // counter
12082 		__gshared static bool[string] knownWinClasses;
12083 		static bool altPressed = false;
12084 
12085 		HANDLE oldCursor;
12086 
12087 		void hideCursor () {
12088 			if(curHidden == 0)
12089 				oldCursor = SetCursor(null);
12090 			++curHidden;
12091 		}
12092 
12093 		void showCursor () {
12094 			--curHidden;
12095 			if(curHidden == 0) {
12096 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
12097 			}
12098 		}
12099 
12100 
12101 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
12102 
12103 		void setMinSize (int minwidth, int minheight) {
12104 			minWidth = minwidth;
12105 			minHeight = minheight;
12106 		}
12107 		void setMaxSize (int maxwidth, int maxheight) {
12108 			maxWidth = maxwidth;
12109 			maxHeight = maxheight;
12110 		}
12111 
12112 		// FIXME i'm not sure that Windows has this functionality
12113 		// though it is nonessential anyway.
12114 		void setResizeGranularity (int granx, int grany) {}
12115 
12116 		ScreenPainter getPainter(bool manualInvalidations) {
12117 			return ScreenPainter(this, hwnd, manualInvalidations);
12118 		}
12119 
12120 		HBITMAP buffer;
12121 
12122 		void setTitle(string title) {
12123 			WCharzBuffer bfr = WCharzBuffer(title);
12124 			SetWindowTextW(hwnd, bfr.ptr);
12125 		}
12126 
12127 		string getTitle() {
12128 			auto len = GetWindowTextLengthW(hwnd);
12129 			if (!len)
12130 				return null;
12131 			wchar[256] tmpBuffer;
12132 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
12133 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12134 			auto str = buffer[0 .. len2];
12135 			return makeUtf8StringFromWindowsString(str);
12136 		}
12137 
12138 		void move(int x, int y) {
12139 			RECT rect;
12140 			GetWindowRect(hwnd, &rect);
12141 			// move it while maintaining the same size...
12142 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
12143 		}
12144 
12145 		void resize(int w, int h) {
12146 			RECT rect;
12147 			GetWindowRect(hwnd, &rect);
12148 
12149 			RECT client;
12150 			GetClientRect(hwnd, &client);
12151 
12152 			rect.right = rect.right - client.right + w;
12153 			rect.bottom = rect.bottom - client.bottom + h;
12154 
12155 			// same position, new size for the client rectangle
12156 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
12157 
12158 			updateOpenglViewportIfNeeded(w, h);
12159 		}
12160 
12161 		void moveResize (int x, int y, int w, int h) {
12162 			// what's given is the client rectangle, we need to adjust
12163 
12164 			RECT rect;
12165 			rect.left = x;
12166 			rect.top = y;
12167 			rect.right = w + x;
12168 			rect.bottom = h + y;
12169 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
12170 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
12171 
12172 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
12173 			updateOpenglViewportIfNeeded(w, h);
12174 			if (windowResized !is null) windowResized(w, h);
12175 		}
12176 
12177 		version(without_opengl) {} else {
12178 			HGLRC ghRC;
12179 			HDC ghDC;
12180 		}
12181 
12182 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
12183 			string cnamec;
12184 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
12185 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
12186 				cnamec = "DSimpleWindow";
12187 			} else {
12188 				cnamec = sdpyWindowClass;
12189 			}
12190 
12191 			WCharzBuffer cn = WCharzBuffer(cnamec);
12192 
12193 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
12194 
12195 			if(cnamec !in knownWinClasses) {
12196 				WNDCLASSEX wc;
12197 
12198 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
12199 				// to the object. Maybe.
12200 				wc.cbSize = wc.sizeof;
12201 				wc.cbClsExtra = 0;
12202 				wc.cbWndExtra = 0;
12203 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
12204 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
12205 				wc.hIcon = LoadIcon(hInstance, null);
12206 				wc.hInstance = hInstance;
12207 				wc.lpfnWndProc = &WndProc;
12208 				wc.lpszClassName = cn.ptr;
12209 				wc.hIconSm = null;
12210 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
12211 				if(!RegisterClassExW(&wc))
12212 					throw new WindowsApiException("RegisterClassExW", GetLastError());
12213 				knownWinClasses[cnamec] = true;
12214 			}
12215 
12216 			int style;
12217 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
12218 
12219 			// FIXME: windowType and customizationFlags
12220 			final switch(windowType) {
12221 				case WindowTypes.normal:
12222 					if(resizability == Resizability.fixedSize) {
12223 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
12224 					} else {
12225 						style = WS_OVERLAPPEDWINDOW;
12226 					}
12227 				break;
12228 				case WindowTypes.undecorated:
12229 					style = WS_POPUP | WS_SYSMENU;
12230 				break;
12231 				case WindowTypes.eventOnly:
12232 					_hidden = true;
12233 				break;
12234 				case WindowTypes.dropdownMenu:
12235 				case WindowTypes.popupMenu:
12236 				case WindowTypes.notification:
12237 					style = WS_POPUP;
12238 					flags |= WS_EX_NOACTIVATE;
12239 				break;
12240 				case WindowTypes.nestedChild:
12241 					style = WS_CHILD;
12242 				break;
12243 				case WindowTypes.minimallyWrapped:
12244 					assert(0, "construct minimally wrapped through the other ctor overlad");
12245 			}
12246 
12247 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12248 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
12249 
12250 			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
12251 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
12252 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
12253 
12254 			if(!hwnd)
12255 				throw new WindowsApiException("CreateWindowEx", GetLastError());
12256 
12257 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12258 				setOpacity(255);
12259 
12260 			SimpleWindow.nativeMapping[hwnd] = this;
12261 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
12262 
12263 			if(windowType == WindowTypes.eventOnly)
12264 				return;
12265 
12266 			HDC hdc = GetDC(hwnd);
12267 
12268 			if(!hdc)
12269 				throw new WindowsApiException("GetDC", GetLastError());
12270 
12271 			version(without_opengl) {}
12272 			else {
12273 				if(opengl == OpenGlOptions.yes) {
12274 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
12275 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
12276 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
12277 					ghDC = hdc;
12278 					PIXELFORMATDESCRIPTOR pfd;
12279 
12280 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
12281 					pfd.nVersion = 1;
12282 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
12283 					pfd.dwLayerMask = PFD_MAIN_PLANE;
12284 					pfd.iPixelType = PFD_TYPE_RGBA;
12285 					pfd.cColorBits = 24;
12286 					pfd.cDepthBits = 24;
12287 					pfd.cAccumBits = 0;
12288 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
12289 
12290 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
12291 
12292 					if (pixelformat == 0)
12293 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
12294 
12295 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
12296 						throw new WindowsApiException("SetPixelFormat", GetLastError());
12297 
12298 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
12299 						// windoze is idiotic: we have to have OpenGL context to get function addresses
12300 						// so we will create fake context to get that stupid address
12301 						auto tmpcc = wglCreateContext(ghDC);
12302 						if (tmpcc !is null) {
12303 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
12304 							wglMakeCurrent(ghDC, tmpcc);
12305 							wglInitOtherFunctions();
12306 						}
12307 					}
12308 
12309 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
12310 						int[9] contextAttribs = [
12311 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
12312 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
12313 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
12314 							// for modern context, set "forward compatibility" flag too
12315 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
12316 							0/*None*/,
12317 						];
12318 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
12319 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
12320 							// activate fallback mode
12321 							// 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;
12322 							ghRC = wglCreateContext(ghDC);
12323 						}
12324 						if (ghRC is null)
12325 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
12326 					} else {
12327 						// try to do at least something
12328 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
12329 							sdpyOpenGLContextVersion = 0;
12330 							ghRC = wglCreateContext(ghDC);
12331 						}
12332 						if (ghRC is null)
12333 							throw new WindowsApiException("wglCreateContext", GetLastError());
12334 					}
12335 				}
12336 			}
12337 
12338 			if(opengl == OpenGlOptions.no) {
12339 				buffer = CreateCompatibleBitmap(hdc, width, height);
12340 
12341 				auto hdcBmp = CreateCompatibleDC(hdc);
12342 				// make sure it's filled with a blank slate
12343 				auto oldBmp = SelectObject(hdcBmp, buffer);
12344 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
12345 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
12346 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
12347 				SelectObject(hdcBmp, oldBmp);
12348 				SelectObject(hdcBmp, oldBrush);
12349 				SelectObject(hdcBmp, oldPen);
12350 				DeleteDC(hdcBmp);
12351 
12352 				bmpWidth = width;
12353 				bmpHeight = height;
12354 
12355 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
12356 			}
12357 
12358 			// We want the window's client area to match the image size
12359 			RECT rcClient, rcWindow;
12360 			POINT ptDiff;
12361 			GetClientRect(hwnd, &rcClient);
12362 			GetWindowRect(hwnd, &rcWindow);
12363 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
12364 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
12365 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
12366 
12367 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
12368 				ShowWindow(hwnd, SW_SHOWNORMAL);
12369 			} else {
12370 				_hidden = true;
12371 			}
12372 			this._visibleForTheFirstTimeCalled = false; // hack!
12373 		}
12374 
12375 
12376 		void dispose() {
12377 			if(buffer)
12378 				DeleteObject(buffer);
12379 		}
12380 
12381 		void closeWindow() {
12382 			if(ghRC) {
12383 				wglDeleteContext(ghRC);
12384 				ghRC = null;
12385 			}
12386 			DestroyWindow(hwnd);
12387 		}
12388 
12389 		bool setOpacity(ubyte alpha) {
12390 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
12391 		}
12392 
12393 		HANDLE currentCursor;
12394 
12395 		// returns zero if it recognized the event
12396 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
12397 			MouseEvent mouse;
12398 
12399 			void mouseEvent(bool isScreen, ulong mods) {
12400 				auto x = LOWORD(lParam);
12401 				auto y = HIWORD(lParam);
12402 				if(isScreen) {
12403 					POINT p;
12404 					p.x = x;
12405 					p.y = y;
12406 					ScreenToClient(hwnd, &p);
12407 					x = cast(ushort) p.x;
12408 					y = cast(ushort) p.y;
12409 				}
12410 
12411 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
12412 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
12413 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
12414 				}
12415 
12416 				mouse.x = x + offsetX;
12417 				mouse.y = y + offsetY;
12418 
12419 				wind.mdx(mouse);
12420 				mouse.modifierState = cast(int) mods;
12421 				mouse.window = wind;
12422 
12423 				if(wind.handleMouseEvent)
12424 					wind.handleMouseEvent(mouse);
12425 			}
12426 
12427 			switch(msg) {
12428 				case WM_GETMINMAXINFO:
12429 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
12430 
12431 					if(wind.minWidth > 0) {
12432 						RECT rect;
12433 						rect.left = 100;
12434 						rect.top = 100;
12435 						rect.right = wind.minWidth + 100;
12436 						rect.bottom = wind.minHeight + 100;
12437 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12438 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12439 
12440 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12441 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12442 					}
12443 
12444 					if(wind.maxWidth < int.max) {
12445 						RECT rect;
12446 						rect.left = 100;
12447 						rect.top = 100;
12448 						rect.right = wind.maxWidth + 100;
12449 						rect.bottom = wind.maxHeight + 100;
12450 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12451 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12452 
12453 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12454 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12455 					}
12456 				break;
12457 				case WM_CHAR:
12458 					wchar c = cast(wchar) wParam;
12459 					if(wind.handleCharEvent)
12460 						wind.handleCharEvent(cast(dchar) c);
12461 				break;
12462 				  case WM_SETFOCUS:
12463 				  case WM_KILLFOCUS:
12464 					wind._focused = (msg == WM_SETFOCUS);
12465 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12466 					if(wind.onFocusChange)
12467 						wind.onFocusChange(msg == WM_SETFOCUS);
12468 				  break;
12469 
12470 				case WM_SYSKEYDOWN:
12471 					goto case;
12472 				case WM_SYSKEYUP:
12473 					if(lParam & (1 << 29)) {
12474 						goto case;
12475 					} else {
12476 						// no window has keyboard focus
12477 						goto default;
12478 					}
12479 				case WM_KEYDOWN:
12480 				case WM_KEYUP:
12481 					KeyEvent ev;
12482 					ev.key = cast(Key) wParam;
12483 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12484 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12485 
12486 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12487 
12488 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12489 						ev.modifierState |= ModifierState.shift;
12490 					//k8: this doesn't work; thanks for nothing, windows
12491 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12492 						ev.modifierState |= ModifierState.alt;*/
12493 					// this never seems to actually be set
12494 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12495 
12496 					if (wParam == 0x12) {
12497 						altPressed = (msg == WM_SYSKEYDOWN);
12498 					}
12499 
12500 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12501 						altPressed = false;
12502 					}
12503 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12504 
12505 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12506 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12507 						ev.modifierState |= ModifierState.ctrl;
12508 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12509 						ev.modifierState |= ModifierState.windows;
12510 					if(GetKeyState(Key.NumLock))
12511 						ev.modifierState |= ModifierState.numLock;
12512 					if(GetKeyState(Key.CapsLock))
12513 						ev.modifierState |= ModifierState.capsLock;
12514 
12515 					/+
12516 					// we always want to send the character too, so let's convert it
12517 					ubyte[256] state;
12518 					wchar[16] buffer;
12519 					GetKeyboardState(state.ptr);
12520 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12521 
12522 					foreach(dchar d; buffer) {
12523 						ev.character = d;
12524 						break;
12525 					}
12526 					+/
12527 
12528 					ev.window = wind;
12529 					if(wind.handleKeyEvent)
12530 						wind.handleKeyEvent(ev);
12531 				break;
12532 				case 0x020a /*WM_MOUSEWHEEL*/:
12533 					// send click
12534 					mouse.type = cast(MouseEventType) 1;
12535 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12536 					mouseEvent(true, LOWORD(wParam));
12537 
12538 					// also send release
12539 					mouse.type = cast(MouseEventType) 2;
12540 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12541 					mouseEvent(true, LOWORD(wParam));
12542 				break;
12543 				case WM_MOUSEMOVE:
12544 					mouse.type = cast(MouseEventType) 0;
12545 					mouseEvent(false, wParam);
12546 				break;
12547 				case WM_LBUTTONDOWN:
12548 				case WM_LBUTTONDBLCLK:
12549 					mouse.type = cast(MouseEventType) 1;
12550 					mouse.button = MouseButton.left;
12551 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12552 					mouseEvent(false, wParam);
12553 				break;
12554 				case WM_LBUTTONUP:
12555 					mouse.type = cast(MouseEventType) 2;
12556 					mouse.button = MouseButton.left;
12557 					mouseEvent(false, wParam);
12558 				break;
12559 				case WM_RBUTTONDOWN:
12560 				case WM_RBUTTONDBLCLK:
12561 					mouse.type = cast(MouseEventType) 1;
12562 					mouse.button = MouseButton.right;
12563 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12564 					mouseEvent(false, wParam);
12565 				break;
12566 				case WM_RBUTTONUP:
12567 					mouse.type = cast(MouseEventType) 2;
12568 					mouse.button = MouseButton.right;
12569 					mouseEvent(false, wParam);
12570 				break;
12571 				case WM_MBUTTONDOWN:
12572 				case WM_MBUTTONDBLCLK:
12573 					mouse.type = cast(MouseEventType) 1;
12574 					mouse.button = MouseButton.middle;
12575 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12576 					mouseEvent(false, wParam);
12577 				break;
12578 				case WM_MBUTTONUP:
12579 					mouse.type = cast(MouseEventType) 2;
12580 					mouse.button = MouseButton.middle;
12581 					mouseEvent(false, wParam);
12582 				break;
12583 				case WM_XBUTTONDOWN:
12584 				case WM_XBUTTONDBLCLK:
12585 					mouse.type = cast(MouseEventType) 1;
12586 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12587 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12588 					mouseEvent(false, wParam);
12589 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12590 				case WM_XBUTTONUP:
12591 					mouse.type = cast(MouseEventType) 2;
12592 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12593 					mouseEvent(false, wParam);
12594 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12595 
12596 				default: return 1;
12597 			}
12598 			return 0;
12599 		}
12600 
12601 		HWND hwnd;
12602 		private int oldWidth;
12603 		private int oldHeight;
12604 		private bool inSizeMove;
12605 
12606 		/++
12607 			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.
12608 
12609 			History:
12610 				Added November 23, 2021
12611 
12612 				Not fully stable, may be moved out of the impl struct.
12613 
12614 				Default value changed to `true` on February 15, 2021
12615 		+/
12616 		bool doLiveResizing = true;
12617 
12618 		package int bmpWidth;
12619 		package int bmpHeight;
12620 
12621 		// the extern(Windows) wndproc should just forward to this
12622 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12623 		try {
12624 			assert(hwnd is this.hwnd);
12625 
12626 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12627 			switch(msg) {
12628 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12629 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12630 					// The main things we can do are select, execute, close, or ignore
12631 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12632 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12633 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12634 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12635 
12636 					// returns the value in the *high order word* of the return value
12637 					// hence the << 16
12638 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12639 				case WM_SETCURSOR:
12640 					if(cast(HWND) wParam !is hwnd)
12641 						return 0; // further processing elsewhere
12642 
12643 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12644 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12645 						return 1;
12646 					} else {
12647 						return DefWindowProc(hwnd, msg, wParam, lParam);
12648 					}
12649 				//break;
12650 
12651 				case WM_CLOSE:
12652 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12653 				break;
12654 				case WM_DESTROY:
12655 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12656 					SimpleWindow.nativeMapping.remove(hwnd);
12657 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12658 
12659 					bool anyImportant = false;
12660 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12661 						if(w.beingOpenKeepsAppOpen) {
12662 							anyImportant = true;
12663 							break;
12664 						}
12665 					if(!anyImportant) {
12666 						PostQuitMessage(0);
12667 					}
12668 				break;
12669 				case 0x02E0 /*WM_DPICHANGED*/:
12670 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12671 
12672 					RECT* prcNewWindow = cast(RECT*)lParam;
12673 					// docs say this is the recommended position and we should honor it
12674 					SetWindowPos(hwnd,
12675 							null,
12676 							prcNewWindow.left,
12677 							prcNewWindow.top,
12678 							prcNewWindow.right - prcNewWindow.left,
12679 							prcNewWindow.bottom - prcNewWindow.top,
12680 							SWP_NOZORDER | SWP_NOACTIVATE);
12681 
12682 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12683 					// im not sure it is completely correct
12684 					// but without it the tabs and such do look weird as things change.
12685 					if(SystemParametersInfoForDpi) {
12686 						LOGFONT lfText;
12687 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12688 						HFONT hFontNew = CreateFontIndirect(&lfText);
12689 						if (hFontNew)
12690 						{
12691 							//DeleteObject(hFontOld);
12692 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12693 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12694 								return TRUE;
12695 							}
12696 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12697 						}
12698 					}
12699 
12700 					if(this.onDpiChanged)
12701 						this.onDpiChanged();
12702 				break;
12703 				case WM_ENTERIDLE:
12704 					// when a menu is up, it stops normal event processing (modal message loop)
12705 					// but this at least gives us a chance to SOMETIMES catch up
12706 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12707 					SimpleWindow.processAllCustomEvents;
12708 					SimpleWindow.processAllCustomEvents;
12709 					SleepEx(0, true);
12710 					break;
12711 				case WM_SIZE:
12712 					if(wParam == 1 /* SIZE_MINIMIZED */)
12713 						break;
12714 					_width = LOWORD(lParam);
12715 					_height = HIWORD(lParam);
12716 
12717 					// I want to avoid tearing in the windows (my code is inefficient
12718 					// so this is a hack around that) so while sizing, we don't trigger,
12719 					// but we do want to trigger on events like mazimize.
12720 					if(!inSizeMove || doLiveResizing)
12721 						goto size_changed;
12722 				break;
12723 				/+
12724 				case WM_SIZING:
12725 					writeln("size");
12726 				break;
12727 				+/
12728 				// I don't like the tearing I get when redrawing on WM_SIZE
12729 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12730 				// so instead it is going to redraw only at the end of a size.
12731 				case 0x0231: /* WM_ENTERSIZEMOVE */
12732 					inSizeMove = true;
12733 				break;
12734 				case 0x0232: /* WM_EXITSIZEMOVE */
12735 					inSizeMove = false;
12736 
12737 					size_changed:
12738 
12739 					// nothing relevant changed, don't bother redrawing
12740 					if(oldWidth == _width && oldHeight == _height) {
12741 						if(msg == 0x0232)
12742 							goto finalize_resize;
12743 						break;
12744 					}
12745 
12746 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12747 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12748 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12749 						// gotta get the double buffer bmp to match the window
12750 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12751 
12752 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12753 						if(resizability != Resizability.automaticallyScaleIfPossible)
12754 						if(_width > bmpWidth || _height > bmpHeight) {
12755 							auto hdc = GetDC(hwnd);
12756 							auto oldBuffer = buffer;
12757 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12758 
12759 							auto hdcBmp = CreateCompatibleDC(hdc);
12760 							auto oldBmp = SelectObject(hdcBmp, buffer);
12761 
12762 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12763 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12764 
12765 							/+
12766 							RECT r;
12767 							r.left = 0;
12768 							r.top = 0;
12769 							r.right = width;
12770 							r.bottom = height;
12771 							auto c = Color.green;
12772 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12773 							FillRect(hdcBmp, &r, brush);
12774 							DeleteObject(brush);
12775 							+/
12776 
12777 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12778 
12779 							bmpWidth = _width;
12780 							bmpHeight = _height;
12781 
12782 							SelectObject(hdcOldBmp, oldOldBmp);
12783 							DeleteDC(hdcOldBmp);
12784 
12785 							SelectObject(hdcBmp, oldBmp);
12786 							DeleteDC(hdcBmp);
12787 
12788 							ReleaseDC(hwnd, hdc);
12789 
12790 							DeleteObject(oldBuffer);
12791 						}
12792 					}
12793 
12794 					updateOpenglViewportIfNeeded(_width, _height);
12795 
12796 					if(resizability != Resizability.automaticallyScaleIfPossible)
12797 					if(windowResized !is null)
12798 						windowResized(_width, _height);
12799 
12800 					/+
12801 					if(inSizeMove) {
12802 						// SimpleWindow.processAllCustomEvents();
12803 						// SimpleWindow.processAllCustomEvents();
12804 
12805 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12806 						//sdpyPrintDebugString("redraw b");
12807 					} else {
12808 					+/ {
12809 						finalize_resize:
12810 						// when it is all done, make sure everything is freshly drawn or there might be
12811 						// weird bugs left.
12812 						SimpleWindow.processAllCustomEvents();
12813 						SimpleWindow.processAllCustomEvents();
12814 
12815 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12816 						// sdpyPrintDebugString("redraw");
12817 					}
12818 
12819 					oldWidth = this._width;
12820 					oldHeight = this._height;
12821 				break;
12822 				case WM_ERASEBKGND:
12823 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12824 					if (!this._visibleForTheFirstTimeCalled) {
12825 						this._visibleForTheFirstTimeCalled = true;
12826 						if (this.visibleForTheFirstTime !is null) {
12827 							this.visibleForTheFirstTime();
12828 						}
12829 					}
12830 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12831 					version(without_opengl) {} else {
12832 						if (openglMode == OpenGlOptions.yes) return 1;
12833 					}
12834 					// call windows default handler, so it can paint standard controls
12835 					goto default;
12836 				case WM_CTLCOLORBTN:
12837 				case WM_CTLCOLORSTATIC:
12838 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12839 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12840 					GetSysColorBrush(COLOR_3DFACE);
12841 				//break;
12842 				case WM_SHOWWINDOW:
12843 					this._visible = (wParam != 0);
12844 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12845 						this._visibleForTheFirstTimeCalled = true;
12846 						if (this.visibleForTheFirstTime !is null) {
12847 							this.visibleForTheFirstTime();
12848 						}
12849 					}
12850 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12851 					break;
12852 				case WM_PAINT: {
12853 					if (!this._visibleForTheFirstTimeCalled) {
12854 						this._visibleForTheFirstTimeCalled = true;
12855 						if (this.visibleForTheFirstTime !is null) {
12856 							this.visibleForTheFirstTime();
12857 						}
12858 					}
12859 
12860 					BITMAP bm;
12861 					PAINTSTRUCT ps;
12862 
12863 					HDC hdc = BeginPaint(hwnd, &ps);
12864 
12865 					if(openglMode == OpenGlOptions.no) {
12866 
12867 						HDC hdcMem = CreateCompatibleDC(hdc);
12868 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12869 
12870 						GetObject(buffer, bm.sizeof, &bm);
12871 
12872 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12873 						if(resizability == Resizability.automaticallyScaleIfPossible)
12874 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12875 						else
12876 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12877 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12878 
12879 						SelectObject(hdcMem, hbmOld);
12880 						DeleteDC(hdcMem);
12881 						EndPaint(hwnd, &ps);
12882 					} else {
12883 						EndPaint(hwnd, &ps);
12884 						version(without_opengl) {} else
12885 							redrawOpenGlSceneSoon();
12886 					}
12887 				} break;
12888 				  default:
12889 					return DefWindowProc(hwnd, msg, wParam, lParam);
12890 			}
12891 			 return 0;
12892 
12893 		}
12894 		catch(Throwable t) {
12895 			sdpyPrintDebugString(t.toString);
12896 			return 0;
12897 		}
12898 		}
12899 	}
12900 
12901 	mixin template NativeImageImplementation() {
12902 		HBITMAP handle;
12903 		ubyte* rawData;
12904 
12905 	final:
12906 
12907 		Color getPixel(int x, int y) @system {
12908 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12909 			// remember, bmps are upside down
12910 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12911 
12912 			Color c;
12913 			if(enableAlpha)
12914 				c.a = rawData[offset + 3];
12915 			else
12916 				c.a = 255;
12917 			c.b = rawData[offset + 0];
12918 			c.g = rawData[offset + 1];
12919 			c.r = rawData[offset + 2];
12920 			c.unPremultiply();
12921 			return c;
12922 		}
12923 
12924 		void setPixel(int x, int y, Color c) @system {
12925 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12926 			// remember, bmps are upside down
12927 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12928 
12929 			if(enableAlpha)
12930 				c.premultiply();
12931 
12932 			rawData[offset + 0] = c.b;
12933 			rawData[offset + 1] = c.g;
12934 			rawData[offset + 2] = c.r;
12935 			if(enableAlpha)
12936 				rawData[offset + 3] = c.a;
12937 		}
12938 
12939 		void convertToRgbaBytes(ubyte[] where) @system {
12940 			assert(where.length == this.width * this.height * 4);
12941 
12942 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12943 			int idx = 0;
12944 			int offset = itemsPerLine * (height - 1);
12945 			// remember, bmps are upside down
12946 			for(int y = height - 1; y >= 0; y--) {
12947 				auto offsetStart = offset;
12948 				for(int x = 0; x < width; x++) {
12949 					where[idx + 0] = rawData[offset + 2]; // r
12950 					where[idx + 1] = rawData[offset + 1]; // g
12951 					where[idx + 2] = rawData[offset + 0]; // b
12952 					if(enableAlpha) {
12953 						where[idx + 3] = rawData[offset + 3]; // a
12954 						unPremultiplyRgba(where[idx .. idx + 4]);
12955 						offset++;
12956 					} else
12957 						where[idx + 3] = 255; // a
12958 					idx += 4;
12959 					offset += 3;
12960 				}
12961 
12962 				offset = offsetStart - itemsPerLine;
12963 			}
12964 		}
12965 
12966 		void setFromRgbaBytes(in ubyte[] what) @system {
12967 			assert(what.length == this.width * this.height * 4);
12968 
12969 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12970 			int idx = 0;
12971 			int offset = itemsPerLine * (height - 1);
12972 			// remember, bmps are upside down
12973 			for(int y = height - 1; y >= 0; y--) {
12974 				auto offsetStart = offset;
12975 				for(int x = 0; x < width; x++) {
12976 					if(enableAlpha) {
12977 						auto a = what[idx + 3];
12978 
12979 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12980 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12981 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12982 						rawData[offset + 3] = a; // a
12983 						//premultiplyBgra(rawData[offset .. offset + 4]);
12984 						offset++;
12985 					} else {
12986 						rawData[offset + 2] = what[idx + 0]; // r
12987 						rawData[offset + 1] = what[idx + 1]; // g
12988 						rawData[offset + 0] = what[idx + 2]; // b
12989 					}
12990 					idx += 4;
12991 					offset += 3;
12992 				}
12993 
12994 				offset = offsetStart - itemsPerLine;
12995 			}
12996 		}
12997 
12998 
12999 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
13000 			BITMAPINFO infoheader;
13001 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
13002 			infoheader.bmiHeader.biWidth = width;
13003 			infoheader.bmiHeader.biHeight = height;
13004 			infoheader.bmiHeader.biPlanes = 1;
13005 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
13006 			infoheader.bmiHeader.biCompression = BI_RGB;
13007 
13008 			handle = CreateDIBSection(
13009 				null,
13010 				&infoheader,
13011 				DIB_RGB_COLORS,
13012 				cast(void**) &rawData,
13013 				null,
13014 				0);
13015 			if(handle is null)
13016 				throw new WindowsApiException("create image failed", GetLastError());
13017 
13018 		}
13019 
13020 		void dispose() {
13021 			DeleteObject(handle);
13022 		}
13023 	}
13024 
13025 	enum KEY_ESCAPE = 27;
13026 }
13027 version(X11) {
13028 	/// This is the default font used. You might change this before doing anything else with
13029 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
13030 	/// for cross-platform compatibility.
13031 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13032 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13033 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
13034 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
13035 
13036 	alias int delegate(XEvent) NativeEventHandler;
13037 	alias Window NativeWindowHandle;
13038 
13039 	enum KEY_ESCAPE = 9;
13040 
13041 	mixin template NativeScreenPainterImplementation() {
13042 		Display* display;
13043 		Drawable d;
13044 		Drawable destiny;
13045 
13046 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
13047 		GC gc;
13048 
13049 		__gshared bool fontAttempted;
13050 
13051 		__gshared XFontStruct* defaultfont;
13052 		__gshared XFontSet defaultfontset;
13053 
13054 		XFontStruct* font;
13055 		XFontSet fontset;
13056 
13057 		void create(PaintingHandle window) {
13058 			this.display = XDisplayConnection.get();
13059 
13060 			Drawable buffer = None;
13061 			if(auto sw = cast(SimpleWindow) this.window) {
13062 				buffer = sw.impl.buffer;
13063 				this.destiny = cast(Drawable) window;
13064 			} else {
13065 				buffer = cast(Drawable) window;
13066 				this.destiny = None;
13067 			}
13068 
13069 			this.d = cast(Drawable) buffer;
13070 
13071 			auto dgc = DefaultGC(display, DefaultScreen(display));
13072 
13073 			this.gc = XCreateGC(display, d, 0, null);
13074 
13075 			XCopyGC(display, dgc, 0xffffffff, this.gc);
13076 
13077 			ensureDefaultFontLoaded();
13078 
13079 			font = defaultfont;
13080 			fontset = defaultfontset;
13081 
13082 			if(font) {
13083 				XSetFont(display, gc, font.fid);
13084 			}
13085 		}
13086 
13087 		static void ensureDefaultFontLoaded() {
13088 			if(!fontAttempted) {
13089 				auto display = XDisplayConnection.get;
13090 				auto font = XLoadQueryFont(display, xfontstr.ptr);
13091 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
13092 				if(font is null) {
13093 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
13094 					font = XLoadQueryFont(display, xfontstr.ptr);
13095 				}
13096 
13097 				char** lol;
13098 				int lol2;
13099 				char* lol3;
13100 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
13101 
13102 				fontAttempted = true;
13103 
13104 				defaultfont = font;
13105 				defaultfontset = fontset;
13106 			}
13107 		}
13108 
13109 		arsd.color.Rectangle _clipRectangle;
13110 		void setClipRectangle(int x, int y, int width, int height) {
13111 			auto old = _clipRectangle;
13112 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
13113 			if(old == _clipRectangle)
13114 				return;
13115 
13116 			if(width == 0 || height == 0) {
13117 				XSetClipMask(display, gc, None);
13118 
13119 				if(xrenderPicturePainter) {
13120 
13121 					XRectangle[1] rects;
13122 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
13123 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13124 				}
13125 
13126 				version(with_xft) {
13127 					if(xftFont is null || xftDraw is null)
13128 						return;
13129 					XftDrawSetClip(xftDraw, null);
13130 				}
13131 			} else {
13132 				XRectangle[1] rects;
13133 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
13134 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
13135 
13136 				if(xrenderPicturePainter)
13137 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13138 
13139 				version(with_xft) {
13140 					if(xftFont is null || xftDraw is null)
13141 						return;
13142 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
13143 				}
13144 			}
13145 		}
13146 
13147 		version(with_xft) {
13148 			XftFont* xftFont;
13149 			XftDraw* xftDraw;
13150 
13151 			XftColor xftColor;
13152 
13153 			void updateXftColor() {
13154 				if(xftFont is null)
13155 					return;
13156 
13157 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
13158 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
13159 
13160 				XftColorAllocValue(
13161 					display,
13162 					DefaultVisual(display, DefaultScreen(display)),
13163 					DefaultColormap(display, 0),
13164 					&colorIn,
13165 					&xftColor
13166 				);
13167 			}
13168 		}
13169 
13170 		private OperatingSystemFont _activeFont;
13171 		void setFont(OperatingSystemFont font) {
13172 			_activeFont = font;
13173 			version(with_xft) {
13174 				if(font && font.isXft && font.xftFont)
13175 					this.xftFont = font.xftFont;
13176 				else
13177 					this.xftFont = null;
13178 
13179 				if(this.xftFont) {
13180 					if(xftDraw is null) {
13181 						xftDraw = XftDrawCreate(
13182 							display,
13183 							d,
13184 							DefaultVisual(display, DefaultScreen(display)),
13185 							DefaultColormap(display, 0)
13186 						);
13187 
13188 						updateXftColor();
13189 					}
13190 
13191 					return;
13192 				}
13193 			}
13194 
13195 			if(font && font.font) {
13196 				this.font = font.font;
13197 				this.fontset = font.fontset;
13198 				XSetFont(display, gc, font.font.fid);
13199 			} else {
13200 				this.font = defaultfont;
13201 				this.fontset = defaultfontset;
13202 			}
13203 
13204 		}
13205 
13206 		private Picture xrenderPicturePainter;
13207 
13208 		bool manualInvalidations;
13209 		void invalidateRect(Rectangle invalidRect) {
13210 			// FIXME if manualInvalidations
13211 		}
13212 
13213 		void dispose() {
13214 			this.rasterOp = RasterOp.normal;
13215 
13216 			if(xrenderPicturePainter) {
13217 				XRenderFreePicture(display, xrenderPicturePainter);
13218 				xrenderPicturePainter = None;
13219 			}
13220 
13221 			// FIXME: this.window.width/height is probably wrong
13222 
13223 			// src x,y     then dest x, y
13224 			if(destiny != None) {
13225 				// FIXME: if manual invalidations we can actually only copy some of the area.
13226 				// if(manualInvalidations)
13227 				XSetClipMask(display, gc, None);
13228 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
13229 			}
13230 
13231 			XFreeGC(display, gc);
13232 
13233 			version(with_xft)
13234 			if(xftDraw) {
13235 				XftDrawDestroy(xftDraw);
13236 				xftDraw = null;
13237 			}
13238 
13239 			/+
13240 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
13241 			if(font && font !is defaultfont) {
13242 				XFreeFont(display, font);
13243 				font = null;
13244 			}
13245 			if(fontset && fontset !is defaultfontset) {
13246 				XFreeFontSet(display, fontset);
13247 				fontset = null;
13248 			}
13249 			+/
13250 			XFlush(display);
13251 
13252 			if(window.paintingFinishedDg !is null)
13253 				window.paintingFinishedDg()();
13254 		}
13255 
13256 		bool backgroundIsNotTransparent = true;
13257 		bool foregroundIsNotTransparent = true;
13258 
13259 		bool _penInitialized = false;
13260 		Pen _activePen;
13261 
13262 		Color _outlineColor;
13263 		Color _fillColor;
13264 
13265 		@property void pen(Pen p) {
13266 			if(_penInitialized && p == _activePen) {
13267 				return;
13268 			}
13269 			_penInitialized = true;
13270 			_activePen = p;
13271 			_outlineColor = p.color;
13272 
13273 			int style;
13274 
13275 			byte dashLength;
13276 
13277 			final switch(p.style) {
13278 				case Pen.Style.Solid:
13279 					style = 0 /*LineSolid*/;
13280 				break;
13281 				case Pen.Style.Dashed:
13282 					style = 1 /*LineOnOffDash*/;
13283 					dashLength = 4;
13284 				break;
13285 				case Pen.Style.Dotted:
13286 					style = 1 /*LineOnOffDash*/;
13287 					dashLength = 1;
13288 				break;
13289 			}
13290 
13291 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
13292 			if(dashLength)
13293 				XSetDashes(display, gc, 0, &dashLength, 1);
13294 
13295 			if(p.color.a == 0) {
13296 				foregroundIsNotTransparent = false;
13297 				return;
13298 			}
13299 
13300 			foregroundIsNotTransparent = true;
13301 
13302 			XSetForeground(display, gc, colorToX(p.color, display));
13303 
13304 			version(with_xft)
13305 				updateXftColor();
13306 		}
13307 
13308 		RasterOp _currentRasterOp;
13309 		bool _currentRasterOpInitialized = false;
13310 		@property void rasterOp(RasterOp op) {
13311 			if(_currentRasterOpInitialized && _currentRasterOp == op)
13312 				return;
13313 			_currentRasterOp = op;
13314 			_currentRasterOpInitialized = true;
13315 			int mode;
13316 			final switch(op) {
13317 				case RasterOp.normal:
13318 					mode = GXcopy;
13319 				break;
13320 				case RasterOp.xor:
13321 					mode = GXxor;
13322 				break;
13323 			}
13324 			XSetFunction(display, gc, mode);
13325 		}
13326 
13327 
13328 		bool _fillColorInitialized = false;
13329 
13330 		@property void fillColor(Color c) {
13331 			if(_fillColorInitialized && _fillColor == c)
13332 				return; // already good, no need to waste time calling it
13333 			_fillColor = c;
13334 			_fillColorInitialized = true;
13335 			if(c.a == 0) {
13336 				backgroundIsNotTransparent = false;
13337 				return;
13338 			}
13339 
13340 			backgroundIsNotTransparent = true;
13341 
13342 			XSetBackground(display, gc, colorToX(c, display));
13343 
13344 		}
13345 
13346 		void swapColors() {
13347 			auto tmp = _fillColor;
13348 			fillColor = _outlineColor;
13349 			auto newPen = _activePen;
13350 			newPen.color = tmp;
13351 			pen(newPen);
13352 		}
13353 
13354 		uint colorToX(Color c, Display* display) {
13355 			auto visual = DefaultVisual(display, DefaultScreen(display));
13356 			import core.bitop;
13357 			uint color = 0;
13358 			{
13359 			auto startBit = bsf(visual.red_mask);
13360 			auto lastBit = bsr(visual.red_mask);
13361 			auto r = cast(uint) c.r;
13362 			r >>= 7 - (lastBit - startBit);
13363 			r <<= startBit;
13364 			color |= r;
13365 			}
13366 			{
13367 			auto startBit = bsf(visual.green_mask);
13368 			auto lastBit = bsr(visual.green_mask);
13369 			auto g = cast(uint) c.g;
13370 			g >>= 7 - (lastBit - startBit);
13371 			g <<= startBit;
13372 			color |= g;
13373 			}
13374 			{
13375 			auto startBit = bsf(visual.blue_mask);
13376 			auto lastBit = bsr(visual.blue_mask);
13377 			auto b = cast(uint) c.b;
13378 			b >>= 7 - (lastBit - startBit);
13379 			b <<= startBit;
13380 			color |= b;
13381 			}
13382 
13383 
13384 
13385 			return color;
13386 		}
13387 
13388 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
13389 			// source x, source y
13390 			if(ix >= i.width) return;
13391 			if(iy >= i.height) return;
13392 			if(ix + w > i.width) w = i.width - ix;
13393 			if(iy + h > i.height) h = i.height - iy;
13394 			if(i.usingXshm)
13395 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
13396 			else
13397 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
13398 		}
13399 
13400 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
13401 			if(s.enableAlpha) {
13402 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
13403 				if(this.xrenderPicturePainter == None) {
13404 					XRenderPictureAttributes attrs;
13405 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
13406 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
13407 
13408 					// need to initialize the clip
13409 					XRectangle[1] rects;
13410 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
13411 
13412 					if(_clipRectangle != Rectangle.init)
13413 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13414 				}
13415 
13416 				XRenderComposite(
13417 					display,
13418 					3, // PicOpOver
13419 					s.xrenderPicture,
13420 					None,
13421 					this.xrenderPicturePainter,
13422 					ix,
13423 					iy,
13424 					0,
13425 					0,
13426 					x,
13427 					y,
13428 					w ? w : s.width,
13429 					h ? h : s.height
13430 				);
13431 			} else {
13432 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
13433 			}
13434 		}
13435 
13436 		int fontHeight() {
13437 			version(with_xft)
13438 				if(xftFont !is null)
13439 					return xftFont.height;
13440 			if(font)
13441 				return font.max_bounds.ascent + font.max_bounds.descent;
13442 			return 12; // pretty common default...
13443 		}
13444 
13445 		int textWidth(in char[] line) {
13446 			version(with_xft)
13447 			if(xftFont) {
13448 				if(line.length == 0)
13449 					return 0;
13450 				XGlyphInfo extents;
13451 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
13452 				return extents.width;
13453 			}
13454 
13455 			if(fontset) {
13456 				if(line.length == 0)
13457 					return 0;
13458 				XRectangle rect;
13459 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13460 
13461 				return rect.width;
13462 			}
13463 
13464 			if(font)
13465 				// FIXME: unicode
13466 				return XTextWidth( font, line.ptr, cast(int) line.length);
13467 			else
13468 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13469 		}
13470 
13471 		Size textSize(in char[] text) {
13472 			auto maxWidth = 0;
13473 			auto lineHeight = fontHeight;
13474 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13475 			foreach(line; text.split('\n')) {
13476 				int textWidth = this.textWidth(line);
13477 				if(textWidth > maxWidth)
13478 					maxWidth = textWidth;
13479 				h += lineHeight + 4;
13480 			}
13481 			return Size(maxWidth, h);
13482 		}
13483 
13484 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13485 			const(char)[] text;
13486 			version(with_xft)
13487 			if(xftFont) {
13488 				text = originalText;
13489 				goto loaded;
13490 			}
13491 
13492 			if(fontset)
13493 				text = originalText;
13494 			else {
13495 				text.reserve(originalText.length);
13496 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13497 				// then strip the rest so there isn't garbage
13498 				foreach(dchar ch; originalText)
13499 					if(ch < 256)
13500 						text ~= cast(ubyte) ch;
13501 					else
13502 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13503 			}
13504 			loaded:
13505 			if(text.length == 0)
13506 				return;
13507 
13508 			// FIXME: should we clip it to the bounding box?
13509 			int textHeight = fontHeight;
13510 
13511 			auto lines = text.split('\n');
13512 
13513 			const lineHeight = textHeight;
13514 			textHeight *= lines.length;
13515 
13516 			int cy = y;
13517 
13518 			if(alignment & TextAlignment.VerticalBottom) {
13519 				if(y2 <= 0)
13520 					return;
13521 				auto h = y2 - y;
13522 				if(h > textHeight) {
13523 					cy += h - textHeight;
13524 					cy -= lineHeight / 2;
13525 				}
13526 			} else if(alignment & TextAlignment.VerticalCenter) {
13527 				if(y2 <= 0)
13528 					return;
13529 				auto h = y2 - y;
13530 				if(textHeight < h) {
13531 					cy += (h - textHeight) / 2;
13532 					//cy -= lineHeight / 4;
13533 				}
13534 			}
13535 
13536 			foreach(line; text.split('\n')) {
13537 				int textWidth = this.textWidth(line);
13538 
13539 				int px = x, py = cy;
13540 
13541 				if(alignment & TextAlignment.Center) {
13542 					if(x2 <= 0)
13543 						return;
13544 					auto w = x2 - x;
13545 					if(w > textWidth)
13546 						px += (w - textWidth) / 2;
13547 				} else if(alignment & TextAlignment.Right) {
13548 					if(x2 <= 0)
13549 						return;
13550 					auto pos = x2 - textWidth;
13551 					if(pos > x)
13552 						px = pos;
13553 				}
13554 
13555 				version(with_xft)
13556 				if(xftFont) {
13557 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13558 
13559 					goto carry_on;
13560 				}
13561 
13562 				if(fontset)
13563 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13564 				else
13565 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13566 				carry_on:
13567 				cy += lineHeight + 4;
13568 			}
13569 		}
13570 
13571 		void drawPixel(int x, int y) {
13572 			XDrawPoint(display, d, gc, x, y);
13573 		}
13574 
13575 		// The basic shapes, outlined
13576 
13577 		void drawLine(int x1, int y1, int x2, int y2) {
13578 			if(foregroundIsNotTransparent)
13579 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13580 		}
13581 
13582 		void drawRectangle(int x, int y, int width, int height) {
13583 			if(backgroundIsNotTransparent) {
13584 				swapColors();
13585 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13586 				swapColors();
13587 			}
13588 			// 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
13589 			if(foregroundIsNotTransparent)
13590 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13591 		}
13592 
13593 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
13594 			int[4] radii = borderRadius;
13595 			auto r = Rectangle(upperLeft, lowerRight);
13596 
13597 			if(backgroundIsNotTransparent) {
13598 				swapColors();
13599 				// FIXME these overlap and thus draw the pixels multiple times
13600 				XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius);
13601 				XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13602 				swapColors();
13603 			}
13604 
13605 			drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top);
13606 			drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1);
13607 			drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2);
13608 			drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2);
13609 
13610 			//drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
13611 
13612 			drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64);
13613 			drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64);
13614 			drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64);
13615 			drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64);
13616 		}
13617 
13618 
13619 		/// Arguments are the points of the bounding rectangle
13620 		void drawEllipse(int x1, int y1, int x2, int y2) {
13621 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13622 		}
13623 
13624 		// NOTE: start and finish are in units of degrees * 64
13625 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
13626 			if(backgroundIsNotTransparent) {
13627 				swapColors();
13628 				XFillArc(display, d, gc, x1, y1, width, height, start, length);
13629 				swapColors();
13630 			}
13631 			if(foregroundIsNotTransparent) {
13632 				XDrawArc(display, d, gc, x1, y1, width, height, start, length);
13633 
13634 				// Windows draws the straight lines on the edges too so FIXME sort of
13635 			}
13636 		}
13637 
13638 		void drawPolygon(Point[] vertexes) {
13639 			XPoint[16] pointsBuffer;
13640 			XPoint[] points;
13641 			if(vertexes.length <= pointsBuffer.length)
13642 				points = pointsBuffer[0 .. vertexes.length];
13643 			else
13644 				points.length = vertexes.length;
13645 
13646 			foreach(i, p; vertexes) {
13647 				points[i].x = cast(short) p.x;
13648 				points[i].y = cast(short) p.y;
13649 			}
13650 
13651 			if(backgroundIsNotTransparent) {
13652 				swapColors();
13653 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13654 				swapColors();
13655 			}
13656 			if(foregroundIsNotTransparent) {
13657 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13658 			}
13659 		}
13660 	}
13661 
13662 	/* XRender { */
13663 
13664 	struct XRenderColor {
13665 		ushort red;
13666 		ushort green;
13667 		ushort blue;
13668 		ushort alpha;
13669 	}
13670 
13671 	alias Picture = XID;
13672 	alias PictFormat = XID;
13673 
13674 	struct XGlyphInfo {
13675 		ushort width;
13676 		ushort height;
13677 		short x;
13678 		short y;
13679 		short xOff;
13680 		short yOff;
13681 	}
13682 
13683 struct XRenderDirectFormat {
13684     short   red;
13685     short   redMask;
13686     short   green;
13687     short   greenMask;
13688     short   blue;
13689     short   blueMask;
13690     short   alpha;
13691     short   alphaMask;
13692 }
13693 
13694 struct XRenderPictFormat {
13695     PictFormat		id;
13696     int			type;
13697     int			depth;
13698     XRenderDirectFormat	direct;
13699     Colormap		colormap;
13700 }
13701 
13702 enum PictFormatID	=   (1 << 0);
13703 enum PictFormatType	=   (1 << 1);
13704 enum PictFormatDepth	=   (1 << 2);
13705 enum PictFormatRed	=   (1 << 3);
13706 enum PictFormatRedMask  =(1 << 4);
13707 enum PictFormatGreen	=   (1 << 5);
13708 enum PictFormatGreenMask=(1 << 6);
13709 enum PictFormatBlue	=   (1 << 7);
13710 enum PictFormatBlueMask =(1 << 8);
13711 enum PictFormatAlpha	=   (1 << 9);
13712 enum PictFormatAlphaMask=(1 << 10);
13713 enum PictFormatColormap =(1 << 11);
13714 
13715 struct XRenderPictureAttributes {
13716 	int 		repeat;
13717 	Picture		alpha_map;
13718 	int			alpha_x_origin;
13719 	int			alpha_y_origin;
13720 	int			clip_x_origin;
13721 	int			clip_y_origin;
13722 	Pixmap		clip_mask;
13723 	Bool		graphics_exposures;
13724 	int			subwindow_mode;
13725 	int			poly_edge;
13726 	int			poly_mode;
13727 	Atom		dither;
13728 	Bool		component_alpha;
13729 }
13730 
13731 alias int XFixed;
13732 
13733 struct XPointFixed {
13734     XFixed  x, y;
13735 }
13736 
13737 struct XCircle {
13738     XFixed x;
13739     XFixed y;
13740     XFixed radius;
13741 }
13742 
13743 struct XTransform {
13744     XFixed[3][3]  matrix;
13745 }
13746 
13747 struct XFilters {
13748     int	    nfilter;
13749     char    **filter;
13750     int	    nalias;
13751     short   *alias_;
13752 }
13753 
13754 struct XIndexValue {
13755     c_ulong    pixel;
13756     ushort   red, green, blue, alpha;
13757 }
13758 
13759 struct XAnimCursor {
13760     Cursor	    cursor;
13761     c_ulong   delay;
13762 }
13763 
13764 struct XLinearGradient {
13765     XPointFixed p1;
13766     XPointFixed p2;
13767 }
13768 
13769 struct XRadialGradient {
13770     XCircle inner;
13771     XCircle outer;
13772 }
13773 
13774 struct XConicalGradient {
13775     XPointFixed center;
13776     XFixed angle; /* in degrees */
13777 }
13778 
13779 enum PictStandardARGB32  = 0;
13780 enum PictStandardRGB24   = 1;
13781 enum PictStandardA8	 =  2;
13782 enum PictStandardA4	 =  3;
13783 enum PictStandardA1	 =  4;
13784 enum PictStandardNUM	 =  5;
13785 
13786 interface XRender {
13787 extern(C) @nogc:
13788 
13789 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13790 
13791 	Status XRenderQueryVersion (Display *dpy,
13792 			int     *major_versionp,
13793 			int     *minor_versionp);
13794 
13795 	Status XRenderQueryFormats (Display *dpy);
13796 
13797 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13798 
13799 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13800 
13801 	XRenderPictFormat *
13802 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13803 
13804 	XRenderPictFormat *
13805 		XRenderFindFormat (Display			*dpy,
13806 				c_ulong		mask,
13807 				const XRenderPictFormat	*templ,
13808 				int				count);
13809 	XRenderPictFormat *
13810 		XRenderFindStandardFormat (Display		*dpy,
13811 				int			format);
13812 
13813 	XIndexValue *
13814 		XRenderQueryPictIndexValues(Display			*dpy,
13815 				const XRenderPictFormat	*format,
13816 				int				*num);
13817 
13818 	Picture XRenderCreatePicture(
13819 		Display *dpy,
13820 		Drawable drawable,
13821 		const XRenderPictFormat *format,
13822 		c_ulong valuemask,
13823 		const XRenderPictureAttributes *attributes);
13824 
13825 	void XRenderChangePicture (Display				*dpy,
13826 				Picture				picture,
13827 				c_ulong			valuemask,
13828 				const XRenderPictureAttributes  *attributes);
13829 
13830 	void
13831 		XRenderSetPictureClipRectangles (Display	    *dpy,
13832 				Picture	    picture,
13833 				int		    xOrigin,
13834 				int		    yOrigin,
13835 				const XRectangle *rects,
13836 				int		    n);
13837 
13838 	void
13839 		XRenderSetPictureClipRegion (Display	    *dpy,
13840 				Picture	    picture,
13841 				Region	    r);
13842 
13843 	void
13844 		XRenderSetPictureTransform (Display	    *dpy,
13845 				Picture	    picture,
13846 				XTransform	    *transform);
13847 
13848 	void
13849 		XRenderFreePicture (Display                   *dpy,
13850 				Picture                   picture);
13851 
13852 	void
13853 		XRenderComposite (Display   *dpy,
13854 				int	    op,
13855 				Picture   src,
13856 				Picture   mask,
13857 				Picture   dst,
13858 				int	    src_x,
13859 				int	    src_y,
13860 				int	    mask_x,
13861 				int	    mask_y,
13862 				int	    dst_x,
13863 				int	    dst_y,
13864 				uint	width,
13865 				uint	height);
13866 
13867 
13868 	Picture XRenderCreateSolidFill (Display *dpy,
13869 			const XRenderColor *color);
13870 
13871 	Picture XRenderCreateLinearGradient (Display *dpy,
13872 			const XLinearGradient *gradient,
13873 			const XFixed *stops,
13874 			const XRenderColor *colors,
13875 			int nstops);
13876 
13877 	Picture XRenderCreateRadialGradient (Display *dpy,
13878 			const XRadialGradient *gradient,
13879 			const XFixed *stops,
13880 			const XRenderColor *colors,
13881 			int nstops);
13882 
13883 	Picture XRenderCreateConicalGradient (Display *dpy,
13884 			const XConicalGradient *gradient,
13885 			const XFixed *stops,
13886 			const XRenderColor *colors,
13887 			int nstops);
13888 
13889 
13890 
13891 	Cursor
13892 		XRenderCreateCursor (Display	    *dpy,
13893 				Picture	    source,
13894 				uint   x,
13895 				uint   y);
13896 
13897 	XFilters *
13898 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13899 
13900 	void
13901 		XRenderSetPictureFilter (Display    *dpy,
13902 				Picture    picture,
13903 				const char *filter,
13904 				XFixed	    *params,
13905 				int	    nparams);
13906 
13907 	Cursor
13908 		XRenderCreateAnimCursor (Display	*dpy,
13909 				int		ncursor,
13910 				XAnimCursor	*cursors);
13911 }
13912 
13913 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13914 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13915 
13916 	/* XRender } */
13917 
13918 	/* Xrandr { */
13919 
13920 struct XRRMonitorInfo {
13921     Atom name;
13922     Bool primary;
13923     Bool automatic;
13924     int noutput;
13925     int x;
13926     int y;
13927     int width;
13928     int height;
13929     int mwidth;
13930     int mheight;
13931     /*RROutput*/ void *outputs;
13932 }
13933 
13934 struct XRRScreenChangeNotifyEvent {
13935     int type;                   /* event base */
13936     c_ulong serial;       /* # of last request processed by server */
13937     Bool send_event;            /* true if this came from a SendEvent request */
13938     Display *display;           /* Display the event was read from */
13939     Window window;              /* window which selected for this event */
13940     Window root;                /* Root window for changed screen */
13941     Time timestamp;             /* when the screen change occurred */
13942     Time config_timestamp;      /* when the last configuration change */
13943     ushort/*SizeID*/ size_index;
13944     ushort/*SubpixelOrder*/ subpixel_order;
13945     ushort/*Rotation*/ rotation;
13946     int width;
13947     int height;
13948     int mwidth;
13949     int mheight;
13950 }
13951 
13952 enum RRScreenChangeNotify = 0;
13953 
13954 enum RRScreenChangeNotifyMask = 1;
13955 
13956 __gshared int xrrEventBase = -1;
13957 
13958 
13959 interface XRandr {
13960 extern(C) @nogc:
13961 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13962 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13963 
13964 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13965 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13966 
13967 	void XRRSelectInput(Display *dpy, Window window, int mask);
13968 }
13969 
13970 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13971 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13972 	/* Xrandr } */
13973 
13974 	/* Xft { */
13975 
13976 	// actually freetype
13977 	alias void FT_Face;
13978 
13979 	// actually fontconfig
13980 	private alias FcBool = int;
13981 	alias void FcCharSet;
13982 	alias void FcPattern;
13983 	alias void FcResult;
13984 	enum FcEndian { FcEndianBig, FcEndianLittle }
13985 	struct FcFontSet {
13986 		int nfont;
13987 		int sfont;
13988 		FcPattern** fonts;
13989 	}
13990 
13991 	// actually XRegion
13992 	struct BOX {
13993 		short x1, x2, y1, y2;
13994 	}
13995 	struct _XRegion {
13996 		c_long size;
13997 		c_long numRects;
13998 		BOX* rects;
13999 		BOX extents;
14000 	}
14001 
14002 	alias Region = _XRegion*;
14003 
14004 	// ok actually Xft
14005 
14006 	struct XftFontInfo;
14007 
14008 	struct XftFont {
14009 		int         ascent;
14010 		int         descent;
14011 		int         height;
14012 		int         max_advance_width;
14013 		FcCharSet*  charset;
14014 		FcPattern*  pattern;
14015 	}
14016 
14017 	struct XftDraw;
14018 
14019 	struct XftColor {
14020 		c_ulong pixel;
14021 		XRenderColor color;
14022 	}
14023 
14024 	struct XftCharSpec {
14025 		dchar           ucs4;
14026 		short           x;
14027 		short           y;
14028 	}
14029 
14030 	struct XftCharFontSpec {
14031 		XftFont         *font;
14032 		dchar           ucs4;
14033 		short           x;
14034 		short           y;
14035 	}
14036 
14037 	struct XftGlyphSpec {
14038 		uint            glyph;
14039 		short           x;
14040 		short           y;
14041 	}
14042 
14043 	struct XftGlyphFontSpec {
14044 		XftFont         *font;
14045 		uint            glyph;
14046 		short           x;
14047 		short           y;
14048 	}
14049 
14050 	interface Xft {
14051 	extern(C) @nogc pure:
14052 
14053 	Bool XftColorAllocName (Display  *dpy,
14054 				const Visual   *visual,
14055 				Colormap cmap,
14056 				const char     *name,
14057 				XftColor *result);
14058 
14059 	Bool XftColorAllocValue (Display         *dpy,
14060 				Visual          *visual,
14061 				Colormap        cmap,
14062 				const XRenderColor    *color,
14063 				XftColor        *result);
14064 
14065 	void XftColorFree (Display   *dpy,
14066 				Visual    *visual,
14067 				Colormap  cmap,
14068 				XftColor  *color);
14069 
14070 	Bool XftDefaultHasRender (Display *dpy);
14071 
14072 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
14073 
14074 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
14075 
14076 	XftDraw * XftDrawCreate (Display   *dpy,
14077 		       Drawable  drawable,
14078 		       Visual    *visual,
14079 		       Colormap  colormap);
14080 
14081 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
14082 			     Pixmap   bitmap);
14083 
14084 	XftDraw * XftDrawCreateAlpha (Display *dpy,
14085 			    Pixmap  pixmap,
14086 			    int     depth);
14087 
14088 	void XftDrawChange (XftDraw  *draw,
14089 		       Drawable drawable);
14090 
14091 	Display * XftDrawDisplay (XftDraw *draw);
14092 
14093 	Drawable XftDrawDrawable (XftDraw *draw);
14094 
14095 	Colormap XftDrawColormap (XftDraw *draw);
14096 
14097 	Visual * XftDrawVisual (XftDraw *draw);
14098 
14099 	void XftDrawDestroy (XftDraw *draw);
14100 
14101 	Picture XftDrawPicture (XftDraw *draw);
14102 
14103 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
14104 
14105 	void XftDrawGlyphs (XftDraw          *draw,
14106 				const XftColor *color,
14107 				XftFont          *pub,
14108 				int              x,
14109 				int              y,
14110 				const uint  *glyphs,
14111 				int              nglyphs);
14112 
14113 	void XftDrawString8 (XftDraw             *draw,
14114 				const XftColor    *color,
14115 				XftFont             *pub,
14116 				int                 x,
14117 				int                 y,
14118 				const char     *string,
14119 				int                 len);
14120 
14121 	void XftDrawString16 (XftDraw            *draw,
14122 				const XftColor   *color,
14123 				XftFont            *pub,
14124 				int                x,
14125 				int                y,
14126 				const wchar   *string,
14127 				int                len);
14128 
14129 	void XftDrawString32 (XftDraw            *draw,
14130 				const XftColor   *color,
14131 				XftFont            *pub,
14132 				int                x,
14133 				int                y,
14134 				const dchar   *string,
14135 				int                len);
14136 
14137 	void XftDrawStringUtf8 (XftDraw          *draw,
14138 				const XftColor *color,
14139 				XftFont          *pub,
14140 				int              x,
14141 				int              y,
14142 				const char  *string,
14143 				int              len);
14144 	void XftDrawStringUtf16 (XftDraw             *draw,
14145 				const XftColor    *color,
14146 				XftFont             *pub,
14147 				int                 x,
14148 				int                 y,
14149 				const char     *string,
14150 				FcEndian            endian,
14151 				int                 len);
14152 
14153 	void XftDrawCharSpec (XftDraw                *draw,
14154 				const XftColor       *color,
14155 				XftFont                *pub,
14156 				const XftCharSpec    *chars,
14157 				int                    len);
14158 
14159 	void XftDrawCharFontSpec (XftDraw                    *draw,
14160 				const XftColor           *color,
14161 				const XftCharFontSpec    *chars,
14162 				int                        len);
14163 
14164 	void XftDrawGlyphSpec (XftDraw               *draw,
14165 				const XftColor      *color,
14166 				XftFont               *pub,
14167 				const XftGlyphSpec  *glyphs,
14168 				int                   len);
14169 
14170 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
14171 				const XftColor          *color,
14172 				const XftGlyphFontSpec  *glyphs,
14173 				int                       len);
14174 
14175 	void XftDrawRect (XftDraw            *draw,
14176 				const XftColor   *color,
14177 				int                x,
14178 				int                y,
14179 				uint       width,
14180 				uint       height);
14181 
14182 	Bool XftDrawSetClip (XftDraw     *draw,
14183 				Region      r);
14184 
14185 
14186 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
14187 				int                   xOrigin,
14188 				int                   yOrigin,
14189 				const XRectangle    *rects,
14190 				int                   n);
14191 
14192 	void XftDrawSetSubwindowMode (XftDraw    *draw,
14193 				int        mode);
14194 
14195 	void XftGlyphExtents (Display            *dpy,
14196 				XftFont            *pub,
14197 				const uint    *glyphs,
14198 				int                nglyphs,
14199 				XGlyphInfo         *extents);
14200 
14201 	void XftTextExtents8 (Display            *dpy,
14202 				XftFont            *pub,
14203 				const char    *string,
14204 				int                len,
14205 				XGlyphInfo         *extents);
14206 
14207 	void XftTextExtents16 (Display           *dpy,
14208 				XftFont           *pub,
14209 				const wchar  *string,
14210 				int               len,
14211 				XGlyphInfo        *extents);
14212 
14213 	void XftTextExtents32 (Display           *dpy,
14214 				XftFont           *pub,
14215 				const dchar  *string,
14216 				int               len,
14217 				XGlyphInfo        *extents);
14218 
14219 	void XftTextExtentsUtf8 (Display         *dpy,
14220 				XftFont         *pub,
14221 				const char *string,
14222 				int             len,
14223 				XGlyphInfo      *extents);
14224 
14225 	void XftTextExtentsUtf16 (Display            *dpy,
14226 				XftFont            *pub,
14227 				const char    *string,
14228 				FcEndian           endian,
14229 				int                len,
14230 				XGlyphInfo         *extents);
14231 
14232 	FcPattern * XftFontMatch (Display           *dpy,
14233 				int               screen,
14234 				const FcPattern *pattern,
14235 				FcResult          *result);
14236 
14237 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
14238 
14239 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
14240 
14241 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
14242 
14243 	FT_Face XftLockFace (XftFont *pub);
14244 
14245 	void XftUnlockFace (XftFont *pub);
14246 
14247 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
14248 
14249 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
14250 
14251 	dchar XftFontInfoHash (const XftFontInfo *fi);
14252 
14253 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
14254 
14255 	XftFont * XftFontOpenInfo (Display        *dpy,
14256 				FcPattern      *pattern,
14257 				XftFontInfo    *fi);
14258 
14259 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
14260 
14261 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
14262 
14263 	void XftFontClose (Display *dpy, XftFont *pub);
14264 
14265 	FcBool XftInitFtLibrary();
14266 	void XftFontLoadGlyphs (Display          *dpy,
14267 				XftFont          *pub,
14268 				FcBool           need_bitmaps,
14269 				const uint  *glyphs,
14270 				int              nglyph);
14271 
14272 	void XftFontUnloadGlyphs (Display            *dpy,
14273 				XftFont            *pub,
14274 				const uint    *glyphs,
14275 				int                nglyph);
14276 
14277 	FcBool XftFontCheckGlyph (Display  *dpy,
14278 				XftFont  *pub,
14279 				FcBool   need_bitmaps,
14280 				uint  glyph,
14281 				uint  *missing,
14282 				int      *nmissing);
14283 
14284 	FcBool XftCharExists (Display      *dpy,
14285 				XftFont      *pub,
14286 				dchar    ucs4);
14287 
14288 	uint XftCharIndex (Display       *dpy,
14289 				XftFont       *pub,
14290 				dchar      ucs4);
14291 	FcBool XftInit (const char *config);
14292 
14293 	int XftGetVersion ();
14294 
14295 	FcFontSet * XftListFonts (Display   *dpy,
14296 				int       screen,
14297 				...);
14298 
14299 	FcPattern *XftNameParse (const char *name);
14300 
14301 	void XftGlyphRender (Display         *dpy,
14302 				int             op,
14303 				Picture         src,
14304 				XftFont         *pub,
14305 				Picture         dst,
14306 				int             srcx,
14307 				int             srcy,
14308 				int             x,
14309 				int             y,
14310 				const uint *glyphs,
14311 				int             nglyphs);
14312 
14313 	void XftGlyphSpecRender (Display                 *dpy,
14314 				int                     op,
14315 				Picture                 src,
14316 				XftFont                 *pub,
14317 				Picture                 dst,
14318 				int                     srcx,
14319 				int                     srcy,
14320 				const XftGlyphSpec    *glyphs,
14321 				int                     nglyphs);
14322 
14323 	void XftCharSpecRender (Display              *dpy,
14324 				int                  op,
14325 				Picture              src,
14326 				XftFont              *pub,
14327 				Picture              dst,
14328 				int                  srcx,
14329 				int                  srcy,
14330 				const XftCharSpec  *chars,
14331 				int                  len);
14332 	void XftGlyphFontSpecRender (Display                     *dpy,
14333 				int                         op,
14334 				Picture                     src,
14335 				Picture                     dst,
14336 				int                         srcx,
14337 				int                         srcy,
14338 				const XftGlyphFontSpec    *glyphs,
14339 				int                         nglyphs);
14340 
14341 	void XftCharFontSpecRender (Display                  *dpy,
14342 				int                      op,
14343 				Picture                  src,
14344 				Picture                  dst,
14345 				int                      srcx,
14346 				int                      srcy,
14347 				const XftCharFontSpec  *chars,
14348 				int                      len);
14349 
14350 	void XftTextRender8 (Display         *dpy,
14351 				int             op,
14352 				Picture         src,
14353 				XftFont         *pub,
14354 				Picture         dst,
14355 				int             srcx,
14356 				int             srcy,
14357 				int             x,
14358 				int             y,
14359 				const char *string,
14360 				int             len);
14361 	void XftTextRender16 (Display            *dpy,
14362 				int                op,
14363 				Picture            src,
14364 				XftFont            *pub,
14365 				Picture            dst,
14366 				int                srcx,
14367 				int                srcy,
14368 				int                x,
14369 				int                y,
14370 				const wchar   *string,
14371 				int                len);
14372 
14373 	void XftTextRender16BE (Display          *dpy,
14374 				int              op,
14375 				Picture          src,
14376 				XftFont          *pub,
14377 				Picture          dst,
14378 				int              srcx,
14379 				int              srcy,
14380 				int              x,
14381 				int              y,
14382 				const char  *string,
14383 				int              len);
14384 
14385 	void XftTextRender16LE (Display          *dpy,
14386 				int              op,
14387 				Picture          src,
14388 				XftFont          *pub,
14389 				Picture          dst,
14390 				int              srcx,
14391 				int              srcy,
14392 				int              x,
14393 				int              y,
14394 				const char  *string,
14395 				int              len);
14396 
14397 	void XftTextRender32 (Display            *dpy,
14398 				int                op,
14399 				Picture            src,
14400 				XftFont            *pub,
14401 				Picture            dst,
14402 				int                srcx,
14403 				int                srcy,
14404 				int                x,
14405 				int                y,
14406 				const dchar   *string,
14407 				int                len);
14408 
14409 	void XftTextRender32BE (Display          *dpy,
14410 				int              op,
14411 				Picture          src,
14412 				XftFont          *pub,
14413 				Picture          dst,
14414 				int              srcx,
14415 				int              srcy,
14416 				int              x,
14417 				int              y,
14418 				const char  *string,
14419 				int              len);
14420 
14421 	void XftTextRender32LE (Display          *dpy,
14422 				int              op,
14423 				Picture          src,
14424 				XftFont          *pub,
14425 				Picture          dst,
14426 				int              srcx,
14427 				int              srcy,
14428 				int              x,
14429 				int              y,
14430 				const char  *string,
14431 				int              len);
14432 
14433 	void XftTextRenderUtf8 (Display          *dpy,
14434 				int              op,
14435 				Picture          src,
14436 				XftFont          *pub,
14437 				Picture          dst,
14438 				int              srcx,
14439 				int              srcy,
14440 				int              x,
14441 				int              y,
14442 				const char  *string,
14443 				int              len);
14444 
14445 	void XftTextRenderUtf16 (Display         *dpy,
14446 				int             op,
14447 				Picture         src,
14448 				XftFont         *pub,
14449 				Picture         dst,
14450 				int             srcx,
14451 				int             srcy,
14452 				int             x,
14453 				int             y,
14454 				const char *string,
14455 				FcEndian        endian,
14456 				int             len);
14457 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
14458 
14459 	}
14460 
14461 	interface FontConfig {
14462 	extern(C) @nogc pure:
14463 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
14464 		void FcFontSetDestroy(FcFontSet*);
14465 		char* FcNameUnparse(const FcPattern *);
14466 	}
14467 
14468 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
14469 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
14470 
14471 
14472 	/* Xft } */
14473 
14474 	class XDisconnectException : Exception {
14475 		bool userRequested;
14476 		this(bool userRequested = true) {
14477 			this.userRequested = userRequested;
14478 			super("X disconnected");
14479 		}
14480 	}
14481 
14482 	/++
14483 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14484 
14485 		Please note that it returns
14486 	+/
14487 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14488 
14489 		static XErrorEvent[] errorBuffer;
14490 
14491 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14492 			errorBuffer ~= *evt;
14493 			return 0;
14494 		}
14495 
14496 		auto savedErrorHandler = XSetErrorHandler(&handler);
14497 
14498 		try {
14499 			dg();
14500 		} finally {
14501 			XSync(XDisplayConnection.get, 0/*False*/);
14502 			XSetErrorHandler(savedErrorHandler);
14503 		}
14504 
14505 		auto bfr = errorBuffer;
14506 		errorBuffer = null;
14507 
14508 		return bfr;
14509 	}
14510 
14511 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14512 	class XDisplayConnection {
14513 		private __gshared Display* display;
14514 		private __gshared XIM xim;
14515 		private __gshared char* displayName;
14516 
14517 		private __gshared int connectionSequence_;
14518 		private __gshared bool isLocal_;
14519 
14520 		/// use this for lazy caching when reconnection
14521 		static int connectionSequenceNumber() { return connectionSequence_; }
14522 
14523 		/++
14524 			Guesses if the connection appears to be local.
14525 
14526 			History:
14527 				Added June 3, 2021
14528 		+/
14529 		static @property bool isLocal() nothrow @trusted @nogc {
14530 			return isLocal_;
14531 		}
14532 
14533 		/// Attempts recreation of state, may require application assistance
14534 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14535 		/// then call this, and if successful, reenter the loop.
14536 		static void discardAndRecreate(string newDisplayString = null) {
14537 			if(insideXEventLoop)
14538 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14539 
14540 			// 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
14541 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14542 
14543 			foreach(handle; chnenhm) {
14544 				handle.discardConnectionState();
14545 			}
14546 
14547 			discardState();
14548 
14549 			if(newDisplayString !is null)
14550 				setDisplayName(newDisplayString);
14551 
14552 			auto display = get();
14553 
14554 			foreach(handle; chnenhm) {
14555 				handle.recreateAfterDisconnect();
14556 			}
14557 		}
14558 
14559 		private __gshared EventMask rootEventMask;
14560 
14561 		/++
14562 			Requests the specified input from the root window on the connection, in addition to any other request.
14563 
14564 
14565 			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.
14566 
14567 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14568 		+/
14569 		static void addRootInput(EventMask mask) {
14570 			auto old = rootEventMask;
14571 			rootEventMask |= mask;
14572 			get(); // to ensure display connected
14573 			if(display !is null && rootEventMask != old)
14574 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14575 		}
14576 
14577 		static void discardState() {
14578 			freeImages();
14579 
14580 			foreach(atomPtr; interredAtoms)
14581 				*atomPtr = 0;
14582 			interredAtoms = null;
14583 			interredAtoms.assumeSafeAppend();
14584 
14585 			ScreenPainterImplementation.fontAttempted = false;
14586 			ScreenPainterImplementation.defaultfont = null;
14587 			ScreenPainterImplementation.defaultfontset = null;
14588 
14589 			Image.impl.xshmQueryCompleted = false;
14590 			Image.impl._xshmAvailable = false;
14591 
14592 			SimpleWindow.nativeMapping = null;
14593 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14594 			// GlobalHotkeyManager
14595 
14596 			display = null;
14597 			xim = null;
14598 		}
14599 
14600 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14601 		private static void createXIM () {
14602 			import core.stdc.locale : setlocale, LC_ALL;
14603 			import core.stdc.stdio : stderr, fprintf;
14604 			import core.stdc.stdlib : free;
14605 			import core.stdc.string : strdup;
14606 
14607 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14608 
14609 			auto olocale = strdup(setlocale(LC_ALL, null));
14610 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14611 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14612 
14613 			//fprintf(stderr, "opening IM...\n");
14614 			foreach (string s; mtry) {
14615 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14616 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14617 			}
14618 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14619 		}
14620 
14621 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14622 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14623 		static struct ImgList {
14624 			size_t img; // class; hide it from GC
14625 			ImgList* next;
14626 		}
14627 
14628 		static __gshared ImgList* imglist = null;
14629 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14630 
14631 		static void registerImage (Image img) {
14632 			if (!imglistLocked && img !is null) {
14633 				import core.stdc.stdlib : malloc;
14634 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14635 				assert(it !is null); // do proper checks
14636 				it.img = cast(size_t)cast(void*)img;
14637 				it.next = imglist;
14638 				imglist = it;
14639 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14640 			}
14641 		}
14642 
14643 		static void unregisterImage (Image img) {
14644 			if (!imglistLocked && img !is null) {
14645 				import core.stdc.stdlib : free;
14646 				ImgList* prev = null;
14647 				ImgList* cur = imglist;
14648 				while (cur !is null) {
14649 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14650 					prev = cur;
14651 					cur = cur.next;
14652 				}
14653 				if (cur !is null) {
14654 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14655 					free(cur);
14656 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14657 				} else {
14658 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14659 				}
14660 			}
14661 		}
14662 
14663 		static void freeImages () { // needed for discardAndRecreate
14664 			imglistLocked = true;
14665 			scope(exit) imglistLocked = false;
14666 			ImgList* cur = imglist;
14667 			ImgList* next = null;
14668 			while (cur !is null) {
14669 				import core.stdc.stdlib : free;
14670 				next = cur.next;
14671 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14672 				(cast(Image)cast(void*)cur.img).dispose();
14673 				free(cur);
14674 				cur = next;
14675 			}
14676 			imglist = null;
14677 		}
14678 
14679 		/// can be used to override normal handling of display name
14680 		/// from environment and/or command line
14681 		static setDisplayName(string newDisplayName) {
14682 			displayName = cast(char*) (newDisplayName ~ '\0');
14683 		}
14684 
14685 		/// resets to the default display string
14686 		static resetDisplayName() {
14687 			displayName = null;
14688 		}
14689 
14690 		///
14691 		static Display* get() {
14692 			if(display is null) {
14693 				if(!librariesSuccessfullyLoaded)
14694 					throw new Exception("Unable to load X11 client libraries");
14695 				display = XOpenDisplay(displayName);
14696 
14697 				isLocal_ = false;
14698 
14699 				connectionSequence_++;
14700 				if(display is null)
14701 					throw new Exception("Unable to open X display");
14702 
14703 				auto str = display.display_name;
14704 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14705 				// and otherwise it probably isn't
14706 				if(str is null || (str[0] != ':' && str[0] != '/'))
14707 					isLocal_ = false;
14708 				else
14709 					isLocal_ = true;
14710 
14711 				debug(sdpy_x_errors) {
14712 					XSetErrorHandler(&adrlogger);
14713 					XSynchronize(display, true);
14714 
14715 					extern(C) int wtf() {
14716 						if(errorHappened) {
14717 							asm { int 3; }
14718 							errorHappened = false;
14719 						}
14720 						return 0;
14721 					}
14722 					XSetAfterFunction(display, &wtf);
14723 				}
14724 
14725 
14726 				XSetIOErrorHandler(&x11ioerrCB);
14727 				Bool sup;
14728 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14729 				createXIM();
14730 				version(with_eventloop) {
14731 					import arsd.eventloop;
14732 					addFileEventListeners(display.fd, &eventListener, null, null);
14733 				}
14734 			}
14735 
14736 			return display;
14737 		}
14738 
14739 		extern(C)
14740 		static int x11ioerrCB(Display* dpy) {
14741 			throw new XDisconnectException(false);
14742 		}
14743 
14744 		version(with_eventloop) {
14745 			import arsd.eventloop;
14746 			static void eventListener(OsFileHandle fd) {
14747 				//this.mtLock();
14748 				//scope(exit) this.mtUnlock();
14749 				while(XPending(display))
14750 					doXNextEvent(display);
14751 			}
14752 		}
14753 
14754 		// close connection on program exit -- we need this to properly free all images
14755 		static ~this () {
14756 			// the gui thread must clean up after itself or else Xlib might deadlock
14757 			// using this flag on any thread destruction is the easiest way i know of
14758 			// (shared static this is run by the LAST thread to exit, which may not be
14759 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14760 			if(thisIsGuiThread)
14761 				close();
14762 		}
14763 
14764 		///
14765 		static void close() {
14766 			if(display is null)
14767 				return;
14768 
14769 			version(with_eventloop) {
14770 				import arsd.eventloop;
14771 				removeFileEventListeners(display.fd);
14772 			}
14773 
14774 			// now remove all registered images to prevent shared memory leaks
14775 			freeImages();
14776 
14777 			// tbh I don't know why it is doing this but like if this happens to run
14778 			// from the other thread there's frequent hanging inside here.
14779 			if(thisIsGuiThread)
14780 				XCloseDisplay(display);
14781 			display = null;
14782 		}
14783 	}
14784 
14785 	mixin template NativeImageImplementation() {
14786 		XImage* handle;
14787 		ubyte* rawData;
14788 
14789 		XShmSegmentInfo shminfo;
14790 		bool premultiply = true;
14791 
14792 		__gshared bool xshmQueryCompleted;
14793 		__gshared bool _xshmAvailable;
14794 		public static @property bool xshmAvailable() {
14795 			if(!xshmQueryCompleted) {
14796 				int i1, i2, i3;
14797 				xshmQueryCompleted = true;
14798 
14799 				if(!XDisplayConnection.isLocal)
14800 					_xshmAvailable = false;
14801 				else
14802 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14803 			}
14804 			return _xshmAvailable;
14805 		}
14806 
14807 		bool usingXshm;
14808 	final:
14809 
14810 		private __gshared bool xshmfailed;
14811 
14812 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14813 			auto display = XDisplayConnection.get();
14814 			assert(display !is null);
14815 			auto screen = DefaultScreen(display);
14816 
14817 			// it will only use shared memory for somewhat largish images,
14818 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14819 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14820 
14821 
14822 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14823 				// the actual use still fails. For example, if the program is in a container and permission denied
14824 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14825 				//
14826 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14827 
14828 
14829 				// synchronize so preexisting buffers are clear
14830 				XSync(display, false);
14831 				xshmfailed = false;
14832 
14833 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14834 
14835 
14836 				usingXshm = true;
14837 				handle = XShmCreateImage(
14838 					display,
14839 					DefaultVisual(display, screen),
14840 					enableAlpha ? 32: 24,
14841 					ImageFormat.ZPixmap,
14842 					null,
14843 					&shminfo,
14844 					width, height);
14845 				if(handle is null)
14846 					goto abortXshm1;
14847 
14848 				if(handle.bytes_per_line != 4 * width)
14849 					goto abortXshm2;
14850 
14851 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14852 				if(shminfo.shmid < 0)
14853 					goto abortXshm3;
14854 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14855 				if(rawData == cast(ubyte*) -1)
14856 					goto abortXshm4;
14857 				shminfo.readOnly = 0;
14858 				XShmAttach(display, &shminfo);
14859 
14860 				// and now to the final error check to ensure it actually worked.
14861 				XSync(display, false);
14862 				if(xshmfailed)
14863 					goto abortXshm5;
14864 
14865 				XSetErrorHandler(oldErrorHandler);
14866 
14867 				XDisplayConnection.registerImage(this);
14868 				// if I don't flush here there's a chance the dtor will run before the
14869 				// ctor and lead to a bad value X error. While this hurts the efficiency
14870 				// it is local anyway so prolly better to keep it simple
14871 				XFlush(display);
14872 
14873 				return;
14874 
14875 				abortXshm5:
14876 					shmdt(shminfo.shmaddr);
14877 					rawData = null;
14878 
14879 				abortXshm4:
14880 					shmctl(shminfo.shmid, IPC_RMID, null);
14881 
14882 				abortXshm3:
14883 					// nothing needed, the shmget failed so there's nothing to free
14884 
14885 				abortXshm2:
14886 					XDestroyImage(handle);
14887 					handle = null;
14888 
14889 				abortXshm1:
14890 					XSetErrorHandler(oldErrorHandler);
14891 					usingXshm = false;
14892 					handle = null;
14893 
14894 					shminfo = typeof(shminfo).init;
14895 
14896 					_xshmAvailable = false; // don't try again in the future
14897 
14898 					// writeln("fallingback");
14899 
14900 					goto fallback;
14901 
14902 			} else {
14903 				fallback:
14904 
14905 				if (forcexshm) throw new Exception("can't create XShm Image");
14906 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14907 				import core.stdc.stdlib : malloc;
14908 				rawData = cast(ubyte*) malloc(width * height * 4);
14909 
14910 				handle = XCreateImage(
14911 					display,
14912 					DefaultVisual(display, screen),
14913 					enableAlpha ? 32 : 24, // bpp
14914 					ImageFormat.ZPixmap,
14915 					0, // offset
14916 					rawData,
14917 					width, height,
14918 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14919 			}
14920 		}
14921 
14922 		void dispose() {
14923 			// note: this calls free(rawData) for us
14924 			if(handle) {
14925 				if (usingXshm) {
14926 					XDisplayConnection.unregisterImage(this);
14927 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14928 				}
14929 				XDestroyImage(handle);
14930 				if(usingXshm) {
14931 					shmdt(shminfo.shmaddr);
14932 					shmctl(shminfo.shmid, IPC_RMID, null);
14933 				}
14934 				handle = null;
14935 			}
14936 		}
14937 
14938 		Color getPixel(int x, int y) @system {
14939 			auto offset = (y * width + x) * 4;
14940 			Color c;
14941 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14942 			c.b = rawData[offset + 0];
14943 			c.g = rawData[offset + 1];
14944 			c.r = rawData[offset + 2];
14945 			if(enableAlpha && premultiply)
14946 				c.unPremultiply;
14947 			return c;
14948 		}
14949 
14950 		void setPixel(int x, int y, Color c) @system {
14951 			if(enableAlpha && premultiply)
14952 				c.premultiply();
14953 			auto offset = (y * width + x) * 4;
14954 			rawData[offset + 0] = c.b;
14955 			rawData[offset + 1] = c.g;
14956 			rawData[offset + 2] = c.r;
14957 			if(enableAlpha)
14958 				rawData[offset + 3] = c.a;
14959 		}
14960 
14961 		void convertToRgbaBytes(ubyte[] where) @system {
14962 			assert(where.length == this.width * this.height * 4);
14963 
14964 			// if rawData had a length....
14965 			//assert(rawData.length == where.length);
14966 			for(int idx = 0; idx < where.length; idx += 4) {
14967 				where[idx + 0] = rawData[idx + 2]; // r
14968 				where[idx + 1] = rawData[idx + 1]; // g
14969 				where[idx + 2] = rawData[idx + 0]; // b
14970 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14971 
14972 				if(enableAlpha && premultiply)
14973 					unPremultiplyRgba(where[idx .. idx + 4]);
14974 			}
14975 		}
14976 
14977 		void setFromRgbaBytes(in ubyte[] where) @system {
14978 			assert(where.length == this.width * this.height * 4);
14979 
14980 			// if rawData had a length....
14981 			//assert(rawData.length == where.length);
14982 			for(int idx = 0; idx < where.length; idx += 4) {
14983 				rawData[idx + 2] = where[idx + 0]; // r
14984 				rawData[idx + 1] = where[idx + 1]; // g
14985 				rawData[idx + 0] = where[idx + 2]; // b
14986 				if(enableAlpha) {
14987 					rawData[idx + 3] = where[idx + 3]; // a
14988 					if(premultiply)
14989 						premultiplyBgra(rawData[idx .. idx + 4]);
14990 				}
14991 			}
14992 		}
14993 
14994 	}
14995 
14996 	mixin template NativeSimpleWindowImplementation() {
14997 		GC gc;
14998 		Window window;
14999 		Display* display;
15000 
15001 		Pixmap buffer;
15002 		int bufferw, bufferh; // size of the buffer; can be bigger than window
15003 		XIC xic; // input context
15004 		int curHidden = 0; // counter
15005 		Cursor blankCurPtr = 0;
15006 		int cursorSequenceNumber = 0;
15007 		int warpEventCount = 0; // number of mouse movement events to eat
15008 
15009 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
15010 		X11GetSelectionHandler[Atom] getSelectionHandlers;
15011 
15012 		version(without_opengl) {} else
15013 		GLXContext glc;
15014 
15015 		private void fixFixedSize(bool forced=false) (int width, int height) {
15016 			if (forced || this.resizability == Resizability.fixedSize) {
15017 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
15018 				XSizeHints sh;
15019 				static if (!forced) {
15020 					c_long spr;
15021 					XGetWMNormalHints(display, window, &sh, &spr);
15022 					sh.flags |= PMaxSize | PMinSize;
15023 				} else {
15024 					sh.flags = PMaxSize | PMinSize;
15025 				}
15026 				sh.min_width = width;
15027 				sh.min_height = height;
15028 				sh.max_width = width;
15029 				sh.max_height = height;
15030 				XSetWMNormalHints(display, window, &sh);
15031 				//XFlush(display);
15032 			}
15033 		}
15034 
15035 		ScreenPainter getPainter(bool manualInvalidations) {
15036 			return ScreenPainter(this, window, manualInvalidations);
15037 		}
15038 
15039 		void move(int x, int y) {
15040 			XMoveWindow(display, window, x, y);
15041 		}
15042 
15043 		void resize(int w, int h) {
15044 			if (w < 1) w = 1;
15045 			if (h < 1) h = 1;
15046 			XResizeWindow(display, window, w, h);
15047 
15048 			// calling this now to avoid waiting for the server to
15049 			// acknowledge the resize; draws without returning to the
15050 			// event loop will thus actually work. the server's event
15051 			// btw might overrule this and resize it again
15052 			recordX11Resize(display, this, w, h);
15053 
15054 			updateOpenglViewportIfNeeded(w, h);
15055 		}
15056 
15057 		void moveResize (int x, int y, int w, int h) {
15058 			if (w < 1) w = 1;
15059 			if (h < 1) h = 1;
15060 			XMoveResizeWindow(display, window, x, y, w, h);
15061 			updateOpenglViewportIfNeeded(w, h);
15062 		}
15063 
15064 		void hideCursor () {
15065 			if (curHidden++ == 0) {
15066 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
15067 					static const(char)[1] cmbmp = 0;
15068 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
15069 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
15070 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
15071 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
15072 					XFreePixmap(display, pm);
15073 				}
15074 				XDefineCursor(display, window, blankCurPtr);
15075 			}
15076 		}
15077 
15078 		void showCursor () {
15079 			if (--curHidden == 0) XUndefineCursor(display, window);
15080 		}
15081 
15082 		void warpMouse (int x, int y) {
15083 			// here i will send dummy "ignore next mouse motion" event,
15084 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
15085 			// and we don't need to report it to the user (as warping is
15086 			// used when the user needs movement deltas).
15087 			//XClientMessageEvent xclient;
15088 			XEvent e;
15089 			e.xclient.type = EventType.ClientMessage;
15090 			e.xclient.window = window;
15091 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15092 			e.xclient.format = 32;
15093 			e.xclient.data.l[0] = 0;
15094 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
15095 			//{ 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]); }
15096 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15097 			// now warp pointer...
15098 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
15099 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
15100 			// ...and flush
15101 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
15102 			XFlush(display);
15103 		}
15104 
15105 		void sendDummyEvent () {
15106 			// here i will send dummy event to ping event queue
15107 			XEvent e;
15108 			e.xclient.type = EventType.ClientMessage;
15109 			e.xclient.window = window;
15110 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15111 			e.xclient.format = 32;
15112 			e.xclient.data.l[0] = 0;
15113 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15114 			XFlush(display);
15115 		}
15116 
15117 		void setTitle(string title) {
15118 			if (title.ptr is null) title = "";
15119 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15120 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15121 			XTextProperty windowName;
15122 			windowName.value = title.ptr;
15123 			windowName.encoding = XA_UTF8; //XA_STRING;
15124 			windowName.format = 8;
15125 			windowName.nitems = cast(uint)title.length;
15126 			XSetWMName(display, window, &windowName);
15127 			char[1024] namebuf = 0;
15128 			auto maxlen = namebuf.length-1;
15129 			if (maxlen > title.length) maxlen = title.length;
15130 			namebuf[0..maxlen] = title[0..maxlen];
15131 			XStoreName(display, window, namebuf.ptr);
15132 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
15133 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
15134 		}
15135 
15136 		string[] getTitles() {
15137 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15138 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15139 			XTextProperty textProp;
15140 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
15141 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
15142 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
15143 				} else
15144 					return [];
15145 			} else
15146 				return null;
15147 		}
15148 
15149 		string getTitle() {
15150 			auto titles = getTitles();
15151 			return titles.length ? titles[0] : null;
15152 		}
15153 
15154 		void setMinSize (int minwidth, int minheight) {
15155 			import core.stdc.config : c_long;
15156 			if (minwidth < 1) minwidth = 1;
15157 			if (minheight < 1) minheight = 1;
15158 			XSizeHints sh;
15159 			c_long spr;
15160 			XGetWMNormalHints(display, window, &sh, &spr);
15161 			sh.min_width = minwidth;
15162 			sh.min_height = minheight;
15163 			sh.flags |= PMinSize;
15164 			XSetWMNormalHints(display, window, &sh);
15165 			flushGui();
15166 		}
15167 
15168 		void setMaxSize (int maxwidth, int maxheight) {
15169 			import core.stdc.config : c_long;
15170 			if (maxwidth < 1) maxwidth = 1;
15171 			if (maxheight < 1) maxheight = 1;
15172 			XSizeHints sh;
15173 			c_long spr;
15174 			XGetWMNormalHints(display, window, &sh, &spr);
15175 			sh.max_width = maxwidth;
15176 			sh.max_height = maxheight;
15177 			sh.flags |= PMaxSize;
15178 			XSetWMNormalHints(display, window, &sh);
15179 			flushGui();
15180 		}
15181 
15182 		void setResizeGranularity (int granx, int grany) {
15183 			import core.stdc.config : c_long;
15184 			if (granx < 1) granx = 1;
15185 			if (grany < 1) grany = 1;
15186 			XSizeHints sh;
15187 			c_long spr;
15188 			XGetWMNormalHints(display, window, &sh, &spr);
15189 			sh.width_inc = granx;
15190 			sh.height_inc = grany;
15191 			sh.flags |= PResizeInc;
15192 			XSetWMNormalHints(display, window, &sh);
15193 			flushGui();
15194 		}
15195 
15196 		void setOpacity (uint opacity) {
15197 			arch_ulong o = opacity;
15198 			if (opacity == uint.max)
15199 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
15200 			else
15201 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
15202 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
15203 		}
15204 
15205 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted {
15206 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
15207 			display = XDisplayConnection.get();
15208 			auto screen = DefaultScreen(display);
15209 
15210 			bool overrideRedirect = false;
15211 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
15212 				overrideRedirect = true;
15213 
15214 			version(without_opengl) {}
15215 			else {
15216 				if(opengl == OpenGlOptions.yes) {
15217 					GLXFBConfig fbconf = null;
15218 					XVisualInfo* vi = null;
15219 					bool useLegacy = false;
15220 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
15221 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
15222 						int[23] visualAttribs = [
15223 							GLX_X_RENDERABLE , 1/*True*/,
15224 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
15225 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
15226 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
15227 							GLX_RED_SIZE     , 8,
15228 							GLX_GREEN_SIZE   , 8,
15229 							GLX_BLUE_SIZE    , 8,
15230 							GLX_ALPHA_SIZE   , 8,
15231 							GLX_DEPTH_SIZE   , 24,
15232 							GLX_STENCIL_SIZE , 8,
15233 							GLX_DOUBLEBUFFER , 1/*True*/,
15234 							0/*None*/,
15235 						];
15236 						int fbcount;
15237 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
15238 						if (fbcount == 0) {
15239 							useLegacy = true; // try to do at least something
15240 						} else {
15241 							// pick the FB config/visual with the most samples per pixel
15242 							int bestidx = -1, bestns = -1;
15243 							foreach (int fbi; 0..fbcount) {
15244 								int sb, samples;
15245 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
15246 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
15247 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
15248 							}
15249 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
15250 							fbconf = fbc[bestidx];
15251 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
15252 							XFree(fbc);
15253 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
15254 						}
15255 					}
15256 					if (vi is null || useLegacy) {
15257 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
15258 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
15259 						useLegacy = true;
15260 					}
15261 					if (vi is null) throw new Exception("no open gl visual found");
15262 
15263 					XSetWindowAttributes swa;
15264 					auto root = RootWindow(display, screen);
15265 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
15266 
15267 					swa.override_redirect = overrideRedirect;
15268 
15269 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15270 						0, 0, width, height,
15271 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
15272 
15273 					// now try to use `glXCreateContextAttribsARB()` if it's here
15274 					if (!useLegacy) {
15275 						// request fairly advanced context, even with stencil buffer!
15276 						int[9] contextAttribs = [
15277 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
15278 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
15279 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
15280 							// for modern context, set "forward compatibility" flag too
15281 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
15282 							0/*None*/,
15283 						];
15284 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
15285 						if (glc is null && sdpyOpenGLContextAllowFallback) {
15286 							sdpyOpenGLContextVersion = 0;
15287 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15288 						}
15289 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
15290 					} else {
15291 						// fallback to old GLX call
15292 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
15293 							sdpyOpenGLContextVersion = 0;
15294 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15295 						}
15296 					}
15297 					// sync to ensure any errors generated are processed
15298 					XSync(display, 0/*False*/);
15299 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
15300 					if(glc is null)
15301 						throw new Exception("glc");
15302 				}
15303 			}
15304 
15305 			if(opengl == OpenGlOptions.no) {
15306 
15307 				XSetWindowAttributes swa;
15308 				swa.background_pixel = WhitePixel(display, screen);
15309 				swa.border_pixel = BlackPixel(display, screen);
15310 				swa.override_redirect = overrideRedirect;
15311 				auto root = RootWindow(display, screen);
15312 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
15313 
15314 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15315 					0, 0, width, height,
15316 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
15317 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
15318 
15319 
15320 
15321 				/*
15322 				window = XCreateSimpleWindow(
15323 					display,
15324 					parent is null ? RootWindow(display, screen) : parent.impl.window,
15325 					0, 0, // x, y
15326 					width, height,
15327 					1, // border width
15328 					BlackPixel(display, screen), // border
15329 					WhitePixel(display, screen)); // background
15330 				*/
15331 
15332 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
15333 				bufferw = width;
15334 				bufferh = height;
15335 
15336 				gc = DefaultGC(display, screen);
15337 
15338 				// clear out the buffer to get us started...
15339 				XSetForeground(display, gc, WhitePixel(display, screen));
15340 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
15341 				XSetForeground(display, gc, BlackPixel(display, screen));
15342 			}
15343 
15344 			// input context
15345 			//TODO: create this only for top-level windows, and reuse that?
15346 			populateXic();
15347 
15348 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
15349 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
15350 			// window class
15351 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
15352 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
15353 				XClassHint klass;
15354 				XWMHints wh;
15355 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15356 					wh.input = true;
15357 					wh.flags |= InputHint;
15358 				}
15359 				XSizeHints size;
15360 				klass.res_name = sdpyWindowClassStr;
15361 				klass.res_class = sdpyWindowClassStr;
15362 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
15363 			}
15364 
15365 			setTitle(title);
15366 			SimpleWindow.nativeMapping[window] = this;
15367 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
15368 
15369 			// This gives our window a close button
15370 			if (windowType != WindowTypes.eventOnly) {
15371 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
15372 				int useAtoms;
15373 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15374 					useAtoms = 2;
15375 				} else {
15376 					useAtoms = 1;
15377 				}
15378 				assert(useAtoms <= atoms.length);
15379 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
15380 			}
15381 
15382 			// FIXME: windowType and customizationFlags
15383 			Atom[8] wsatoms; // here, due to goto
15384 			int wmsacount = 0; // here, due to goto
15385 
15386 			try
15387 			final switch(windowType) {
15388 				case WindowTypes.normal:
15389 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15390 				break;
15391 				case WindowTypes.undecorated:
15392 					motifHideDecorations();
15393 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15394 				break;
15395 				case WindowTypes.eventOnly:
15396 					_hidden = true;
15397 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
15398 					goto hiddenWindow;
15399 				//break;
15400 				case WindowTypes.nestedChild:
15401 					// handled in XCreateWindow calls
15402 				break;
15403 
15404 				case WindowTypes.dropdownMenu:
15405 					motifHideDecorations();
15406 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
15407 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15408 				break;
15409 				case WindowTypes.popupMenu:
15410 					motifHideDecorations();
15411 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
15412 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15413 				break;
15414 				case WindowTypes.notification:
15415 					motifHideDecorations();
15416 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
15417 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15418 				break;
15419 				case WindowTypes.minimallyWrapped:
15420 					assert(0, "don't create a minimallyWrapped thing explicitly!");
15421 				/+
15422 				case WindowTypes.menu:
15423 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15424 					motifHideDecorations();
15425 				break;
15426 				case WindowTypes.desktop:
15427 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
15428 				break;
15429 				case WindowTypes.dock:
15430 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
15431 				break;
15432 				case WindowTypes.toolbar:
15433 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
15434 				break;
15435 				case WindowTypes.menu:
15436 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15437 				break;
15438 				case WindowTypes.utility:
15439 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
15440 				break;
15441 				case WindowTypes.splash:
15442 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
15443 				break;
15444 				case WindowTypes.dialog:
15445 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
15446 				break;
15447 				case WindowTypes.tooltip:
15448 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
15449 				break;
15450 				case WindowTypes.notification:
15451 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
15452 				break;
15453 				case WindowTypes.combo:
15454 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
15455 				break;
15456 				case WindowTypes.dnd:
15457 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
15458 				break;
15459 				+/
15460 			}
15461 			catch(Exception e) {
15462 				// XInternAtom failed, prolly a WM
15463 				// that doesn't support these things
15464 			}
15465 
15466 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
15467 			// the two following flags may be ignored by WM
15468 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
15469 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
15470 
15471 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
15472 
15473 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
15474 
15475 			// What would be ideal here is if they only were
15476 			// selected if there was actually an event handler
15477 			// for them...
15478 
15479 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15480 
15481 			hiddenWindow:
15482 
15483 			// set the pid property for lookup later by window managers
15484 			// a standard convenience
15485 			import core.sys.posix.unistd;
15486 			arch_ulong pid = getpid();
15487 
15488 			XChangeProperty(
15489 				display,
15490 				impl.window,
15491 				GetAtom!("_NET_WM_PID", true)(display),
15492 				XA_CARDINAL,
15493 				32 /* bits */,
15494 				0 /*PropModeReplace*/,
15495 				&pid,
15496 				1);
15497 
15498 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15499 				if(parent is null) assert(0);
15500 				XChangeProperty(
15501 					display,
15502 					impl.window,
15503 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15504 					XA_WINDOW,
15505 					32 /* bits */,
15506 					0 /*PropModeReplace*/,
15507 					&parent.impl.window,
15508 					1);
15509 
15510 			}
15511 
15512 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15513 				XMapWindow(display, window);
15514 			} else {
15515 				_hidden = true;
15516 			}
15517 		}
15518 
15519 		void populateXic() {
15520 			if (XDisplayConnection.xim !is null) {
15521 				xic = XCreateIC(XDisplayConnection.xim,
15522 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15523 						/*XNClientWindow*/"clientWindow".ptr, window,
15524 						/*XNFocusWindow*/"focusWindow".ptr, window,
15525 						null);
15526 				if (xic is null) {
15527 					import core.stdc.stdio : stderr, fprintf;
15528 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15529 				}
15530 			}
15531 		}
15532 
15533 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15534 			auto mask = EventMask.ExposureMask |
15535 				EventMask.KeyPressMask |
15536 				EventMask.KeyReleaseMask |
15537 				EventMask.PropertyChangeMask |
15538 				EventMask.FocusChangeMask |
15539 				EventMask.StructureNotifyMask |
15540 				EventMask.SubstructureNotifyMask |
15541 				EventMask.VisibilityChangeMask
15542 				| EventMask.ButtonPressMask
15543 				| EventMask.ButtonReleaseMask
15544 			;
15545 
15546 			// xshm is our shortcut for local connections
15547 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15548 				mask |= EventMask.PointerMotionMask;
15549 			else
15550 				mask |= EventMask.ButtonMotionMask;
15551 
15552 			XSelectInput(display, window, mask);
15553 		}
15554 
15555 
15556 		void setNetWMWindowType(Atom type) {
15557 			Atom[2] atoms;
15558 
15559 			atoms[0] = type;
15560 			// generic fallback
15561 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15562 
15563 			XChangeProperty(
15564 				display,
15565 				impl.window,
15566 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15567 				XA_ATOM,
15568 				32 /* bits */,
15569 				0 /*PropModeReplace*/,
15570 				atoms.ptr,
15571 				cast(int) atoms.length);
15572 		}
15573 
15574 		void motifHideDecorations(bool hide = true) {
15575 			MwmHints hints;
15576 			hints.flags = MWM_HINTS_DECORATIONS;
15577 			hints.decorations = hide ? 0 : 1;
15578 
15579 			XChangeProperty(
15580 				display,
15581 				impl.window,
15582 				GetAtom!"_MOTIF_WM_HINTS"(display),
15583 				GetAtom!"_MOTIF_WM_HINTS"(display),
15584 				32 /* bits */,
15585 				0 /*PropModeReplace*/,
15586 				&hints,
15587 				hints.sizeof / 4);
15588 		}
15589 
15590 		/*k8: unused
15591 		void createOpenGlContext() {
15592 
15593 		}
15594 		*/
15595 
15596 		void closeWindow() {
15597 			// I can't close this or a child window closing will
15598 			// break events for everyone. So I'm just leaking it right
15599 			// now and that is probably perfectly fine...
15600 			version(none)
15601 			if (customEventFDRead != -1) {
15602 				import core.sys.posix.unistd : close;
15603 				auto same = customEventFDRead == customEventFDWrite;
15604 
15605 				close(customEventFDRead);
15606 				if(!same)
15607 					close(customEventFDWrite);
15608 				customEventFDRead = -1;
15609 				customEventFDWrite = -1;
15610 			}
15611 
15612 			version(without_opengl) {} else
15613 			if(glc !is null) {
15614 				glXDestroyContext(display, glc);
15615 				glc = null;
15616 			}
15617 
15618 			if(buffer)
15619 				XFreePixmap(display, buffer);
15620 			bufferw = bufferh = 0;
15621 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15622 			XDestroyWindow(display, window);
15623 			XFlush(display);
15624 		}
15625 
15626 		void dispose() {
15627 		}
15628 
15629 		bool destroyed = false;
15630 	}
15631 
15632 	bool insideXEventLoop;
15633 }
15634 
15635 version(X11) {
15636 
15637 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15638 
15639 	private class ResizeEvent {
15640 		int width, height;
15641 	}
15642 
15643 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15644 		if(win.windowType == WindowTypes.minimallyWrapped)
15645 			return;
15646 
15647 		if(win.pendingResizeEvent is null) {
15648 			win.pendingResizeEvent = new ResizeEvent();
15649 			win.addEventListener((ResizeEvent re) {
15650 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15651 			});
15652 		}
15653 		win.pendingResizeEvent.width = width;
15654 		win.pendingResizeEvent.height = height;
15655 		if(!win.eventQueued!ResizeEvent) {
15656 			win.postEvent(win.pendingResizeEvent);
15657 		}
15658 	}
15659 
15660 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15661 		if(win.windowType == WindowTypes.minimallyWrapped)
15662 			return;
15663 		if(win.closed)
15664 			return;
15665 
15666 		if(width != win.width || height != win.height) {
15667 
15668 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15669 			win._width = width;
15670 			win._height = height;
15671 
15672 			if(win.openglMode == OpenGlOptions.no) {
15673 				// FIXME: could this be more efficient?
15674 
15675 				if (win.bufferw < width || win.bufferh < height) {
15676 					//{ 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); }
15677 					// grow the internal buffer to match the window...
15678 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15679 					{
15680 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15681 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15682 						scope(exit) XFreeGC(win.display, xgc);
15683 						XSetClipMask(win.display, xgc, None);
15684 						XSetForeground(win.display, xgc, 0);
15685 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15686 					}
15687 					XCopyArea(display,
15688 						cast(Drawable) win.buffer,
15689 						cast(Drawable) newPixmap,
15690 						win.gc, 0, 0,
15691 						win.bufferw < width ? win.bufferw : win.width,
15692 						win.bufferh < height ? win.bufferh : win.height,
15693 						0, 0);
15694 
15695 					XFreePixmap(display, win.buffer);
15696 					win.buffer = newPixmap;
15697 					win.bufferw = width;
15698 					win.bufferh = height;
15699 				}
15700 
15701 				// clear unused parts of the buffer
15702 				if (win.bufferw > width || win.bufferh > height) {
15703 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15704 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15705 					scope(exit) XFreeGC(win.display, xgc);
15706 					XSetClipMask(win.display, xgc, None);
15707 					XSetForeground(win.display, xgc, 0);
15708 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15709 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15710 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15711 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15712 				}
15713 
15714 			}
15715 
15716 			win.updateOpenglViewportIfNeeded(width, height);
15717 
15718 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15719 
15720 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15721 			if(win.windowResized !is null) {
15722 				XUnlockDisplay(display);
15723 				scope(exit) XLockDisplay(display);
15724 				win.windowResized(width, height);
15725 			}
15726 		}
15727 	}
15728 
15729 
15730 	/// Platform-specific, you might use it when doing a custom event loop.
15731 	bool doXNextEvent(Display* display) {
15732 		bool done;
15733 		XEvent e;
15734 		XNextEvent(display, &e);
15735 		version(sddddd) {
15736 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15737 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15738 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15739 			}
15740 		}
15741 
15742 		// filter out compose events
15743 		if (XFilterEvent(&e, None)) {
15744 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15745 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15746 			return false;
15747 		}
15748 		// process keyboard mapping changes
15749 		if (e.type == EventType.KeymapNotify) {
15750 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15751 			XRefreshKeyboardMapping(&e.xmapping);
15752 			return false;
15753 		}
15754 
15755 		version(with_eventloop)
15756 			import arsd.eventloop;
15757 
15758 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15759 			// see windows impl's comments
15760 			XUnlockDisplay(display);
15761 			scope(exit) XLockDisplay(display);
15762 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15763 			if(ret == 0)
15764 				return done;
15765 		}
15766 
15767 
15768 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15769 			if(win.getNativeEventHandler !is null) {
15770 				XUnlockDisplay(display);
15771 				scope(exit) XLockDisplay(display);
15772 				auto ret = win.getNativeEventHandler()(e);
15773 				if(ret == 0)
15774 					return done;
15775 			}
15776 		}
15777 
15778 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15779 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15780 				// we get this because of the RRScreenChangeNotifyMask
15781 
15782 				// this isn't actually an ideal way to do it since it wastes time
15783 				// but meh it is simple and it works.
15784 				win.actualDpiLoadAttempted = false;
15785 				SimpleWindow.xRandrInfoLoadAttemped = false;
15786 				win.updateActualDpi(); // trigger a reload
15787 			}
15788 		}
15789 
15790 		switch(e.type) {
15791 		  case EventType.SelectionClear:
15792 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15793 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15794 				// writeln("SelectionClear");
15795 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15796 			}
15797 		  break;
15798 		  case EventType.SelectionRequest:
15799 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15800 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15801 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15802 				XUnlockDisplay(display);
15803 				scope(exit) XLockDisplay(display);
15804 				(*ssh).handleRequest(e);
15805 			}
15806 		  break;
15807 		  case EventType.PropertyNotify:
15808 			// printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15809 
15810 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15811 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15812 					ssh.sendMoreIncr(&e.xproperty);
15813 			}
15814 
15815 
15816 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15817 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15818 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15819 					Atom target;
15820 					int format;
15821 					arch_ulong bytesafter, length;
15822 					void* value;
15823 
15824 					ubyte[] s;
15825 					Atom targetToKeep;
15826 
15827 					XGetWindowProperty(
15828 						e.xproperty.display,
15829 						e.xproperty.window,
15830 						e.xproperty.atom,
15831 						0,
15832 						100000 /* length */,
15833 						true, /* erase it to signal we got it and want more */
15834 						0 /*AnyPropertyType*/,
15835 						&target, &format, &length, &bytesafter, &value);
15836 
15837 					if(!targetToKeep)
15838 						targetToKeep = target;
15839 
15840 					auto id = (cast(ubyte*) value)[0 .. length];
15841 
15842 					handler.handleIncrData(targetToKeep, id);
15843 					if(length == 0) {
15844 						win.getSelectionHandlers.remove(e.xproperty.atom);
15845 					}
15846 
15847 					XFree(value);
15848 				}
15849 			}
15850 		  break;
15851 		  case EventType.SelectionNotify:
15852 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15853 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15854 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15855 					XUnlockDisplay(display);
15856 					scope(exit) XLockDisplay(display);
15857 					handler.handleData(None, null);
15858 					win.getSelectionHandlers.remove(e.xproperty.atom);
15859 				} else {
15860 					Atom target;
15861 					int format;
15862 					arch_ulong bytesafter, length;
15863 					void* value;
15864 					XGetWindowProperty(
15865 						e.xselection.display,
15866 						e.xselection.requestor,
15867 						e.xselection.property,
15868 						0,
15869 						100000 /* length */,
15870 						//false, /* don't erase it */
15871 						true, /* do erase it lol */
15872 						0 /*AnyPropertyType*/,
15873 						&target, &format, &length, &bytesafter, &value);
15874 
15875 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15876 
15877 					{
15878 						XUnlockDisplay(display);
15879 						scope(exit) XLockDisplay(display);
15880 
15881 						if(target == XA_ATOM) {
15882 							// initial request, see what they are able to work with and request the best one
15883 							// we can handle, if available
15884 
15885 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15886 							Atom best = handler.findBestFormat(answer);
15887 
15888 							/+
15889 							writeln("got ", answer);
15890 							foreach(a; answer)
15891 								writeln(XGetAtomName(display, a).stringz);
15892 							writeln("best ", best);
15893 							+/
15894 
15895 							if(best != None) {
15896 								// actually request the best format
15897 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15898 							}
15899 						} else if(target == GetAtom!"INCR"(display)) {
15900 							// incremental
15901 
15902 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15903 
15904 							// signal the sending program that we see
15905 							// the incr and are ready to receive more.
15906 							XDeleteProperty(
15907 								e.xselection.display,
15908 								e.xselection.requestor,
15909 								e.xselection.property);
15910 						} else {
15911 							// unsupported type... maybe, forward, then we done with it
15912 							if(target != None) {
15913 								handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15914 								win.getSelectionHandlers.remove(e.xproperty.atom);
15915 							}
15916 						}
15917 					}
15918 					XFree(value);
15919 					/*
15920 					XDeleteProperty(
15921 						e.xselection.display,
15922 						e.xselection.requestor,
15923 						e.xselection.property);
15924 					*/
15925 				}
15926 			}
15927 		  break;
15928 		  case EventType.ConfigureNotify:
15929 			auto event = e.xconfigure;
15930 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15931 				if(win.windowType == WindowTypes.minimallyWrapped)
15932 					break;
15933 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
15934 
15935 				/+
15936 					The ICCCM says window managers must send a synthetic event when the window
15937 					is moved but NOT when it is resized. In the resize case, an event is sent
15938 					with position (0, 0) which can be wrong and break the dpi calculations.
15939 
15940 					So we only consider the synthetic events from the WM and otherwise
15941 					need to wait for some other event to get the position which... sucks.
15942 
15943 					I'd rather not have windows changing their layout on mouse motion after
15944 					switching monitors... might be forced to but for now just ignoring it.
15945 
15946 					Easiest way to switch monitors without sending a size position is by
15947 					maximize or fullscreen in a setup like mine, but on most setups those
15948 					work on the monitor it is already living on, so it should be ok most the
15949 					time.
15950 				+/
15951 				if(event.send_event) {
15952 					win.screenPositionKnown = true;
15953 					win.screenPositionX = event.x;
15954 					win.screenPositionY = event.y;
15955 					win.updateActualDpi();
15956 				}
15957 
15958 				win.updateIMEPopupLocation();
15959 				recordX11ResizeAsync(display, *win, event.width, event.height);
15960 			}
15961 		  break;
15962 		  case EventType.Expose:
15963 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15964 				if(win.windowType == WindowTypes.minimallyWrapped)
15965 					break;
15966 				// if it is closing from a popup menu, it can get
15967 				// an Expose event right by the end and trigger a
15968 				// BadDrawable error ... we'll just check
15969 				// closed to handle that.
15970 				if((*win).closed) break;
15971 				if((*win).openglMode == OpenGlOptions.no) {
15972 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15973 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15974 					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);
15975 				} else {
15976 					// need to redraw the scene somehow
15977 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15978 						XUnlockDisplay(display);
15979 						scope(exit) XLockDisplay(display);
15980 						version(without_opengl) {} else
15981 						win.redrawOpenGlSceneSoon();
15982 					}
15983 				}
15984 			}
15985 		  break;
15986 		  case EventType.FocusIn:
15987 		  case EventType.FocusOut:
15988 
15989 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15990 				/+
15991 
15992 				void info(string detail) {
15993 					string s;
15994 					// import std.conv;
15995 					// import std.datetime;
15996 					s ~= to!string(Clock.currTime);
15997 					s ~= " ";
15998 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15999 					s ~= " ";
16000 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
16001 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
16002 					s ~= detail;
16003 					s ~= " ";
16004 
16005 					sdpyPrintDebugString(s);
16006 
16007 				}
16008 
16009 				switch(e.xfocus.detail) {
16010 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
16011 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
16012 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
16013 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
16014 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
16015 					case NotifyDetail.NotifyPointer: info("pointer"); break;
16016 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
16017 					case NotifyDetail.NotifyDetailNone: info("none"); break;
16018 					default:
16019 
16020 				}
16021 				+/
16022 
16023 
16024 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
16025 					break; // just ignore these they seem irrelevant
16026 
16027 				auto old = win._focused;
16028 				win._focused = e.type == EventType.FocusIn;
16029 
16030 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
16031 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
16032 					win._focused = true;
16033 
16034 				if(win.demandingAttention)
16035 					demandAttention(*win, false);
16036 
16037 				win.updateIMEFocused();
16038 
16039 				if(old != win._focused && win.onFocusChange) {
16040 					XUnlockDisplay(display);
16041 					scope(exit) XLockDisplay(display);
16042 					win.onFocusChange(win._focused);
16043 				}
16044 			}
16045 		  break;
16046 		  case EventType.VisibilityNotify:
16047 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16048 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
16049 						if (win.visibilityChanged !is null) {
16050 								XUnlockDisplay(display);
16051 								scope(exit) XLockDisplay(display);
16052 								win.visibilityChanged(false);
16053 							}
16054 					} else {
16055 						if (win.visibilityChanged !is null) {
16056 							XUnlockDisplay(display);
16057 							scope(exit) XLockDisplay(display);
16058 							win.visibilityChanged(true);
16059 						}
16060 					}
16061 				}
16062 				break;
16063 		  case EventType.ClientMessage:
16064 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
16065 					// "ignore next mouse motion" event, increment ignore counter for teh window
16066 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16067 						++(*win).warpEventCount;
16068 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
16069 					} else {
16070 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
16071 					}
16072 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
16073 					// user clicked the close button on the window manager
16074 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16075 						XUnlockDisplay(display);
16076 						scope(exit) XLockDisplay(display);
16077 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
16078 					}
16079 
16080 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
16081 					// writeln("HAPPENED");
16082 					// user clicked the close button on the window manager
16083 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16084 						XUnlockDisplay(display);
16085 						scope(exit) XLockDisplay(display);
16086 
16087 						auto setTo = *win;
16088 
16089 						if(win.setRequestedInputFocus !is null) {
16090 							auto s = win.setRequestedInputFocus();
16091 							if(s !is null) {
16092 								setTo = s;
16093 							}
16094 						}
16095 
16096 						assert(setTo !is null);
16097 
16098 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
16099 
16100 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
16101 					}
16102 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
16103 					foreach(nai; NotificationAreaIcon.activeIcons)
16104 						nai.newManager();
16105 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16106 
16107 					bool xDragWindow = true;
16108 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
16109 						//XDefineCursor(display, xDragWindow.impl.window,
16110 							//writeln("XdndStatus ", e.xclient.data.l);
16111 					}
16112 					if(auto dh = win.dropHandler) {
16113 
16114 						static Atom[3] xFormatsBuffer;
16115 						static Atom[] xFormats;
16116 
16117 						void resetXFormats() {
16118 							xFormatsBuffer[] = 0;
16119 							xFormats = xFormatsBuffer[];
16120 						}
16121 
16122 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
16123 							// on Windows it is supposed to return the effect you actually do FIXME
16124 
16125 							auto sourceWindow =  e.xclient.data.l[0];
16126 
16127 							xFormatsBuffer[0] = e.xclient.data.l[2];
16128 							xFormatsBuffer[1] = e.xclient.data.l[3];
16129 							xFormatsBuffer[2] = e.xclient.data.l[4];
16130 
16131 							if(e.xclient.data.l[1] & 1) {
16132 								// can just grab it all but like we don't necessarily need them...
16133 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
16134 							} else {
16135 								int len;
16136 								foreach(fmt; xFormatsBuffer)
16137 									if(fmt) len++;
16138 								xFormats = xFormatsBuffer[0 .. len];
16139 							}
16140 
16141 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
16142 
16143 							dh.dragEnter(&pkg);
16144 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
16145 
16146 							auto pack = e.xclient.data.l[2];
16147 
16148 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
16149 
16150 
16151 							XClientMessageEvent xclient;
16152 
16153 							xclient.type = EventType.ClientMessage;
16154 							xclient.window = e.xclient.data.l[0];
16155 							xclient.message_type = GetAtom!"XdndStatus"(display);
16156 							xclient.format = 32;
16157 							xclient.data.l[0] = win.impl.window;
16158 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
16159 							auto r = result.consistentWithin;
16160 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
16161 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
16162 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
16163 
16164 							XSendEvent(
16165 								display,
16166 								e.xclient.data.l[0],
16167 								false,
16168 								EventMask.NoEventMask,
16169 								cast(XEvent*) &xclient
16170 							);
16171 
16172 
16173 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
16174 							//writeln("XdndLeave");
16175 							// drop cancelled.
16176 							// data.l[0] is the source window
16177 							dh.dragLeave();
16178 
16179 							resetXFormats();
16180 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
16181 							// drop happening, should fetch data, then send finished
16182 							// writeln("XdndDrop");
16183 
16184 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
16185 
16186 							dh.drop(&pkg);
16187 
16188 							resetXFormats();
16189 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
16190 							// writeln("XdndFinished");
16191 
16192 							dh.finish();
16193 						}
16194 
16195 					}
16196 				}
16197 		  break;
16198 		  case EventType.MapNotify:
16199 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
16200 					(*win)._visible = true;
16201 					if (!(*win)._visibleForTheFirstTimeCalled) {
16202 						(*win)._visibleForTheFirstTimeCalled = true;
16203 						if ((*win).visibleForTheFirstTime !is null) {
16204 							XUnlockDisplay(display);
16205 							scope(exit) XLockDisplay(display);
16206 							(*win).visibleForTheFirstTime();
16207 						}
16208 					}
16209 					if ((*win).visibilityChanged !is null) {
16210 						XUnlockDisplay(display);
16211 						scope(exit) XLockDisplay(display);
16212 						(*win).visibilityChanged(true);
16213 					}
16214 				}
16215 		  break;
16216 		  case EventType.UnmapNotify:
16217 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
16218 					win._visible = false;
16219 					if (win.visibilityChanged !is null) {
16220 						XUnlockDisplay(display);
16221 						scope(exit) XLockDisplay(display);
16222 						win.visibilityChanged(false);
16223 					}
16224 			}
16225 		  break;
16226 		  case EventType.DestroyNotify:
16227 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
16228 				if(win.destroyed)
16229 					break; // might get a notification both for itself and from its parent
16230 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
16231 				win._closed = true; // just in case
16232 				win.destroyed = true;
16233 				if (win.xic !is null) {
16234 					XDestroyIC(win.xic);
16235 					win.xic = null; // just in case
16236 				}
16237 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
16238 				bool anyImportant = false;
16239 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
16240 					if(w.beingOpenKeepsAppOpen) {
16241 						anyImportant = true;
16242 						break;
16243 					}
16244 				if(!anyImportant) {
16245 					EventLoop.quitApplication();
16246 					done = true;
16247 				}
16248 			}
16249 			auto window = e.xdestroywindow.window;
16250 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
16251 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
16252 
16253 			version(with_eventloop) {
16254 				if(done) exit();
16255 			}
16256 		  break;
16257 
16258 		  case EventType.MotionNotify:
16259 			MouseEvent mouse;
16260 			auto event = e.xmotion;
16261 
16262 			mouse.type = MouseEventType.motion;
16263 			mouse.x = event.x;
16264 			mouse.y = event.y;
16265 			mouse.modifierState = event.state;
16266 
16267 			mouse.timestamp = event.time;
16268 
16269 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
16270 				mouse.window = *win;
16271 				if (win.warpEventCount > 0) {
16272 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
16273 					--(*win).warpEventCount;
16274 					(*win).mdx(mouse); // so deltas will be correctly updated
16275 				} else {
16276 					win.warpEventCount = 0; // just in case
16277 					(*win).mdx(mouse);
16278 					if((*win).handleMouseEvent) {
16279 						XUnlockDisplay(display);
16280 						scope(exit) XLockDisplay(display);
16281 						(*win).handleMouseEvent(mouse);
16282 					}
16283 				}
16284 			}
16285 
16286 		  	version(with_eventloop)
16287 				send(mouse);
16288 		  break;
16289 		  case EventType.ButtonPress:
16290 		  case EventType.ButtonRelease:
16291 			MouseEvent mouse;
16292 			auto event = e.xbutton;
16293 
16294 			mouse.timestamp = event.time;
16295 
16296 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
16297 			mouse.x = event.x;
16298 			mouse.y = event.y;
16299 
16300 			static Time lastMouseDownTime = 0;
16301 			static int lastMouseDownButton = -1;
16302 
16303 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
16304 			if(e.type == EventType.ButtonPress) {
16305 				lastMouseDownTime = event.time;
16306 				lastMouseDownButton = event.button;
16307 			}
16308 
16309 			switch(event.button) {
16310 				case 1: mouse.button = MouseButton.left; break; // left
16311 				case 2: mouse.button = MouseButton.middle; break; // middle
16312 				case 3: mouse.button = MouseButton.right; break; // right
16313 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
16314 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
16315 				case 6: break; // idk
16316 				case 7: break; // idk
16317 				case 8: mouse.button = MouseButton.backButton; break;
16318 				case 9: mouse.button = MouseButton.forwardButton; break;
16319 				default:
16320 			}
16321 
16322 			// FIXME: double check this
16323 			mouse.modifierState = event.state;
16324 
16325 			//mouse.modifierState = event.detail;
16326 
16327 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
16328 				mouse.window = *win;
16329 				(*win).mdx(mouse);
16330 				if((*win).handleMouseEvent) {
16331 					XUnlockDisplay(display);
16332 					scope(exit) XLockDisplay(display);
16333 					(*win).handleMouseEvent(mouse);
16334 				}
16335 			}
16336 			version(with_eventloop)
16337 				send(mouse);
16338 		  break;
16339 
16340 		  case EventType.KeyPress:
16341 		  case EventType.KeyRelease:
16342 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
16343 			KeyEvent ke;
16344 			ke.pressed = e.type == EventType.KeyPress;
16345 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
16346 
16347 			auto sym = XKeycodeToKeysym(
16348 				XDisplayConnection.get(),
16349 				e.xkey.keycode,
16350 				0);
16351 
16352 			ke.key = cast(Key) sym;//e.xkey.keycode;
16353 
16354 			ke.modifierState = e.xkey.state;
16355 
16356 			// writefln("%x", sym);
16357 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
16358 			int charbuflen = 0; // return value of XwcLookupString
16359 			if (ke.pressed) {
16360 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
16361 				if (win !is null && win.xic !is null) {
16362 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
16363 					Status status;
16364 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
16365 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
16366 				} else {
16367 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
16368 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
16369 					char[16] buffer;
16370 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
16371 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
16372 				}
16373 			}
16374 
16375 			// if there's no char, subst one
16376 			if (charbuflen == 0) {
16377 				switch (sym) {
16378 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
16379 					case 0xff8d: // keypad enter
16380 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
16381 					default : // ignore
16382 				}
16383 			}
16384 
16385 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
16386 				ke.window = *win;
16387 
16388 
16389 				if(win.inputProxy)
16390 					win = &win.inputProxy;
16391 
16392 				// char events are separate since they are on Windows too
16393 				// also, xcompose can generate long char sequences
16394 				// don't send char events if Meta and/or Hyper is pressed
16395 				// TODO: ctrl+char should only send control chars; not yet
16396 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
16397 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
16398 				}
16399 
16400 				dchar[32] charsComingBuffer;
16401 				int charsComingPosition;
16402 				dchar[] charsComing = charsComingBuffer[];
16403 
16404 				if (ke.pressed && charbuflen > 0) {
16405 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
16406 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
16407 						if(charsComingPosition >= charsComing.length)
16408 							charsComing.length = charsComingPosition + 8;
16409 
16410 						charsComing[charsComingPosition++] = ch;
16411 					}
16412 
16413 					charsComing = charsComing[0 .. charsComingPosition];
16414 				} else {
16415 					charsComing = null;
16416 				}
16417 
16418 				ke.charsPossible = charsComing;
16419 
16420 				if (win.handleKeyEvent) {
16421 					XUnlockDisplay(display);
16422 					scope(exit) XLockDisplay(display);
16423 					win.handleKeyEvent(ke);
16424 				}
16425 
16426 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
16427 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
16428 					XUnlockDisplay(display);
16429 					scope(exit) XLockDisplay(display);
16430 					foreach(ch; charsComing)
16431 						win.handleCharEvent(ch);
16432 				}
16433 			}
16434 
16435 			version(with_eventloop)
16436 				send(ke);
16437 		  break;
16438 		  default:
16439 		}
16440 
16441 		return done;
16442 	}
16443 }
16444 
16445 /* *************************************** */
16446 /*      Done with simpledisplay stuff      */
16447 /* *************************************** */
16448 
16449 // Necessary C library bindings follow
16450 version(Windows) {} else
16451 version(X11) {
16452 
16453 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
16454 
16455 // X11 bindings needed here
16456 /*
16457 	A little of this is from the bindings project on
16458 	D Source and some of it is copy/paste from the C
16459 	header.
16460 
16461 	The DSource listing consistently used D's long
16462 	where C used long. That's wrong - C long is 32 bit, so
16463 	it should be int in D. I changed that here.
16464 
16465 	Note:
16466 	This isn't complete, just took what I needed for myself.
16467 */
16468 
16469 import core.stdc.stddef : wchar_t;
16470 
16471 interface XLib {
16472 extern(C) nothrow @nogc {
16473 	char* XResourceManagerString(Display*);
16474 	void XrmInitialize();
16475 	XrmDatabase XrmGetStringDatabase(char* data);
16476 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
16477 
16478 	Cursor XCreateFontCursor(Display*, uint shape);
16479 	int XDefineCursor(Display* display, Window w, Cursor cursor);
16480 	int XUndefineCursor(Display* display, Window w);
16481 
16482 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
16483 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
16484 	int XFreeCursor(Display* display, Cursor cursor);
16485 
16486 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16487 
16488 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16489 
16490 	XVaNestedList XVaCreateNestedList(int unused, ...);
16491 
16492 	char *XKeysymToString(KeySym keysym);
16493 	KeySym XKeycodeToKeysym(
16494 		Display*		/* display */,
16495 		KeyCode		/* keycode */,
16496 		int			/* index */
16497 	);
16498 
16499 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16500 
16501 	int XFree(void*);
16502 	int XDeleteProperty(Display *display, Window w, Atom property);
16503 
16504 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16505 
16506 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16507 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16508 		*actual_type_return, int *actual_format_return, arch_ulong
16509 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16510 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16511 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16512 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16513 
16514 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16515 
16516 	Window XGetSelectionOwner(Display *display, Atom selection);
16517 
16518 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16519 
16520 	char** XListFonts(Display*, const char*, int, int*);
16521 	void XFreeFontNames(char**);
16522 
16523 	Display* XOpenDisplay(const char*);
16524 	int XCloseDisplay(Display*);
16525 
16526 	int function() XSynchronize(Display*, bool);
16527 	int function() XSetAfterFunction(Display*, int function() proc);
16528 
16529 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16530 
16531 	Bool XSupportsLocale();
16532 	char* XSetLocaleModifiers(const(char)* modifier_list);
16533 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16534 	Status XCloseOM(XOM om);
16535 
16536 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16537 	Status XCloseIM(XIM im);
16538 
16539 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16540 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16541 	Display* XDisplayOfIM(XIM im);
16542 	char* XLocaleOfIM(XIM im);
16543 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16544 	void XDestroyIC(XIC ic);
16545 	void XSetICFocus(XIC ic);
16546 	void XUnsetICFocus(XIC ic);
16547 	//wchar_t* XwcResetIC(XIC ic);
16548 	char* XmbResetIC(XIC ic);
16549 	char* Xutf8ResetIC(XIC ic);
16550 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16551 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16552 	XIM XIMOfIC(XIC ic);
16553 
16554 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16555 
16556 
16557 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16558 	int XFreeFont(Display *display, XFontStruct *font_struct);
16559 	int XSetFont(Display* display, GC gc, Font font);
16560 	int XTextWidth(XFontStruct*, scope const char*, int);
16561 
16562 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16563 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16564 
16565 	Window XCreateSimpleWindow(
16566 		Display*	/* display */,
16567 		Window		/* parent */,
16568 		int			/* x */,
16569 		int			/* y */,
16570 		uint		/* width */,
16571 		uint		/* height */,
16572 		uint		/* border_width */,
16573 		uint		/* border */,
16574 		uint		/* background */
16575 	);
16576 	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);
16577 
16578 	int XReparentWindow(Display*, Window, Window, int, int);
16579 	int XClearWindow(Display*, Window);
16580 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16581 	int XMoveWindow(Display*, Window, int, int);
16582 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16583 
16584 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16585 
16586 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16587 
16588 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16589 
16590 	XImage *XCreateImage(
16591 		Display*		/* display */,
16592 		Visual*		/* visual */,
16593 		uint	/* depth */,
16594 		int			/* format */,
16595 		int			/* offset */,
16596 		ubyte*		/* data */,
16597 		uint	/* width */,
16598 		uint	/* height */,
16599 		int			/* bitmap_pad */,
16600 		int			/* bytes_per_line */
16601 	);
16602 
16603 	Status XInitImage (XImage* image);
16604 
16605 	Atom XInternAtom(
16606 		Display*		/* display */,
16607 		const char*	/* atom_name */,
16608 		Bool		/* only_if_exists */
16609 	);
16610 
16611 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16612 	char* XGetAtomName(Display*, Atom);
16613 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16614 
16615 	int XPutImage(
16616 		Display*	/* display */,
16617 		Drawable	/* d */,
16618 		GC			/* gc */,
16619 		XImage*	/* image */,
16620 		int			/* src_x */,
16621 		int			/* src_y */,
16622 		int			/* dest_x */,
16623 		int			/* dest_y */,
16624 		uint		/* width */,
16625 		uint		/* height */
16626 	);
16627 
16628 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16629 
16630 
16631 	int XDestroyWindow(
16632 		Display*	/* display */,
16633 		Window		/* w */
16634 	);
16635 
16636 	int XDestroyImage(XImage*);
16637 
16638 	int XSelectInput(
16639 		Display*	/* display */,
16640 		Window		/* w */,
16641 		EventMask	/* event_mask */
16642 	);
16643 
16644 	int XMapWindow(
16645 		Display*	/* display */,
16646 		Window		/* w */
16647 	);
16648 
16649 	Status XIconifyWindow(Display*, Window, int);
16650 	int XMapRaised(Display*, Window);
16651 	int XMapSubwindows(Display*, Window);
16652 
16653 	int XNextEvent(
16654 		Display*	/* display */,
16655 		XEvent*		/* event_return */
16656 	);
16657 
16658 	int XMaskEvent(Display*, arch_long, XEvent*);
16659 
16660 	Bool XFilterEvent(XEvent *event, Window window);
16661 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16662 
16663 	Status XSetWMProtocols(
16664 		Display*	/* display */,
16665 		Window		/* w */,
16666 		Atom*		/* protocols */,
16667 		int			/* count */
16668 	);
16669 
16670 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16671 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16672 
16673 
16674 	Status XInitThreads();
16675 	void XLockDisplay (Display* display);
16676 	void XUnlockDisplay (Display* display);
16677 
16678 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16679 
16680 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16681 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16682 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16683 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16684 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16685 
16686 
16687 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16688 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16689 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16690 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16691 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16692 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16693 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16694 	int XDrawPoint(Display*, Drawable, GC, int, int);
16695 	int XSetForeground(Display*, GC, uint);
16696 	int XSetBackground(Display*, GC, uint);
16697 
16698 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16699 	void XFreeFontSet(Display*, XFontSet);
16700 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16701 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16702 
16703 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16704 
16705 
16706 //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);
16707 
16708 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16709 	int XSetFunction(Display*, GC, int);
16710 
16711 	GC XCreateGC(Display*, Drawable, uint, void*);
16712 	int XCopyGC(Display*, GC, uint, GC);
16713 	int XFreeGC(Display*, GC);
16714 
16715 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16716 	bool XCheckMaskEvent(Display*, int, XEvent*);
16717 
16718 	int XPending(Display*);
16719 	int XEventsQueued(Display* display, int mode);
16720 
16721 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16722 	int XFreePixmap(Display*, Pixmap);
16723 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16724 	int XFlush(Display*);
16725 	int XBell(Display*, int);
16726 	int XSync(Display*, bool);
16727 
16728 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16729 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16730 
16731 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16732 	int XUngrabKeyboard(Display*, Time);
16733 
16734 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16735 
16736 	KeySym XStringToKeysym(const char *string);
16737 
16738 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16739 
16740 	Window XDefaultRootWindow(Display*);
16741 
16742 	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);
16743 
16744 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16745 
16746 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16747 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16748 
16749 	Status XAllocColor(Display*, Colormap, XColor*);
16750 
16751 	int XWithdrawWindow(Display*, Window, int);
16752 	int XUnmapWindow(Display*, Window);
16753 	int XLowerWindow(Display*, Window);
16754 	int XRaiseWindow(Display*, Window);
16755 
16756 	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);
16757 	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);
16758 
16759 	int XGetInputFocus(Display*, Window*, int*);
16760 	int XSetInputFocus(Display*, Window, int, Time);
16761 
16762 	XErrorHandler XSetErrorHandler(XErrorHandler);
16763 
16764 	int XGetErrorText(Display*, int, char*, int);
16765 
16766 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16767 
16768 
16769 	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);
16770 	int XUngrabPointer(Display *display, Time time);
16771 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16772 
16773 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16774 
16775 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16776 	int XSetClipMask(Display*, GC, Pixmap);
16777 	int XSetClipOrigin(Display*, GC, int, int);
16778 
16779 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16780 
16781 	void XSetWMName(Display*, Window, XTextProperty*);
16782 	Status XGetWMName(Display*, Window, XTextProperty*);
16783 	int XStoreName(Display* display, Window w, const(char)* window_name);
16784 
16785 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16786 
16787 }
16788 }
16789 
16790 interface Xext {
16791 extern(C) nothrow @nogc {
16792 	Status XShmAttach(Display*, XShmSegmentInfo*);
16793 	Status XShmDetach(Display*, XShmSegmentInfo*);
16794 	Status XShmPutImage(
16795 		Display*            /* dpy */,
16796 		Drawable            /* d */,
16797 		GC                  /* gc */,
16798 		XImage*             /* image */,
16799 		int                 /* src_x */,
16800 		int                 /* src_y */,
16801 		int                 /* dst_x */,
16802 		int                 /* dst_y */,
16803 		uint        /* src_width */,
16804 		uint        /* src_height */,
16805 		Bool                /* send_event */
16806 	);
16807 
16808 	Status XShmQueryExtension(Display*);
16809 
16810 	XImage *XShmCreateImage(
16811 		Display*            /* dpy */,
16812 		Visual*             /* visual */,
16813 		uint        /* depth */,
16814 		int                 /* format */,
16815 		char*               /* data */,
16816 		XShmSegmentInfo*    /* shminfo */,
16817 		uint        /* width */,
16818 		uint        /* height */
16819 	);
16820 
16821 	Pixmap XShmCreatePixmap(
16822 		Display*            /* dpy */,
16823 		Drawable            /* d */,
16824 		char*               /* data */,
16825 		XShmSegmentInfo*    /* shminfo */,
16826 		uint        /* width */,
16827 		uint        /* height */,
16828 		uint        /* depth */
16829 	);
16830 
16831 }
16832 }
16833 
16834 	// this requires -lXpm
16835 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16836 
16837 
16838 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16839 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16840 shared static this() {
16841 	xlib.loadDynamicLibrary();
16842 	xext.loadDynamicLibrary();
16843 }
16844 
16845 
16846 extern(C) nothrow @nogc {
16847 
16848 alias XrmDatabase = void*;
16849 struct XrmValue {
16850 	uint size;
16851 	void* addr;
16852 }
16853 
16854 struct XVisualInfo {
16855 	Visual* visual;
16856 	VisualID visualid;
16857 	int screen;
16858 	uint depth;
16859 	int c_class;
16860 	c_ulong red_mask;
16861 	c_ulong green_mask;
16862 	c_ulong blue_mask;
16863 	int colormap_size;
16864 	int bits_per_rgb;
16865 }
16866 
16867 enum VisualNoMask=	0x0;
16868 enum VisualIDMask=	0x1;
16869 enum VisualScreenMask=0x2;
16870 enum VisualDepthMask=	0x4;
16871 enum VisualClassMask=	0x8;
16872 enum VisualRedMaskMask=0x10;
16873 enum VisualGreenMaskMask=0x20;
16874 enum VisualBlueMaskMask=0x40;
16875 enum VisualColormapSizeMask=0x80;
16876 enum VisualBitsPerRGBMask=0x100;
16877 enum VisualAllMask=	0x1FF;
16878 
16879 enum AnyKey = 0;
16880 enum AnyModifier = 1 << 15;
16881 
16882 // XIM and other crap
16883 struct _XOM {}
16884 struct _XIM {}
16885 struct _XIC {}
16886 alias XOM = _XOM*;
16887 alias XIM = _XIM*;
16888 alias XIC = _XIC*;
16889 
16890 alias XVaNestedList = void*;
16891 
16892 alias XIMStyle = arch_ulong;
16893 enum : arch_ulong {
16894 	XIMPreeditArea      = 0x0001,
16895 	XIMPreeditCallbacks = 0x0002,
16896 	XIMPreeditPosition  = 0x0004,
16897 	XIMPreeditNothing   = 0x0008,
16898 	XIMPreeditNone      = 0x0010,
16899 	XIMStatusArea       = 0x0100,
16900 	XIMStatusCallbacks  = 0x0200,
16901 	XIMStatusNothing    = 0x0400,
16902 	XIMStatusNone       = 0x0800,
16903 }
16904 
16905 
16906 /* X Shared Memory Extension functions */
16907 	//pragma(lib, "Xshm");
16908 	alias arch_ulong ShmSeg;
16909 	struct XShmSegmentInfo {
16910 		ShmSeg shmseg;
16911 		int shmid;
16912 		ubyte* shmaddr;
16913 		Bool readOnly;
16914 	}
16915 
16916 	// and the necessary OS functions
16917 	int shmget(int, size_t, int);
16918 	void* shmat(int, scope const void*, int);
16919 	int shmdt(scope const void*);
16920 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16921 
16922 	enum IPC_PRIVATE = 0;
16923 	enum IPC_CREAT = 512;
16924 	enum IPC_RMID = 0;
16925 
16926 /* MIT-SHM end */
16927 
16928 
16929 enum MappingType:int {
16930 	MappingModifier		=0,
16931 	MappingKeyboard		=1,
16932 	MappingPointer		=2
16933 }
16934 
16935 /* ImageFormat -- PutImage, GetImage */
16936 enum ImageFormat:int {
16937 	XYBitmap	=0,	/* depth 1, XYFormat */
16938 	XYPixmap	=1,	/* depth == drawable depth */
16939 	ZPixmap	=2	/* depth == drawable depth */
16940 }
16941 
16942 enum ModifierName:int {
16943 	ShiftMapIndex	=0,
16944 	LockMapIndex	=1,
16945 	ControlMapIndex	=2,
16946 	Mod1MapIndex	=3,
16947 	Mod2MapIndex	=4,
16948 	Mod3MapIndex	=5,
16949 	Mod4MapIndex	=6,
16950 	Mod5MapIndex	=7
16951 }
16952 
16953 enum ButtonMask:int {
16954 	Button1Mask	=1<<8,
16955 	Button2Mask	=1<<9,
16956 	Button3Mask	=1<<10,
16957 	Button4Mask	=1<<11,
16958 	Button5Mask	=1<<12,
16959 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16960 }
16961 
16962 enum KeyOrButtonMask:uint {
16963 	ShiftMask	=1<<0,
16964 	LockMask	=1<<1,
16965 	ControlMask	=1<<2,
16966 	Mod1Mask	=1<<3,
16967 	Mod2Mask	=1<<4,
16968 	Mod3Mask	=1<<5,
16969 	Mod4Mask	=1<<6,
16970 	Mod5Mask	=1<<7,
16971 	Button1Mask	=1<<8,
16972 	Button2Mask	=1<<9,
16973 	Button3Mask	=1<<10,
16974 	Button4Mask	=1<<11,
16975 	Button5Mask	=1<<12,
16976 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16977 }
16978 
16979 enum ButtonName:int {
16980 	Button1	=1,
16981 	Button2	=2,
16982 	Button3	=3,
16983 	Button4	=4,
16984 	Button5	=5
16985 }
16986 
16987 /* Notify modes */
16988 enum NotifyModes:int
16989 {
16990 	NotifyNormal		=0,
16991 	NotifyGrab			=1,
16992 	NotifyUngrab		=2,
16993 	NotifyWhileGrabbed	=3
16994 }
16995 enum NotifyHint = 1;	/* for MotionNotify events */
16996 
16997 /* Notify detail */
16998 enum NotifyDetail:int
16999 {
17000 	NotifyAncestor			=0,
17001 	NotifyVirtual			=1,
17002 	NotifyInferior			=2,
17003 	NotifyNonlinear			=3,
17004 	NotifyNonlinearVirtual	=4,
17005 	NotifyPointer			=5,
17006 	NotifyPointerRoot		=6,
17007 	NotifyDetailNone		=7
17008 }
17009 
17010 /* Visibility notify */
17011 
17012 enum VisibilityNotify:int
17013 {
17014 VisibilityUnobscured		=0,
17015 VisibilityPartiallyObscured	=1,
17016 VisibilityFullyObscured		=2
17017 }
17018 
17019 
17020 enum WindowStackingMethod:int
17021 {
17022 	Above		=0,
17023 	Below		=1,
17024 	TopIf		=2,
17025 	BottomIf	=3,
17026 	Opposite	=4
17027 }
17028 
17029 /* Circulation request */
17030 enum CirculationRequest:int
17031 {
17032 	PlaceOnTop		=0,
17033 	PlaceOnBottom	=1
17034 }
17035 
17036 enum PropertyNotification:int
17037 {
17038 	PropertyNewValue	=0,
17039 	PropertyDelete		=1
17040 }
17041 
17042 enum ColorMapNotification:int
17043 {
17044 	ColormapUninstalled	=0,
17045 	ColormapInstalled		=1
17046 }
17047 
17048 
17049 	struct _XPrivate {}
17050 	struct _XrmHashBucketRec {}
17051 
17052 	alias void* XPointer;
17053 	alias void* XExtData;
17054 
17055 	version( X86_64 ) {
17056 		alias ulong XID;
17057 		alias ulong arch_ulong;
17058 		alias long arch_long;
17059 	} else version (AArch64) {
17060 		alias ulong XID;
17061 		alias ulong arch_ulong;
17062 		alias long arch_long;
17063 	} else {
17064 		alias uint XID;
17065 		alias uint arch_ulong;
17066 		alias int arch_long;
17067 	}
17068 
17069 	alias XID Window;
17070 	alias XID Drawable;
17071 	alias XID Pixmap;
17072 
17073 	alias arch_ulong Atom;
17074 	alias int Bool;
17075 	alias Display XDisplay;
17076 
17077 	alias int ByteOrder;
17078 	alias arch_ulong Time;
17079 	alias void ScreenFormat;
17080 
17081 	struct XImage {
17082 		int width, height;			/* size of image */
17083 		int xoffset;				/* number of pixels offset in X direction */
17084 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
17085 		void *data;					/* pointer to image data */
17086 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
17087 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
17088 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
17089 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
17090 		int depth;					/* depth of image */
17091 		int bytes_per_line;			/* accelarator to next line */
17092 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
17093 		arch_ulong red_mask;	/* bits in z arrangment */
17094 		arch_ulong green_mask;
17095 		arch_ulong blue_mask;
17096 		XPointer obdata;			/* hook for the object routines to hang on */
17097 		static struct F {				/* image manipulation routines */
17098 			XImage* function(
17099 				XDisplay* 			/* display */,
17100 				Visual*				/* visual */,
17101 				uint				/* depth */,
17102 				int					/* format */,
17103 				int					/* offset */,
17104 				ubyte*				/* data */,
17105 				uint				/* width */,
17106 				uint				/* height */,
17107 				int					/* bitmap_pad */,
17108 				int					/* bytes_per_line */) create_image;
17109 			int function(XImage *) destroy_image;
17110 			arch_ulong function(XImage *, int, int) get_pixel;
17111 			int function(XImage *, int, int, arch_ulong) put_pixel;
17112 			XImage* function(XImage *, int, int, uint, uint) sub_image;
17113 			int function(XImage *, arch_long) add_pixel;
17114 		}
17115 		F f;
17116 	}
17117 	version(X86_64) static assert(XImage.sizeof == 136);
17118 	else version(X86) static assert(XImage.sizeof == 88);
17119 
17120 struct XCharStruct {
17121 	short       lbearing;       /* origin to left edge of raster */
17122 	short       rbearing;       /* origin to right edge of raster */
17123 	short       width;          /* advance to next char's origin */
17124 	short       ascent;         /* baseline to top edge of raster */
17125 	short       descent;        /* baseline to bottom edge of raster */
17126 	ushort attributes;  /* per char flags (not predefined) */
17127 }
17128 
17129 /*
17130  * To allow arbitrary information with fonts, there are additional properties
17131  * returned.
17132  */
17133 struct XFontProp {
17134 	Atom name;
17135 	arch_ulong card32;
17136 }
17137 
17138 alias Atom Font;
17139 
17140 struct XFontStruct {
17141 	XExtData *ext_data;           /* Hook for extension to hang data */
17142 	Font fid;                     /* Font ID for this font */
17143 	uint direction;           /* Direction the font is painted */
17144 	uint min_char_or_byte2;   /* First character */
17145 	uint max_char_or_byte2;   /* Last character */
17146 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
17147 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
17148 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
17149 	uint default_char;        /* Char to print for undefined character */
17150 	int n_properties;             /* How many properties there are */
17151 	XFontProp *properties;        /* Pointer to array of additional properties*/
17152 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
17153 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
17154 	XCharStruct *per_char;        /* first_char to last_char information */
17155 	int ascent;                   /* Max extent above baseline for spacing */
17156 	int descent;                  /* Max descent below baseline for spacing */
17157 }
17158 
17159 
17160 /*
17161  * Definitions of specific events.
17162  */
17163 struct XKeyEvent
17164 {
17165 	int type;			/* of event */
17166 	arch_ulong serial;		/* # of last request processed by server */
17167 	Bool send_event;	/* true if this came from a SendEvent request */
17168 	Display *display;	/* Display the event was read from */
17169 	Window window;	        /* "event" window it is reported relative to */
17170 	Window root;	        /* root window that the event occurred on */
17171 	Window subwindow;	/* child window */
17172 	Time time;		/* milliseconds */
17173 	int x, y;		/* pointer x, y coordinates in event window */
17174 	int x_root, y_root;	/* coordinates relative to root */
17175 	KeyOrButtonMask state;	/* key or button mask */
17176 	uint keycode;	/* detail */
17177 	Bool same_screen;	/* same screen flag */
17178 }
17179 version(X86_64) static assert(XKeyEvent.sizeof == 96);
17180 alias XKeyEvent XKeyPressedEvent;
17181 alias XKeyEvent XKeyReleasedEvent;
17182 
17183 struct XButtonEvent
17184 {
17185 	int type;		/* of event */
17186 	arch_ulong serial;	/* # of last request processed by server */
17187 	Bool send_event;	/* true if this came from a SendEvent request */
17188 	Display *display;	/* Display the event was read from */
17189 	Window window;	        /* "event" window it is reported relative to */
17190 	Window root;	        /* root window that the event occurred on */
17191 	Window subwindow;	/* child window */
17192 	Time time;		/* milliseconds */
17193 	int x, y;		/* pointer x, y coordinates in event window */
17194 	int x_root, y_root;	/* coordinates relative to root */
17195 	KeyOrButtonMask state;	/* key or button mask */
17196 	uint button;	/* detail */
17197 	Bool same_screen;	/* same screen flag */
17198 }
17199 alias XButtonEvent XButtonPressedEvent;
17200 alias XButtonEvent XButtonReleasedEvent;
17201 
17202 struct XMotionEvent{
17203 	int type;		/* of event */
17204 	arch_ulong serial;	/* # of last request processed by server */
17205 	Bool send_event;	/* true if this came from a SendEvent request */
17206 	Display *display;	/* Display the event was read from */
17207 	Window window;	        /* "event" window reported relative to */
17208 	Window root;	        /* root window that the event occurred on */
17209 	Window subwindow;	/* child window */
17210 	Time time;		/* milliseconds */
17211 	int x, y;		/* pointer x, y coordinates in event window */
17212 	int x_root, y_root;	/* coordinates relative to root */
17213 	KeyOrButtonMask state;	/* key or button mask */
17214 	byte is_hint;		/* detail */
17215 	Bool same_screen;	/* same screen flag */
17216 }
17217 alias XMotionEvent XPointerMovedEvent;
17218 
17219 struct XCrossingEvent{
17220 	int type;		/* of event */
17221 	arch_ulong serial;	/* # of last request processed by server */
17222 	Bool send_event;	/* true if this came from a SendEvent request */
17223 	Display *display;	/* Display the event was read from */
17224 	Window window;	        /* "event" window reported relative to */
17225 	Window root;	        /* root window that the event occurred on */
17226 	Window subwindow;	/* child window */
17227 	Time time;		/* milliseconds */
17228 	int x, y;		/* pointer x, y coordinates in event window */
17229 	int x_root, y_root;	/* coordinates relative to root */
17230 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
17231 	NotifyDetail detail;
17232 	/*
17233 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17234 	 * NotifyNonlinear,NotifyNonlinearVirtual
17235 	 */
17236 	Bool same_screen;	/* same screen flag */
17237 	Bool focus;		/* Boolean focus */
17238 	KeyOrButtonMask state;	/* key or button mask */
17239 }
17240 alias XCrossingEvent XEnterWindowEvent;
17241 alias XCrossingEvent XLeaveWindowEvent;
17242 
17243 struct XFocusChangeEvent{
17244 	int type;		/* FocusIn or FocusOut */
17245 	arch_ulong serial;	/* # of last request processed by server */
17246 	Bool send_event;	/* true if this came from a SendEvent request */
17247 	Display *display;	/* Display the event was read from */
17248 	Window window;		/* window of event */
17249 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
17250 				   NotifyGrab, NotifyUngrab */
17251 	NotifyDetail detail;
17252 	/*
17253 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17254 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
17255 	 * NotifyPointerRoot, NotifyDetailNone
17256 	 */
17257 }
17258 alias XFocusChangeEvent XFocusInEvent;
17259 alias XFocusChangeEvent XFocusOutEvent;
17260 
17261 enum CWBackPixmap              = (1L<<0);
17262 enum CWBackPixel               = (1L<<1);
17263 enum CWBorderPixmap            = (1L<<2);
17264 enum CWBorderPixel             = (1L<<3);
17265 enum CWBitGravity              = (1L<<4);
17266 enum CWWinGravity              = (1L<<5);
17267 enum CWBackingStore            = (1L<<6);
17268 enum CWBackingPlanes           = (1L<<7);
17269 enum CWBackingPixel            = (1L<<8);
17270 enum CWOverrideRedirect        = (1L<<9);
17271 enum CWSaveUnder               = (1L<<10);
17272 enum CWEventMask               = (1L<<11);
17273 enum CWDontPropagate           = (1L<<12);
17274 enum CWColormap                = (1L<<13);
17275 enum CWCursor                  = (1L<<14);
17276 
17277 struct XWindowAttributes {
17278 	int x, y;			/* location of window */
17279 	int width, height;		/* width and height of window */
17280 	int border_width;		/* border width of window */
17281 	int depth;			/* depth of window */
17282 	Visual *visual;			/* the associated visual structure */
17283 	Window root;			/* root of screen containing window */
17284 	int class_;			/* InputOutput, InputOnly*/
17285 	int bit_gravity;		/* one of the bit gravity values */
17286 	int win_gravity;		/* one of the window gravity values */
17287 	int backing_store;		/* NotUseful, WhenMapped, Always */
17288 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
17289 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
17290 	Bool save_under;		/* boolean, should bits under be saved? */
17291 	Colormap colormap;		/* color map to be associated with window */
17292 	Bool map_installed;		/* boolean, is color map currently installed*/
17293 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
17294 	arch_long all_event_masks;		/* set of events all people have interest in*/
17295 	arch_long your_event_mask;		/* my event mask */
17296 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
17297 	Bool override_redirect;		/* boolean value for override-redirect */
17298 	Screen *screen;			/* back pointer to correct screen */
17299 }
17300 
17301 enum IsUnmapped = 0;
17302 enum IsUnviewable = 1;
17303 enum IsViewable = 2;
17304 
17305 struct XSetWindowAttributes {
17306 	Pixmap background_pixmap;/* background, None, or ParentRelative */
17307 	arch_ulong background_pixel;/* background pixel */
17308 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
17309 	arch_ulong border_pixel;/* border pixel value */
17310 	int bit_gravity;         /* one of bit gravity values */
17311 	int win_gravity;         /* one of the window gravity values */
17312 	int backing_store;       /* NotUseful, WhenMapped, Always */
17313 	arch_ulong backing_planes;/* planes to be preserved if possible */
17314 	arch_ulong backing_pixel;/* value to use in restoring planes */
17315 	Bool save_under;         /* should bits under be saved? (popups) */
17316 	arch_long event_mask;         /* set of events that should be saved */
17317 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
17318 	Bool override_redirect;  /* boolean value for override_redirect */
17319 	Colormap colormap;       /* color map to be associated with window */
17320 	Cursor cursor;           /* cursor to be displayed (or None) */
17321 }
17322 
17323 
17324 alias int Status;
17325 
17326 
17327 enum EventMask:int
17328 {
17329 	NoEventMask				=0,
17330 	KeyPressMask			=1<<0,
17331 	KeyReleaseMask			=1<<1,
17332 	ButtonPressMask			=1<<2,
17333 	ButtonReleaseMask		=1<<3,
17334 	EnterWindowMask			=1<<4,
17335 	LeaveWindowMask			=1<<5,
17336 	PointerMotionMask		=1<<6,
17337 	PointerMotionHintMask	=1<<7,
17338 	Button1MotionMask		=1<<8,
17339 	Button2MotionMask		=1<<9,
17340 	Button3MotionMask		=1<<10,
17341 	Button4MotionMask		=1<<11,
17342 	Button5MotionMask		=1<<12,
17343 	ButtonMotionMask		=1<<13,
17344 	KeymapStateMask		=1<<14,
17345 	ExposureMask			=1<<15,
17346 	VisibilityChangeMask	=1<<16,
17347 	StructureNotifyMask		=1<<17,
17348 	ResizeRedirectMask		=1<<18,
17349 	SubstructureNotifyMask	=1<<19,
17350 	SubstructureRedirectMask=1<<20,
17351 	FocusChangeMask			=1<<21,
17352 	PropertyChangeMask		=1<<22,
17353 	ColormapChangeMask		=1<<23,
17354 	OwnerGrabButtonMask		=1<<24
17355 }
17356 
17357 struct MwmHints {
17358 	c_ulong flags;
17359 	c_ulong functions;
17360 	c_ulong decorations;
17361 	c_long input_mode;
17362 	c_ulong status;
17363 }
17364 
17365 enum {
17366 	MWM_HINTS_FUNCTIONS = (1L << 0),
17367 	MWM_HINTS_DECORATIONS =  (1L << 1),
17368 
17369 	MWM_FUNC_ALL = (1L << 0),
17370 	MWM_FUNC_RESIZE = (1L << 1),
17371 	MWM_FUNC_MOVE = (1L << 2),
17372 	MWM_FUNC_MINIMIZE = (1L << 3),
17373 	MWM_FUNC_MAXIMIZE = (1L << 4),
17374 	MWM_FUNC_CLOSE = (1L << 5),
17375 
17376 	MWM_DECOR_ALL = (1L << 0),
17377 	MWM_DECOR_BORDER = (1L << 1),
17378 	MWM_DECOR_RESIZEH = (1L << 2),
17379 	MWM_DECOR_TITLE = (1L << 3),
17380 	MWM_DECOR_MENU = (1L << 4),
17381 	MWM_DECOR_MINIMIZE = (1L << 5),
17382 	MWM_DECOR_MAXIMIZE = (1L << 6),
17383 }
17384 
17385 import core.stdc.config : c_long, c_ulong;
17386 
17387 	/* Size hints mask bits */
17388 
17389 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
17390 	enum   USSize      = (1L << 1)          /* user specified width, height */;
17391 	enum   PPosition   = (1L << 2)          /* program specified position */;
17392 	enum   PSize       = (1L << 3)          /* program specified size */;
17393 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
17394 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
17395 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
17396 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
17397 	enum   PBaseSize   = (1L << 8);
17398 	enum   PWinGravity = (1L << 9);
17399 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
17400 	struct XSizeHints {
17401 		arch_long flags;         /* marks which fields in this structure are defined */
17402 		int x, y;           /* Obsolete */
17403 		int width, height;  /* Obsolete */
17404 		int min_width, min_height;
17405 		int max_width, max_height;
17406 		int width_inc, height_inc;
17407 		struct Aspect {
17408 			int x;       /* numerator */
17409 			int y;       /* denominator */
17410 		}
17411 
17412 		Aspect min_aspect;
17413 		Aspect max_aspect;
17414 		int base_width, base_height;
17415 		int win_gravity;
17416 		/* this structure may be extended in the future */
17417 	}
17418 
17419 
17420 
17421 enum EventType:int
17422 {
17423 	KeyPress			=2,
17424 	KeyRelease			=3,
17425 	ButtonPress			=4,
17426 	ButtonRelease		=5,
17427 	MotionNotify		=6,
17428 	EnterNotify			=7,
17429 	LeaveNotify			=8,
17430 	FocusIn				=9,
17431 	FocusOut			=10,
17432 	KeymapNotify		=11,
17433 	Expose				=12,
17434 	GraphicsExpose		=13,
17435 	NoExpose			=14,
17436 	VisibilityNotify	=15,
17437 	CreateNotify		=16,
17438 	DestroyNotify		=17,
17439 	UnmapNotify		=18,
17440 	MapNotify			=19,
17441 	MapRequest			=20,
17442 	ReparentNotify		=21,
17443 	ConfigureNotify		=22,
17444 	ConfigureRequest	=23,
17445 	GravityNotify		=24,
17446 	ResizeRequest		=25,
17447 	CirculateNotify		=26,
17448 	CirculateRequest	=27,
17449 	PropertyNotify		=28,
17450 	SelectionClear		=29,
17451 	SelectionRequest	=30,
17452 	SelectionNotify		=31,
17453 	ColormapNotify		=32,
17454 	ClientMessage		=33,
17455 	MappingNotify		=34,
17456 	LASTEvent			=35	/* must be bigger than any event # */
17457 }
17458 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
17459 struct XKeymapEvent
17460 {
17461 	int type;
17462 	arch_ulong serial;	/* # of last request processed by server */
17463 	Bool send_event;	/* true if this came from a SendEvent request */
17464 	Display *display;	/* Display the event was read from */
17465 	Window window;
17466 	byte[32] key_vector;
17467 }
17468 
17469 struct XExposeEvent
17470 {
17471 	int type;
17472 	arch_ulong serial;	/* # of last request processed by server */
17473 	Bool send_event;	/* true if this came from a SendEvent request */
17474 	Display *display;	/* Display the event was read from */
17475 	Window window;
17476 	int x, y;
17477 	int width, height;
17478 	int count;		/* if non-zero, at least this many more */
17479 }
17480 
17481 struct XGraphicsExposeEvent{
17482 	int type;
17483 	arch_ulong serial;	/* # of last request processed by server */
17484 	Bool send_event;	/* true if this came from a SendEvent request */
17485 	Display *display;	/* Display the event was read from */
17486 	Drawable drawable;
17487 	int x, y;
17488 	int width, height;
17489 	int count;		/* if non-zero, at least this many more */
17490 	int major_code;		/* core is CopyArea or CopyPlane */
17491 	int minor_code;		/* not defined in the core */
17492 }
17493 
17494 struct XNoExposeEvent{
17495 	int type;
17496 	arch_ulong serial;	/* # of last request processed by server */
17497 	Bool send_event;	/* true if this came from a SendEvent request */
17498 	Display *display;	/* Display the event was read from */
17499 	Drawable drawable;
17500 	int major_code;		/* core is CopyArea or CopyPlane */
17501 	int minor_code;		/* not defined in the core */
17502 }
17503 
17504 struct XVisibilityEvent{
17505 	int type;
17506 	arch_ulong serial;	/* # of last request processed by server */
17507 	Bool send_event;	/* true if this came from a SendEvent request */
17508 	Display *display;	/* Display the event was read from */
17509 	Window window;
17510 	VisibilityNotify state;		/* Visibility state */
17511 }
17512 
17513 struct XCreateWindowEvent{
17514 	int type;
17515 	arch_ulong serial;	/* # of last request processed by server */
17516 	Bool send_event;	/* true if this came from a SendEvent request */
17517 	Display *display;	/* Display the event was read from */
17518 	Window parent;		/* parent of the window */
17519 	Window window;		/* window id of window created */
17520 	int x, y;		/* window location */
17521 	int width, height;	/* size of window */
17522 	int border_width;	/* border width */
17523 	Bool override_redirect;	/* creation should be overridden */
17524 }
17525 
17526 struct XDestroyWindowEvent
17527 {
17528 	int type;
17529 	arch_ulong serial;		/* # of last request processed by server */
17530 	Bool send_event;	/* true if this came from a SendEvent request */
17531 	Display *display;	/* Display the event was read from */
17532 	Window event;
17533 	Window window;
17534 }
17535 
17536 struct XUnmapEvent
17537 {
17538 	int type;
17539 	arch_ulong serial;		/* # of last request processed by server */
17540 	Bool send_event;	/* true if this came from a SendEvent request */
17541 	Display *display;	/* Display the event was read from */
17542 	Window event;
17543 	Window window;
17544 	Bool from_configure;
17545 }
17546 
17547 struct XMapEvent
17548 {
17549 	int type;
17550 	arch_ulong serial;		/* # of last request processed by server */
17551 	Bool send_event;	/* true if this came from a SendEvent request */
17552 	Display *display;	/* Display the event was read from */
17553 	Window event;
17554 	Window window;
17555 	Bool override_redirect;	/* Boolean, is override set... */
17556 }
17557 
17558 struct XMapRequestEvent
17559 {
17560 	int type;
17561 	arch_ulong serial;	/* # of last request processed by server */
17562 	Bool send_event;	/* true if this came from a SendEvent request */
17563 	Display *display;	/* Display the event was read from */
17564 	Window parent;
17565 	Window window;
17566 }
17567 
17568 struct XReparentEvent
17569 {
17570 	int type;
17571 	arch_ulong serial;	/* # of last request processed by server */
17572 	Bool send_event;	/* true if this came from a SendEvent request */
17573 	Display *display;	/* Display the event was read from */
17574 	Window event;
17575 	Window window;
17576 	Window parent;
17577 	int x, y;
17578 	Bool override_redirect;
17579 }
17580 
17581 struct XConfigureEvent
17582 {
17583 	int type;
17584 	arch_ulong serial;	/* # of last request processed by server */
17585 	Bool send_event;	/* true if this came from a SendEvent request */
17586 	Display *display;	/* Display the event was read from */
17587 	Window event;
17588 	Window window;
17589 	int x, y;
17590 	int width, height;
17591 	int border_width;
17592 	Window above;
17593 	Bool override_redirect;
17594 }
17595 
17596 struct XGravityEvent
17597 {
17598 	int type;
17599 	arch_ulong serial;	/* # of last request processed by server */
17600 	Bool send_event;	/* true if this came from a SendEvent request */
17601 	Display *display;	/* Display the event was read from */
17602 	Window event;
17603 	Window window;
17604 	int x, y;
17605 }
17606 
17607 struct XResizeRequestEvent
17608 {
17609 	int type;
17610 	arch_ulong serial;	/* # of last request processed by server */
17611 	Bool send_event;	/* true if this came from a SendEvent request */
17612 	Display *display;	/* Display the event was read from */
17613 	Window window;
17614 	int width, height;
17615 }
17616 
17617 struct  XConfigureRequestEvent
17618 {
17619 	int type;
17620 	arch_ulong serial;	/* # of last request processed by server */
17621 	Bool send_event;	/* true if this came from a SendEvent request */
17622 	Display *display;	/* Display the event was read from */
17623 	Window parent;
17624 	Window window;
17625 	int x, y;
17626 	int width, height;
17627 	int border_width;
17628 	Window above;
17629 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17630 	arch_ulong value_mask;
17631 }
17632 
17633 struct XCirculateEvent
17634 {
17635 	int type;
17636 	arch_ulong serial;	/* # of last request processed by server */
17637 	Bool send_event;	/* true if this came from a SendEvent request */
17638 	Display *display;	/* Display the event was read from */
17639 	Window event;
17640 	Window window;
17641 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17642 }
17643 
17644 struct XCirculateRequestEvent
17645 {
17646 	int type;
17647 	arch_ulong serial;	/* # of last request processed by server */
17648 	Bool send_event;	/* true if this came from a SendEvent request */
17649 	Display *display;	/* Display the event was read from */
17650 	Window parent;
17651 	Window window;
17652 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17653 }
17654 
17655 struct XPropertyEvent
17656 {
17657 	int type;
17658 	arch_ulong serial;	/* # of last request processed by server */
17659 	Bool send_event;	/* true if this came from a SendEvent request */
17660 	Display *display;	/* Display the event was read from */
17661 	Window window;
17662 	Atom atom;
17663 	Time time;
17664 	PropertyNotification state;		/* NewValue, Deleted */
17665 }
17666 
17667 struct XSelectionClearEvent
17668 {
17669 	int type;
17670 	arch_ulong serial;	/* # of last request processed by server */
17671 	Bool send_event;	/* true if this came from a SendEvent request */
17672 	Display *display;	/* Display the event was read from */
17673 	Window window;
17674 	Atom selection;
17675 	Time time;
17676 }
17677 
17678 struct XSelectionRequestEvent
17679 {
17680 	int type;
17681 	arch_ulong serial;	/* # of last request processed by server */
17682 	Bool send_event;	/* true if this came from a SendEvent request */
17683 	Display *display;	/* Display the event was read from */
17684 	Window owner;
17685 	Window requestor;
17686 	Atom selection;
17687 	Atom target;
17688 	Atom property;
17689 	Time time;
17690 }
17691 
17692 struct XSelectionEvent
17693 {
17694 	int type;
17695 	arch_ulong serial;	/* # of last request processed by server */
17696 	Bool send_event;	/* true if this came from a SendEvent request */
17697 	Display *display;	/* Display the event was read from */
17698 	Window requestor;
17699 	Atom selection;
17700 	Atom target;
17701 	Atom property;		/* ATOM or None */
17702 	Time time;
17703 }
17704 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17705 
17706 struct XColormapEvent
17707 {
17708 	int type;
17709 	arch_ulong serial;	/* # of last request processed by server */
17710 	Bool send_event;	/* true if this came from a SendEvent request */
17711 	Display *display;	/* Display the event was read from */
17712 	Window window;
17713 	Colormap colormap;	/* COLORMAP or None */
17714 	Bool new_;		/* C++ */
17715 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17716 }
17717 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17718 
17719 struct XClientMessageEvent
17720 {
17721 	int type;
17722 	arch_ulong serial;	/* # of last request processed by server */
17723 	Bool send_event;	/* true if this came from a SendEvent request */
17724 	Display *display;	/* Display the event was read from */
17725 	Window window;
17726 	Atom message_type;
17727 	int format;
17728 	union Data{
17729 		byte[20] b;
17730 		short[10] s;
17731 		arch_ulong[5] l;
17732 	}
17733 	Data data;
17734 
17735 }
17736 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17737 
17738 struct XMappingEvent
17739 {
17740 	int type;
17741 	arch_ulong serial;	/* # of last request processed by server */
17742 	Bool send_event;	/* true if this came from a SendEvent request */
17743 	Display *display;	/* Display the event was read from */
17744 	Window window;		/* unused */
17745 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17746 				   MappingPointer */
17747 	int first_keycode;	/* first keycode */
17748 	int count;		/* defines range of change w. first_keycode*/
17749 }
17750 
17751 struct XErrorEvent
17752 {
17753 	int type;
17754 	Display *display;	/* Display the event was read from */
17755 	XID resourceid;		/* resource id */
17756 	arch_ulong serial;	/* serial number of failed request */
17757 	ubyte error_code;	/* error code of failed request */
17758 	ubyte request_code;	/* Major op-code of failed request */
17759 	ubyte minor_code;	/* Minor op-code of failed request */
17760 }
17761 
17762 struct XAnyEvent
17763 {
17764 	int type;
17765 	arch_ulong serial;	/* # of last request processed by server */
17766 	Bool send_event;	/* true if this came from a SendEvent request */
17767 	Display *display;/* Display the event was read from */
17768 	Window window;	/* window on which event was requested in event mask */
17769 }
17770 
17771 union XEvent{
17772 	int type;		/* must not be changed; first element */
17773 	XAnyEvent xany;
17774 	XKeyEvent xkey;
17775 	XButtonEvent xbutton;
17776 	XMotionEvent xmotion;
17777 	XCrossingEvent xcrossing;
17778 	XFocusChangeEvent xfocus;
17779 	XExposeEvent xexpose;
17780 	XGraphicsExposeEvent xgraphicsexpose;
17781 	XNoExposeEvent xnoexpose;
17782 	XVisibilityEvent xvisibility;
17783 	XCreateWindowEvent xcreatewindow;
17784 	XDestroyWindowEvent xdestroywindow;
17785 	XUnmapEvent xunmap;
17786 	XMapEvent xmap;
17787 	XMapRequestEvent xmaprequest;
17788 	XReparentEvent xreparent;
17789 	XConfigureEvent xconfigure;
17790 	XGravityEvent xgravity;
17791 	XResizeRequestEvent xresizerequest;
17792 	XConfigureRequestEvent xconfigurerequest;
17793 	XCirculateEvent xcirculate;
17794 	XCirculateRequestEvent xcirculaterequest;
17795 	XPropertyEvent xproperty;
17796 	XSelectionClearEvent xselectionclear;
17797 	XSelectionRequestEvent xselectionrequest;
17798 	XSelectionEvent xselection;
17799 	XColormapEvent xcolormap;
17800 	XClientMessageEvent xclient;
17801 	XMappingEvent xmapping;
17802 	XErrorEvent xerror;
17803 	XKeymapEvent xkeymap;
17804 	arch_ulong[24] pad;
17805 }
17806 
17807 
17808 	struct Display {
17809 		XExtData *ext_data;	/* hook for extension to hang data */
17810 		_XPrivate *private1;
17811 		int fd;			/* Network socket. */
17812 		int private2;
17813 		int proto_major_version;/* major version of server's X protocol */
17814 		int proto_minor_version;/* minor version of servers X protocol */
17815 		char *vendor;		/* vendor of the server hardware */
17816 	    	XID private3;
17817 		XID private4;
17818 		XID private5;
17819 		int private6;
17820 		XID function(Display*)resource_alloc;/* allocator function */
17821 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17822 		int bitmap_unit;	/* padding and data requirements */
17823 		int bitmap_pad;		/* padding requirements on bitmaps */
17824 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17825 		int nformats;		/* number of pixmap formats in list */
17826 		ScreenFormat *pixmap_format;	/* pixmap format list */
17827 		int private8;
17828 		int release;		/* release of the server */
17829 		_XPrivate *private9;
17830 		_XPrivate *private10;
17831 		int qlen;		/* Length of input event queue */
17832 		arch_ulong last_request_read; /* seq number of last event read */
17833 		arch_ulong request;	/* sequence number of last request. */
17834 		XPointer private11;
17835 		XPointer private12;
17836 		XPointer private13;
17837 		XPointer private14;
17838 		uint max_request_size; /* maximum number 32 bit words in request*/
17839 		_XrmHashBucketRec *db;
17840 		int function  (Display*)private15;
17841 		char *display_name;	/* "host:display" string used on this connect*/
17842 		int default_screen;	/* default screen for operations */
17843 		int nscreens;		/* number of screens on this server*/
17844 		Screen *screens;	/* pointer to list of screens */
17845 		arch_ulong motion_buffer;	/* size of motion buffer */
17846 		arch_ulong private16;
17847 		int min_keycode;	/* minimum defined keycode */
17848 		int max_keycode;	/* maximum defined keycode */
17849 		XPointer private17;
17850 		XPointer private18;
17851 		int private19;
17852 		byte *xdefaults;	/* contents of defaults from server */
17853 		/* there is more to this structure, but it is private to Xlib */
17854 	}
17855 
17856 	// I got these numbers from a C program as a sanity test
17857 	version(X86_64) {
17858 		static assert(Display.sizeof == 296);
17859 		static assert(XPointer.sizeof == 8);
17860 		static assert(XErrorEvent.sizeof == 40);
17861 		static assert(XAnyEvent.sizeof == 40);
17862 		static assert(XMappingEvent.sizeof == 56);
17863 		static assert(XEvent.sizeof == 192);
17864     	} else version (AArch64) {
17865         	// omit check for aarch64
17866 	} else {
17867 		static assert(Display.sizeof == 176);
17868 		static assert(XPointer.sizeof == 4);
17869 		static assert(XEvent.sizeof == 96);
17870 	}
17871 
17872 struct Depth
17873 {
17874 	int depth;		/* this depth (Z) of the depth */
17875 	int nvisuals;		/* number of Visual types at this depth */
17876 	Visual *visuals;	/* list of visuals possible at this depth */
17877 }
17878 
17879 alias void* GC;
17880 alias c_ulong VisualID;
17881 alias XID Colormap;
17882 alias XID Cursor;
17883 alias XID KeySym;
17884 alias uint KeyCode;
17885 enum None = 0;
17886 }
17887 
17888 version(without_opengl) {}
17889 else {
17890 extern(C) nothrow @nogc {
17891 
17892 
17893 static if(!SdpyIsUsingIVGLBinds) {
17894 enum GLX_USE_GL=            1;       /* support GLX rendering */
17895 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17896 enum GLX_LEVEL=             3;       /* level in plane stacking */
17897 enum GLX_RGBA=              4;       /* true if RGBA mode */
17898 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17899 enum GLX_STEREO=            6;       /* stereo buffering supported */
17900 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17901 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17902 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17903 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17904 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17905 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17906 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17907 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17908 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17909 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17910 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17911 
17912 
17913 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17914 
17915 
17916 
17917 enum GL_TRUE = 1;
17918 enum GL_FALSE = 0;
17919 }
17920 
17921 alias XID GLXContextID;
17922 alias XID GLXPixmap;
17923 alias XID GLXDrawable;
17924 alias XID GLXPbuffer;
17925 alias XID GLXWindow;
17926 alias XID GLXFBConfigID;
17927 alias void* GLXContext;
17928 
17929 }
17930 }
17931 
17932 enum AllocNone = 0;
17933 
17934 extern(C) {
17935 	/* WARNING, this type not in Xlib spec */
17936 	extern(C) alias XIOErrorHandler = int function (Display* display);
17937 }
17938 
17939 extern(C) nothrow
17940 alias XErrorHandler = int function(Display*, XErrorEvent*);
17941 
17942 extern(C) nothrow @nogc {
17943 struct Screen{
17944 	XExtData *ext_data;		/* hook for extension to hang data */
17945 	Display *display;		/* back pointer to display structure */
17946 	Window root;			/* Root window id. */
17947 	int width, height;		/* width and height of screen */
17948 	int mwidth, mheight;	/* width and height of  in millimeters */
17949 	int ndepths;			/* number of depths possible */
17950 	Depth *depths;			/* list of allowable depths on the screen */
17951 	int root_depth;			/* bits per pixel */
17952 	Visual *root_visual;	/* root visual */
17953 	GC default_gc;			/* GC for the root root visual */
17954 	Colormap cmap;			/* default color map */
17955 	uint white_pixel;
17956 	uint black_pixel;		/* White and Black pixel values */
17957 	int max_maps, min_maps;	/* max and min color maps */
17958 	int backing_store;		/* Never, WhenMapped, Always */
17959 	bool save_unders;
17960 	int root_input_mask;	/* initial root input mask */
17961 }
17962 
17963 struct Visual
17964 {
17965 	XExtData *ext_data;	/* hook for extension to hang data */
17966 	VisualID visualid;	/* visual id of this visual */
17967 	int class_;			/* class of screen (monochrome, etc.) */
17968 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17969 	int bits_per_rgb;	/* log base 2 of distinct color values */
17970 	int map_entries;	/* color map entries */
17971 }
17972 
17973 	alias Display* _XPrivDisplay;
17974 
17975 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system {
17976 		assert(dpy !is null);
17977 		return &dpy.screens[scr];
17978 	}
17979 
17980 	extern(D) Window RootWindow(Display *dpy,int scr) {
17981 		return ScreenOfDisplay(dpy,scr).root;
17982 	}
17983 
17984 	struct XWMHints {
17985 		arch_long flags;
17986 		Bool input;
17987 		int initial_state;
17988 		Pixmap icon_pixmap;
17989 		Window icon_window;
17990 		int icon_x, icon_y;
17991 		Pixmap icon_mask;
17992 		XID window_group;
17993 	}
17994 
17995 	struct XClassHint {
17996 		char* res_name;
17997 		char* res_class;
17998 	}
17999 
18000 	extern(D) int DefaultScreen(Display *dpy) {
18001 		return dpy.default_screen;
18002 	}
18003 
18004 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
18005 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
18006 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
18007 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
18008 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
18009 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
18010 
18011 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
18012 
18013 	enum int AnyPropertyType = 0;
18014 	enum int Success = 0;
18015 
18016 	enum int RevertToNone = None;
18017 	enum int PointerRoot = 1;
18018 	enum Time CurrentTime = 0;
18019 	enum int RevertToPointerRoot = PointerRoot;
18020 	enum int RevertToParent = 2;
18021 
18022 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
18023 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
18024 	}
18025 
18026 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
18027 		return ScreenOfDisplay(dpy,scr).root_visual;
18028 	}
18029 
18030 	extern(D) GC DefaultGC(Display *dpy,int scr) {
18031 		return ScreenOfDisplay(dpy,scr).default_gc;
18032 	}
18033 
18034 	extern(D) uint BlackPixel(Display *dpy,int scr) {
18035 		return ScreenOfDisplay(dpy,scr).black_pixel;
18036 	}
18037 
18038 	extern(D) uint WhitePixel(Display *dpy,int scr) {
18039 		return ScreenOfDisplay(dpy,scr).white_pixel;
18040 	}
18041 
18042 	alias void* XFontSet; // i think
18043 	struct XmbTextItem {
18044 		char* chars;
18045 		int nchars;
18046 		int delta;
18047 		XFontSet font_set;
18048 	}
18049 
18050 	struct XTextItem {
18051 		char* chars;
18052 		int nchars;
18053 		int delta;
18054 		Font font;
18055 	}
18056 
18057 	enum {
18058 		GXclear        = 0x0, /* 0 */
18059 		GXand          = 0x1, /* src AND dst */
18060 		GXandReverse   = 0x2, /* src AND NOT dst */
18061 		GXcopy         = 0x3, /* src */
18062 		GXandInverted  = 0x4, /* NOT src AND dst */
18063 		GXnoop         = 0x5, /* dst */
18064 		GXxor          = 0x6, /* src XOR dst */
18065 		GXor           = 0x7, /* src OR dst */
18066 		GXnor          = 0x8, /* NOT src AND NOT dst */
18067 		GXequiv        = 0x9, /* NOT src XOR dst */
18068 		GXinvert       = 0xa, /* NOT dst */
18069 		GXorReverse    = 0xb, /* src OR NOT dst */
18070 		GXcopyInverted = 0xc, /* NOT src */
18071 		GXorInverted   = 0xd, /* NOT src OR dst */
18072 		GXnand         = 0xe, /* NOT src OR NOT dst */
18073 		GXset          = 0xf, /* 1 */
18074 	}
18075 	enum QueueMode : int {
18076 		QueuedAlready,
18077 		QueuedAfterReading,
18078 		QueuedAfterFlush
18079 	}
18080 
18081 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
18082 
18083 	struct XPoint {
18084 		short x;
18085 		short y;
18086 	}
18087 
18088 	enum CoordMode:int {
18089 		CoordModeOrigin = 0,
18090 		CoordModePrevious = 1
18091 	}
18092 
18093 	enum PolygonShape:int {
18094 		Complex = 0,
18095 		Nonconvex = 1,
18096 		Convex = 2
18097 	}
18098 
18099 	struct XTextProperty {
18100 		const(char)* value;		/* same as Property routines */
18101 		Atom encoding;			/* prop type */
18102 		int format;				/* prop data format: 8, 16, or 32 */
18103 		arch_ulong nitems;		/* number of data items in value */
18104 	}
18105 
18106 	version( X86_64 ) {
18107 		static assert(XTextProperty.sizeof == 32);
18108 	}
18109 
18110 
18111 	struct XGCValues {
18112 		int function_;           /* logical operation */
18113 		arch_ulong plane_mask;/* plane mask */
18114 		arch_ulong foreground;/* foreground pixel */
18115 		arch_ulong background;/* background pixel */
18116 		int line_width;         /* line width */
18117 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
18118 		int cap_style;          /* CapNotLast, CapButt,
18119 					   CapRound, CapProjecting */
18120 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
18121 		int fill_style;         /* FillSolid, FillTiled,
18122 					   FillStippled, FillOpaeueStippled */
18123 		int fill_rule;          /* EvenOddRule, WindingRule */
18124 		int arc_mode;           /* ArcChord, ArcPieSlice */
18125 		Pixmap tile;            /* tile pixmap for tiling operations */
18126 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
18127 		int ts_x_origin;        /* offset for tile or stipple operations */
18128 		int ts_y_origin;
18129 		Font font;              /* default text font for text operations */
18130 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
18131 		Bool graphics_exposures;/* boolean, should exposures be generated */
18132 		int clip_x_origin;      /* origin for clipping */
18133 		int clip_y_origin;
18134 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
18135 		int dash_offset;        /* patterned/dashed line information */
18136 		char dashes;
18137 	}
18138 
18139 	struct XColor {
18140 		arch_ulong pixel;
18141 		ushort red, green, blue;
18142 		byte flags;
18143 		byte pad;
18144 	}
18145 
18146 	struct XRectangle {
18147 		short x;
18148 		short y;
18149 		ushort width;
18150 		ushort height;
18151 	}
18152 
18153 	enum ClipByChildren = 0;
18154 	enum IncludeInferiors = 1;
18155 
18156 	enum Atom XA_PRIMARY = 1;
18157 	enum Atom XA_SECONDARY = 2;
18158 	enum Atom XA_STRING = 31;
18159 	enum Atom XA_CARDINAL = 6;
18160 	enum Atom XA_WM_NAME = 39;
18161 	enum Atom XA_ATOM = 4;
18162 	enum Atom XA_WINDOW = 33;
18163 	enum Atom XA_WM_HINTS = 35;
18164 	enum int PropModeAppend = 2;
18165 	enum int PropModeReplace = 0;
18166 	enum int PropModePrepend = 1;
18167 
18168 	enum int CopyFromParent = 0;
18169 	enum int InputOutput = 1;
18170 
18171 	// XWMHints
18172 	enum InputHint = 1 << 0;
18173 	enum StateHint = 1 << 1;
18174 	enum IconPixmapHint = (1L << 2);
18175 	enum IconWindowHint = (1L << 3);
18176 	enum IconPositionHint = (1L << 4);
18177 	enum IconMaskHint = (1L << 5);
18178 	enum WindowGroupHint = (1L << 6);
18179 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
18180 	enum XUrgencyHint = (1L << 8);
18181 
18182 	// GC Components
18183 	enum GCFunction           =   (1L<<0);
18184 	enum GCPlaneMask         =    (1L<<1);
18185 	enum GCForeground       =     (1L<<2);
18186 	enum GCBackground      =      (1L<<3);
18187 	enum GCLineWidth      =       (1L<<4);
18188 	enum GCLineStyle     =        (1L<<5);
18189 	enum GCCapStyle     =         (1L<<6);
18190 	enum GCJoinStyle   =          (1L<<7);
18191 	enum GCFillStyle  =           (1L<<8);
18192 	enum GCFillRule  =            (1L<<9);
18193 	enum GCTile     =             (1L<<10);
18194 	enum GCStipple           =    (1L<<11);
18195 	enum GCTileStipXOrigin  =     (1L<<12);
18196 	enum GCTileStipYOrigin =      (1L<<13);
18197 	enum GCFont               =   (1L<<14);
18198 	enum GCSubwindowMode     =    (1L<<15);
18199 	enum GCGraphicsExposures=     (1L<<16);
18200 	enum GCClipXOrigin     =      (1L<<17);
18201 	enum GCClipYOrigin    =       (1L<<18);
18202 	enum GCClipMask      =        (1L<<19);
18203 	enum GCDashOffset   =         (1L<<20);
18204 	enum GCDashList    =          (1L<<21);
18205 	enum GCArcMode    =           (1L<<22);
18206 	enum GCLastBit   =            22;
18207 
18208 
18209 	enum int WithdrawnState = 0;
18210 	enum int NormalState = 1;
18211 	enum int IconicState = 3;
18212 
18213 }
18214 } else version (OSXCocoa) {
18215 
18216 /+
18217 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
18218 +/
18219 
18220 	private __gshared AppDelegate globalAppDelegate;
18221 
18222 	extern(Objective-C)
18223 	class AppDelegate : NSObject, NSApplicationDelegate {
18224 		override static AppDelegate alloc() @selector("alloc");
18225 
18226 
18227 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
18228 			SimpleWindow.processAllCustomEvents();
18229 		}
18230 
18231 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
18232 			immutable style = NSWindowStyleMask.resizable |
18233 				NSWindowStyleMask.closable |
18234 				NSWindowStyleMask.miniaturizable |
18235 				NSWindowStyleMask.titled;
18236 
18237 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
18238 
18239 			{
18240 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
18241 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
18242 				mainMenu.setSubmenu(menu, item);
18243 
18244 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
18245 				newItem.target = NSApp;
18246 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
18247 				newItem2.target = NSApp;
18248 			}
18249 
18250 			{
18251 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
18252 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
18253 				mainMenu.setSubmenu(menu, item);
18254 
18255 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
18256 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
18257 			}
18258 
18259 
18260 			NSApp.menu = mainMenu;
18261 
18262 
18263 			// auto controller = ViewController.alloc.init;
18264 
18265 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
18266 
18267 			/+
18268 			this.window = window;
18269 			this.controller = controller;
18270 			+/
18271 		}
18272 
18273 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
18274 			NSApplication.shared_.activateIgnoringOtherApps(true);
18275 		}
18276 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
18277 			return true;
18278 		}
18279 	}
18280 
18281 	extern(Objective-C)
18282 	class SDWindowDelegate : NSObject, NSWindowDelegate {
18283 		override static SDWindowDelegate alloc() @selector("alloc");
18284 		override SDWindowDelegate init() @selector("init");
18285 
18286 		SimpleWindow simpleWindow;
18287 
18288 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
18289 			auto window = cast(void*) notification.object;
18290 
18291 			// FIXME: do i need to release it?
18292 			SimpleWindow.nativeMapping.remove(window);
18293 		}
18294 
18295 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
18296 			if(simpleWindow.windowResized) {
18297 				// FIXME: automaticallyScaleIfPossible behaviors
18298 
18299 				simpleWindow._width = cast(int) frameSize.width;
18300 				simpleWindow._height = cast(int) frameSize.height;
18301 
18302 				simpleWindow.view.setFrameSize(frameSize);
18303 
18304 				/+
18305 				auto size = simpleWindow.view.frame.size;
18306 				writeln(cast(int) size.width, "x", cast(int) size.height);
18307 				+/
18308 
18309 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
18310 
18311 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
18312 
18313 				// simpleWindow.view.setNeedsDisplay(true);
18314 			}
18315 
18316 			return frameSize;
18317 		}
18318 
18319 		/+
18320 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
18321 			if(simpleWindow.windowResized) {
18322 				auto window = simpleWindow.window;
18323 				auto rect = window.contentRectForFrameRect(window.frame);
18324 				import std.stdio; writeln(window.frame.size);
18325 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
18326 			}
18327 		}
18328 		+/
18329 	}
18330 
18331 	extern(Objective-C)
18332 	class SDGraphicsView : NSView {
18333 		SimpleWindow simpleWindow;
18334 
18335 		override static SDGraphicsView alloc() @selector("alloc");
18336 		override SDGraphicsView init() @selector("init") {
18337 			super.init();
18338 			return this;
18339 		}
18340 
18341 		override void drawRect(NSRect rect) @selector("drawRect:") {
18342 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
18343 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18344 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
18345 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18346 			CGImageRelease(cgImage);
18347 		}
18348 
18349 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
18350 			MouseEvent me;
18351 			me.type = type;
18352 
18353 			auto pos = event.locationInWindow;
18354 
18355 			me.x = cast(int) pos.x;
18356 			me.y = cast(int) (simpleWindow.height - pos.y);
18357 
18358 			me.dx = 0; // FIXME
18359 			me.dy = 0; // FIXME
18360 
18361 			me.button = button;
18362 			me.modifierState = cast(uint) event.modifierFlags;
18363 			me.window = simpleWindow;
18364 
18365 			me.doubleClick = false;
18366 
18367 			if(simpleWindow && simpleWindow.handleMouseEvent)
18368 				simpleWindow.handleMouseEvent(me);
18369 		}
18370 
18371 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
18372 			// writeln(event.pressedMouseButtons);
18373 
18374 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18375 		}
18376 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
18377 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
18378 		}
18379 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
18380 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
18381 		}
18382 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
18383 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
18384 		}
18385 		/+
18386 			// FIXME
18387 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
18388 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18389 		}
18390 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
18391 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18392 		}
18393 		+/
18394 
18395 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
18396 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
18397 		}
18398 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
18399 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
18400 		}
18401 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
18402 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
18403 		}
18404 
18405 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
18406 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
18407 		}
18408 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
18409 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
18410 		}
18411 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
18412 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
18413 		}
18414 
18415 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
18416 			import std.stdio;
18417 			writeln(event.deltaY);
18418 		}
18419 
18420 		override void keyDown(NSEvent event) @selector("keyDown:") {
18421 			// the event may have multiple characters, and we send them all at once.
18422 			if (simpleWindow.handleCharEvent) {
18423 				auto chars = DeifiedNSString(event.characters);
18424 				foreach (dchar dc; chars.str)
18425 					simpleWindow.handleCharEvent(dc);
18426 			}
18427 
18428 			keyHelper(event, true);
18429 		}
18430 
18431 		override void keyUp(NSEvent event) @selector("keyUp:") {
18432 			keyHelper(event, false);
18433 		}
18434 
18435 		private void keyHelper(NSEvent event, bool pressed) {
18436 			if(simpleWindow.handleKeyEvent) {
18437 				KeyEvent ev;
18438 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
18439 				ev.pressed = pressed;
18440 				ev.hardwareCode = cast(ubyte) event.keyCode;
18441 				ev.modifierState = cast(uint) event.modifierFlags;
18442 				ev.window = simpleWindow;
18443 
18444 				simpleWindow.handleKeyEvent(ev);
18445 			}
18446 		}
18447 
18448 		override bool isFlipped() @selector("isFlipped") {
18449 			return true;
18450 		}
18451 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
18452 			return true;
18453 		}
18454 
18455 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
18456 			if(simpleWindow && simpleWindow.handlePulse)
18457 				simpleWindow.handlePulse();
18458 			/+
18459 			setNeedsDisplay = true;
18460 			+/
18461 		}
18462 	}
18463 
18464 private:
18465 	alias const(void)* CFStringRef;
18466 	alias const(void)* CFAllocatorRef;
18467 	alias const(void)* CFTypeRef;
18468 	alias const(void)* CGColorSpaceRef;
18469 	alias const(void)* CGImageRef;
18470 	alias ulong CGBitmapInfo;
18471 	alias NSGraphicsContext CGContextRef;
18472 
18473 	alias NSPoint CGPoint;
18474 	alias NSSize CGSize;
18475 	alias NSRect CGRect;
18476 
18477 	struct CGAffineTransform {
18478 		double a, b, c, d, tx, ty;
18479 	}
18480 
18481 	enum NSApplicationActivationPolicyRegular = 0;
18482 	enum NSBackingStoreBuffered = 2;
18483 	enum kCFStringEncodingUTF8 = 0x08000100;
18484 
18485 	enum : size_t {
18486 		NSBorderlessWindowMask = 0,
18487 		NSTitledWindowMask = 1 << 0,
18488 		NSClosableWindowMask = 1 << 1,
18489 		NSMiniaturizableWindowMask = 1 << 2,
18490 		NSResizableWindowMask = 1 << 3,
18491 		NSTexturedBackgroundWindowMask = 1 << 8
18492 	}
18493 
18494 	enum : ulong {
18495 		kCGImageAlphaNone,
18496 		kCGImageAlphaPremultipliedLast,
18497 		kCGImageAlphaPremultipliedFirst,
18498 		kCGImageAlphaLast,
18499 		kCGImageAlphaFirst,
18500 		kCGImageAlphaNoneSkipLast,
18501 		kCGImageAlphaNoneSkipFirst
18502 	}
18503 	enum : ulong {
18504 		kCGBitmapAlphaInfoMask = 0x1F,
18505 		kCGBitmapFloatComponents = (1 << 8),
18506 		kCGBitmapByteOrderMask = 0x7000,
18507 		kCGBitmapByteOrderDefault = (0 << 12),
18508 		kCGBitmapByteOrder16Little = (1 << 12),
18509 		kCGBitmapByteOrder32Little = (2 << 12),
18510 		kCGBitmapByteOrder16Big = (3 << 12),
18511 		kCGBitmapByteOrder32Big = (4 << 12)
18512 	}
18513 	enum CGPathDrawingMode {
18514 		kCGPathFill,
18515 		kCGPathEOFill,
18516 		kCGPathStroke,
18517 		kCGPathFillStroke,
18518 		kCGPathEOFillStroke
18519 	}
18520 	enum objc_AssociationPolicy : size_t {
18521 		OBJC_ASSOCIATION_ASSIGN = 0,
18522 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18523 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18524 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18525 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18526 	}
18527 
18528 	extern(C) {
18529 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18530 		void CGContextRelease(CGContextRef c);
18531 		ubyte* CGBitmapContextGetData(CGContextRef c);
18532 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18533 		size_t CGBitmapContextGetWidth(CGContextRef c);
18534 		size_t CGBitmapContextGetHeight(CGContextRef c);
18535 
18536 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18537 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18538 
18539 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18540 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18541 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18542 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18543 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18544 		void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
18545 
18546 		void CGContextBeginPath(CGContextRef c);
18547 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18548 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18549 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18550 		void CGContextAddRect(CGContextRef c, CGRect rect);
18551 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18552 		void CGContextSaveGState(CGContextRef c);
18553 		void CGContextRestoreGState(CGContextRef c);
18554 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18555 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18556 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18557 
18558 		void CGImageRelease(CGImageRef image);
18559 	}
18560 } else static assert(0, "Unsupported operating system");
18561 
18562 
18563 version(OSXCocoa) {
18564 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18565 	//
18566 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18567 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18568 	//
18569 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18570 	// Probably won't even fully compile right now
18571 
18572 	private enum double PI = 3.14159265358979323;
18573 
18574 	alias NSWindow NativeWindowHandle;
18575 	alias void delegate(NSid) NativeEventHandler;
18576 
18577 	enum KEY_ESCAPE = 27;
18578 
18579 	mixin template NativeImageImplementation() {
18580 		CGContextRef context;
18581 		ubyte* rawData;
18582 
18583 		final:
18584 
18585 		void convertToRgbaBytes(ubyte[] where) {
18586 			assert(where.length == this.width * this.height * 4);
18587 
18588 			// if rawData had a length....
18589 			//assert(rawData.length == where.length);
18590 			for(long idx = 0; idx < where.length; idx += 4) {
18591 				auto alpha = rawData[idx + 3];
18592 				if(alpha == 255) {
18593 					where[idx + 0] = rawData[idx + 0]; // r
18594 					where[idx + 1] = rawData[idx + 1]; // g
18595 					where[idx + 2] = rawData[idx + 2]; // b
18596 					where[idx + 3] = rawData[idx + 3]; // a
18597 				} else {
18598 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18599 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18600 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18601 					where[idx + 3] = rawData[idx + 3]; // a
18602 
18603 				}
18604 			}
18605 		}
18606 
18607 		void setFromRgbaBytes(in ubyte[] where) {
18608 			// FIXME: this is probably wrong
18609 			assert(where.length == this.width * this.height * 4);
18610 
18611 			// if rawData had a length....
18612 			//assert(rawData.length == where.length);
18613 			for(long idx = 0; idx < where.length; idx += 4) {
18614 				auto alpha = where[idx + 3];
18615 				if(alpha == 255) {
18616 					rawData[idx + 0] = where[idx + 0]; // r
18617 					rawData[idx + 1] = where[idx + 1]; // g
18618 					rawData[idx + 2] = where[idx + 2]; // b
18619 					rawData[idx + 3] = where[idx + 3]; // a
18620 				} else if(alpha == 0) {
18621 					rawData[idx + 0] = 0;
18622 					rawData[idx + 1] = 0;
18623 					rawData[idx + 2] = 0;
18624 					rawData[idx + 3] = 0;
18625 				} else {
18626 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18627 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18628 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18629 					rawData[idx + 3] = where[idx + 3]; // a
18630 				}
18631 			}
18632 		}
18633 
18634 
18635 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18636 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18637 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18638 			CGColorSpaceRelease(colorSpace);
18639 			rawData = CGBitmapContextGetData(context);
18640 		}
18641 		void dispose() {
18642 			CGContextRelease(context);
18643 		}
18644 
18645 		void setPixel(int x, int y, Color c) {
18646 			auto offset = (y * width + x) * 4;
18647 			if (c.a == 255) {
18648 				rawData[offset + 0] = c.r;
18649 				rawData[offset + 1] = c.g;
18650 				rawData[offset + 2] = c.b;
18651 				rawData[offset + 3] = c.a;
18652 			} else {
18653 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18654 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18655 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18656 				rawData[offset + 3] = c.a;
18657 			}
18658 		}
18659 	}
18660 
18661 	mixin template NativeScreenPainterImplementation() {
18662 		CGContextRef context;
18663 		ubyte[4] _outlineComponents;
18664 		NSView view;
18665 
18666 		Pen _activePen;
18667 		Color _fillColor;
18668 		Rectangle _clipRectangle;
18669 		OperatingSystemFont _font;
18670 
18671 		OperatingSystemFont getFont() {
18672 			if(_font is null) {
18673 				static OperatingSystemFont _defaultFont;
18674 				if(_defaultFont is null) {
18675 					_defaultFont = new OperatingSystemFont();
18676 					_defaultFont.loadDefault();
18677 				}
18678 				_font = _defaultFont;
18679 			}
18680 
18681 			return _font;
18682 		}
18683 
18684 		void create(PaintingHandle window) {
18685 			// this.destiny = window;
18686 			if(auto sw = cast(SimpleWindow) this.window) {
18687 				context = sw.drawingContext;
18688 				view = sw.view;
18689 			} else {
18690 				throw new NotYetImplementedException();
18691 			}
18692 		}
18693 
18694 		void dispose() {
18695 			view.setNeedsDisplay(true);
18696 		}
18697 
18698 		bool manualInvalidations;
18699 		void invalidateRect(Rectangle invalidRect) { }
18700 
18701 		// NotYetImplementedException
18702 		void rasterOp(RasterOp op) {
18703 		}
18704 		void setClipRectangle(int, int, int, int) {
18705 		}
18706 		Size textSize(in char[] txt) {
18707 			auto font = getFont();
18708 			return Size(font.stringWidth(txt), font.height());
18709 		}
18710 
18711 		void setFont(OperatingSystemFont font) {
18712 			_font = font;
18713 			//font.font.setInContext(context);
18714 		}
18715 		int fontHeight() {
18716 			auto font = getFont();
18717 			return font.height;
18718 		}
18719 
18720 		// end
18721 
18722 		void pen(Pen pen) {
18723 			_activePen = pen;
18724 			auto color = pen.color; // FIXME
18725 			double alphaComponent = color.a/255.0f;
18726 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
18727 
18728 			double[2] patternBuffer;
18729 			double[] pattern;
18730 			final switch(pen.style) {
18731 				case Pen.Style.Solid:
18732 					pattern = null;
18733 				break;
18734 				case Pen.Style.Dashed:
18735 					patternBuffer[0] = 4;
18736 					patternBuffer[1] = 1;
18737 					pattern = patternBuffer[];
18738 				break;
18739 				case Pen.Style.Dotted:
18740 					patternBuffer[0] = 1;
18741 					patternBuffer[1] = 1;
18742 					pattern = patternBuffer[];
18743 				break;
18744 			}
18745 
18746 			CGContextSetLineDash(context, 0, pattern.ptr, pattern.length);
18747 
18748 			if (color.a != 255) {
18749 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
18750 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
18751 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
18752 				_outlineComponents[3] = color.a;
18753 			} else {
18754 				_outlineComponents[0] = color.r;
18755 				_outlineComponents[1] = color.g;
18756 				_outlineComponents[2] = color.b;
18757 				_outlineComponents[3] = color.a;
18758 			}
18759 		}
18760 
18761 		@property void fillColor(Color color) {
18762 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
18763 		}
18764 
18765 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
18766 		// NotYetImplementedException for upper left/width/height
18767 			auto cgImage = CGBitmapContextCreateImage(image.context);
18768 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
18769 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18770 			CGImageRelease(cgImage);
18771 		}
18772 
18773 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
18774 		// FIXME: is this efficient?
18775 			auto cgImage = CGBitmapContextCreateImage(s.handle);
18776 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
18777 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18778 			CGImageRelease(cgImage);
18779 		}
18780 
18781 
18782 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
18783 		// FIXME: alignment
18784 			if (_outlineComponents[3] != 0) {
18785 				CGContextSaveGState(context);
18786 				auto invAlpha = 1.0f/_outlineComponents[3];
18787 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
18788 												  _outlineComponents[1]*invAlpha,
18789 												  _outlineComponents[2]*invAlpha,
18790 												  _outlineComponents[3]/255.0f);
18791 
18792 
18793 
18794 				// FIXME: should we clip it to the bounding box?
18795 				int textHeight = fontHeight;
18796 
18797 				auto lines = text.split('\n');
18798 
18799 				const lineHeight = textHeight;
18800 				textHeight *= lines.length;
18801 
18802 				int cy = y;
18803 
18804 				if(alignment & TextAlignment.VerticalBottom) {
18805 					if(y2 <= 0)
18806 						return;
18807 					auto h = y2 - y;
18808 					if(h > textHeight) {
18809 						cy += h - textHeight;
18810 						cy -= lineHeight / 2;
18811 					}
18812 				} else if(alignment & TextAlignment.VerticalCenter) {
18813 					if(y2 <= 0)
18814 						return;
18815 					auto h = y2 - y;
18816 					if(textHeight < h) {
18817 						cy += (h - textHeight) / 2;
18818 						//cy -= lineHeight / 4;
18819 					}
18820 				}
18821 
18822 				foreach(line; text.split('\n')) {
18823 					int textWidth = this.textSize(line).width;
18824 
18825 					int px = x, py = cy;
18826 
18827 					if(alignment & TextAlignment.Center) {
18828 						if(x2 <= 0)
18829 							return;
18830 						auto w = x2 - x;
18831 						if(w > textWidth)
18832 							px += (w - textWidth) / 2;
18833 					} else if(alignment & TextAlignment.Right) {
18834 						if(x2 <= 0)
18835 							return;
18836 						auto pos = x2 - textWidth;
18837 						if(pos > x)
18838 							px = pos;
18839 					}
18840 
18841 					CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length);
18842 
18843 					carry_on:
18844 					cy += lineHeight + 4;
18845 				}
18846 
18847 // auto cfstr = cast(NSid)createCFString(text);
18848 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
18849 // NSPoint(x, y), null);
18850 // CFRelease(cfstr);
18851 				CGContextRestoreGState(context);
18852 			}
18853 		}
18854 
18855 		void drawPixel(int x, int y) {
18856 			auto rawData = CGBitmapContextGetData(context);
18857 			auto width = CGBitmapContextGetWidth(context);
18858 			auto height = CGBitmapContextGetHeight(context);
18859 			auto offset = ((height - y - 1) * width + x) * 4;
18860 			rawData[offset .. offset+4] = _outlineComponents;
18861 		}
18862 
18863 		void drawLine(int x1, int y1, int x2, int y2) {
18864 			CGPoint[2] linePoints;
18865 			linePoints[0] = CGPoint(x1, y1);
18866 			linePoints[1] = CGPoint(x2, y2);
18867 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
18868 		}
18869 
18870 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
18871 			drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded
18872 		}
18873 
18874 		void drawRectangle(int x, int y, int width, int height) {
18875 			CGContextBeginPath(context);
18876 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18877 			CGContextAddRect(context, rect);
18878 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18879 		}
18880 
18881 		void drawEllipse(int x1, int y1, int x2, int y2) {
18882 			CGContextBeginPath(context);
18883 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18884 			CGContextAddEllipseInRect(context, rect);
18885 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18886 		}
18887 
18888 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
18889 			// @@@BUG@@@ Does not support elliptic arc (width != height).
18890 			CGContextBeginPath(context);
18891 			int clockwise = 0;
18892 			if(length < 0) {
18893 				clockwise = 1;
18894 				length = -length;
18895 			}
18896 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18897 							start*PI/(180*64), (start+length)*PI/(180*64), clockwise);
18898 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18899 		}
18900 
18901 		void drawPolygon(Point[] intPoints) {
18902 			CGContextBeginPath(context);
18903 			CGPoint[16] pointsBuffer;
18904 			CGPoint[] points;
18905 			if(intPoints.length <= pointsBuffer.length)
18906 				points = pointsBuffer[0 .. intPoints.length];
18907 			else
18908 				points = new CGPoint[](intPoints.length);
18909 
18910 			foreach(idx, pt; intPoints)
18911 				points[idx] = CGPoint(pt.x, pt.y);
18912 
18913 			CGContextAddLines(context, points.ptr, points.length);
18914 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18915 		}
18916 	}
18917 
18918 	private bool appInitialized = false;
18919 	void initializeApp() {
18920 		if(appInitialized)
18921 			return;
18922 		synchronized {
18923 			if(appInitialized)
18924 				return;
18925 
18926 			auto app = NSApp(); // ensure the is initialized
18927 
18928 			auto dg = AppDelegate.alloc;
18929 			globalAppDelegate = dg;
18930 			NSApp.delegate_ = dg;
18931 
18932 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
18933 
18934 			appInitialized = true;
18935 		}
18936 	}
18937 
18938 	mixin template NativeSimpleWindowImplementation() {
18939 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18940 			initializeApp();
18941 
18942 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18943 
18944 			auto window = NSWindow.alloc.initWithContentRect(
18945 				contentRect,
18946 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
18947 				NSBackingStoreType.buffered,
18948 				true
18949 			);
18950 
18951 			SimpleWindow.nativeMapping[cast(void*) window] = this;
18952 
18953 			window.title = MacString(title).borrow;
18954 
18955 			auto dg = SDWindowDelegate.alloc.init;
18956 			dg.simpleWindow = this;
18957 			window.delegate_ = dg;
18958 
18959 			auto view = SDGraphicsView.alloc.init;
18960 			assert(view !is null);
18961 			window.contentView = view;
18962 			this.view = view;
18963 			view.simpleWindow = this;
18964 
18965 			window.center();
18966 
18967 			window.makeKeyAndOrderFront(null);
18968 
18969 			// no need to make a bitmap on mac since everything is double buffered already
18970 
18971 			// create area to draw on.
18972 			createNewDrawingContext(width, height);
18973 
18974 			window.setBackgroundColor(NSColor.whiteColor);
18975 		}
18976 
18977 		void createNewDrawingContext(int width, int height) {
18978 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
18979 			if(this.drawingContext)
18980 				CGContextRelease(this.drawingContext);
18981 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18982 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18983 			CGColorSpaceRelease(colorSpace);
18984 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18985 			auto matrix = CGContextGetTextMatrix(drawingContext);
18986 			matrix.c = -matrix.c;
18987 			matrix.d = -matrix.d;
18988 			CGContextSetTextMatrix(drawingContext, matrix);
18989 
18990 		}
18991 
18992 		void dispose() {
18993 			closeWindow();
18994 			// window.release(); // closing the window does this automatically i think
18995 		}
18996 		void closeWindow() {
18997 			if(timer)
18998 				timer.invalidate();
18999 			window.close();
19000 		}
19001 
19002 		ScreenPainter getPainter(bool manualInvalidations) {
19003 			return ScreenPainter(this, this.window, manualInvalidations);
19004 		}
19005 
19006 		NSWindow window;
19007 		NSTimer timer;
19008 		NSView view;
19009 		CGContextRef drawingContext;
19010 	}
19011 }
19012 
19013 version(without_opengl) {} else
19014 extern(System) nothrow @nogc {
19015 	//enum uint GL_VERSION = 0x1F02;
19016 	//const(char)* glGetString (/*GLenum*/uint);
19017 	version(X11) {
19018 	static if (!SdpyIsUsingIVGLBinds) {
19019 
19020 		enum GLX_X_RENDERABLE = 0x8012;
19021 		enum GLX_DRAWABLE_TYPE = 0x8010;
19022 		enum GLX_RENDER_TYPE = 0x8011;
19023 		enum GLX_X_VISUAL_TYPE = 0x22;
19024 		enum GLX_TRUE_COLOR = 0x8002;
19025 		enum GLX_WINDOW_BIT = 0x00000001;
19026 		enum GLX_RGBA_BIT = 0x00000001;
19027 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
19028 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
19029 		enum GLX_SAMPLES = 0x186a1;
19030 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19031 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19032 	}
19033 
19034 		// GLX_EXT_swap_control
19035 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
19036 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
19037 
19038 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
19039 		extern(System) {
19040 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
19041 		}
19042 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
19043 
19044 		// this made public so we don't have to get it again and again
19045 		public bool glXCreateContextAttribsARB_present () @system {
19046 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
19047 				// get it
19048 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
19049 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
19050 			}
19051 			return (glXCreateContextAttribsARBFn !is null);
19052 		}
19053 
19054 		// this made public so we don't have to get it again and again
19055 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system {
19056 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
19057 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
19058 		}
19059 
19060 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
19061 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
19062 
19063 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
19064 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
19065 			if (_glx_swapInterval_fn is null) {
19066 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
19067 				if (_glx_swapInterval_fn is null) {
19068 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
19069 					return;
19070 				}
19071 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
19072 			}
19073 
19074 			if(glXSwapIntervalMESA is null) {
19075 				// it seems to require both to actually take effect on many computers
19076 				// idk why
19077 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
19078 				if(glXSwapIntervalMESA is null)
19079 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
19080 			}
19081 
19082 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
19083 				glXSwapIntervalMESA(wait ? 1 : 0);
19084 
19085 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
19086 		}
19087 	} else version(Windows) {
19088 	static if (!SdpyIsUsingIVGLBinds) {
19089 	enum GL_TRUE = 1;
19090 	enum GL_FALSE = 0;
19091 
19092 	public void* glbindGetProcAddress (const(char)* name) {
19093 		void* res = wglGetProcAddress(name);
19094 		if (res is null) {
19095 			/+
19096 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
19097 			import core.sys.windows.windef, core.sys.windows.winbase;
19098 			__gshared HINSTANCE dll = null;
19099 			if (dll is null) {
19100 				dll = LoadLibraryA("opengl32.dll");
19101 				if (dll is null) return null; // <32, but idc
19102 			}
19103 			res = GetProcAddress(dll, name);
19104 			+/
19105 			res = GetProcAddress(gl.libHandle, name);
19106 		}
19107 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
19108 		return res;
19109 	}
19110 	}
19111 
19112 
19113  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
19114         void wglSetVSync(bool wait) {
19115 		if(wglSwapIntervalEXT is null) {
19116 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
19117 			if(wglSwapIntervalEXT is null)
19118 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
19119 		}
19120 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
19121 			return;
19122 
19123 		wglSwapIntervalEXT(wait ? 1 : 0);
19124 	}
19125 
19126 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19127 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19128 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
19129 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
19130 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
19131 
19132 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
19133 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
19134 
19135 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
19136 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
19137 
19138 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
19139 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
19140 
19141 		void wglInitOtherFunctions () {
19142 			if (wglCreateContextAttribsARB is null) {
19143 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
19144 			}
19145 		}
19146 	}
19147 
19148 	static if (!SdpyIsUsingIVGLBinds) {
19149 
19150 	interface GL {
19151 		extern(System) @nogc nothrow:
19152 
19153 		void glGetIntegerv(int, void*);
19154 		void glMatrixMode(int);
19155 		void glPushMatrix();
19156 		void glLoadIdentity();
19157 		void glOrtho(double, double, double, double, double, double);
19158 		void glFrustum(double, double, double, double, double, double);
19159 
19160 		void glPopMatrix();
19161 		void glEnable(int);
19162 		void glDisable(int);
19163 		void glClear(int);
19164 		void glBegin(int);
19165 		void glVertex2f(float, float);
19166 		void glVertex3f(float, float, float);
19167 		void glEnd();
19168 		void glColor3b(byte, byte, byte);
19169 		void glColor3ub(ubyte, ubyte, ubyte);
19170 		void glColor4b(byte, byte, byte, byte);
19171 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
19172 		void glColor3i(int, int, int);
19173 		void glColor3ui(uint, uint, uint);
19174 		void glColor4i(int, int, int, int);
19175 		void glColor4ui(uint, uint, uint, uint);
19176 		void glColor3f(float, float, float);
19177 		void glColor4f(float, float, float, float);
19178 		void glTranslatef(float, float, float);
19179 		void glScalef(float, float, float);
19180 		version(X11) {
19181 			void glSecondaryColor3b(byte, byte, byte);
19182 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
19183 			void glSecondaryColor3i(int, int, int);
19184 			void glSecondaryColor3ui(uint, uint, uint);
19185 			void glSecondaryColor3f(float, float, float);
19186 		}
19187 
19188 		void glDrawElements(int, int, int, void*);
19189 
19190 		void glRotatef(float, float, float, float);
19191 
19192 		uint glGetError();
19193 
19194 		void glDeleteTextures(int, uint*);
19195 
19196 
19197 		void glRasterPos2i(int, int);
19198 		void glDrawPixels(int, int, uint, uint, void*);
19199 		void glClearColor(float, float, float, float);
19200 
19201 
19202 		void glPixelStorei(uint, int);
19203 
19204 		void glGenTextures(uint, uint*);
19205 		void glBindTexture(int, int);
19206 		void glTexParameteri(uint, uint, int);
19207 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19208 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
19209 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
19210 			/*GLsizei*/int width, /*GLsizei*/int height,
19211 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19212 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19213 
19214 		void glLineWidth(int);
19215 
19216 
19217 		void glTexCoord2f(float, float);
19218 		void glVertex2i(int, int);
19219 		void glBlendFunc (int, int);
19220 		void glDepthFunc (int);
19221 		void glViewport(int, int, int, int);
19222 
19223 		void glClearDepth(double);
19224 
19225 		void glReadBuffer(uint);
19226 		void glReadPixels(int, int, int, int, int, int, void*);
19227 
19228 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
19229 
19230 		void glFlush();
19231 		void glFinish();
19232 
19233 		version(Windows) {
19234 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
19235 			HGLRC wglCreateContext(HDC);
19236 			HGLRC wglCreateLayerContext(HDC, int);
19237 			BOOL wglDeleteContext(HGLRC);
19238 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
19239 			HGLRC wglGetCurrentContext();
19240 			HDC wglGetCurrentDC();
19241 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
19242 			PROC wglGetProcAddress(LPCSTR);
19243 			BOOL wglMakeCurrent(HDC, HGLRC);
19244 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
19245 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
19246 			BOOL wglShareLists(HGLRC, HGLRC);
19247 			BOOL wglSwapLayerBuffers(HDC, UINT);
19248 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
19249 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
19250 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19251 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19252 		}
19253 
19254 	}
19255 
19256 	interface GL3 {
19257 		extern(System) @nogc nothrow:
19258 
19259 		void glGenVertexArrays(GLsizei, GLuint*);
19260 		void glBindVertexArray(GLuint);
19261 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
19262 		void glGenerateMipmap(GLenum);
19263 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
19264 		void glStencilMask(GLuint);
19265 		void glStencilFunc(GLenum, GLint, GLuint);
19266 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19267 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19268 		GLuint glCreateProgram();
19269 		GLuint glCreateShader(GLenum);
19270 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
19271 		void glCompileShader(GLuint);
19272 		void glGetShaderiv(GLuint, GLenum, GLint*);
19273 		void glAttachShader(GLuint, GLuint);
19274 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
19275 		void glLinkProgram(GLuint);
19276 		void glGetProgramiv(GLuint, GLenum, GLint*);
19277 		void glDeleteProgram(GLuint);
19278 		void glDeleteShader(GLuint);
19279 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
19280 		void glGenBuffers(GLsizei, GLuint*);
19281 
19282 		void glUniform1f(GLint location, GLfloat v0);
19283 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
19284 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
19285 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
19286 		void glUniform1i(GLint location, GLint v0);
19287 		void glUniform2i(GLint location, GLint v0, GLint v1);
19288 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
19289 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
19290 		void glUniform1ui(GLint location, GLuint v0);
19291 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
19292 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
19293 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
19294 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
19295 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
19296 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
19297 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
19298 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
19299 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
19300 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
19301 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
19302 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
19303 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
19304 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
19305 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
19306 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19307 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19308 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19309 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19310 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19311 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19312 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19313 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19314 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19315 
19316 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
19317 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
19318 		void glDrawArrays(GLenum, GLint, GLsizei);
19319 		void glStencilOp(GLenum, GLenum, GLenum);
19320 		void glUseProgram(GLuint);
19321 		void glCullFace(GLenum);
19322 		void glFrontFace(GLenum);
19323 		void glActiveTexture(GLenum);
19324 		void glBindBuffer(GLenum, GLuint);
19325 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
19326 		void glEnableVertexAttribArray(GLuint);
19327 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
19328 		void glUniform1i(GLint, GLint);
19329 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
19330 		void glDisableVertexAttribArray(GLuint);
19331 		void glDeleteBuffers(GLsizei, const(GLuint)*);
19332 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
19333 		void glLogicOp (GLenum opcode);
19334 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
19335 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
19336 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
19337 		GLenum glCheckFramebufferStatus (GLenum target);
19338 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
19339 	}
19340 
19341 	interface GL4 {
19342 		extern(System) @nogc nothrow:
19343 
19344 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
19345 			/*GLsizei*/int width, /*GLsizei*/int height,
19346 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19347 	}
19348 
19349 	interface GLU {
19350 		extern(System) @nogc nothrow:
19351 
19352 		void gluLookAt(double, double, double, double, double, double, double, double, double);
19353 		void gluPerspective(double, double, double, double);
19354 
19355 		char* gluErrorString(uint);
19356 	}
19357 
19358 
19359 	enum GL_RED = 0x1903;
19360 	enum GL_ALPHA = 0x1906;
19361 
19362 	enum uint GL_FRONT = 0x0404;
19363 
19364 	enum uint GL_BLEND = 0x0be2;
19365 	enum uint GL_LEQUAL = 0x0203;
19366 
19367 
19368 	enum uint GL_RGB = 0x1907;
19369 	enum uint GL_BGRA = 0x80e1;
19370 	enum uint GL_RGBA = 0x1908;
19371 	enum uint GL_RGBA8 = 0x8058;
19372 	enum uint GL_TEXTURE_2D =   0x0DE1;
19373 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
19374 	enum uint GL_NEAREST = 0x2600;
19375 	enum uint GL_LINEAR = 0x2601;
19376 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
19377 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
19378 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
19379 	enum uint GL_REPEAT = 0x2901;
19380 	enum uint GL_CLAMP = 0x2900;
19381 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
19382 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
19383 	enum uint GL_DECAL = 0x2101;
19384 	enum uint GL_MODULATE = 0x2100;
19385 	enum uint GL_TEXTURE_ENV = 0x2300;
19386 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
19387 	enum uint GL_REPLACE = 0x1E01;
19388 	enum uint GL_LIGHTING = 0x0B50;
19389 	enum uint GL_DITHER = 0x0BD0;
19390 
19391 	enum uint GL_NO_ERROR = 0;
19392 
19393 
19394 
19395 	enum int GL_VIEWPORT = 0x0BA2;
19396 	enum int GL_MODELVIEW = 0x1700;
19397 	enum int GL_TEXTURE = 0x1702;
19398 	enum int GL_PROJECTION = 0x1701;
19399 	enum int GL_DEPTH_TEST = 0x0B71;
19400 
19401 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
19402 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
19403 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
19404 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
19405 
19406 	enum int GL_POINTS = 0x0000;
19407 	enum int GL_LINES =  0x0001;
19408 	enum int GL_LINE_LOOP = 0x0002;
19409 	enum int GL_LINE_STRIP = 0x0003;
19410 	enum int GL_TRIANGLES = 0x0004;
19411 	enum int GL_TRIANGLE_STRIP = 5;
19412 	enum int GL_TRIANGLE_FAN = 6;
19413 	enum int GL_QUADS = 7;
19414 	enum int GL_QUAD_STRIP = 8;
19415 	enum int GL_POLYGON = 9;
19416 
19417 	alias GLvoid = void;
19418 	alias GLboolean = ubyte;
19419 	alias GLint = int;
19420 	alias GLuint = uint;
19421 	alias GLenum = uint;
19422 	alias GLchar = char;
19423 	alias GLsizei = int;
19424 	alias GLfloat = float;
19425 	alias GLintptr = size_t;
19426 	alias GLsizeiptr = ptrdiff_t;
19427 
19428 
19429 	enum uint GL_INVALID_ENUM = 0x0500;
19430 
19431 	enum uint GL_ZERO = 0;
19432 	enum uint GL_ONE = 1;
19433 
19434 	enum uint GL_BYTE = 0x1400;
19435 	enum uint GL_UNSIGNED_BYTE = 0x1401;
19436 	enum uint GL_SHORT = 0x1402;
19437 	enum uint GL_UNSIGNED_SHORT = 0x1403;
19438 	enum uint GL_INT = 0x1404;
19439 	enum uint GL_UNSIGNED_INT = 0x1405;
19440 	enum uint GL_FLOAT = 0x1406;
19441 	enum uint GL_2_BYTES = 0x1407;
19442 	enum uint GL_3_BYTES = 0x1408;
19443 	enum uint GL_4_BYTES = 0x1409;
19444 	enum uint GL_DOUBLE = 0x140A;
19445 
19446 	enum uint GL_STREAM_DRAW = 0x88E0;
19447 
19448 	enum uint GL_CCW = 0x0901;
19449 
19450 	enum uint GL_STENCIL_TEST = 0x0B90;
19451 	enum uint GL_SCISSOR_TEST = 0x0C11;
19452 
19453 	enum uint GL_EQUAL = 0x0202;
19454 	enum uint GL_NOTEQUAL = 0x0205;
19455 
19456 	enum uint GL_ALWAYS = 0x0207;
19457 	enum uint GL_KEEP = 0x1E00;
19458 
19459 	enum uint GL_INCR = 0x1E02;
19460 
19461 	enum uint GL_INCR_WRAP = 0x8507;
19462 	enum uint GL_DECR_WRAP = 0x8508;
19463 
19464 	enum uint GL_CULL_FACE = 0x0B44;
19465 	enum uint GL_BACK = 0x0405;
19466 
19467 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
19468 	enum uint GL_VERTEX_SHADER = 0x8B31;
19469 
19470 	enum uint GL_COMPILE_STATUS = 0x8B81;
19471 	enum uint GL_LINK_STATUS = 0x8B82;
19472 
19473 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
19474 
19475 	enum uint GL_STATIC_DRAW = 0x88E4;
19476 
19477 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
19478 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
19479 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
19480 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
19481 
19482 	enum uint GL_GENERATE_MIPMAP = 0x8191;
19483 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
19484 
19485 	enum uint GL_TEXTURE0 = 0x84C0U;
19486 	enum uint GL_TEXTURE1 = 0x84C1U;
19487 
19488 	enum uint GL_ARRAY_BUFFER = 0x8892;
19489 
19490 	enum uint GL_SRC_COLOR = 0x0300;
19491 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
19492 	enum uint GL_SRC_ALPHA = 0x0302;
19493 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
19494 	enum uint GL_DST_ALPHA = 0x0304;
19495 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
19496 	enum uint GL_DST_COLOR = 0x0306;
19497 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
19498 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
19499 
19500 	enum uint GL_INVERT = 0x150AU;
19501 
19502 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
19503 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
19504 
19505 	enum uint GL_FRAMEBUFFER = 0x8D40U;
19506 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
19507 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
19508 
19509 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
19510 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
19511 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
19512 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
19513 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
19514 
19515 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
19516 	enum uint GL_CLEAR = 0x1500U;
19517 	enum uint GL_COPY = 0x1503U;
19518 	enum uint GL_XOR = 0x1506U;
19519 
19520 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
19521 
19522 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
19523 
19524 	}
19525 }
19526 
19527 /++
19528 	History:
19529 		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.
19530 +/
19531 __gshared bool gluSuccessfullyLoaded = true;
19532 
19533 version(without_opengl) {} else {
19534 static if(!SdpyIsUsingIVGLBinds) {
19535 	version(Windows) {
19536 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
19537 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
19538 	} else {
19539 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
19540 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
19541 	}
19542 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
19543 
19544 
19545 	shared static this() {
19546 		gl.loadDynamicLibrary();
19547 
19548 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
19549 		// unless those functions are actually used
19550 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
19551 		glu.loadDynamicLibrary();
19552 	}
19553 }
19554 }
19555 
19556 /++
19557 	Convenience method for converting D arrays to opengl buffer data
19558 
19559 	I would LOVE to overload it with the original glBufferData, but D won't
19560 	let me since glBufferData is a function pointer :(
19561 
19562 	Added: August 25, 2020 (version 8.5)
19563 +/
19564 version(without_opengl) {} else
19565 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
19566 	glBufferData(target, data.length, data.ptr, usage);
19567 }
19568 
19569 /++
19570 	History:
19571 		Added September 1, 2024
19572 +/
19573 version(without_opengl) {} else
19574 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) {
19575 	glBufferSubData(target, offset, data.length, data.ptr);
19576 }
19577 
19578 /++
19579 	Convenience class for using opengl shaders.
19580 
19581 	Ensure that you've loaded opengl 3+ and set your active
19582 	context before trying to use this.
19583 
19584 	Added: August 25, 2020 (version 8.5)
19585 +/
19586 version(without_opengl) {} else
19587 final class OpenGlShader {
19588 	private int shaderProgram_;
19589 	private @property void shaderProgram(int a) {
19590 		shaderProgram_ = a;
19591 	}
19592 	/// Get the program ID for use in OpenGL functions.
19593 	public @property int shaderProgram() {
19594 		return shaderProgram_;
19595 	}
19596 
19597 	/++
19598 
19599 	+/
19600 	static struct Source {
19601 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19602 		string code; ///
19603 	}
19604 
19605 	/++
19606 		Helper method to just compile some shader code and check for errors
19607 		while you do glCreateShader, etc. on the outside yourself.
19608 
19609 		This just does `glShaderSource` and `glCompileShader` for the given code.
19610 
19611 		If you the OpenGlShader class constructor, you never need to call this yourself.
19612 	+/
19613 	static void compile(int sid, Source code) {
19614 		const(char)*[1] buffer;
19615 		int[1] lengthBuffer;
19616 
19617 		buffer[0] = code.code.ptr;
19618 		lengthBuffer[0] = cast(int) code.code.length;
19619 
19620 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19621 		glCompileShader(sid);
19622 
19623 		int success;
19624 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19625 		if(!success) {
19626 			char[512] info;
19627 			int len;
19628 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19629 
19630 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19631 		}
19632 	}
19633 
19634 	/++
19635 		Calls `glLinkProgram` and throws if error a occurs.
19636 
19637 		If you the OpenGlShader class constructor, you never need to call this yourself.
19638 	+/
19639 	static void link(int shaderProgram) {
19640 		glLinkProgram(shaderProgram);
19641 		int success;
19642 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19643 		if(!success) {
19644 			char[512] info;
19645 			int len;
19646 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19647 
19648 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19649 		}
19650 	}
19651 
19652 	/++
19653 		Constructs the shader object by calling `glCreateProgram`, then
19654 		compiling each given [Source], and finally, linking them together.
19655 
19656 		Throws: on compile or link failure.
19657 	+/
19658 	this(Source[] codes...) {
19659 		shaderProgram = glCreateProgram();
19660 
19661 		int[16] shadersBufferStack;
19662 
19663 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19664 			shadersBufferStack[0 .. codes.length] :
19665 			new int[](codes.length);
19666 
19667 		foreach(idx, code; codes) {
19668 			shadersBuffer[idx] = glCreateShader(code.type);
19669 
19670 			compile(shadersBuffer[idx], code);
19671 
19672 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19673 		}
19674 
19675 		link(shaderProgram);
19676 
19677 		foreach(s; shadersBuffer)
19678 			glDeleteShader(s);
19679 	}
19680 
19681 	/// Calls `glUseProgram(this.shaderProgram)`
19682 	void use() {
19683 		glUseProgram(this.shaderProgram);
19684 	}
19685 
19686 	/// Deletes the program.
19687 	void delete_() {
19688 		glDeleteProgram(shaderProgram);
19689 		shaderProgram = 0;
19690 	}
19691 
19692 	/++
19693 		[OpenGlShader.uniforms].name gives you one of these.
19694 
19695 		You can get the id out of it or just assign
19696 	+/
19697 	static struct Uniform {
19698 		/// the id passed to glUniform*
19699 		int id;
19700 
19701 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
19702 		void opAssign(float x, float y, float z, float w) {
19703 			if(id != -1)
19704 			glUniform4f(id, x, y, z, w);
19705 		}
19706 
19707 		void opAssign(float x) {
19708 			if(id != -1)
19709 			glUniform1f(id, x);
19710 		}
19711 
19712 		void opAssign(float x, float y) {
19713 			if(id != -1)
19714 			glUniform2f(id, x, y);
19715 		}
19716 
19717 		void opAssign(T)(T t) {
19718 			t.glUniform(id);
19719 		}
19720 	}
19721 
19722 	static struct UniformsHelper {
19723 		OpenGlShader _shader;
19724 
19725 		@property Uniform opDispatch(string name)() {
19726 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
19727 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
19728 			//if(i == -1)
19729 				//throw new Exception("Could not find uniform " ~ name);
19730 			return Uniform(i);
19731 		}
19732 
19733 		@property void opDispatch(string name, T)(T t) {
19734 			Uniform f = this.opDispatch!name;
19735 			t.glUniform(f);
19736 		}
19737 	}
19738 
19739 	/++
19740 		Gives access to the uniforms through dot access.
19741 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
19742 	+/
19743 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
19744 }
19745 
19746 version(without_opengl) {} else {
19747 /++
19748 	A static container of experimental types and value constructors for opengl 3+ shaders.
19749 
19750 
19751 	You can declare variables like:
19752 
19753 	```
19754 	OGL.vec3f something;
19755 	```
19756 
19757 	But generally it would be used with [OpenGlShader]'s uniform helpers like
19758 
19759 	```
19760 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19761 	```
19762 
19763 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19764 
19765 
19766 	History:
19767 		Added December 7, 2021. Not yet stable.
19768 +/
19769 final class OGL {
19770 	static:
19771 
19772 	private template typeFromSpecifier(string specifier) {
19773 		static if(specifier == "f")
19774 			alias typeFromSpecifier = GLfloat;
19775 		else static if(specifier == "i")
19776 			alias typeFromSpecifier = GLint;
19777 		else static if(specifier == "ui")
19778 			alias typeFromSpecifier = GLuint;
19779 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19780 	}
19781 
19782 	private template CommonType(T...) {
19783 		static if(T.length == 1)
19784 			alias CommonType = T[0];
19785 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19786 			alias CommonType = CommonType!(C, T[2 .. $]);
19787 	}
19788 
19789 	private template typesToSpecifier(T...) {
19790 		static if(is(CommonType!T == float))
19791 			enum typesToSpecifier = "f";
19792 		else static if(is(CommonType!T == int))
19793 			enum typesToSpecifier = "i";
19794 		else static if(is(CommonType!T == uint))
19795 			enum typesToSpecifier = "ui";
19796 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19797 	}
19798 
19799 	private template genNames(size_t dim, size_t dim2 = 0) {
19800 		string helper() {
19801 			string s;
19802 			if(dim2) {
19803 				static if(__VERSION__ < 2102)
19804 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug
19805 				else
19806 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;";
19807 			} else {
19808 				if(dim > 0) s ~= "type x = 0;";
19809 				if(dim > 1) s ~= "type y = 0;";
19810 				if(dim > 2) s ~= "type z = 0;";
19811 				if(dim > 3) s ~= "type w = 0;";
19812 			}
19813 
19814 			s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }";
19815 			if(dim2)
19816 				s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }";
19817 
19818 			return s;
19819 		}
19820 
19821 		enum genNames = helper();
19822 	}
19823 
19824 	// there's vec, arrays of vec, mat, and arrays of mat
19825 	template opDispatch(string name)
19826 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19827 	{
19828 		static if(name[4] == 'x') {
19829 			enum dimX = cast(int) (name[3] - '0');
19830 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19831 
19832 			enum dimY = cast(int) (name[5] - '0');
19833 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19834 
19835 			enum isArray = name[$ - 1] == 'v';
19836 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19837 			alias type = typeFromSpecifier!typeSpecifier;
19838 		} else {
19839 			enum dim = cast(int) (name[3] - '0');
19840 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19841 			enum isArray = name[$ - 1] == 'v';
19842 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19843 			alias type = typeFromSpecifier!typeSpecifier;
19844 		}
19845 
19846 		align(1)
19847 		struct opDispatch {
19848 			align(1):
19849 			static if(name[4] == 'x')
19850 				mixin(genNames!(dimX, dimY));
19851 			else
19852 				mixin(genNames!dim);
19853 
19854 			private void glUniform(OpenGlShader.Uniform assignTo) {
19855 				glUniform(assignTo.id);
19856 			}
19857 			private void glUniform(int assignTo) {
19858 				static if(name[4] == 'x') {
19859 					static if(name[3] == name[5]) {
19860 						// import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY);
19861 						mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]);
19862 					} else {
19863 						mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr);
19864 					}
19865 				} else
19866 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19867 			}
19868 		}
19869 	}
19870 
19871 	auto vec(T...)(T members) {
19872 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19873 	}
19874 }
19875 
19876 void checkGlError() {
19877 	auto error = glGetError();
19878 	int[] errors;
19879 	string[] errorStrings;
19880 	while(error != GL_NO_ERROR) {
19881 		errors ~= error;
19882 		switch(error) {
19883 			case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break;
19884 			case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break;
19885 			case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break;
19886 			case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break;
19887 			case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break;
19888 			case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break;
19889 			default: errorStrings ~= "idk";
19890 		}
19891 		error = glGetError();
19892 	}
19893 	if(errors.length)
19894 		throw ArsdException!"glGetError"(errors, errorStrings);
19895 }
19896 
19897 /++
19898 	A matrix for simple uses that easily integrates with [OpenGlShader].
19899 
19900 	Might not be useful to you since it only as some simple functions and
19901 	probably isn't that fast.
19902 
19903 	Note it uses an inline static array for its storage, so copying it
19904 	may be expensive.
19905 +/
19906 struct BasicMatrix(int columns, int rows, T = float) {
19907 	static import core.stdc.math;
19908 	static if(is(T == float)) {
19909 		alias cos = core.stdc.math.cosf;
19910 		alias sin = core.stdc.math.sinf;
19911 	} else {
19912 		alias cos = core.stdc.math.cos;
19913 		alias sin = core.stdc.math.sin;
19914 	}
19915 
19916 	T[columns * rows] data = 0.0;
19917 
19918 	/++
19919 
19920 	+/
19921 	this(T[columns * rows] data) {
19922 		this.data = data;
19923 	}
19924 
19925 	/++
19926 		Basic operations that operate *in place*.
19927 	+/
19928 	static if(columns == 4 && rows == 4)
19929 	void translate(T x, T y, T z) {
19930 		BasicMatrix m = [
19931 			1, 0, 0, x,
19932 			0, 1, 0, y,
19933 			0, 0, 1, z,
19934 			0, 0, 0, 1
19935 		];
19936 
19937 		this *= m;
19938 	}
19939 
19940 	/// ditto
19941 	static if(columns == 4 && rows == 4)
19942 	void scale(T x, T y, T z) {
19943 		BasicMatrix m = [
19944 			x, 0, 0, 0,
19945 			0, y, 0, 0,
19946 			0, 0, z, 0,
19947 			0, 0, 0, 1
19948 		];
19949 
19950 		this *= m;
19951 	}
19952 
19953 	/// ditto
19954 	static if(columns == 4 && rows == 4)
19955 	void rotateX(T theta) {
19956 		BasicMatrix m = [
19957 			1,          0,           0, 0,
19958 			0, cos(theta), -sin(theta), 0,
19959 			0, sin(theta),  cos(theta), 0,
19960 			0,          0,           0, 1
19961 		];
19962 
19963 		this *= m;
19964 	}
19965 
19966 	/// ditto
19967 	static if(columns == 4 && rows == 4)
19968 	void rotateY(T theta) {
19969 		BasicMatrix m = [
19970 			 cos(theta), 0,  sin(theta), 0,
19971 			          0, 1,           0, 0,
19972 			-sin(theta), 0,  cos(theta), 0,
19973 			          0, 0,           0, 1
19974 		];
19975 
19976 		this *= m;
19977 	}
19978 
19979 	/// ditto
19980 	static if(columns == 4 && rows == 4)
19981 	void rotateZ(T theta) {
19982 		BasicMatrix m = [
19983 			cos(theta), -sin(theta), 0, 0,
19984 			sin(theta),  cos(theta), 0, 0,
19985 			         0,           0, 1, 0,
19986 				 0,           0, 0, 1
19987 		];
19988 
19989 		this *= m;
19990 	}
19991 
19992 	/++
19993 
19994 	+/
19995 	static if(columns == rows)
19996 	static BasicMatrix identity() {
19997 		BasicMatrix m;
19998 		foreach(i; 0 .. columns)
19999 			m.data[0 + i + i * columns] = 1.0;
20000 		return m;
20001 	}
20002 
20003 	static if(columns == rows)
20004 	void loadIdentity() {
20005 		this = identity();
20006 	}
20007 
20008 	static if(columns == 4 && rows == 4)
20009 	static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) {
20010 		return BasicMatrix([
20011 			2/(r-l),       0,        0, -(r+l)/(r-l),
20012 			      0, 2/(t-b),        0, -(t+b)/(t-b),
20013 			      0,       0, -2/(f-n), -(f+n)/(f-n),
20014 			      0,       0,        0,            1
20015 		]);
20016 	}
20017 
20018 	static if(columns == 4 && rows == 4)
20019 	void loadOrtho(T l, T r, T b, T t, T n, T f) {
20020 		this = ortho(l, r, b, t, n, f);
20021 	}
20022 
20023 	void opOpAssign(string op : "+")(const BasicMatrix rhs) {
20024 		this.data[] += rhs.data;
20025 	}
20026 	void opOpAssign(string op : "-")(const BasicMatrix rhs) {
20027 		this.data[] -= rhs.data;
20028 	}
20029 	void opOpAssign(string op : "*")(const T rhs) {
20030 		this.data[] *= rhs;
20031 	}
20032 	void opOpAssign(string op : "/")(const T rhs) {
20033 		this.data[] /= rhs;
20034 	}
20035 	void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) {
20036 		static assert(columns == rhsRows);
20037 		auto multiplySize = columns;
20038 
20039 		auto tmp = this.data; // copy cuz it is a value type
20040 
20041 		int idx = 0;
20042 		foreach(r; 0 .. rows)
20043 		foreach(c; 0 .. columns) {
20044 			T sum = 0.0;
20045 
20046 			foreach(i; 0 .. multiplySize)
20047 				sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c];
20048 
20049 			tmp[idx++] = sum;
20050 		}
20051 
20052 		this.data = tmp;
20053 	}
20054 }
20055 
20056 unittest {
20057 	auto m = BasicMatrix!(2, 2)([
20058 		1, 2,
20059 		3, 4
20060 	]);
20061 
20062 	auto m2 = BasicMatrix!(2, 2)([
20063 		5, 6,
20064 		7, 8
20065 	]);
20066 
20067 	import std.conv;
20068 	m *= m2;
20069 	assert(m.data == [
20070 		19, 22,
20071 		43, 50
20072 	], to!string(m.data));
20073 }
20074 
20075 
20076 
20077 class GlObjectBase {
20078 	protected uint _vao;
20079 	protected uint _elementsCount;
20080 
20081 	protected uint element_buffer;
20082 
20083 	void gen() {
20084 		glGenVertexArrays(1, &_vao);
20085 	}
20086 
20087 	void bind() {
20088 		glBindVertexArray(_vao);
20089 	}
20090 
20091 	void dispose() {
20092 		glDeleteVertexArrays(1, &_vao);
20093 	}
20094 
20095 	void draw() {
20096 		bind();
20097 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20098 		glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null);
20099 	}
20100 }
20101 
20102 /++
20103 
20104 +/
20105 class GlObject(T) : GlObjectBase {
20106 	protected uint VBO;
20107 
20108 	this(T[] arr, uint[] indices) {
20109 		gen();
20110 		bind();
20111 
20112 		glGenBuffers(1, &VBO);
20113 		glGenBuffers(1, &element_buffer);
20114 
20115 		glBindBuffer(GL_ARRAY_BUFFER, VBO);
20116 		glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW);
20117 
20118 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20119 		glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
20120 		_elementsCount = cast(int) indices.length;
20121 
20122 		foreach(int idx, memberName; __traits(allMembers, T)) {
20123 			static if(memberName != "__ctor") {
20124 			static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) {
20125 				glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof);
20126 				glEnableVertexAttribArray(idx);
20127 			} else static assert(0); }
20128 		}
20129 	}
20130 
20131 	static string generateShaderDefinitions() {
20132 		string code;
20133 
20134 		foreach(idx, memberName; __traits(allMembers, T)) {
20135 			// never use stringof ladies and gents it has a LU thing at the end of it
20136 			static if(memberName != "__ctor")
20137 			code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n";
20138 		}
20139 
20140 		return code;
20141 	}
20142 }
20143 
20144 private string typeToGl(T)() {
20145 	static if(is(T == float[4]))
20146 		return "vec4";
20147 	else static if(is(T == float[3]))
20148 		return "vec3";
20149 	else static if(is(T == float[2]))
20150 		return "vec2";
20151 	else static assert(0, T.stringof);
20152 }
20153 
20154 
20155 }
20156 
20157 version(linux) {
20158 	version(with_eventloop) {} else {
20159 		private int epollFd = -1;
20160 		void prepareEventLoop() {
20161 			if(epollFd != -1)
20162 				return; // already initialized, no need to do it again
20163 			import ep = core.sys.linux.epoll;
20164 
20165 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
20166 			if(epollFd == -1)
20167 				throw new Exception("epoll create failure");
20168 		}
20169 	}
20170 } else version(Posix) {
20171 	void prepareEventLoop() {}
20172 }
20173 
20174 version(X11) {
20175 	import core.stdc.locale : LC_ALL; // rdmd fix
20176 	__gshared bool sdx_isUTF8Locale;
20177 
20178 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
20179 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
20180 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
20181 	// anal magic is here. I (Ketmar) hope you like it.
20182 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
20183 	// always return correct unicode symbols. The detection is here 'cause user can change locale
20184 	// later.
20185 
20186 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
20187 	shared static this () @system {
20188 		if(!librariesSuccessfullyLoaded)
20189 			return;
20190 
20191 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
20192 
20193 		// this doesn't hurt; it may add some locking, but the speed is still
20194 		// allows doing 60 FPS videogames; also, ignore the result, as most
20195 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
20196 		// never seen this failing).
20197 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
20198 
20199 		setlocale(LC_ALL, "");
20200 		// check if out locale is UTF-8
20201 		auto lct = setlocale(LC_CTYPE, null);
20202 		if (lct is null) {
20203 			sdx_isUTF8Locale = false;
20204 		} else {
20205 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
20206 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
20207 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
20208 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
20209 				{
20210 					sdx_isUTF8Locale = true;
20211 					break;
20212 				}
20213 			}
20214 		}
20215 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
20216 	}
20217 }
20218 
20219 class ExperimentalTextComponent2 {
20220 	/+
20221 		Stage 1: get it working monospace
20222 		Stage 2: use proportional font
20223 		Stage 3: allow changes in inline style
20224 		Stage 4: allow new fonts and sizes in the middle
20225 		Stage 5: optimize gap buffer
20226 		Stage 6: optimize layout
20227 		Stage 7: word wrap
20228 		Stage 8: justification
20229 		Stage 9: editing, selection, etc.
20230 
20231 			Operations:
20232 				insert text
20233 				overstrike text
20234 				select
20235 				cut
20236 				modify
20237 	+/
20238 
20239 	/++
20240 		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.
20241 	+/
20242 	this(SimpleWindow window) {
20243 		this.window = window;
20244 	}
20245 
20246 	private SimpleWindow window;
20247 
20248 
20249 	/++
20250 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
20251 		representing the internal parts. The first pass is focused on the x parameter, then the
20252 		renderer is responsible for going back to the parts in the current line and calling
20253 		adjustDownForAscent to change the y params.
20254 	+/
20255 	static interface ComponentRenderHelper {
20256 
20257 		/+
20258 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
20259 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
20260 			to move (adjust y to make room for new line) until you get back to the same position,
20261 			then you can stop - if one thing is unchanged, nothing after it is changed too.
20262 
20263 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
20264 			once you reach something that is unchanged, you can stop.
20265 		+/
20266 
20267 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
20268 
20269 		int ascent() const;
20270 		int descent() const;
20271 
20272 		int advance() const;
20273 
20274 		bool endsWithExplititLineBreak() const;
20275 	}
20276 
20277 	static interface RenderResult {
20278 		/++
20279 			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.
20280 		+/
20281 		void popFront();
20282 		@property bool empty() const;
20283 		@property ComponentRenderHelper front() const;
20284 
20285 		void repositionForNextLine(Point baseline, int availableWidth);
20286 	}
20287 
20288 	static interface ComponentInFlow {
20289 		void draw(ScreenPainter painter);
20290 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
20291 
20292 		bool startsWithExplicitLineBreak() const;
20293 	}
20294 
20295 	static class TextFlowComponent : ComponentInFlow {
20296 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
20297 
20298 		Color foreground;
20299 		Color background;
20300 
20301 		OperatingSystemFont font; // should NEVER be null
20302 
20303 		ubyte attributes; // underline, strike through, display on new block
20304 
20305 		version(Windows)
20306 			const(wchar)[] content;
20307 		else
20308 			const(char)[] content; // this should NEVER have a newline, except at the end
20309 
20310 		RenderedComponent[] rendered; // entirely controlled by [rerender]
20311 
20312 		// could prolly put some spacing around it too like margin / padding
20313 
20314 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
20315 			in { assert(font !is null);
20316 			     assert(!font.isNull); }
20317 			do
20318 		{
20319 			this.foreground = f;
20320 			this.background = b;
20321 			this.font = font;
20322 
20323 			this.attributes = attr;
20324 			version(Windows) {
20325 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
20326 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
20327 				auto buffer = new wchar[](sz);
20328 				this.content = makeWindowsString(c, buffer, conversionFlags);
20329 			} else {
20330 				this.content = c.dup;
20331 			}
20332 		}
20333 
20334 		void draw(ScreenPainter painter) {
20335 			painter.setFont(this.font);
20336 			painter.outlineColor = this.foreground;
20337 			painter.fillColor = Color.transparent;
20338 			foreach(rendered; this.rendered) {
20339 				// the component works in term of baseline,
20340 				// but the painter works in term of upper left bounding box
20341 				// so need to translate that
20342 
20343 				if(this.background.a) {
20344 					painter.fillColor = this.background;
20345 					painter.outlineColor = this.background;
20346 
20347 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
20348 
20349 					painter.outlineColor = this.foreground;
20350 					painter.fillColor = Color.transparent;
20351 				}
20352 
20353 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
20354 
20355 				// FIXME: strike through, underline, highlight selection, etc.
20356 			}
20357 		}
20358 	}
20359 
20360 	// I could split the parts into words on render
20361 	// for easier word-wrap, each one being an unbreakable "inline-block"
20362 	private TextFlowComponent[] parts;
20363 	private int needsRerenderFrom;
20364 
20365 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
20366 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
20367 		parts ~= new TextFlowComponent(f, b, font, attr, c);
20368 	}
20369 
20370 	static struct RenderedComponent {
20371 		int startX;
20372 		int startY;
20373 		short width;
20374 		// 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!
20375 		// for individual chars in here you've gotta process on demand
20376 		version(Windows)
20377 			const(wchar)[] slice;
20378 		else
20379 			const(char)[] slice;
20380 	}
20381 
20382 
20383 	void rerender(Rectangle boundingBox) {
20384 		Point baseline = boundingBox.upperLeft;
20385 
20386 		this.boundingBox.left = boundingBox.left;
20387 		this.boundingBox.top = boundingBox.top;
20388 
20389 		auto remainingParts = parts;
20390 
20391 		int largestX;
20392 
20393 
20394 		foreach(part; parts)
20395 			part.font.prepareContext(window);
20396 		scope(exit)
20397 		foreach(part; parts)
20398 			part.font.releaseContext();
20399 
20400 		calculateNextLine:
20401 
20402 		int nextLineHeight = 0;
20403 		int nextBiggestDescent = 0;
20404 
20405 		foreach(part; remainingParts) {
20406 			auto height = part.font.ascent;
20407 			if(height > nextLineHeight)
20408 				nextLineHeight = height;
20409 			if(part.font.descent > nextBiggestDescent)
20410 				nextBiggestDescent = part.font.descent;
20411 			if(part.content.length && part.content[$-1] == '\n')
20412 				break;
20413 		}
20414 
20415 		baseline.y += nextLineHeight;
20416 		auto lineStart = baseline;
20417 
20418 		while(remainingParts.length) {
20419 			remainingParts[0].rendered = null;
20420 
20421 			bool eol;
20422 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
20423 				eol = true;
20424 
20425 			// FIXME: word wrap
20426 			auto font = remainingParts[0].font;
20427 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
20428 			auto width = font.stringWidth(slice, window);
20429 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
20430 
20431 			remainingParts = remainingParts[1 .. $];
20432 			baseline.x += width;
20433 
20434 			if(eol) {
20435 				baseline.y += nextBiggestDescent;
20436 				if(baseline.x > largestX)
20437 					largestX = baseline.x;
20438 				baseline.x = lineStart.x;
20439 				goto calculateNextLine;
20440 			}
20441 		}
20442 
20443 		if(baseline.x > largestX)
20444 			largestX = baseline.x;
20445 
20446 		this.boundingBox.right = largestX;
20447 		this.boundingBox.bottom = baseline.y;
20448 	}
20449 
20450 	// you must call rerender first!
20451 	void draw(ScreenPainter painter) {
20452 		foreach(part; parts) {
20453 			part.draw(painter);
20454 		}
20455 	}
20456 
20457 	struct IdentifyResult {
20458 		TextFlowComponent part;
20459 		int charIndexInPart;
20460 		int totalCharIndex = -1; // if this is -1, it just means the end
20461 
20462 		Rectangle boundingBox;
20463 	}
20464 
20465 	IdentifyResult identify(Point pt, bool exact = false) {
20466 		if(parts.length == 0)
20467 			return IdentifyResult(null, 0);
20468 
20469 		if(pt.y < boundingBox.top) {
20470 			if(exact)
20471 				return IdentifyResult(null, 1);
20472 			return IdentifyResult(parts[0], 0);
20473 		}
20474 		if(pt.y > boundingBox.bottom) {
20475 			if(exact)
20476 				return IdentifyResult(null, 2);
20477 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
20478 		}
20479 
20480 		int tci = 0;
20481 
20482 		// I should probably like binary search this or something...
20483 		foreach(ref part; parts) {
20484 			foreach(rendered; part.rendered) {
20485 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
20486 				if(rect.contains(pt)) {
20487 					auto x = pt.x - rendered.startX;
20488 					auto estimatedIdx = x / part.font.averageWidth;
20489 
20490 					if(estimatedIdx < 0)
20491 						estimatedIdx = 0;
20492 
20493 					if(estimatedIdx > rendered.slice.length)
20494 						estimatedIdx = cast(int) rendered.slice.length;
20495 
20496 					int idx;
20497 					int x1, x2;
20498 					if(part.font.isMonospace) {
20499 						auto w = part.font.averageWidth;
20500 						if(!exact && x > (estimatedIdx + 1) * w)
20501 							return IdentifyResult(null, 4);
20502 						idx = estimatedIdx;
20503 						x1 = idx * w;
20504 						x2 = (idx + 1) * w;
20505 					} else {
20506 						idx = estimatedIdx;
20507 
20508 						part.font.prepareContext(window);
20509 						scope(exit) part.font.releaseContext();
20510 
20511 						// int iterations;
20512 
20513 						while(true) {
20514 							// iterations++;
20515 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
20516 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
20517 
20518 							x1 += rendered.startX;
20519 							x2 += rendered.startX;
20520 
20521 							if(pt.x < x1) {
20522 								if(idx == 0) {
20523 									if(exact)
20524 										return IdentifyResult(null, 6);
20525 									else
20526 										break;
20527 								}
20528 								idx--;
20529 							} else if(pt.x > x2) {
20530 								idx++;
20531 								if(idx > rendered.slice.length) {
20532 									if(exact)
20533 										return IdentifyResult(null, 5);
20534 									else
20535 										break;
20536 								}
20537 							} else if(pt.x >= x1 && pt.x <= x2) {
20538 								if(idx)
20539 									idx--; // point it at the original index
20540 								break; // we fit
20541 							}
20542 						}
20543 
20544 						// writeln(iterations)
20545 					}
20546 
20547 
20548 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
20549 				}
20550 			}
20551 			tci += cast(int) part.content.length; // FIXME: utf-8?
20552 		}
20553 		return IdentifyResult(null, 3);
20554 	}
20555 
20556 	Rectangle boundingBox; // only set after [rerender]
20557 
20558 	// text will be positioned around the exclusion zone
20559 	static struct ExclusionZone {
20560 
20561 	}
20562 
20563 	ExclusionZone[] exclusionZones;
20564 }
20565 
20566 
20567 // Don't use this yet. When I'm happy with it, I will move it to the
20568 // regular module namespace.
20569 mixin template ExperimentalTextComponent() {
20570 
20571 static:
20572 
20573 	alias Rectangle = arsd.color.Rectangle;
20574 
20575 	struct ForegroundColor {
20576 		Color color;
20577 		alias color this;
20578 
20579 		this(Color c) {
20580 			color = c;
20581 		}
20582 
20583 		this(int r, int g, int b, int a = 255) {
20584 			color = Color(r, g, b, a);
20585 		}
20586 
20587 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
20588 			return ForegroundColor(mixin("Color." ~ s));
20589 		}
20590 	}
20591 
20592 	struct BackgroundColor {
20593 		Color color;
20594 		alias color this;
20595 
20596 		this(Color c) {
20597 			color = c;
20598 		}
20599 
20600 		this(int r, int g, int b, int a = 255) {
20601 			color = Color(r, g, b, a);
20602 		}
20603 
20604 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
20605 			return BackgroundColor(mixin("Color." ~ s));
20606 		}
20607 	}
20608 
20609 	static class InlineElement {
20610 		string text;
20611 
20612 		BlockElement containingBlock;
20613 
20614 		Color color = Color.black;
20615 		Color backgroundColor = Color.transparent;
20616 		ushort styles;
20617 
20618 		string font;
20619 		int fontSize;
20620 
20621 		int lineHeight;
20622 
20623 		void* identifier;
20624 
20625 		Rectangle boundingBox;
20626 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
20627 
20628 		bool isMergeCompatible(InlineElement other) {
20629 			return
20630 				containingBlock is other.containingBlock &&
20631 				color == other.color &&
20632 				backgroundColor == other.backgroundColor &&
20633 				styles == other.styles &&
20634 				font == other.font &&
20635 				fontSize == other.fontSize &&
20636 				lineHeight == other.lineHeight &&
20637 				true;
20638 		}
20639 
20640 		int xOfIndex(size_t index) {
20641 			if(index < letterXs.length)
20642 				return letterXs[index];
20643 			else
20644 				return boundingBox.right;
20645 		}
20646 
20647 		InlineElement clone() {
20648 			auto ie = new InlineElement();
20649 			ie.tupleof = this.tupleof;
20650 			return ie;
20651 		}
20652 
20653 		InlineElement getPreviousInlineElement() {
20654 			InlineElement prev = null;
20655 			foreach(ie; this.containingBlock.parts) {
20656 				if(ie is this)
20657 					break;
20658 				prev = ie;
20659 			}
20660 			if(prev is null) {
20661 				BlockElement pb;
20662 				BlockElement cb = this.containingBlock;
20663 				moar:
20664 				foreach(ie; this.containingBlock.containingLayout.blocks) {
20665 					if(ie is cb)
20666 						break;
20667 					pb = ie;
20668 				}
20669 				if(pb is null)
20670 					return null;
20671 				if(pb.parts.length == 0) {
20672 					cb = pb;
20673 					goto moar;
20674 				}
20675 
20676 				prev = pb.parts[$-1];
20677 
20678 			}
20679 			return prev;
20680 		}
20681 
20682 		InlineElement getNextInlineElement() {
20683 			InlineElement next = null;
20684 			foreach(idx, ie; this.containingBlock.parts) {
20685 				if(ie is this) {
20686 					if(idx + 1 < this.containingBlock.parts.length)
20687 						next = this.containingBlock.parts[idx + 1];
20688 					break;
20689 				}
20690 			}
20691 			if(next is null) {
20692 				BlockElement n;
20693 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
20694 					if(ie is this.containingBlock) {
20695 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
20696 							n = this.containingBlock.containingLayout.blocks[idx + 1];
20697 						break;
20698 					}
20699 				}
20700 				if(n is null)
20701 					return null;
20702 
20703 				if(n.parts.length)
20704 					next = n.parts[0];
20705 				else {} // FIXME
20706 
20707 			}
20708 			return next;
20709 		}
20710 
20711 	}
20712 
20713 	// Block elements are used entirely for positioning inline elements,
20714 	// which are the things that are actually drawn.
20715 	class BlockElement {
20716 		InlineElement[] parts;
20717 		uint alignment;
20718 
20719 		int whiteSpace; // pre, pre-wrap, wrap
20720 
20721 		TextLayout containingLayout;
20722 
20723 		// inputs
20724 		Point where;
20725 		Size minimumSize;
20726 		Size maximumSize;
20727 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
20728 		void* identifier;
20729 
20730 		Rectangle margin;
20731 		Rectangle padding;
20732 
20733 		// outputs
20734 		Rectangle[] boundingBoxes;
20735 	}
20736 
20737 	struct TextIdentifyResult {
20738 		InlineElement element;
20739 		int offset;
20740 
20741 		private TextIdentifyResult fixupNewline() {
20742 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
20743 				offset--;
20744 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
20745 				offset--;
20746 			}
20747 			return this;
20748 		}
20749 	}
20750 
20751 	class TextLayout {
20752 		BlockElement[] blocks;
20753 		Rectangle boundingBox_;
20754 		Rectangle boundingBox() { return boundingBox_; }
20755 		void boundingBox(Rectangle r) {
20756 			if(r != boundingBox_) {
20757 				boundingBox_ = r;
20758 				layoutInvalidated = true;
20759 			}
20760 		}
20761 
20762 		Rectangle contentBoundingBox() {
20763 			Rectangle r;
20764 			foreach(block; blocks)
20765 			foreach(ie; block.parts) {
20766 				if(ie.boundingBox.right > r.right)
20767 					r.right = ie.boundingBox.right;
20768 				if(ie.boundingBox.bottom > r.bottom)
20769 					r.bottom = ie.boundingBox.bottom;
20770 			}
20771 			return r;
20772 		}
20773 
20774 		BlockElement[] getBlocks() {
20775 			return blocks;
20776 		}
20777 
20778 		InlineElement[] getTexts() {
20779 			InlineElement[] elements;
20780 			foreach(block; blocks)
20781 				elements ~= block.parts;
20782 			return elements;
20783 		}
20784 
20785 		string getPlainText() {
20786 			string text;
20787 			foreach(block; blocks)
20788 				foreach(part; block.parts)
20789 					text ~= part.text;
20790 			return text;
20791 		}
20792 
20793 		string getHtml() {
20794 			return null; // FIXME
20795 		}
20796 
20797 		this(Rectangle boundingBox) {
20798 			this.boundingBox = boundingBox;
20799 		}
20800 
20801 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
20802 			auto be = new BlockElement();
20803 			be.containingLayout = this;
20804 			if(after is null)
20805 				blocks ~= be;
20806 			else {
20807 				foreach(idx, b; blocks) {
20808 					if(b is after.containingBlock) {
20809 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
20810 						break;
20811 					}
20812 				}
20813 			}
20814 			return be;
20815 		}
20816 
20817 		void clear() {
20818 			blocks = null;
20819 			selectionStart = selectionEnd = caret = Caret.init;
20820 		}
20821 
20822 		void addText(Args...)(Args args) {
20823 			if(blocks.length == 0)
20824 				addBlock();
20825 
20826 			InlineElement ie = new InlineElement();
20827 			foreach(idx, arg; args) {
20828 				static if(is(typeof(arg) == ForegroundColor))
20829 					ie.color = arg;
20830 				else static if(is(typeof(arg) == TextFormat)) {
20831 					if(arg & 0x8000) // ~TextFormat.something turns it off
20832 						ie.styles &= arg;
20833 					else
20834 						ie.styles |= arg;
20835 				} else static if(is(typeof(arg) == string)) {
20836 					static if(idx == 0 && args.length > 1)
20837 						static assert(0, "Put styles before the string.");
20838 					size_t lastLineIndex;
20839 					foreach(cidx, char a; arg) {
20840 						if(a == '\n') {
20841 							ie.text = arg[lastLineIndex .. cidx + 1];
20842 							lastLineIndex = cidx + 1;
20843 							ie.containingBlock = blocks[$-1];
20844 							blocks[$-1].parts ~= ie.clone;
20845 							ie.text = null;
20846 						} else {
20847 
20848 						}
20849 					}
20850 
20851 					ie.text = arg[lastLineIndex .. $];
20852 					ie.containingBlock = blocks[$-1];
20853 					blocks[$-1].parts ~= ie.clone;
20854 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
20855 				}
20856 			}
20857 
20858 			invalidateLayout();
20859 		}
20860 
20861 		void tryMerge(InlineElement into, InlineElement what) {
20862 			if(!into.isMergeCompatible(what)) {
20863 				return; // cannot merge, different configs
20864 			}
20865 
20866 			// cool, can merge, bring text together...
20867 			into.text ~= what.text;
20868 
20869 			// and remove what
20870 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
20871 				if(what.containingBlock.parts[a] is what) {
20872 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
20873 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
20874 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
20875 
20876 				}
20877 			}
20878 
20879 			// FIXME: ensure no other carets have a reference to it
20880 		}
20881 
20882 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
20883 		TextIdentifyResult identify(int x, int y, bool exact = false) {
20884 			TextIdentifyResult inexactMatch;
20885 			foreach(block; blocks) {
20886 				foreach(part; block.parts) {
20887 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
20888 
20889 						// FIXME binary search
20890 						int tidx;
20891 						int lastX;
20892 						foreach_reverse(idxo, lx; part.letterXs) {
20893 							int idx = cast(int) idxo;
20894 							if(lx <= x) {
20895 								if(lastX && lastX - x < x - lx)
20896 									tidx = idx + 1;
20897 								else
20898 									tidx = idx;
20899 								break;
20900 							}
20901 							lastX = lx;
20902 						}
20903 
20904 						return TextIdentifyResult(part, tidx).fixupNewline;
20905 					} else if(!exact) {
20906 						// we're not in the box, but are we on the same line?
20907 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
20908 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
20909 					}
20910 				}
20911 			}
20912 
20913 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
20914 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
20915 
20916 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
20917 		}
20918 
20919 		void moveCaretToPixelCoordinates(int x, int y) {
20920 			auto result = identify(x, y);
20921 			caret.inlineElement = result.element;
20922 			caret.offset = result.offset;
20923 		}
20924 
20925 		void selectToPixelCoordinates(int x, int y) {
20926 			auto result = identify(x, y);
20927 
20928 			if(y < caretLastDrawnY1) {
20929 				// on a previous line, carat is selectionEnd
20930 				selectionEnd = caret;
20931 
20932 				selectionStart = Caret(this, result.element, result.offset);
20933 			} else if(y > caretLastDrawnY2) {
20934 				// on a later line
20935 				selectionStart = caret;
20936 
20937 				selectionEnd = Caret(this, result.element, result.offset);
20938 			} else {
20939 				// on the same line...
20940 				if(x <= caretLastDrawnX) {
20941 					selectionEnd = caret;
20942 					selectionStart = Caret(this, result.element, result.offset);
20943 				} else {
20944 					selectionStart = caret;
20945 					selectionEnd = Caret(this, result.element, result.offset);
20946 				}
20947 
20948 			}
20949 		}
20950 
20951 
20952 		/// Call this if the inputs change. It will reflow everything
20953 		void redoLayout(ScreenPainter painter) {
20954 			//painter.setClipRectangle(boundingBox);
20955 			auto pos = Point(boundingBox.left, boundingBox.top);
20956 
20957 			int lastHeight;
20958 			void nl() {
20959 				pos.x = boundingBox.left;
20960 				pos.y += lastHeight;
20961 			}
20962 			foreach(block; blocks) {
20963 				nl();
20964 				foreach(part; block.parts) {
20965 					part.letterXs = null;
20966 
20967 					auto size = painter.textSize(part.text);
20968 					version(Windows)
20969 						if(part.text.length && part.text[$-1] == '\n')
20970 							size.height /= 2; // windows counts the new line at the end, but we don't want that
20971 
20972 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
20973 
20974 					foreach(idx, char c; part.text) {
20975 							// FIXME: unicode
20976 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
20977 					}
20978 
20979 					pos.x += size.width;
20980 					if(pos.x >= boundingBox.right) {
20981 						pos.y += size.height;
20982 						pos.x = boundingBox.left;
20983 						lastHeight = 0;
20984 					} else {
20985 						lastHeight = size.height;
20986 					}
20987 
20988 					if(part.text.length && part.text[$-1] == '\n')
20989 						nl();
20990 				}
20991 			}
20992 
20993 			layoutInvalidated = false;
20994 		}
20995 
20996 		bool layoutInvalidated = true;
20997 		void invalidateLayout() {
20998 			layoutInvalidated = true;
20999 		}
21000 
21001 // FIXME: caret can remain sometimes when inserting
21002 // FIXME: inserting at the beginning once you already have something can eff it up.
21003 		void drawInto(ScreenPainter painter, bool focused = false) {
21004 			if(layoutInvalidated)
21005 				redoLayout(painter);
21006 			foreach(block; blocks) {
21007 				foreach(part; block.parts) {
21008 					painter.outlineColor = part.color;
21009 					painter.fillColor = part.backgroundColor;
21010 
21011 					auto pos = part.boundingBox.upperLeft;
21012 					auto size = part.boundingBox.size;
21013 
21014 					painter.drawText(pos, part.text);
21015 					if(part.styles & TextFormat.underline)
21016 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
21017 					if(part.styles & TextFormat.strikethrough)
21018 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
21019 				}
21020 			}
21021 
21022 			// on every redraw, I will force the caret to be
21023 			// redrawn too, in order to eliminate perceived lag
21024 			// when moving around with the mouse.
21025 			eraseCaret(painter);
21026 
21027 			if(focused) {
21028 				highlightSelection(painter);
21029 				drawCaret(painter);
21030 			}
21031 		}
21032 
21033 		Color selectionXorColor = Color(255, 255, 127);
21034 
21035 		void highlightSelection(ScreenPainter painter) {
21036 			if(selectionStart is selectionEnd)
21037 				return; // no selection
21038 
21039 			if(selectionStart.inlineElement is null) return;
21040 			if(selectionEnd.inlineElement is null) return;
21041 
21042 			assert(selectionStart.inlineElement !is null);
21043 			assert(selectionEnd.inlineElement !is null);
21044 
21045 			painter.rasterOp = RasterOp.xor;
21046 			painter.outlineColor = Color.transparent;
21047 			painter.fillColor = selectionXorColor;
21048 
21049 			auto at = selectionStart.inlineElement;
21050 			auto atOffset = selectionStart.offset;
21051 			bool done;
21052 			while(at) {
21053 				auto box = at.boundingBox;
21054 				if(atOffset < at.letterXs.length)
21055 					box.left = at.letterXs[atOffset];
21056 
21057 				if(at is selectionEnd.inlineElement) {
21058 					if(selectionEnd.offset < at.letterXs.length)
21059 						box.right = at.letterXs[selectionEnd.offset];
21060 					done = true;
21061 				}
21062 
21063 				painter.drawRectangle(box.upperLeft, box.width, box.height);
21064 
21065 				if(done)
21066 					break;
21067 
21068 				at = at.getNextInlineElement();
21069 				atOffset = 0;
21070 			}
21071 		}
21072 
21073 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
21074 		bool caretShowingOnScreen = false;
21075 		void drawCaret(ScreenPainter painter) {
21076 			//painter.setClipRectangle(boundingBox);
21077 			int x, y1, y2;
21078 			if(caret.inlineElement is null) {
21079 				x = boundingBox.left;
21080 				y1 = boundingBox.top + 2;
21081 				y2 = boundingBox.top + painter.fontHeight;
21082 			} else {
21083 				x = caret.inlineElement.xOfIndex(caret.offset);
21084 				y1 = caret.inlineElement.boundingBox.top + 2;
21085 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21086 			}
21087 
21088 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
21089 				eraseCaret(painter);
21090 
21091 			painter.pen = Pen(Color.white, 1);
21092 			painter.rasterOp = RasterOp.xor;
21093 			painter.drawLine(
21094 				Point(x, y1),
21095 				Point(x, y2)
21096 			);
21097 			painter.rasterOp = RasterOp.normal;
21098 			caretShowingOnScreen = !caretShowingOnScreen;
21099 
21100 			if(caretShowingOnScreen) {
21101 				caretLastDrawnX = x;
21102 				caretLastDrawnY1 = y1;
21103 				caretLastDrawnY2 = y2;
21104 			}
21105 		}
21106 
21107 		Rectangle caretBoundingBox() {
21108 			int x, y1, y2;
21109 			if(caret.inlineElement is null) {
21110 				x = boundingBox.left;
21111 				y1 = boundingBox.top + 2;
21112 				y2 = boundingBox.top + 16;
21113 			} else {
21114 				x = caret.inlineElement.xOfIndex(caret.offset);
21115 				y1 = caret.inlineElement.boundingBox.top + 2;
21116 				y2 = caret.inlineElement.boundingBox.bottom - 2;
21117 			}
21118 
21119 			return Rectangle(x, y1, x + 1, y2);
21120 		}
21121 
21122 		void eraseCaret(ScreenPainter painter) {
21123 			//painter.setClipRectangle(boundingBox);
21124 			if(!caretShowingOnScreen) return;
21125 			painter.pen = Pen(Color.white, 1);
21126 			painter.rasterOp = RasterOp.xor;
21127 			painter.drawLine(
21128 				Point(caretLastDrawnX, caretLastDrawnY1),
21129 				Point(caretLastDrawnX, caretLastDrawnY2)
21130 			);
21131 
21132 			caretShowingOnScreen = false;
21133 			painter.rasterOp = RasterOp.normal;
21134 		}
21135 
21136 		/// Caret movement api
21137 		/// These should give the user a logical result based on what they see on screen...
21138 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
21139 		void moveUp() {
21140 			if(caret.inlineElement is null) return;
21141 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21142 			auto y = caret.inlineElement.boundingBox.top + 2;
21143 
21144 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21145 			if(y < 0)
21146 				return;
21147 
21148 			auto i = identify(x, y);
21149 
21150 			if(i.element) {
21151 				caret.inlineElement = i.element;
21152 				caret.offset = i.offset;
21153 			}
21154 		}
21155 		void moveDown() {
21156 			if(caret.inlineElement is null) return;
21157 			auto x = caret.inlineElement.xOfIndex(caret.offset);
21158 			auto y = caret.inlineElement.boundingBox.bottom - 2;
21159 
21160 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
21161 
21162 			auto i = identify(x, y);
21163 			if(i.element) {
21164 				caret.inlineElement = i.element;
21165 				caret.offset = i.offset;
21166 			}
21167 		}
21168 		void moveLeft() {
21169 			if(caret.inlineElement is null) return;
21170 			if(caret.offset)
21171 				caret.offset--;
21172 			else {
21173 				auto p = caret.inlineElement.getPreviousInlineElement();
21174 				if(p) {
21175 					caret.inlineElement = p;
21176 					if(p.text.length && p.text[$-1] == '\n')
21177 						caret.offset = cast(int) p.text.length - 1;
21178 					else
21179 						caret.offset = cast(int) p.text.length;
21180 				}
21181 			}
21182 		}
21183 		void moveRight() {
21184 			if(caret.inlineElement is null) return;
21185 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
21186 				caret.offset++;
21187 			} else {
21188 				auto p = caret.inlineElement.getNextInlineElement();
21189 				if(p) {
21190 					caret.inlineElement = p;
21191 					caret.offset = 0;
21192 				}
21193 			}
21194 		}
21195 		void moveHome() {
21196 			if(caret.inlineElement is null) return;
21197 			auto x = 0;
21198 			auto y = caret.inlineElement.boundingBox.top + 2;
21199 
21200 			auto i = identify(x, y);
21201 
21202 			if(i.element) {
21203 				caret.inlineElement = i.element;
21204 				caret.offset = i.offset;
21205 			}
21206 		}
21207 		void moveEnd() {
21208 			if(caret.inlineElement is null) return;
21209 			auto x = int.max;
21210 			auto y = caret.inlineElement.boundingBox.top + 2;
21211 
21212 			auto i = identify(x, y);
21213 
21214 			if(i.element) {
21215 				caret.inlineElement = i.element;
21216 				caret.offset = i.offset;
21217 			}
21218 
21219 		}
21220 		void movePageUp(ref Caret caret) {}
21221 		void movePageDown(ref Caret caret) {}
21222 
21223 		void moveDocumentStart(ref Caret caret) {
21224 			if(blocks.length && blocks[0].parts.length)
21225 				caret = Caret(this, blocks[0].parts[0], 0);
21226 			else
21227 				caret = Caret.init;
21228 		}
21229 
21230 		void moveDocumentEnd(ref Caret caret) {
21231 			if(blocks.length) {
21232 				auto parts = blocks[$-1].parts;
21233 				if(parts.length) {
21234 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
21235 				} else {
21236 					caret = Caret.init;
21237 				}
21238 			} else
21239 				caret = Caret.init;
21240 		}
21241 
21242 		void deleteSelection() {
21243 			if(selectionStart is selectionEnd)
21244 				return;
21245 
21246 			if(selectionStart.inlineElement is null) return;
21247 			if(selectionEnd.inlineElement is null) return;
21248 
21249 			assert(selectionStart.inlineElement !is null);
21250 			assert(selectionEnd.inlineElement !is null);
21251 
21252 			auto at = selectionStart.inlineElement;
21253 
21254 			if(selectionEnd.inlineElement is at) {
21255 				// same element, need to chop out
21256 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
21257 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
21258 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
21259 			} else {
21260 				// different elements, we can do it with slicing
21261 				at.text = at.text[0 .. selectionStart.offset];
21262 				if(selectionStart.offset < at.letterXs.length)
21263 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
21264 
21265 				at = at.getNextInlineElement();
21266 
21267 				while(at) {
21268 					if(at is selectionEnd.inlineElement) {
21269 						at.text = at.text[selectionEnd.offset .. $];
21270 						if(selectionEnd.offset < at.letterXs.length)
21271 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
21272 						selectionEnd.offset = 0;
21273 						break;
21274 					} else {
21275 						auto cfd = at;
21276 						cfd.text = null; // delete the whole thing
21277 
21278 						at = at.getNextInlineElement();
21279 
21280 						if(cfd.text.length == 0) {
21281 							// and remove cfd
21282 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
21283 								if(cfd.containingBlock.parts[a] is cfd) {
21284 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
21285 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
21286 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
21287 
21288 								}
21289 							}
21290 						}
21291 					}
21292 				}
21293 			}
21294 
21295 			caret = selectionEnd;
21296 			selectNone();
21297 
21298 			invalidateLayout();
21299 
21300 		}
21301 
21302 		/// Plain text editing api. These work at the current caret inside the selected inline element.
21303 		void insert(in char[] text) {
21304 			foreach(dchar ch; text)
21305 				insert(ch);
21306 		}
21307 		/// ditto
21308 		void insert(dchar ch) {
21309 
21310 			bool selectionDeleted = false;
21311 			if(selectionStart !is selectionEnd) {
21312 				deleteSelection();
21313 				selectionDeleted = true;
21314 			}
21315 
21316 			if(ch == 127) {
21317 				delete_();
21318 				return;
21319 			}
21320 			if(ch == 8) {
21321 				if(!selectionDeleted)
21322 					backspace();
21323 				return;
21324 			}
21325 
21326 			invalidateLayout();
21327 
21328 			if(ch == 13) ch = 10;
21329 			auto e = caret.inlineElement;
21330 			if(e is null) {
21331 				addText("" ~ cast(char) ch) ; // FIXME
21332 				return;
21333 			}
21334 
21335 			if(caret.offset == e.text.length) {
21336 				e.text ~= cast(char) ch; // FIXME
21337 				caret.offset++;
21338 				if(ch == 10) {
21339 					auto c = caret.inlineElement.clone;
21340 					c.text = null;
21341 					c.letterXs = null;
21342 					insertPartAfter(c,e);
21343 					caret = Caret(this, c, 0);
21344 				}
21345 			} else {
21346 				// FIXME cast char sucks
21347 				if(ch == 10) {
21348 					auto c = caret.inlineElement.clone;
21349 					c.text = e.text[caret.offset .. $];
21350 					if(caret.offset < c.letterXs.length)
21351 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
21352 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
21353 					if(caret.offset <= e.letterXs.length) {
21354 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
21355 					}
21356 					insertPartAfter(c,e);
21357 					caret = Caret(this, c, 0);
21358 				} else {
21359 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
21360 					caret.offset++;
21361 				}
21362 			}
21363 		}
21364 
21365 		void insertPartAfter(InlineElement what, InlineElement where) {
21366 			foreach(idx, p; where.containingBlock.parts) {
21367 				if(p is where) {
21368 					if(idx + 1 == where.containingBlock.parts.length)
21369 						where.containingBlock.parts ~= what;
21370 					else
21371 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
21372 					return;
21373 				}
21374 			}
21375 		}
21376 
21377 		void cleanupStructures() {
21378 			for(size_t i = 0; i < blocks.length; i++) {
21379 				auto block = blocks[i];
21380 				for(size_t a = 0; a < block.parts.length; a++) {
21381 					auto part = block.parts[a];
21382 					if(part.text.length == 0) {
21383 						for(size_t b = a; b < block.parts.length - 1; b++)
21384 							block.parts[b] = block.parts[b+1];
21385 						block.parts = block.parts[0 .. $-1];
21386 					}
21387 				}
21388 				if(block.parts.length == 0) {
21389 					for(size_t a = i; a < blocks.length - 1; a++)
21390 						blocks[a] = blocks[a+1];
21391 					blocks = blocks[0 .. $-1];
21392 				}
21393 			}
21394 		}
21395 
21396 		void backspace() {
21397 			try_again:
21398 			auto e = caret.inlineElement;
21399 			if(e is null)
21400 				return;
21401 			if(caret.offset == 0) {
21402 				auto prev = e.getPreviousInlineElement();
21403 				if(prev is null)
21404 					return;
21405 				auto newOffset = cast(int) prev.text.length;
21406 				tryMerge(prev, e);
21407 				caret.inlineElement = prev;
21408 				caret.offset = prev is null ? 0 : newOffset;
21409 
21410 				goto try_again;
21411 			} else if(caret.offset == e.text.length) {
21412 				e.text = e.text[0 .. $-1];
21413 				caret.offset--;
21414 			} else {
21415 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
21416 				caret.offset--;
21417 			}
21418 			//cleanupStructures();
21419 
21420 			invalidateLayout();
21421 		}
21422 		void delete_() {
21423 			if(selectionStart !is selectionEnd)
21424 				deleteSelection();
21425 			else {
21426 				auto before = caret;
21427 				moveRight();
21428 				if(caret != before) {
21429 					backspace();
21430 				}
21431 			}
21432 
21433 			invalidateLayout();
21434 		}
21435 		void overstrike() {}
21436 
21437 		/// Selection API. See also: caret movement.
21438 		void selectAll() {
21439 			moveDocumentStart(selectionStart);
21440 			moveDocumentEnd(selectionEnd);
21441 		}
21442 		bool selectNone() {
21443 			if(selectionStart != selectionEnd) {
21444 				selectionStart = selectionEnd = Caret.init;
21445 				return true;
21446 			}
21447 			return false;
21448 		}
21449 
21450 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
21451 		/// They will modify the current selection if there is one and will splice one in if needed.
21452 		void changeAttributes() {}
21453 
21454 
21455 		/// Text search api. They manipulate the selection and/or caret.
21456 		void findText(string text) {}
21457 		void findIndex(size_t textIndex) {}
21458 
21459 		// sample event handlers
21460 
21461 		void handleEvent(KeyEvent event) {
21462 			//if(event.type == KeyEvent.Type.KeyPressed) {
21463 
21464 			//}
21465 		}
21466 
21467 		void handleEvent(dchar ch) {
21468 
21469 		}
21470 
21471 		void handleEvent(MouseEvent event) {
21472 
21473 		}
21474 
21475 		bool contentEditable; // can it be edited?
21476 		bool contentCaretable; // is there a caret/cursor that moves around in there?
21477 		bool contentSelectable; // selectable?
21478 
21479 		Caret caret;
21480 		Caret selectionStart;
21481 		Caret selectionEnd;
21482 
21483 		bool insertMode;
21484 	}
21485 
21486 	struct Caret {
21487 		TextLayout layout;
21488 		InlineElement inlineElement;
21489 		int offset;
21490 	}
21491 
21492 	enum TextFormat : ushort {
21493 		// decorations
21494 		underline = 1,
21495 		strikethrough = 2,
21496 
21497 		// font selectors
21498 
21499 		bold = 0x4000 | 1, // weight 700
21500 		light = 0x4000 | 2, // weight 300
21501 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
21502 		// bold | light is really invalid but should give weight 500
21503 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
21504 
21505 		italic = 0x4000 | 8,
21506 		smallcaps = 0x4000 | 16,
21507 	}
21508 
21509 	void* findFont(string family, int weight, TextFormat formats) {
21510 		return null;
21511 	}
21512 
21513 }
21514 
21515 /++
21516 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21517 
21518 	History:
21519 		Added February 19, 2021
21520 +/
21521 /// Group: drag_and_drop
21522 interface DropHandler {
21523 	/++
21524 		Called when the drag enters the handler's area.
21525 	+/
21526 	DragAndDropAction dragEnter(DropPackage*);
21527 	/++
21528 		Called when the drag leaves the handler's area or is
21529 		cancelled. You should free your resources when this is called.
21530 	+/
21531 	void dragLeave();
21532 	/++
21533 		Called continually as the drag moves over the handler's area.
21534 
21535 		Returns: feedback to the dragger
21536 	+/
21537 	DropParameters dragOver(Point pt);
21538 	/++
21539 		The user dropped the data and you should process it now. You can
21540 		access the data through the given [DropPackage].
21541 	+/
21542 	void drop(scope DropPackage*);
21543 	/++
21544 		Called when the drop is complete. You should free whatever temporary
21545 		resources you were using. It is often reasonable to simply forward
21546 		this call to [dragLeave].
21547 	+/
21548 	void finish();
21549 
21550 	/++
21551 		Parameters returned by [DropHandler.drop].
21552 	+/
21553 	static struct DropParameters {
21554 		/++
21555 			Acceptable action over this area.
21556 		+/
21557 		DragAndDropAction action;
21558 		/++
21559 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
21560 
21561 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
21562 		+/
21563 		Rectangle consistentWithin;
21564 	}
21565 }
21566 
21567 /++
21568 	History:
21569 		Added February 19, 2021
21570 +/
21571 /// Group: drag_and_drop
21572 enum DragAndDropAction {
21573 	none = 0,
21574 	copy,
21575 	move,
21576 	link,
21577 	ask,
21578 	custom
21579 }
21580 
21581 /++
21582 	An opaque structure representing dropped data. It contains
21583 	private, platform-specific data that your `drop` function
21584 	should simply forward.
21585 
21586 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21587 
21588 	History:
21589 		Added February 19, 2021
21590 +/
21591 /// Group: drag_and_drop
21592 struct DropPackage {
21593 	/++
21594 		Lists the available formats as magic numbers. You should compare these
21595 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
21596 		understand the passed data.
21597 	+/
21598 	DraggableData.FormatId[] availableFormats() {
21599 		version(X11) {
21600 			return xFormats;
21601 		} else version(Windows) {
21602 			if(pDataObj is null)
21603 				return null;
21604 
21605 			typeof(return) ret;
21606 
21607 			IEnumFORMATETC ef;
21608 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
21609 				FORMATETC fmt;
21610 				ULONG fetched;
21611 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
21612 					if(fetched == 0)
21613 						break;
21614 
21615 					if(fmt.lindex != -1)
21616 						continue;
21617 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
21618 						continue;
21619 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
21620 						continue;
21621 
21622 					ret ~= fmt.cfFormat;
21623 				}
21624 			}
21625 
21626 			return ret;
21627 		} else throw new NotYetImplementedException();
21628 	}
21629 
21630 	/++
21631 		Gets data from the drop and optionally accepts it.
21632 
21633 		Returns:
21634 			void because the data is fed asynchronously through the `dg` parameter.
21635 
21636 		Params:
21637 			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.
21638 
21639 			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.
21640 
21641 			Calling `getData` again after accepting a drop is not permitted.
21642 
21643 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
21644 
21645 			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.
21646 
21647 		Throws:
21648 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
21649 
21650 		History:
21651 			Included in first release of [DropPackage].
21652 	+/
21653 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
21654 		version(X11) {
21655 
21656 			auto display = XDisplayConnection.get();
21657 			auto selectionAtom = GetAtom!"XdndSelection"(display);
21658 			auto best = format;
21659 
21660 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
21661 
21662 				XDisplay* display;
21663 				Atom selectionAtom;
21664 				DraggableData.FormatId best;
21665 				DraggableData.FormatId format;
21666 				void delegate(scope ubyte[] data) dg;
21667 				DragAndDropAction acceptedAction;
21668 				Window sourceWindow;
21669 				SimpleWindow win;
21670 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
21671 					this.display = display;
21672 					this.win = win;
21673 					this.sourceWindow = sourceWindow;
21674 					this.format = format;
21675 					this.selectionAtom = selectionAtom;
21676 					this.best = best;
21677 					this.dg = dg;
21678 					this.acceptedAction = acceptedAction;
21679 				}
21680 
21681 
21682 				mixin X11GetSelectionHandler_Basics;
21683 
21684 				void handleData(Atom target, in ubyte[] data) {
21685 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
21686 
21687 					dg(cast(ubyte[]) data);
21688 
21689 					if(acceptedAction != DragAndDropAction.none) {
21690 						auto display = XDisplayConnection.get;
21691 
21692 						XClientMessageEvent xclient;
21693 
21694 						xclient.type = EventType.ClientMessage;
21695 						xclient.window = sourceWindow;
21696 						xclient.message_type = GetAtom!"XdndFinished"(display);
21697 						xclient.format = 32;
21698 						xclient.data.l[0] = win.impl.window;
21699 						xclient.data.l[1] = 1; // drop successful
21700 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
21701 
21702 						XSendEvent(
21703 							display,
21704 							sourceWindow,
21705 							false,
21706 							EventMask.NoEventMask,
21707 							cast(XEvent*) &xclient
21708 						);
21709 
21710 						XFlush(display);
21711 					}
21712 				}
21713 
21714 				Atom findBestFormat(Atom[] answer) {
21715 					Atom best = None;
21716 					foreach(option; answer) {
21717 						if(option == format) {
21718 							best = option;
21719 							break;
21720 						}
21721 						/*
21722 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
21723 							best = option;
21724 							break;
21725 						} else if(option == XA_STRING) {
21726 							best = option;
21727 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
21728 							best = option;
21729 						}
21730 						*/
21731 					}
21732 					return best;
21733 				}
21734 			}
21735 
21736 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
21737 
21738 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
21739 
21740 		} else version(Windows) {
21741 
21742 			// clean up like DragLeave
21743 			// pass effect back up
21744 
21745 			FORMATETC t;
21746 			assert(format >= 0 && format <= ushort.max);
21747 			t.cfFormat = cast(ushort) format;
21748 			t.lindex = -1;
21749 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21750 			t.tymed = TYMED.TYMED_HGLOBAL;
21751 
21752 			STGMEDIUM m;
21753 
21754 			if(pDataObj.GetData(&t, &m) != S_OK) {
21755 				// fail
21756 			} else {
21757 				// succeed, take the data and clean up
21758 
21759 				// FIXME: ensure it is legit HGLOBAL
21760 				auto handle = m.hGlobal;
21761 
21762 				if(handle) {
21763 					auto sz = GlobalSize(handle);
21764 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
21765 						scope(exit) GlobalUnlock(handle);
21766 						scope(exit) GlobalFree(handle);
21767 
21768 						auto data = ptr[0 .. sz];
21769 
21770 						dg(data);
21771 					}
21772 				}
21773 			}
21774 		}
21775 	}
21776 
21777 	private:
21778 
21779 	version(X11) {
21780 		SimpleWindow win;
21781 		Window sourceWindow;
21782 		Time dataTimestamp;
21783 
21784 		Atom[] xFormats;
21785 	}
21786 	version(Windows) {
21787 		IDataObject pDataObj;
21788 	}
21789 }
21790 
21791 /++
21792 	A generic helper base class for making a drop handler with a preference list of custom types.
21793 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
21794 	droppers too.
21795 
21796 	It assumes the whole window it used, but you can subclass to change that.
21797 
21798 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21799 
21800 	History:
21801 		Added February 19, 2021
21802 +/
21803 /// Group: drag_and_drop
21804 class GenericDropHandlerBase : DropHandler {
21805 	// no fancy state here so no need to do anything here
21806 	void finish() { }
21807 	void dragLeave() { }
21808 
21809 	private DragAndDropAction acceptedAction;
21810 	private DraggableData.FormatId acceptedFormat;
21811 	private void delegate(scope ubyte[]) acceptedHandler;
21812 
21813 	struct FormatHandler {
21814 		DraggableData.FormatId format;
21815 		void delegate(scope ubyte[]) handler;
21816 	}
21817 
21818 	protected abstract FormatHandler[] formatHandlers();
21819 
21820 	DragAndDropAction dragEnter(DropPackage* pkg) {
21821 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
21822 		foreach(fmt; formatHandlers())
21823 		foreach(f; pkg.availableFormats())
21824 			if(f == fmt.format) {
21825 				acceptedFormat = f;
21826 				acceptedHandler = fmt.handler;
21827 				return acceptedAction = DragAndDropAction.copy;
21828 			}
21829 		return acceptedAction = DragAndDropAction.none;
21830 	}
21831 	DropParameters dragOver(Point pt) {
21832 		return DropParameters(acceptedAction);
21833 	}
21834 
21835 	void drop(scope DropPackage* dropPackage) {
21836 		if(!acceptedFormat || acceptedHandler is null) {
21837 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
21838 			return; // prolly shouldn't happen anyway...
21839 		}
21840 
21841 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
21842 	}
21843 }
21844 
21845 /++
21846 	A simple handler for making your window accept drops of plain text.
21847 
21848 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21849 
21850 	History:
21851 		Added February 22, 2021
21852 +/
21853 /// Group: drag_and_drop
21854 class TextDropHandler : GenericDropHandlerBase {
21855 	private void delegate(in char[] text) dg;
21856 
21857 	/++
21858 
21859 	+/
21860 	this(void delegate(in char[] text) dg) {
21861 		this.dg = dg;
21862 	}
21863 
21864 	protected override FormatHandler[] formatHandlers() {
21865 		version(X11)
21866 			return [
21867 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21868 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21869 			];
21870 		else version(Windows)
21871 			return [
21872 				FormatHandler(CF_UNICODETEXT, &translator),
21873 			];
21874 		else throw new NotYetImplementedException();
21875 	}
21876 
21877 	private void translator(scope ubyte[] data) {
21878 		version(X11)
21879 			dg(cast(char[]) data);
21880 		else version(Windows)
21881 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21882 	}
21883 }
21884 
21885 /++
21886 	A simple handler for making your window accept drops of files, issued to you as file names.
21887 
21888 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21889 
21890 	History:
21891 		Added February 22, 2021
21892 +/
21893 /// Group: drag_and_drop
21894 
21895 class FilesDropHandler : GenericDropHandlerBase {
21896 	private void delegate(in char[][]) dg;
21897 
21898 	/++
21899 
21900 	+/
21901 	this(void delegate(in char[][] fileNames) dg) {
21902 		this.dg = dg;
21903 	}
21904 
21905 	protected override FormatHandler[] formatHandlers() {
21906 		version(X11)
21907 			return [
21908 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
21909 			];
21910 		else version(Windows)
21911 			return [
21912 				FormatHandler(CF_HDROP, &translator),
21913 			];
21914 		else throw new NotYetImplementedException();
21915 	}
21916 
21917 	private void translator(scope ubyte[] data) @system {
21918 		version(X11) {
21919 			char[] listString = cast(char[]) data;
21920 			char[][16] buffer;
21921 			int count;
21922 			char[][] result = buffer[];
21923 
21924 			void commit(char[] s) {
21925 				if(count == result.length)
21926 					result.length += 16;
21927 				if(s.length > 7 && s[0 ..7] == "file://")
21928 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
21929 				result[count++] = s;
21930 			}
21931 
21932 			size_t last;
21933 			foreach(idx, char c; listString) {
21934 				if(c == '\n') {
21935 					commit(listString[last .. idx - 1]); // a \r
21936 					last = idx + 1; // a \n
21937 				}
21938 			}
21939 
21940 			if(last < listString.length) {
21941 				commit(listString[last .. $]);
21942 			}
21943 
21944 			// FIXME: they are uris now, should I translate it to local file names?
21945 			// of course the host name is supposed to be there cuz of X rokking...
21946 
21947 			dg(result[0 .. count]);
21948 		} else version(Windows) {
21949 
21950 			static struct DROPFILES {
21951 				DWORD pFiles;
21952 				POINT pt;
21953 				BOOL  fNC;
21954 				BOOL  fWide;
21955 			}
21956 
21957 
21958 			const(char)[][16] buffer;
21959 			int count;
21960 			const(char)[][] result = buffer[];
21961 			size_t last;
21962 
21963 			void commitA(in char[] stuff) {
21964 				if(count == result.length)
21965 					result.length += 16;
21966 				result[count++] = stuff;
21967 			}
21968 
21969 			void commitW(in wchar[] stuff) {
21970 				commitA(makeUtf8StringFromWindowsString(stuff));
21971 			}
21972 
21973 			void magic(T)(T chars) {
21974 				size_t idx;
21975 				while(chars[idx]) {
21976 					last = idx;
21977 					while(chars[idx]) {
21978 						idx++;
21979 					}
21980 					static if(is(T == char*))
21981 						commitA(chars[last .. idx]);
21982 					else
21983 						commitW(chars[last .. idx]);
21984 					idx++;
21985 				}
21986 			}
21987 
21988 			auto df = cast(DROPFILES*) data.ptr;
21989 			if(df.fWide) {
21990 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
21991 				magic(chars);
21992 			} else {
21993 				char* chars = cast(char*) (data.ptr + df.pFiles);
21994 				magic(chars);
21995 			}
21996 			dg(result[0 .. count]);
21997 		}
21998 		else throw new NotYetImplementedException();
21999 	}
22000 }
22001 
22002 /++
22003 	Interface to describe data being dragged. See also [draggable] helper function.
22004 
22005 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22006 
22007 	History:
22008 		Added February 19, 2021
22009 +/
22010 interface DraggableData {
22011 	version(X11)
22012 		alias FormatId = Atom;
22013 	else
22014 		alias FormatId = uint;
22015 	/++
22016 		Gets the platform-specific FormatId associated with the given named format.
22017 
22018 		This may be a MIME type, but may also be other various strings defined by the
22019 		programs you want to interoperate with.
22020 
22021 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
22022 		and convert it to some particular type for you.
22023 	+/
22024 	static FormatId getFormatId(string name)() {
22025 		version(X11)
22026 			return GetAtom!name(XDisplayConnection.get);
22027 		else version(Windows) {
22028 			static UINT cache;
22029 			if(!cache)
22030 				cache = RegisterClipboardFormatA(name);
22031 			return cache;
22032 		} else
22033 			throw new NotYetImplementedException();
22034 	}
22035 
22036 	/++
22037 		Looks up a string to represent the name for the given format, if there is one.
22038 
22039 		You should avoid using this function because it is slow. It is provided more for
22040 		debugging than for primary use.
22041 	+/
22042 	static string getFormatName(FormatId format) {
22043 		version(X11) {
22044 			if(format == 0)
22045 				return "None";
22046 			else
22047 				return getAtomName(format, XDisplayConnection.get);
22048 		} else version(Windows) {
22049 			switch(format) {
22050 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
22051 				case CF_DIBV5: return "CF_DIBV5";
22052 				case CF_RIFF: return "CF_RIFF";
22053 				case CF_WAVE: return "CF_WAVE";
22054 				case CF_HDROP: return "CF_HDROP";
22055 				default:
22056 					char[1024] name;
22057 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
22058 					return name[0 .. count].idup;
22059 			}
22060 		} else throw new NotYetImplementedException();
22061 	}
22062 
22063 	FormatId[] availableFormats();
22064 	// Return the slice of data you filled, empty slice if done.
22065 	// this is to support the incremental thing
22066 	ubyte[] getData(FormatId format, return scope ubyte[] data);
22067 
22068 	size_t dataLength(FormatId format);
22069 }
22070 
22071 /++
22072 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22073 
22074 	History:
22075 		Added February 19, 2021
22076 +/
22077 DraggableData draggable(string s) {
22078 	version(X11)
22079 	return new class X11SetSelectionHandler_Text, DraggableData {
22080 		this() {
22081 			super(s);
22082 		}
22083 
22084 		override FormatId[] availableFormats() {
22085 			return X11SetSelectionHandler_Text.availableFormats();
22086 		}
22087 
22088 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
22089 			return X11SetSelectionHandler_Text.getData(format, data);
22090 		}
22091 
22092 		size_t dataLength(FormatId format) {
22093 			return s.length;
22094 		}
22095 	};
22096 	else version(Windows)
22097 	return new class DraggableData {
22098 		FormatId[] availableFormats() {
22099 			return [CF_UNICODETEXT];
22100 		}
22101 
22102 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
22103 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
22104 		}
22105 
22106 		size_t dataLength(FormatId format) {
22107 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
22108 		}
22109 	};
22110 	else
22111 	throw new NotYetImplementedException();
22112 }
22113 
22114 /++
22115 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22116 
22117 	History:
22118 		Added February 19, 2021
22119 +/
22120 /// Group: drag_and_drop
22121 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
22122 in {
22123 	assert(window !is null);
22124 	assert(handler !is null);
22125 }
22126 do
22127 {
22128 	version(X11) {
22129 		auto sh = cast(X11SetSelectionHandler) handler;
22130 		if(sh is null) {
22131 			// gotta make my own adapter.
22132 			sh = new class X11SetSelectionHandler {
22133 				mixin X11SetSelectionHandler_Basics;
22134 
22135 				Atom[] availableFormats() { return handler.availableFormats(); }
22136 				ubyte[] getData(Atom format, return scope ubyte[] data) {
22137 					return handler.getData(format, data);
22138 				}
22139 
22140 				// since the drop selection is only ever used once it isn't important
22141 				// to reset it.
22142 				void done() {}
22143 			};
22144 		}
22145 		return doDragDropX11(window, sh, action);
22146 	} else version(Windows) {
22147 		return doDragDropWindows(window, handler, action);
22148 	} else throw new NotYetImplementedException();
22149 }
22150 
22151 version(Windows)
22152 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
22153 	IDataObject obj = new class IDataObject {
22154 		ULONG refCount;
22155 		ULONG AddRef() {
22156 			return ++refCount;
22157 		}
22158 		ULONG Release() {
22159 			return --refCount;
22160 		}
22161 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22162 			if (IID_IUnknown == *riid) {
22163 				*ppv = cast(void*) cast(IUnknown) this;
22164 			}
22165 			else if (IID_IDataObject == *riid) {
22166 				*ppv = cast(void*) cast(IDataObject) this;
22167 			}
22168 			else {
22169 				*ppv = null;
22170 				return E_NOINTERFACE;
22171 			}
22172 
22173 			AddRef();
22174 			return NOERROR;
22175 		}
22176 
22177 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
22178 			//  writeln("Advise");
22179 			return E_NOTIMPL;
22180 		}
22181 		HRESULT DUnadvise(DWORD dwConnection) {
22182 			return E_NOTIMPL;
22183 		}
22184 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
22185 			//  writeln("EnumDAdvise");
22186 			return OLE_E_ADVISENOTSUPPORTED;
22187 		}
22188 		// tell what formats it supports
22189 
22190 		FORMATETC[] types;
22191 		this() {
22192 			FORMATETC t;
22193 			foreach(ty; handler.availableFormats()) {
22194 				assert(ty <= ushort.max && ty >= 0);
22195 				t.cfFormat = cast(ushort) ty;
22196 				t.lindex = -1;
22197 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
22198 				t.tymed = TYMED.TYMED_HGLOBAL;
22199 			}
22200 			types ~= t;
22201 		}
22202 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
22203 			if(dwDirection == DATADIR.DATADIR_GET) {
22204 				*ppenumFormatEtc = new class IEnumFORMATETC {
22205 					ULONG refCount;
22206 					ULONG AddRef() {
22207 						return ++refCount;
22208 					}
22209 					ULONG Release() {
22210 						return --refCount;
22211 					}
22212 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22213 						if (IID_IUnknown == *riid) {
22214 							*ppv = cast(void*) cast(IUnknown) this;
22215 						}
22216 						else if (IID_IEnumFORMATETC == *riid) {
22217 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
22218 						}
22219 						else {
22220 							*ppv = null;
22221 							return E_NOINTERFACE;
22222 						}
22223 
22224 						AddRef();
22225 						return NOERROR;
22226 					}
22227 
22228 
22229 					int pos;
22230 					this() {
22231 						pos = 0;
22232 					}
22233 
22234 					HRESULT Clone(IEnumFORMATETC* ppenum) {
22235 						// writeln("clone");
22236 						return E_NOTIMPL; // FIXME
22237 					}
22238 
22239 					// Caller is responsible for freeing memory
22240 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
22241 						// fetched may be null if celt is one
22242 						if(celt != 1)
22243 							return E_NOTIMPL; // FIXME
22244 
22245 						if(celt + pos > types.length)
22246 							return S_FALSE;
22247 
22248 						*rgelt = types[pos++];
22249 
22250 						if(pceltFetched !is null)
22251 							*pceltFetched = 1;
22252 
22253 						// writeln("ok celt ", celt);
22254 						return S_OK;
22255 					}
22256 
22257 					HRESULT Reset() {
22258 						pos = 0;
22259 						return S_OK;
22260 					}
22261 
22262 					HRESULT Skip(ULONG celt) {
22263 						if(celt + pos <= types.length) {
22264 							pos += celt;
22265 							return S_OK;
22266 						}
22267 						return S_FALSE;
22268 					}
22269 				};
22270 
22271 				return S_OK;
22272 			} else
22273 				return E_NOTIMPL;
22274 		}
22275 		// given a format, return the format you'd prefer to use cuz it is identical
22276 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
22277 			// FIXME: prolly could be better but meh
22278 			// writeln("gcf: ", *pformatectIn);
22279 			*pformatetcOut = *pformatectIn;
22280 			return S_OK;
22281 		}
22282 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22283 			foreach(ty; types) {
22284 				if(ty == *pformatetcIn) {
22285 					auto format = ty.cfFormat;
22286 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
22287 					STGMEDIUM medium;
22288 					medium.tymed = TYMED.TYMED_HGLOBAL;
22289 
22290 					auto sz = handler.dataLength(format);
22291 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
22292 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
22293 					if(auto data = cast(wchar*) GlobalLock(handle)) {
22294 						auto slice = data[0 .. sz];
22295 						scope(exit)
22296 							GlobalUnlock(handle);
22297 
22298 						handler.getData(format, cast(ubyte[]) slice[]);
22299 					}
22300 
22301 
22302 					medium.hGlobal = handle; // FIXME
22303 					*pmedium = medium;
22304 					return S_OK;
22305 				}
22306 			}
22307 			return DV_E_FORMATETC;
22308 		}
22309 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22310 			// writeln("GDH: ", *pformatetcIn);
22311 			return E_NOTIMPL; // FIXME
22312 		}
22313 		HRESULT QueryGetData(FORMATETC* pformatetc) {
22314 			auto search = *pformatetc;
22315 			search.tymed &= TYMED.TYMED_HGLOBAL;
22316 			foreach(ty; types)
22317 				if(ty == search) {
22318 					// writeln("QueryGetData ", search, " ", types[0]);
22319 					return S_OK;
22320 				}
22321 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
22322 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
22323 			}
22324 			return S_FALSE;
22325 		}
22326 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
22327 			//  writeln("SetData: ");
22328 			return E_NOTIMPL;
22329 		}
22330 	};
22331 
22332 
22333 	IDropSource src = new class IDropSource {
22334 		ULONG refCount;
22335 		ULONG AddRef() {
22336 			return ++refCount;
22337 		}
22338 		ULONG Release() {
22339 			return --refCount;
22340 		}
22341 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22342 			if (IID_IUnknown == *riid) {
22343 				*ppv = cast(void*) cast(IUnknown) this;
22344 			}
22345 			else if (IID_IDropSource == *riid) {
22346 				*ppv = cast(void*) cast(IDropSource) this;
22347 			}
22348 			else {
22349 				*ppv = null;
22350 				return E_NOINTERFACE;
22351 			}
22352 
22353 			AddRef();
22354 			return NOERROR;
22355 		}
22356 
22357 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
22358 			if(fEscapePressed)
22359 				return DRAGDROP_S_CANCEL;
22360 			if(!(grfKeyState & MK_LBUTTON))
22361 				return DRAGDROP_S_DROP;
22362 			return S_OK;
22363 		}
22364 
22365 		int GiveFeedback(uint dwEffect) {
22366 			return DRAGDROP_S_USEDEFAULTCURSORS;
22367 		}
22368 	};
22369 
22370 	DWORD effect;
22371 
22372 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
22373 
22374 	DROPEFFECT de = win32DragAndDropAction(action);
22375 
22376 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
22377 	// but still prolly a FIXME
22378 
22379 	auto ret = DoDragDrop(obj, src, de, &effect);
22380 	/+
22381 	if(ret == DRAGDROP_S_DROP)
22382 		writeln("drop ", effect);
22383 	else if(ret == DRAGDROP_S_CANCEL)
22384 		writeln("cancel");
22385 	else if(ret == S_OK)
22386 		writeln("ok");
22387 	else writeln(ret);
22388 	+/
22389 
22390 	return ret;
22391 }
22392 
22393 version(Windows)
22394 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
22395 	DROPEFFECT de;
22396 
22397 	with(DragAndDropAction)
22398 	with(DROPEFFECT)
22399 	final switch(action) {
22400 		case none: de = DROPEFFECT_NONE; break;
22401 		case copy: de = DROPEFFECT_COPY; break;
22402 		case move: de = DROPEFFECT_MOVE; break;
22403 		case link: de = DROPEFFECT_LINK; break;
22404 		case ask: throw new Exception("ask not implemented yet");
22405 		case custom: throw new Exception("custom not implemented yet");
22406 	}
22407 
22408 	return de;
22409 }
22410 
22411 
22412 /++
22413 	History:
22414 		Added February 19, 2021
22415 +/
22416 /// Group: drag_and_drop
22417 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
22418 	version(X11) {
22419 		auto display = XDisplayConnection.get;
22420 
22421 		Atom atom = 5; // right???
22422 
22423 		XChangeProperty(
22424 			display,
22425 			window.impl.window,
22426 			GetAtom!"XdndAware"(display),
22427 			XA_ATOM,
22428 			32 /* bits */,
22429 			PropModeReplace,
22430 			&atom,
22431 			1);
22432 
22433 		window.dropHandler = handler;
22434 	} else version(Windows) {
22435 
22436 		initDnd();
22437 
22438 		auto dropTarget = new class (handler) IDropTarget {
22439 			DropHandler handler;
22440 			this(DropHandler handler) {
22441 				this.handler = handler;
22442 			}
22443 			ULONG refCount;
22444 			ULONG AddRef() {
22445 				return ++refCount;
22446 			}
22447 			ULONG Release() {
22448 				return --refCount;
22449 			}
22450 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22451 				if (IID_IUnknown == *riid) {
22452 					*ppv = cast(void*) cast(IUnknown) this;
22453 				}
22454 				else if (IID_IDropTarget == *riid) {
22455 					*ppv = cast(void*) cast(IDropTarget) this;
22456 				}
22457 				else {
22458 					*ppv = null;
22459 					return E_NOINTERFACE;
22460 				}
22461 
22462 				AddRef();
22463 				return NOERROR;
22464 			}
22465 
22466 
22467 			// ///////////////////
22468 
22469 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22470 				DropPackage dropPackage = DropPackage(pDataObj);
22471 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
22472 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
22473 			}
22474 
22475 			HRESULT DragLeave() {
22476 				handler.dragLeave();
22477 				// release the IDataObject if needed
22478 				return S_OK;
22479 			}
22480 
22481 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22482 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
22483 
22484 				*pdwEffect = win32DragAndDropAction(res.action);
22485 				// same as DragEnter basically
22486 				return S_OK;
22487 			}
22488 
22489 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22490 				DropPackage pkg = DropPackage(pDataObj);
22491 				handler.drop(&pkg);
22492 
22493 				return S_OK;
22494 			}
22495 		};
22496 		// Windows can hold on to the handler and try to call it
22497 		// during which time the GC can't see it. so important to
22498 		// manually manage this. At some point i'll FIXME and make
22499 		// all my com instances manually managed since they supposed
22500 		// to respect the refcount.
22501 		import core.memory;
22502 		GC.addRoot(cast(void*) dropTarget);
22503 
22504 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
22505 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
22506 
22507 		window.dropHandler = handler;
22508 	} else throw new NotYetImplementedException();
22509 }
22510 
22511 
22512 
22513 static if(UsingSimpledisplayX11) {
22514 
22515 enum _NET_WM_STATE_ADD = 1;
22516 enum _NET_WM_STATE_REMOVE = 0;
22517 enum _NET_WM_STATE_TOGGLE = 2;
22518 
22519 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
22520 void demandAttention(SimpleWindow window, bool needs = true) {
22521 	demandAttention(window.impl.window, needs);
22522 }
22523 
22524 /// ditto
22525 void demandAttention(Window window, bool needs = true) {
22526 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
22527 }
22528 
22529 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
22530 	auto display = XDisplayConnection.get();
22531 	if(atom == None)
22532 		return; // non-failure error
22533 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
22534 
22535 	XClientMessageEvent xclient;
22536 
22537 	xclient.type = EventType.ClientMessage;
22538 	xclient.window = window;
22539 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
22540 	xclient.format = 32;
22541 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
22542 	xclient.data.l[1] = atom;
22543 	xclient.data.l[2] = atom2;
22544 	xclient.data.l[3] = 1;
22545 	// [3] == source. 0 == unknown, 1 == app, 2 == else
22546 
22547 	XSendEvent(
22548 		display,
22549 		RootWindow(display, DefaultScreen(display)),
22550 		false,
22551 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
22552 		cast(XEvent*) &xclient
22553 	);
22554 
22555 	/+
22556 	XChangeProperty(
22557 		display,
22558 		window.impl.window,
22559 		GetAtom!"_NET_WM_STATE"(display),
22560 		XA_ATOM,
22561 		32 /* bits */,
22562 		PropModeAppend,
22563 		&atom,
22564 		1);
22565 	+/
22566 }
22567 
22568 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
22569 	Atom actionAtom;
22570 	with(DragAndDropAction)
22571 	final switch(action) {
22572 		case none: actionAtom = None; break;
22573 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
22574 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
22575 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
22576 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
22577 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
22578 	}
22579 
22580 	return actionAtom;
22581 }
22582 
22583 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
22584 	// FIXME: I need to show user feedback somehow.
22585 	auto display = XDisplayConnection.get;
22586 
22587 	auto actionAtom = dndActionAtom(display, action);
22588 	assert(actionAtom, "Don't use action none to accept a drop");
22589 
22590 	setX11Selection!"XdndSelection"(window, handler, null);
22591 
22592 	auto oldKeyHandler = window.handleKeyEvent;
22593 	scope(exit) window.handleKeyEvent = oldKeyHandler;
22594 
22595 	auto oldCharHandler = window.handleCharEvent;
22596 	scope(exit) window.handleCharEvent = oldCharHandler;
22597 
22598 	auto oldMouseHandler = window.handleMouseEvent;
22599 	scope(exit) window.handleMouseEvent = oldMouseHandler;
22600 
22601 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
22602 
22603 	import core.sys.posix.sys.time;
22604 	timeval tv;
22605 	gettimeofday(&tv, null);
22606 
22607 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
22608 
22609 	Time lastMouseTimestamp;
22610 
22611 	bool dnding = true;
22612 	Window lastIn = None;
22613 
22614 	void leave() {
22615 		if(lastIn == None)
22616 			return;
22617 
22618 		XEvent ev;
22619 		ev.xclient.type = EventType.ClientMessage;
22620 		ev.xclient.window = lastIn;
22621 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
22622 		ev.xclient.format = 32;
22623 		ev.xclient.data.l[0] = window.impl.window;
22624 
22625 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22626 		XFlush(display);
22627 
22628 		lastIn = None;
22629 	}
22630 
22631 	void enter(Window w) {
22632 		assert(lastIn == None);
22633 
22634 		lastIn = w;
22635 
22636 		XEvent ev;
22637 		ev.xclient.type = EventType.ClientMessage;
22638 		ev.xclient.window = lastIn;
22639 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
22640 		ev.xclient.format = 32;
22641 		ev.xclient.data.l[0] = window.impl.window;
22642 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
22643 
22644 		auto types = handler.availableFormats();
22645 		assert(types.length > 0);
22646 
22647 		ev.xclient.data.l[2] = types[0];
22648 		if(types.length > 1)
22649 			ev.xclient.data.l[3] = types[1];
22650 		if(types.length > 2)
22651 			ev.xclient.data.l[4] = types[2];
22652 
22653 		// FIXME: other types?!?!? and make sure we skip TARGETS
22654 
22655 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22656 		XFlush(display);
22657 	}
22658 
22659 	void position(int rootX, int rootY) {
22660 		assert(lastIn != None);
22661 
22662 		XEvent ev;
22663 		ev.xclient.type = EventType.ClientMessage;
22664 		ev.xclient.window = lastIn;
22665 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
22666 		ev.xclient.format = 32;
22667 		ev.xclient.data.l[0] = window.impl.window;
22668 		ev.xclient.data.l[1] = 0; // reserved
22669 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
22670 		ev.xclient.data.l[3] = dataTimestamp;
22671 		ev.xclient.data.l[4] = actionAtom;
22672 
22673 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22674 		XFlush(display);
22675 
22676 	}
22677 
22678 	void drop() {
22679 		XEvent ev;
22680 		ev.xclient.type = EventType.ClientMessage;
22681 		ev.xclient.window = lastIn;
22682 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
22683 		ev.xclient.format = 32;
22684 		ev.xclient.data.l[0] = window.impl.window;
22685 		ev.xclient.data.l[1] = 0; // reserved
22686 		ev.xclient.data.l[2] = dataTimestamp;
22687 
22688 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22689 		XFlush(display);
22690 
22691 		lastIn = None;
22692 		dnding = false;
22693 	}
22694 
22695 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
22696 	// but idk if i should...
22697 
22698 	window.setEventHandlers(
22699 		delegate(KeyEvent ev) {
22700 			if(ev.pressed == true && ev.key == Key.Escape) {
22701 				// cancel
22702 				dnding = false;
22703 			}
22704 		},
22705 		delegate(MouseEvent ev) {
22706 			if(ev.timestamp < lastMouseTimestamp)
22707 				return;
22708 
22709 			lastMouseTimestamp = ev.timestamp;
22710 
22711 			if(ev.type == MouseEventType.motion) {
22712 				auto display = XDisplayConnection.get;
22713 				auto root = RootWindow(display, DefaultScreen(display));
22714 
22715 				Window topWindow;
22716 				int rootX, rootY;
22717 
22718 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
22719 
22720 				if(topWindow == None)
22721 					return;
22722 
22723 				top:
22724 				if(auto result = topWindow in eligibility) {
22725 					auto dropWindow = *result;
22726 					if(dropWindow == None) {
22727 						leave();
22728 						return;
22729 					}
22730 
22731 					if(dropWindow != lastIn) {
22732 						leave();
22733 						enter(dropWindow);
22734 						position(rootX, rootY);
22735 					} else {
22736 						position(rootX, rootY);
22737 					}
22738 				} else {
22739 					// determine eligibility
22740 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
22741 					if(data.length == 1) {
22742 						// in case there is no WM or it isn't reparenting
22743 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
22744 					} else {
22745 
22746 						Window tryScanChildren(Window search, int maxRecurse) {
22747 							// could be reparenting window manager, so gotta check the next few children too
22748 							Window child;
22749 							int x;
22750 							int y;
22751 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
22752 
22753 							if(child == None)
22754 								return None;
22755 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
22756 							if(data.length == 1) {
22757 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
22758 							} else {
22759 								if(maxRecurse)
22760 									return tryScanChildren(child, maxRecurse - 1);
22761 								else
22762 									return None;
22763 							}
22764 
22765 						}
22766 
22767 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
22768 						auto topResult = tryScanChildren(topWindow, 3);
22769 						// it is easy to have a false negative due to the mouse going over a WM
22770 						// child window like the close button if separate from the frame... so I
22771 						// can't really cache negatives, :(
22772 						if(topResult != None) {
22773 							eligibility[topWindow] = topResult;
22774 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
22775 						}
22776 					}
22777 
22778 				}
22779 
22780 			} else if(ev.type == MouseEventType.buttonReleased) {
22781 				drop();
22782 				dnding = false;
22783 			}
22784 		}
22785 	);
22786 
22787 	window.grabInput();
22788 	scope(exit)
22789 		window.releaseInputGrab();
22790 
22791 
22792 	EventLoop.get.run(() => dnding);
22793 
22794 	return 0;
22795 }
22796 
22797 /// X-specific
22798 TrueColorImage getWindowNetWmIcon(Window window) {
22799 	try {
22800 		auto display = XDisplayConnection.get;
22801 
22802 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
22803 
22804 		if (data.length > arch_ulong.sizeof * 2) {
22805 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
22806 			// these are an array of rgba images that we have to convert into pixmaps ourself
22807 
22808 			int width = cast(int) meta[0];
22809 			int height = cast(int) meta[1];
22810 
22811 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
22812 
22813 			static if(arch_ulong.sizeof == 4) {
22814 				bytes = bytes[0 .. width * height * 4];
22815 				alias imageData = bytes;
22816 			} else static if(arch_ulong.sizeof == 8) {
22817 				bytes = bytes[0 .. width * height * 8];
22818 				auto imageData = new ubyte[](4 * width * height);
22819 			} else static assert(0);
22820 
22821 
22822 
22823 			// this returns ARGB. Remember it is little-endian so
22824 			//                                         we have BGRA
22825 			// our thing uses RGBA, which in little endian, is ABGR
22826 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
22827 				auto r = bytes[idx + 2];
22828 				auto g = bytes[idx + 1];
22829 				auto b = bytes[idx + 0];
22830 				auto a = bytes[idx + 3];
22831 
22832 				imageData[idx2 + 0] = r;
22833 				imageData[idx2 + 1] = g;
22834 				imageData[idx2 + 2] = b;
22835 				imageData[idx2 + 3] = a;
22836 			}
22837 
22838 			return new TrueColorImage(width, height, imageData);
22839 		}
22840 
22841 		return null;
22842 	} catch(Exception e) {
22843 		return null;
22844 	}
22845 }
22846 
22847 } /* UsingSimpledisplayX11 */
22848 
22849 
22850 void loadBinNameToWindowClassName () {
22851 	import core.stdc.stdlib : realloc;
22852 	version(linux) {
22853 		// args[0] MAY be empty, so we'll just use this
22854 		import core.sys.posix.unistd : readlink;
22855 		char[1024] ebuf = void; // 1KB should be enough for everyone!
22856 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
22857 		if (len < 1) return;
22858 	} else /*version(Windows)*/ {
22859 		import core.runtime : Runtime;
22860 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
22861 		auto ebuf = Runtime.args[0];
22862 		auto len = ebuf.length;
22863 	}
22864 	auto pos = len;
22865 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22866 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22867 	if (sdpyWindowClassStr is null) return; // oops
22868 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22869 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22870 }
22871 
22872 /++
22873 	An interface representing a font that is drawn with custom facilities.
22874 
22875 	You might want [OperatingSystemFont] instead, which represents
22876 	a font loaded and drawn by functions native to the operating system.
22877 
22878 	WARNING: I might still change this.
22879 +/
22880 interface DrawableFont : MeasurableFont {
22881 	/++
22882 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22883 
22884 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22885 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22886 		fill color, but that's up to the implementation.
22887 	+/
22888 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22889 
22890 	/++
22891 		Requests that the given string is added to the image cache. You should only do this rarely, but
22892 		if you have a string that you know will be used over and over again, adding it to a cache can
22893 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22894 		to implement this as a do-nothing method).
22895 	+/
22896 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22897 }
22898 
22899 /++
22900 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
22901 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
22902 
22903 	You should also consider [OperatingSystemFont], which loads and draws a font with
22904 	facilities native to the user's operating system. You might also consider
22905 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
22906 	of game, as they have their own ways to draw text too.
22907 
22908 	Be warned: this can be slow, especially on remote connections to the X server, since
22909 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
22910 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
22911 	experiment in your specific case.
22912 
22913 	Please note that the return type of [DrawableFont] also includes an implementation of
22914 	[MeasurableFont].
22915 +/
22916 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
22917 	import arsd.ttf;
22918 	static class ArsdTtfFont : DrawableFont {
22919 		TtfFont font;
22920 		int size;
22921 		this(in ubyte[] data, int size) {
22922 			font = TtfFont(data);
22923 			this.size = size;
22924 
22925 
22926 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
22927 			int ascent_, descent_, line_gap;
22928 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
22929 
22930 			int advance, lsb;
22931 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
22932 			xWidth = cast(int) (advance * scale);
22933 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
22934 			MWidth = cast(int) (advance * scale);
22935 		}
22936 
22937 		private int ascent_;
22938 		private int descent_;
22939 		private int xWidth;
22940 		private int MWidth;
22941 
22942 		bool isMonospace() {
22943 			return xWidth == MWidth;
22944 		}
22945 		int averageWidth() {
22946 			return xWidth;
22947 		}
22948 		int height() {
22949 			return size;
22950 		}
22951 		int ascent() {
22952 			return ascent_;
22953 		}
22954 		int descent() {
22955 			return descent_;
22956 		}
22957 
22958 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
22959 			int width, height;
22960 			font.getStringSize(s, size, width, height);
22961 			return width;
22962 		}
22963 
22964 
22965 
22966 		Sprite[string] cache;
22967 
22968 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
22969 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
22970 			cache[text] = sprite;
22971 		}
22972 
22973 		Image stringToImage(Color fg, Color bg, in char[] text) {
22974 			int width, height;
22975 			auto data = font.renderString(text, size, width, height);
22976 			auto image = new TrueColorImage(width, height);
22977 			int pos = 0;
22978 			foreach(y; 0 .. height)
22979 			foreach(x; 0 .. width) {
22980 				fg.a = data[0];
22981 				bg.a = 255;
22982 				auto color = alphaBlend(fg, bg);
22983 				image.imageData.bytes[pos++] = color.r;
22984 				image.imageData.bytes[pos++] = color.g;
22985 				image.imageData.bytes[pos++] = color.b;
22986 				image.imageData.bytes[pos++] = data[0];
22987 				data = data[1 .. $];
22988 			}
22989 			assert(data.length == 0);
22990 
22991 			return Image.fromMemoryImage(image);
22992 		}
22993 
22994 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
22995 			Sprite sprite = (text in cache) ? *(text in cache) : null;
22996 
22997 			auto fg = painter.impl._outlineColor;
22998 			auto bg = painter.impl._fillColor;
22999 
23000 			if(sprite !is null) {
23001 				auto w = cast(SimpleWindow) painter.window;
23002 				assert(w !is null);
23003 
23004 				sprite.drawAt(painter, upperLeft);
23005 			} else {
23006 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
23007 			}
23008 		}
23009 	}
23010 
23011 	return new ArsdTtfFont(data, size);
23012 }
23013 
23014 class NotYetImplementedException : Exception {
23015 	this(string file = __FILE__, size_t line = __LINE__) {
23016 		super("Not yet implemented", file, line);
23017 	}
23018 }
23019 
23020 ///
23021 __gshared bool librariesSuccessfullyLoaded = true;
23022 ///
23023 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
23024 
23025 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
23026 	mixin(staticForeachReplacement!Iface);
23027 
23028 	void loadDynamicLibrary() @nogc {
23029 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23030 	}
23031 
23032         void loadDynamicLibraryForReal() {
23033                 foreach(name; __traits(derivedMembers, Iface)) {
23034                         mixin("alias tmp = " ~ name ~ ";");
23035                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
23036                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
23037                 }
23038         }
23039 }
23040 
23041 private const(char)[] staticForeachReplacement(Iface)() pure {
23042 /*
23043 	// just this for gdc 9....
23044 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
23045 
23046         static foreach(name; __traits(derivedMembers, Iface))
23047                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23048 */
23049 
23050 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
23051 	size_t pos;
23052 
23053 	void append(in char[] what) {
23054 		if(pos + what.length > code.length)
23055 			code.length = (code.length * 3) / 2;
23056 		code[pos .. pos + what.length] = what[];
23057 		pos += what.length;
23058 	}
23059 
23060         foreach(name; __traits(derivedMembers, Iface)) {
23061                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
23062 		append(name);
23063 		append(`")) `);
23064 		append(name);
23065 		append(";");
23066 	}
23067 
23068 	return code[0 .. pos];
23069 }
23070 
23071 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
23072 	mixin(staticForeachReplacement!Iface);
23073 
23074 	private __gshared void* libHandle;
23075 	private __gshared bool attempted;
23076 
23077         void loadDynamicLibrary() @nogc {
23078 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23079 	}
23080 
23081 	bool loadAttempted() {
23082 		return attempted;
23083 	}
23084 	bool loadSuccessful() {
23085 		return libHandle !is null;
23086 	}
23087 
23088         void loadDynamicLibraryForReal() {
23089 		attempted = true;
23090                 version(Posix) {
23091                         import core.sys.posix.dlfcn;
23092 			version(OSX) {
23093 				version(X11)
23094                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
23095 				else
23096                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
23097 			} else {
23098                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23099 				if(libHandle is null)
23100                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
23101 			}
23102 
23103 			static void* loadsym(void* l, const char* name) {
23104 				import core.stdc.stdlib;
23105 				if(l is null)
23106 					return &abort;
23107 				return dlsym(l, name);
23108 			}
23109                 } else version(Windows) {
23110                         import core.sys.windows.winbase;
23111                         libHandle = LoadLibrary(library ~ ".dll");
23112 			static void* loadsym(void* l, const char* name) {
23113 				import core.stdc.stdlib;
23114 				if(l is null)
23115 					return &abort;
23116 				return GetProcAddress(l, name);
23117 			}
23118                 }
23119                 if(libHandle is null) {
23120 			success = false;
23121                         //throw new Exception("load failure of library " ~ library);
23122 		}
23123                 foreach(name; __traits(derivedMembers, Iface)) {
23124                         mixin("alias tmp = " ~ name ~ ";");
23125                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
23126                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
23127                 }
23128         }
23129 
23130         void unloadDynamicLibrary() {
23131                 version(Posix) {
23132                         import core.sys.posix.dlfcn;
23133                         dlclose(libHandle);
23134                 } else version(Windows) {
23135                         import core.sys.windows.winbase;
23136                         FreeLibrary(libHandle);
23137                 }
23138                 foreach(name; __traits(derivedMembers, Iface))
23139                         mixin(name ~ " = null;");
23140         }
23141 }
23142 
23143 // version(X11)
23144 /++
23145 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
23146 
23147 	$(WARNING
23148 		This function is exempted from stability guarantees.
23149 	)
23150 +/
23151 float customScalingFactorForMonitor(int monitorNumber) @system {
23152 	import core.stdc.stdlib;
23153 	auto val = getenv("ARSD_SCALING_FACTOR");
23154 
23155 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
23156 	if(val is null)
23157 		return 1.0;
23158 
23159 	char[16] buffer = 0;
23160 	int pos;
23161 
23162 	const(char)* at = val;
23163 
23164 	foreach(item; 0 .. monitorNumber + 1) {
23165 		if(*at == 0)
23166 			break; // reuse the last number when we at the end of the string
23167 		pos = 0;
23168 		while(pos + 1 < buffer.length && *at && *at != ';') {
23169 			buffer[pos++] = *at;
23170 			at++;
23171 		}
23172 		if(*at)
23173 			at++; // skip the semicolon
23174 		buffer[pos] = 0;
23175 	}
23176 
23177 	//sdpyPrintDebugString(buffer[0 .. pos]);
23178 
23179 	import core.stdc.math;
23180 	auto f = atof(buffer.ptr);
23181 
23182 	if(f <= 0.0 || isnan(f) || isinf(f))
23183 		return 1.0;
23184 
23185 	return f;
23186 }
23187 
23188 void guiAbortProcess(string msg) {
23189 	import core.stdc.stdlib;
23190 	version(Windows) {
23191 		WCharzBuffer t = WCharzBuffer(msg);
23192 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
23193 	} else {
23194 		import core.stdc.stdio;
23195 		fwrite(msg.ptr, 1, msg.length, stderr);
23196 		msg = "\n";
23197 		fwrite(msg.ptr, 1, msg.length, stderr);
23198 		fflush(stderr);
23199 	}
23200 
23201 	abort();
23202 }
23203 
23204 private int minInternal(int a, int b) {
23205 	return (a < b) ? a : b;
23206 }
23207 
23208 private alias scriptable = arsd_jsvar_compatible;