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 	$(H4 apitrace)
588 
589 	Out of the box, simpledisplay might not work as expected in combination with
590 	[apitrace](https://apitrace.github.io).
591 	However it can be instructed to specifically load the GL/GLX wrapper libraries provided by apitrace instead of
592 	the system libraries. This should restore the lost functionality.
593 
594 	$(NUMBERED_LIST
595 		* Compile with `-version=apitrace`.
596 		* When launching such a simpledisplay app, it must be able to locate the apitrace wrapper libraries.
597 		* Running the app will generate an apitrace trace file.
598 		  It should print a log message similar to "apitrace: loaded into /directory" during startup.
599 	)
600 
601 	There are multiple ways to enable a simpledisplay app to locate the wrapper libraries.
602 
603 	One way to achieved this is by pointing the `LD_LIBRARY_PATH` environment variable to the directory containing
604 	those wrappers.
605 
606 	```sh
607 	LD_LIBRARY_PATH=/path/to/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
608 
609 	# e.g.
610 	LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
611 	```
612 
613 	Alternatively, the simpledisplay app can also be launched via $(I apitrace).
614 
615 	```sh
616 	apitrace trace -a gl ./myapp
617 	```
618 
619 	Another way that seems to work is to preload `glxtrace.so` through `LD_PRELOAD`.
620 
621 	```sh
622 	LD_PRELOAD=/path/to/apitrace/wrappers/glxtrace.so ./myapp
623 	```
624 
625 	Windows_tips:
626 
627 	You can add icons or manifest files to your exe using a resource file.
628 
629 	To create a Windows .ico file, use the gimp or something. I'll write a helper
630 	program later.
631 
632 	Create `yourapp.rc`:
633 
634 	```rc
635 		1 ICON filename.ico
636 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
637 	```
638 
639 	And `yourapp.exe.manifest`:
640 
641 	```xml
642 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
643 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
644 		<assemblyIdentity
645 		    version="1.0.0.0"
646 		    processorArchitecture="*"
647 		    name="CompanyName.ProductName.YourApplication"
648 		    type="win32"
649 		/>
650 		<description>Your application description here.</description>
651 		<dependency>
652 		    <dependentAssembly>
653 			<assemblyIdentity
654 			    type="win32"
655 			    name="Microsoft.Windows.Common-Controls"
656 			    version="6.0.0.0"
657 			    processorArchitecture="*"
658 			    publicKeyToken="6595b64144ccf1df"
659 			    language="*"
660 			/>
661 		    </dependentAssembly>
662 		</dependency>
663 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
664 			<windowsSettings>
665 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
666 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
667 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
668 				<!-- to render crisply in DPI-unaware contexts -->
669 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
670 			</windowsSettings>
671 		</application>
672 		</assembly>
673 	```
674 
675 	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`.
676 
677 	Doing this lets you opt into various new things since Windows XP.
678 
679 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
680 
681 	$(H2 Tips)
682 
683 	$(H3 Name conflicts)
684 
685 	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:
686 
687 	---
688 	static import sdpy = arsd.simpledisplay;
689 	import arsd.simpledisplay : SimpleWindow;
690 
691 	void main() {
692 		auto window = new SimpleWindow();
693 		sdpy.EventLoop.get.run();
694 	}
695 	---
696 
697 	$(H2 $(ID developer-notes) Developer notes)
698 
699 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
700 	implementation though.
701 
702 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
703 	suck. If I was rewriting it, I wouldn't do it that way again.
704 
705 	This file must not have any more required dependencies. If you need bindings, add
706 	them right to this file. Once it gets into druntime and is there for a while, remove
707 	bindings from here to avoid conflicts (or put them in an appropriate version block
708 	so it continues to just work on old dmd), but wait a couple releases before making the
709 	transition so this module remains usable with older versions of dmd.
710 
711 	You may have optional dependencies if needed by putting them in version blocks or
712 	template functions. You may also extend the module with other modules with UFCS without
713 	actually editing this - that is nice to do if you can.
714 
715 	Try to make functions work the same way across operating systems. I typically make
716 	it thinly wrap Windows, then emulate that on Linux.
717 
718 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
719 	Phobos! So try to avoid it.
720 
721 	See more comments throughout the source.
722 
723 	I realize this file is fairly large, but over half that is just bindings at the bottom
724 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
725 	to understand. I suggest you jump around the source by looking for a particular
726 	declaration you're interested in, like `class SimpleWindow` using your editor's search
727 	function, then look at one piece at a time.
728 
729 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
730 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
731 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
732 
733 	I live in the eastern United States, so I will most likely not be around at night in
734 	that US east timezone.
735 
736 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
737 
738 	Building documentation: use my adrdox generator, `dub run adrdox`.
739 
740 	Examples:
741 
742 	$(DIV $(ID Event-example))
743 	$(H3 $(ID event-example) Event example)
744 	This program creates a window and draws events inside them as they
745 	happen, scrolling the text in the window as needed. Run this program
746 	and experiment to get a feel for where basic input events take place
747 	in the library.
748 
749 	---
750 	// dmd example.d simpledisplay.d color.d
751 	import arsd.simpledisplay;
752 	import std.conv;
753 
754 	void main() {
755 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
756 
757 		int y = 0;
758 
759 		void addLine(string text) {
760 			auto painter = window.draw();
761 
762 			if(y + painter.fontHeight >= window.height) {
763 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
764 				y -= painter.fontHeight;
765 			}
766 
767 			painter.outlineColor = Color.red;
768 			painter.fillColor = Color.black;
769 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
770 
771 			painter.outlineColor = Color.white;
772 
773 			painter.drawText(Point(10, y), text);
774 
775 			y += painter.fontHeight;
776 		}
777 
778 		window.eventLoop(1000,
779 		  () {
780 			addLine("Timer went off!");
781 		  },
782 		  (KeyEvent event) {
783 			addLine(to!string(event));
784 		  },
785 		  (MouseEvent event) {
786 			addLine(to!string(event));
787 		  },
788 		  (dchar ch) {
789 			addLine(to!string(ch));
790 		  }
791 		);
792 	}
793 	---
794 
795 	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.
796 
797 	$(COMMENT
798 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
799 
800 	---
801 
802 	---
803 	)
804 
805 	History:
806 		Initial release in April 2011.
807 
808 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
809 
810 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
811 
812 		On October 5, 2024, apitrace support was added for Linux targets.
813 
814 		The ExperimentalTextComponent and ExperimentalTextComponent2 were both removed on April 12, 2025. Use [arsd.textlayouter] or the [arsd.minigui] widgets instead.
815 +/
816 module arsd.simpledisplay;
817 
818 // FIXME: tetris demo
819 // FIXME: space invaders demo
820 // FIXME: asteroids demo
821 
822 /++ $(ID Pong-example)
823 	$(H3 Pong)
824 
825 	This program creates a little Pong-like game. Player one is controlled
826 	with the keyboard.  Player two is controlled with the mouse. It demos
827 	the pulse timer, event handling, and some basic drawing.
828 +/
829 version(demos)
830 unittest {
831 	// dmd example.d simpledisplay.d color.d
832 	import arsd.simpledisplay;
833 
834 	enum paddleMovementSpeed = 8;
835 	enum paddleHeight = 48;
836 
837 	void main() {
838 		auto window = new SimpleWindow(600, 400, "Pong game!");
839 
840 		int playerOnePosition, playerTwoPosition;
841 		int playerOneMovement, playerTwoMovement;
842 		int playerOneScore, playerTwoScore;
843 
844 		int ballX, ballY;
845 		int ballDx, ballDy;
846 
847 		void serve() {
848 			import std.random;
849 
850 			ballX = window.width / 2;
851 			ballY = window.height / 2;
852 			ballDx = uniform(-4, 4) * 3;
853 			ballDy = uniform(-4, 4) * 3;
854 			if(ballDx == 0)
855 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
856 		}
857 
858 		serve();
859 
860 		window.eventLoop(50, // set a 50 ms timer pulls
861 			// This runs once per timer pulse
862 			delegate () {
863 				auto painter = window.draw();
864 
865 				painter.clear();
866 
867 				// Update everyone's motion
868 				playerOnePosition += playerOneMovement;
869 				playerTwoPosition += playerTwoMovement;
870 
871 				ballX += ballDx;
872 				ballY += ballDy;
873 
874 				// Bounce off the top and bottom edges of the window
875 				if(ballY + 7 >= window.height)
876 					ballDy = -ballDy;
877 				if(ballY - 8 <= 0)
878 					ballDy = -ballDy;
879 
880 				// Bounce off the paddle, if it is in position
881 				if(ballX - 8 <= 16) {
882 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
883 						ballDx = -ballDx + 1; // add some speed to keep it interesting
884 						ballDy += playerOneMovement; // and y movement based on your controls too
885 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
886 					} else {
887 						// Missed it
888 						playerTwoScore ++;
889 						serve();
890 					}
891 				}
892 
893 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
894 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
895 						ballDx = -ballDx - 1;
896 						ballDy += playerTwoMovement;
897 						ballX = window.width - 24;
898 					} else {
899 						// Missed it
900 						playerOneScore ++;
901 						serve();
902 					}
903 				}
904 
905 				// Draw the paddles
906 				painter.outlineColor = Color.black;
907 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
908 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
909 
910 				// Draw the ball
911 				painter.fillColor = Color.red;
912 				painter.outlineColor = Color.yellow;
913 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
914 
915 				// Draw the score
916 				painter.outlineColor = Color.blue;
917 				import std.conv;
918 				painter.drawText(Point(64, 4), to!string(playerOneScore));
919 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
920 
921 			},
922 			delegate (KeyEvent event) {
923 				// Player 1's controls are the arrow keys on the keyboard
924 				if(event.key == Key.Down)
925 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
926 				if(event.key == Key.Up)
927 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
928 
929 			},
930 			delegate (MouseEvent event) {
931 				// Player 2's controls are mouse movement while the left button is held down
932 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
933 					if(event.dy > 0)
934 						playerTwoMovement = paddleMovementSpeed;
935 					else if(event.dy < 0)
936 						playerTwoMovement = -paddleMovementSpeed;
937 				} else {
938 					playerTwoMovement = 0;
939 				}
940 			}
941 		);
942 	}
943 }
944 
945 /++ $(H3 $(ID example-minesweeper) Minesweeper)
946 
947 	This minesweeper demo shows how we can implement another classic
948 	game with simpledisplay and shows some mouse input and basic output
949 	code.
950 +/
951 version(demos)
952 unittest {
953 	import arsd.simpledisplay;
954 
955 	enum GameSquare {
956 		mine = 0,
957 		clear,
958 		m1, m2, m3, m4, m5, m6, m7, m8
959 	}
960 
961 	enum UserSquare {
962 		unknown,
963 		revealed,
964 		flagged,
965 		questioned
966 	}
967 
968 	enum GameState {
969 		inProgress,
970 		lose,
971 		win
972 	}
973 
974 	GameSquare[] board;
975 	UserSquare[] userState;
976 	GameState gameState;
977 	int boardWidth;
978 	int boardHeight;
979 
980 	bool isMine(int x, int y) {
981 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
982 			return false;
983 		return board[y * boardWidth + x] == GameSquare.mine;
984 	}
985 
986 	GameState reveal(int x, int y) {
987 		if(board[y * boardWidth + x] == GameSquare.clear) {
988 			floodFill(userState, boardWidth, boardHeight,
989 				UserSquare.unknown, UserSquare.revealed,
990 				x, y,
991 				(x, y) {
992 					if(board[y * boardWidth + x] == GameSquare.clear)
993 						return true;
994 					else {
995 						userState[y * boardWidth + x] = UserSquare.revealed;
996 						return false;
997 					}
998 				});
999 		} else {
1000 			userState[y * boardWidth + x] = UserSquare.revealed;
1001 			if(isMine(x, y))
1002 				return GameState.lose;
1003 		}
1004 
1005 		foreach(state; userState) {
1006 			if(state == UserSquare.unknown || state == UserSquare.questioned)
1007 				return GameState.inProgress;
1008 		}
1009 
1010 		return GameState.win;
1011 	}
1012 
1013 	void initializeBoard(int width, int height, int numberOfMines) {
1014 		boardWidth = width;
1015 		boardHeight = height;
1016 		board.length = width * height;
1017 
1018 		userState.length = width * height;
1019 		userState[] = UserSquare.unknown;
1020 
1021 		import std.algorithm, std.random, std.range;
1022 
1023 		board[] = GameSquare.clear;
1024 
1025 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
1026 			board[minePosition] = GameSquare.mine;
1027 
1028 		int x;
1029 		int y;
1030 		foreach(idx, ref square; board) {
1031 			if(square == GameSquare.clear) {
1032 				int danger = 0;
1033 				danger += isMine(x-1, y-1)?1:0;
1034 				danger += isMine(x-1, y)?1:0;
1035 				danger += isMine(x-1, y+1)?1:0;
1036 				danger += isMine(x, y-1)?1:0;
1037 				danger += isMine(x, y+1)?1:0;
1038 				danger += isMine(x+1, y-1)?1:0;
1039 				danger += isMine(x+1, y)?1:0;
1040 				danger += isMine(x+1, y+1)?1:0;
1041 
1042 				square = cast(GameSquare) (danger + 1);
1043 			}
1044 
1045 			x++;
1046 			if(x == width) {
1047 				x = 0;
1048 				y++;
1049 			}
1050 		}
1051 	}
1052 
1053 	void redraw(SimpleWindow window) {
1054 		import std.conv;
1055 
1056 		auto painter = window.draw();
1057 
1058 		painter.clear();
1059 
1060 		final switch(gameState) with(GameState) {
1061 			case inProgress:
1062 				break;
1063 			case win:
1064 				painter.fillColor = Color.green;
1065 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1066 				return;
1067 			case lose:
1068 				painter.fillColor = Color.red;
1069 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1070 				return;
1071 		}
1072 
1073 		int x = 0;
1074 		int y = 0;
1075 
1076 		foreach(idx, square; board) {
1077 			auto state = userState[idx];
1078 
1079 			final switch(state) with(UserSquare) {
1080 				case unknown:
1081 					painter.outlineColor = Color.black;
1082 					painter.fillColor = Color(128,128,128);
1083 
1084 					painter.drawRectangle(
1085 						Point(x * 20, y * 20),
1086 						20, 20
1087 					);
1088 				break;
1089 				case revealed:
1090 					if(square == GameSquare.clear) {
1091 						painter.outlineColor = Color.white;
1092 						painter.fillColor = Color.white;
1093 
1094 						painter.drawRectangle(
1095 							Point(x * 20, y * 20),
1096 							20, 20
1097 						);
1098 					} else {
1099 						painter.outlineColor = Color.black;
1100 						painter.fillColor = Color.white;
1101 
1102 						painter.drawText(
1103 							Point(x * 20, y * 20),
1104 							to!string(square)[1..2],
1105 							Point(x * 20 + 20, y * 20 + 20),
1106 							TextAlignment.Center | TextAlignment.VerticalCenter);
1107 					}
1108 				break;
1109 				case flagged:
1110 					painter.outlineColor = Color.black;
1111 					painter.fillColor = Color.red;
1112 					painter.drawRectangle(
1113 						Point(x * 20, y * 20),
1114 						20, 20
1115 					);
1116 				break;
1117 				case questioned:
1118 					painter.outlineColor = Color.black;
1119 					painter.fillColor = Color.yellow;
1120 					painter.drawRectangle(
1121 						Point(x * 20, y * 20),
1122 						20, 20
1123 					);
1124 				break;
1125 			}
1126 
1127 			x++;
1128 			if(x == boardWidth) {
1129 				x = 0;
1130 				y++;
1131 			}
1132 		}
1133 
1134 	}
1135 
1136 	void main() {
1137 		auto window = new SimpleWindow(200, 200);
1138 
1139 		initializeBoard(10, 10, 10);
1140 
1141 		redraw(window);
1142 		window.eventLoop(0,
1143 			delegate (MouseEvent me) {
1144 				if(me.type != MouseEventType.buttonPressed)
1145 					return;
1146 				auto x = me.x / 20;
1147 				auto y = me.y / 20;
1148 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1149 					if(me.button == MouseButton.left) {
1150 						gameState = reveal(x, y);
1151 					} else {
1152 						userState[y*boardWidth+x] = UserSquare.flagged;
1153 					}
1154 					redraw(window);
1155 				}
1156 			}
1157 		);
1158 	}
1159 }
1160 
1161 // FIXME: tetris demo
1162 // FIXME: space invaders demo
1163 // FIXME: asteroids demo
1164 
1165 import arsd.core;
1166 
1167 version(D_OpenD) {
1168 	version(OSX)
1169 		version=OSXCocoa;
1170 	version(iOS)
1171 		version=OSXCocoa;
1172 } else {
1173 	version(OSX) version(DigitalMars) version=OSXCocoa;
1174 }
1175 
1176 
1177 
1178 version(Emscripten) {
1179 	version=allow_unimplemented_features;
1180 	version=without_opengl;
1181 }
1182 
1183 
1184 version(OSXCocoa) {
1185 	version=without_opengl;
1186 	version=allow_unimplemented_features;
1187 	// version=OSXCocoa;
1188 	// pragma(linkerDirective, "-framework Cocoa");
1189 }
1190 
1191 version(without_opengl) {
1192 	enum SdpyIsUsingIVGLBinds = false;
1193 } else /*version(Posix)*/ {
1194 	static if (__traits(compiles, (){import iv.glbinds;})) {
1195 		enum SdpyIsUsingIVGLBinds = true;
1196 		public import iv.glbinds;
1197 		//pragma(msg, "SDPY: using iv.glbinds");
1198 	} else {
1199 		enum SdpyIsUsingIVGLBinds = false;
1200 	}
1201 //} else {
1202 //	enum SdpyIsUsingIVGLBinds = false;
1203 }
1204 
1205 version(Windows) {
1206 	//import core.sys.windows.windows;
1207 	import core.sys.windows.winnls;
1208 	import core.sys.windows.windef;
1209 	import core.sys.windows.basetyps;
1210 	import core.sys.windows.winbase;
1211 	import core.sys.windows.winuser;
1212 	import core.sys.windows.shellapi;
1213 	import core.sys.windows.wingdi;
1214 	static import gdi = core.sys.windows.wingdi; // so i
1215 
1216 	pragma(lib, "gdi32");
1217 	pragma(lib, "user32");
1218 
1219 	// for AlphaBlend... a breaking change....
1220 	version(CRuntime_DigitalMars) { } else
1221 		pragma(lib, "msimg32");
1222 
1223 	// core.sys.windows.dwmapi
1224 	private {
1225 		/++
1226 			See_also:
1227 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute
1228 		 +/
1229 		extern extern(Windows) HRESULT DwmGetWindowAttribute(
1230 			HWND hwnd,
1231 			DWORD dwAttribute,
1232 			PVOID pvAttribute,
1233 			DWORD cbAttribute
1234 		) nothrow @nogc;
1235 
1236 		/++
1237 			See_also:
1238 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
1239 		 +/
1240 		extern extern(Windows) HRESULT DwmSetWindowAttribute(
1241 			HWND hwnd,
1242 			DWORD dwAttribute,
1243 			LPCVOID pvAttribute,
1244 			DWORD cbAttribute
1245 		) nothrow @nogc;
1246 
1247 		/++
1248 			See_also:
1249 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
1250 		 +/
1251 		enum DWMWINDOWATTRIBUTE {
1252 			// incomplete, only declare what we need
1253 
1254 			/++
1255 				Usage:
1256 					pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*`
1257 
1258 				$(NOTE
1259 					Requires Windows 11 or later.
1260 				)
1261 			 +/
1262 			DWMWA_WINDOW_CORNER_PREFERENCE = 33,
1263 		}
1264 
1265 		/++
1266 			See_also:
1267 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
1268 		 +/
1269 		enum DWM_WINDOW_CORNER_PREFERENCE {
1270 			/// System decision
1271 			DWMWCP_DEFAULT = 0,
1272 
1273 			/// Never
1274 			DWMWCP_DONOTROUND = 1,
1275 
1276 			// If "appropriate"
1277 			DWMWCP_ROUND = 2,
1278 
1279 			// If "appropriate", but smaller radius
1280 			DWMWCP_ROUNDSMALL = 3
1281 		}
1282 
1283 		bool fromDWM(
1284 			DWM_WINDOW_CORNER_PREFERENCE value,
1285 			out CornerStyle result,
1286 		) @safe pure nothrow @nogc {
1287 			switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1288 				case DWMWCP_DEFAULT:
1289 					result = CornerStyle.automatic;
1290 					return true;
1291 				case DWMWCP_DONOTROUND:
1292 					result = CornerStyle.rectangular;
1293 					return true;
1294 				case DWMWCP_ROUND:
1295 					result = CornerStyle.rounded;
1296 					return true;
1297 				case DWMWCP_ROUNDSMALL:
1298 					result = CornerStyle.roundedSlightly;
1299 					return true;
1300 				default:
1301 					return false;
1302 			}
1303 		}
1304 
1305 		bool toDWM(
1306 			CornerStyle value,
1307 			out DWM_WINDOW_CORNER_PREFERENCE result,
1308 		) @safe pure nothrow @nogc {
1309 			final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1310 				case CornerStyle.automatic:
1311 					result = DWMWCP_DEFAULT;
1312 					return true;
1313 				case CornerStyle.rectangular:
1314 					result = DWMWCP_DONOTROUND;
1315 					return true;
1316 				case CornerStyle.rounded:
1317 					result = DWMWCP_ROUND;
1318 					return true;
1319 				case CornerStyle.roundedSlightly:
1320 					result = DWMWCP_ROUNDSMALL;
1321 					return true;
1322 			}
1323 		}
1324 
1325 		pragma(lib, "dwmapi");
1326 	}
1327 } else version(Emscripten) {
1328 } else version (linux) {
1329 	//k8: this is hack for rdmd. sorry.
1330 	static import core.sys.linux.epoll;
1331 	static import core.sys.linux.timerfd;
1332 }
1333 
1334 
1335 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1336 
1337 // http://wiki.dlang.org/Simpledisplay.d
1338 
1339 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1340 
1341 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1342 // but can i control the scroll lock led
1343 
1344 
1345 // Note: if you are using Image on X, you might want to do:
1346 /*
1347 	static if(UsingSimpledisplayX11) {
1348 		if(!Image.impl.xshmAvailable) {
1349 			// the images will use the slower XPutImage, you might
1350 			// want to consider an alternative method to get better speed
1351 		}
1352 	}
1353 
1354 	If the shared memory extension is available though, simpledisplay uses it
1355 	for a significant speed boost whenever you draw large Images.
1356 */
1357 
1358 // 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.
1359 
1360 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1361 
1362 /*
1363 	Biggest FIXME:
1364 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1365 
1366 		clean up opengl contexts when their windows close
1367 
1368 		fix resizing the bitmaps/pixmaps
1369 */
1370 
1371 // BTW on Windows:
1372 // -L/SUBSYSTEM:WINDOWS:5.0
1373 // to dmd will make a nice windows binary w/o a console if you want that.
1374 
1375 /*
1376 	Stuff to add:
1377 
1378 	use multibyte functions everywhere we can
1379 
1380 	OpenGL windows
1381 	more event stuff
1382 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1383 
1384 
1385 	resizeEvent
1386 		and make the windows non-resizable by default,
1387 		or perhaps stretched (if I can find something in X like StretchBlt)
1388 
1389 	take a screenshot function!
1390 
1391 	Pens and brushes?
1392 	Maybe a global event loop?
1393 
1394 	Mouse deltas
1395 	Key items
1396 */
1397 
1398 /*
1399 From MSDN:
1400 
1401 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1402 
1403 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.
1404 
1405 */
1406 
1407 version(Emscripten) {
1408 
1409 } else version(linux) {
1410 	version = X11;
1411 	version(without_libnotify) {
1412 		// we cool
1413 	}
1414 	else
1415 		version = libnotify;
1416 }
1417 
1418 version(libnotify) {
1419 	pragma(lib, "dl");
1420 	import core.sys.posix.dlfcn;
1421 
1422 	void delegate()[int] libnotify_action_delegates;
1423 	int libnotify_action_delegates_count;
1424 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1425 		auto idx = cast(int) user_data;
1426 		if(auto dgptr = idx in libnotify_action_delegates) {
1427 			(*dgptr)();
1428 			libnotify_action_delegates.remove(idx);
1429 		}
1430 	}
1431 
1432 	struct C_DynamicLibrary {
1433 		void* handle;
1434 		this(string name) {
1435 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1436 			if(handle is null)
1437 				throw new Exception("dlopen");
1438 		}
1439 
1440 		void close() {
1441 			dlclose(handle);
1442 		}
1443 
1444 		~this() {
1445 			// close
1446 		}
1447 
1448 		// FIXME: this looks up by name every time....
1449 		template call(string func, Ret, Args...) {
1450 			extern(C) Ret function(Args) fptr;
1451 			typeof(fptr) call() {
1452 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1453 				return fptr;
1454 			}
1455 		}
1456 	}
1457 
1458 	C_DynamicLibrary* libnotify;
1459 }
1460 
1461 version(OSX) {
1462 	version(OSXCocoa) {}
1463 	else { version = X11; }
1464 }
1465 	//version = OSXCocoa; // this was written by KennyTM
1466 version(FreeBSD)
1467 	version = X11;
1468 version(Solaris)
1469 	version = X11;
1470 
1471 version(X11) {
1472 	version(without_xft) {}
1473 	else version=with_xft;
1474 }
1475 
1476 void featureNotImplemented()() {
1477 	version(allow_unimplemented_features)
1478 		throw new NotYetImplementedException();
1479 	else
1480 		static assert(0);
1481 }
1482 
1483 // these are so the static asserts don't trigger unless you want to
1484 // add support to it for an OS
1485 version(Windows)
1486 	version = with_timer;
1487 version(linux)
1488 	version = with_timer;
1489 version(OSXCocoa)
1490 	version = with_timer;
1491 
1492 version(with_timer)
1493 	enum bool SimpledisplayTimerAvailable = true;
1494 else
1495 	enum bool SimpledisplayTimerAvailable = false;
1496 
1497 /// 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.
1498 version(Windows)
1499 	enum bool UsingSimpledisplayWindows = true;
1500 else
1501 	enum bool UsingSimpledisplayWindows = false;
1502 
1503 /// 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.
1504 version(X11)
1505 	enum bool UsingSimpledisplayX11 = true;
1506 else
1507 	enum bool UsingSimpledisplayX11 = false;
1508 
1509 /// 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.
1510 version(OSXCocoa)
1511 	enum bool UsingSimpledisplayCocoa = true;
1512 else
1513 	enum bool UsingSimpledisplayCocoa = false;
1514 
1515 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1516 version(Windows)
1517 	enum multipleWindowsSupported = true;
1518 else version(Emscripten)
1519 	enum multipleWindowsSupported = false;
1520 else version(X11)
1521 	enum multipleWindowsSupported = true;
1522 else version(OSXCocoa)
1523 	enum multipleWindowsSupported = true;
1524 else
1525 	static assert(0);
1526 
1527 version(without_opengl)
1528 	enum bool OpenGlEnabled = false;
1529 else
1530 	enum bool OpenGlEnabled = true;
1531 
1532 /++
1533 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1534 	If you mix this in above your `main` function, you no longer need to use the linker
1535 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1536 
1537 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1538 
1539 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1540 	stderr writeln. It will fail and throw an exception.
1541 
1542 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1543 
1544 	History:
1545 		Added November 24, 2021 (dub v10.4)
1546 +/
1547 mixin template EnableWindowsSubsystem() {
1548 	version(Windows)
1549 	version(CRuntime_Microsoft) {
1550 		pragma(linkerDirective, "/subsystem:windows");
1551 		version(LDC)
1552 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1553 		else
1554 			pragma(linkerDirective, "/entry:mainCRTStartup");
1555 	}
1556 }
1557 
1558 
1559 /++
1560 	After selecting a type from [WindowTypes], you may further customize
1561 	its behavior by setting one or more of these flags.
1562 
1563 
1564 	The different window types have different meanings of `normal`. If the
1565 	window type already is a good match for what you want to do, you should
1566 	just use [WindowFlags.normal], the default, which will do the right thing
1567 	for your users.
1568 
1569 	The window flags will not always be honored by the operating system
1570 	and window managers; they are hints, not commands.
1571 +/
1572 enum WindowFlags : int {
1573 	normal = 0, ///
1574 	skipTaskbar = 1, ///
1575 	alwaysOnTop = 2, ///
1576 	alwaysOnBottom = 4, ///
1577 	cannotBeActivated = 8, ///
1578 	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.
1579 	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.
1580 	/++
1581 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1582 		it is still a top-level window. This should NOT be set separately for most window types.
1583 
1584 		A transient window will not keep the application open if its main window closes.
1585 
1586 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1587 
1588 
1589 		From the ICCM:
1590 
1591 		$(BLOCKQUOTE
1592 			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.
1593 
1594 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1595 		)
1596 
1597 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1598 
1599 		History:
1600 			Added February 23, 2021 but not yet stabilized.
1601 	+/
1602 	transient = 64,
1603 	/++
1604 		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.
1605 
1606 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1607 
1608 		History:
1609 			Added April 1, 2022
1610 	+/
1611 	managesChildWindowFocus = 128,
1612 
1613 	/++
1614 	+/
1615 	overrideRedirect = 256,
1616 
1617 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1618 }
1619 
1620 /++
1621 	When creating a window, you can pass a type to SimpleWindow's constructor,
1622 	then further customize the window by changing `WindowFlags`.
1623 
1624 
1625 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1626 	use. The others are there to build a foundation for a higher level GUI toolkit,
1627 	but are themselves not as high level as you might think from their names.
1628 
1629 	This list is based on the EMWH spec for X11.
1630 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1631 +/
1632 enum WindowTypes : int {
1633 	/// An ordinary application window.
1634 	normal,
1635 	/// 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.
1636 	undecorated,
1637 	/// 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.
1638 	eventOnly,
1639 	/// A drop down menu, such as from a menu bar
1640 	dropdownMenu,
1641 	/// A popup menu, such as from a right click
1642 	popupMenu,
1643 	/// A popup bubble notification
1644 	notification,
1645 	/*
1646 	menu, /// a tearable menu bar (not override-redirect - contrast to popups)
1647 	toolbar, /// a tearable menu bar (not override-redirect)
1648 	splashScreen, /// a loading splash screen for your application
1649 	desktop, ///
1650 	dockOrPanel, /// think taskbar
1651 	utility, /// a palette or something
1652 	*/
1653 	/// A tiny window showing temporary help text or something.
1654 	tooltip,
1655 	/// only supported on X; will assert fail elsewhere
1656 	dnd,
1657 	/// can also be used for other auto-complete presentations
1658 	comboBoxDropdown,
1659 	/// a dialog box of some sort
1660 	dialog,
1661 	/// a child nested inside the parent. You must pass a parent window to the ctor
1662 	nestedChild,
1663 
1664 	/++
1665 		The type you get when you pass in an existing browser handle, which means most
1666 		of simpledisplay's fancy things will not be done since they were never set up.
1667 
1668 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1669 		failure; you should use the existing handle constructor.
1670 
1671 		History:
1672 			Added November 17, 2022 (previously it would have type `normal`)
1673 	+/
1674 	minimallyWrapped
1675 }
1676 
1677 
1678 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1679 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1680 private __gshared char* sdpyWindowClassStr = null;
1681 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1682 
1683 /**
1684 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1685 	You may want to change context version if you want to use advanced shaders or
1686 	other modern OpenGL techinques. This setting doesn't affect already created
1687 	windows. You may use version 2.1 as your default, which should be supported
1688 	by any box since 2006, so seems to be a reasonable choice.
1689 
1690 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1691 	old context creation code without any version specified. This is the safest
1692 	way to init OpenGL, but it may not give you access to advanced features.
1693 
1694 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1695 */
1696 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1697 
1698 /**
1699 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1700 	pipeline functions, and without "compatible" mode you won't be able to use
1701 	your old non-shader-based code with such contexts. By default SimpleDisplay
1702 	creates compatible context, so you can gradually upgrade your OpenGL code if
1703 	you want to (or leave it as is, as it should "just work").
1704 */
1705 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1706 
1707 /**
1708 	Set to `true` to allow creating OpenGL context with lower version than requested
1709 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1710 	`openGLContextFallbackActivated()` will return `true`.
1711 	*/
1712 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1713 
1714 /**
1715 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1716 	*/
1717 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1718 
1719 /++
1720 	History:
1721 		Added April 24, 2023  (dub v11.0)
1722 +/
1723 version(without_opengl) {} else
1724 auto openGLCurrentContext() {
1725 	version(Windows)
1726 		return wglGetCurrentContext();
1727 	else
1728 		return glXGetCurrentContext();
1729 }
1730 
1731 
1732 /**
1733 	Set window class name for all following `new SimpleWindow()` calls.
1734 
1735 	WARNING! For Windows, you should set your class name before creating any
1736 	window, and NEVER change it after that!
1737 */
1738 void sdpyWindowClass (const(char)[] v) {
1739 	import core.stdc.stdlib : realloc;
1740 	if (v.length == 0) v = "SimpleDisplayWindow";
1741 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1742 	if (sdpyWindowClassStr is null) return; // oops
1743 	sdpyWindowClassStr[0..v.length+1] = 0;
1744 	sdpyWindowClassStr[0..v.length] = v[];
1745 }
1746 
1747 /**
1748 	Get current window class name.
1749 */
1750 string sdpyWindowClass () @trusted {
1751 	if (sdpyWindowClassStr is null) return null;
1752 	foreach (immutable idx; 0..size_t.max-1) {
1753 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1754 	}
1755 	return null;
1756 }
1757 
1758 /++
1759 	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.
1760 
1761 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1762 +/
1763 float[2] getDpi() {
1764 	float[2] dpi;
1765 	version(Windows) {
1766 		HDC screen = GetDC(null);
1767 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1768 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1769 	} else version(X11) {
1770 		auto display = XDisplayConnection.get;
1771 		auto screen = DefaultScreen(display);
1772 
1773 		void fallback() {
1774 			/+
1775 			// 25.4 millimeters in an inch...
1776 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1777 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1778 			+/
1779 
1780 			// the physical size isn't actually as important as the logical size since this is
1781 			// all about scaling really
1782 			dpi[0] = 96;
1783 			dpi[1] = 96;
1784 		}
1785 
1786 		auto xft = getXftDpi();
1787 		if(xft is float.init)
1788 			fallback();
1789 		else {
1790 			dpi[0] = xft;
1791 			dpi[1] = xft;
1792 		}
1793 	}
1794 
1795 	return dpi;
1796 }
1797 
1798 version(X11)
1799 float getXftDpi() {
1800 	auto display = XDisplayConnection.get;
1801 
1802 	char* resourceString = XResourceManagerString(display);
1803 	XrmInitialize();
1804 
1805 	if (resourceString) {
1806 		auto db = XrmGetStringDatabase(resourceString);
1807 		XrmValue value;
1808 		char* type;
1809 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1810 			if (value.addr) {
1811 				import core.stdc.stdlib;
1812 				return atof(cast(char*) value.addr);
1813 			}
1814 		}
1815 	}
1816 
1817 	return float.init;
1818 }
1819 
1820 /++
1821 	Implementation used by [SimpleWindow.takeScreenshot].
1822 
1823 	Params:
1824 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1825 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1826 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1827 		x = the x-offset of the image to capture, from the left.
1828 		y = the y-offset of the image to capture, from the top.
1829 
1830 	History:
1831 		Added on March 14, 2021
1832 
1833 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1834 
1835 +/
1836 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1837 	TrueColorImage got;
1838 	version(X11) {
1839 		auto display = XDisplayConnection.get;
1840 		if(handle == 0)
1841 			handle = RootWindow(display, DefaultScreen(display));
1842 
1843 		if(width == 0 || height == 0) {
1844 			Window root;
1845 			int xpos, ypos;
1846 			uint widthret, heightret, borderret, depthret;
1847 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1848 
1849 			if(width == 0)
1850 				width = widthret;
1851 			if(height == 0)
1852 				height = heightret;
1853 		}
1854 
1855 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1856 
1857 		// https://github.com/adamdruppe/arsd/issues/98
1858 
1859 		auto i = new Image(image);
1860 		got = i.toTrueColorImage();
1861 
1862 		XDestroyImage(image);
1863 	} else version(Windows) {
1864 		auto hdc = GetDC(handle);
1865 		scope(exit) ReleaseDC(handle, hdc);
1866 
1867 		if(width == 0 || height == 0) {
1868 			BITMAP bmHeader;
1869 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1870 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1871 			if(width == 0)
1872 				width = bmHeader.bmWidth;
1873 			if(height == 0)
1874 				height = bmHeader.bmHeight;
1875 		}
1876 
1877 		auto i = new Image(width, height);
1878 		HDC hdcMem = CreateCompatibleDC(hdc);
1879 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1880 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1881 		SelectObject(hdcMem, hbmOld);
1882 		DeleteDC(hdcMem);
1883 
1884 		got = i.toTrueColorImage();
1885 	} else featureNotImplemented();
1886 
1887 	return got;
1888 }
1889 
1890 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1891 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1892 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1893 
1894 version(Windows)
1895 shared static this() {
1896 	auto lib = LoadLibrary("User32.dll");
1897 	if(lib is null)
1898 		return;
1899 	//scope(exit)
1900 		//FreeLibrary(lib);
1901 
1902 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1903 
1904 	if(SetProcessDpiAwarenessContext is null)
1905 		return;
1906 
1907 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1908 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1909 		//writeln(GetLastError());
1910 	}
1911 
1912 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1913 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1914 }
1915 
1916 /++
1917 	Blocking mode for event loop calls associated with a window instance.
1918 
1919 	History:
1920 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1921 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1922 		is, all would block until the application quit.
1923 
1924 		That behavior can still be achieved here with `untilApplicationQuits`,
1925 		or explicitly calling the top-level `EventLoop.get.run` function.
1926 +/
1927 enum BlockingMode {
1928 	/++
1929 		The event loop call will block until the whole application is ready
1930 		to quit if it is the only one running, but if it is nested inside
1931 		another one, it will only block until the window you're calling it on
1932 		closes.
1933 	+/
1934 	automatic             = 0x00,
1935 	/++
1936 		The event loop call will only return when the whole application
1937 		is ready to quit. This usually means all windows have been closed.
1938 
1939 		This is appropriate for your main application event loop.
1940 	+/
1941 	untilApplicationQuits = 0x01,
1942 	/++
1943 		The event loop will return when the window you're calling it on
1944 		closes. If there are other windows still open, they may be destroyed
1945 		unless you have another event loop running later.
1946 
1947 		This might be appropriate for a modal dialog box loop. Remember that
1948 		other windows are still processing input though, so you can end up
1949 		with a lengthy call stack if this happens in a loop, similar to a
1950 		recursive function (well, it literally is a recursive function, just
1951 		not an obvious looking one).
1952 	+/
1953 	untilWindowCloses     = 0x02,
1954 	/++
1955 		If an event loop is already running, this call will immediately
1956 		return, allowing the existing loop to handle it. If not, this call
1957 		will block until the condition you bitwise-or into the flag.
1958 
1959 		The default is to block until the application quits, same as with
1960 		the `automatic` setting (since if it were nested, which triggers until
1961 		window closes in automatic, this flag would instead not block at all),
1962 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1963 		it will only nest until the window closes. You might want that if you are
1964 		going to open two windows simultaneously and want closing just one of them
1965 		to trigger the event loop return.
1966 	+/
1967 	onlyIfNotNested       = 0x10,
1968 }
1969 
1970 /++
1971 	Window corner visuals preference
1972  +/
1973 enum CornerStyle {
1974 	/++
1975 		Use the default style automatically applied by the system or its window manager/compositor.
1976 	 +/
1977 	automatic,
1978 
1979 	/++
1980 		Prefer rectangular window corners
1981 	 +/
1982 	rectangular,
1983 
1984 	/++
1985 		Prefer rounded window corners
1986 	 +/
1987 	rounded,
1988 
1989 	/++
1990 		Prefer slightly-rounded window corners
1991 	 +/
1992 	roundedSlightly,
1993 }
1994 
1995 /++
1996 	The flagship window class.
1997 
1998 
1999 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
2000 	out of more advanced or complex features of the underlying windowing system.
2001 
2002 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
2003 	and get a suitable window to work with.
2004 
2005 	From there, you can opt into additional features, like custom resizability and OpenGL support
2006 	with the next two constructor arguments. Or, if you need even more, you can set a window type
2007 	and customization flags with the final two constructor arguments.
2008 
2009 	If none of that works for you, you can also create a window using native function calls, then
2010 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
2011 	though, if you do this, managing the window is still your own responsibility! Notably, you
2012 	will need to destroy it yourself.
2013 +/
2014 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
2015 
2016 	/++
2017 		Copies the window's current state into a [TrueColorImage].
2018 
2019 		Be warned: this can be a very slow operation
2020 
2021 		History:
2022 			Actually implemented on March 14, 2021
2023 	+/
2024 	TrueColorImage takeScreenshot() {
2025 		version(Windows)
2026 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
2027 		else version(OSXCocoa)
2028 			throw new NotYetImplementedException();
2029 		else
2030 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
2031 	}
2032 
2033 	/++
2034 		Returns the actual logical DPI for the window on its current display monitor. If the window
2035 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
2036 
2037 		Please note this function may return zero if it doesn't know the answer!
2038 
2039 
2040 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
2041 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
2042 
2043 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
2044 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
2045 		window primarily resides on by checking the center point of the window against the monitor map.
2046 
2047 		Returns:
2048 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
2049 			assumes the X and Y dpi are the same.
2050 
2051 		History:
2052 			Added November 26, 2021 (dub v10.4)
2053 
2054 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
2055 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
2056 			that.
2057 
2058 		Bugs:
2059 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
2060 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
2061 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
2062 			and 1.5 on the secondary monitor.
2063 
2064 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
2065 			is a historical misnomer - the real thing of interest is the scale factor and due to
2066 			compatibility concerns the scale would modify dpi values to trick applications. But since
2067 			that's the terminology common out there, I used it too.
2068 
2069 		See_Also:
2070 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
2071 			as this since the window many be on a different monitor, but it is a reasonable fallback
2072 			to use if `actualDpi` returns 0.
2073 
2074 			[onDpiChanged] is changed when `actualDpi` has changed.
2075 	+/
2076 	int actualDpi() {
2077 		version(X11) bool useFallbackDpi = false;
2078 		if(!actualDpiLoadAttempted) {
2079 			// FIXME: do the actual monitor we are on
2080 			// and on X this is a good chance to load the monitor map.
2081 			version(Windows) {
2082 				if(GetDpiForWindow)
2083 					actualDpi_ = GetDpiForWindow(impl.hwnd);
2084 			} else version(X11) {
2085 				if(!xRandrInfoLoadAttemped) {
2086 					xRandrInfoLoadAttemped = true;
2087 					if(!XRandrLibrary.attempted) {
2088 						XRandrLibrary.loadDynamicLibrary();
2089 					}
2090 
2091 					if(XRandrLibrary.loadSuccessful) {
2092 						auto display = XDisplayConnection.get;
2093 						int scratch;
2094 						int major, minor;
2095 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
2096 							goto fallback;
2097 
2098 						XRRQueryVersion(display, &major, &minor);
2099 						if(major <= 1 && minor < 5)
2100 							goto fallback;
2101 
2102 						int count;
2103 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
2104 						if(monitors is null)
2105 							goto fallback;
2106 						scope(exit) XRRFreeMonitors(monitors);
2107 
2108 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
2109 						MonitorInfo.info.assumeSafeAppend();
2110 						foreach(idx, monitor; monitors[0 .. count]) {
2111 							MonitorInfo.info ~= MonitorInfo(
2112 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2113 								Size(monitor.mwidth, monitor.mheight),
2114 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
2115 							);
2116 
2117 							/+
2118 							if(monitor.mwidth == 0 || monitor.mheight == 0)
2119 							// unknown physical size, just guess 96 to avoid divide by zero
2120 							MonitorInfo.info ~= MonitorInfo(
2121 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2122 								Size(monitor.mwidth, monitor.mheight),
2123 								96
2124 							);
2125 							else
2126 							// and actual thing
2127 							MonitorInfo.info ~= MonitorInfo(
2128 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2129 								Size(monitor.mwidth, monitor.mheight),
2130 								minInternal(
2131 									// millimeter to int then rounding up.
2132 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
2133 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
2134 								)
2135 							);
2136 							+/
2137 						}
2138 					// writeln("Here", MonitorInfo.info);
2139 					}
2140 				}
2141 
2142 				if(XRandrLibrary.loadSuccessful) {
2143 					updateActualDpi(true);
2144 					// writeln("updated");
2145 
2146 					if(!requestedInput) {
2147 						// this is what requests live updates should the configuration change
2148 						// each time you select input, it sends an initial event, so very important
2149 						// to not get into a loop of selecting input, getting event, updating data,
2150 						// and reselecting input...
2151 						requestedInput = true;
2152 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
2153 						// writeln("requested input");
2154 					}
2155 				} else {
2156 					fallback:
2157 					// make sure we disable events that aren't coming
2158 					xrrEventBase = -1;
2159 					// best guess... respect the custom scaling user command to some extent at least though
2160 					useFallbackDpi = true;
2161 				}
2162 			} else version(OSXCocoa) {
2163 				actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME
2164 			}
2165 			actualDpiLoadAttempted = true;
2166 		} else version(X11) if(MonitorInfo.info.length == 0) {
2167 			useFallbackDpi = true;
2168 		}
2169 
2170 		version(X11)
2171 		if(useFallbackDpi || actualDpi_ == 0) // FIXME: the actualDpi_ will be populated eventually when we get the first synthetic configure event from the window manager, but that might be a little while so actualDpi_ can be 0 until then...
2172 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
2173 		return actualDpi_;
2174 	}
2175 
2176 	private int actualDpi_;
2177 	private bool actualDpiLoadAttempted;
2178 
2179 	version(X11) private {
2180 		bool requestedInput;
2181 		static bool xRandrInfoLoadAttemped;
2182 		struct MonitorInfo {
2183 			Rectangle position;
2184 			Size size;
2185 			int dpi;
2186 
2187 			static MonitorInfo[] info;
2188 		}
2189 		bool screenPositionKnown;
2190 		int screenPositionX;
2191 		int screenPositionY;
2192 		void updateActualDpi(bool loadingNow = false) {
2193 			if(!loadingNow && !actualDpiLoadAttempted)
2194 				actualDpi(); // just to make it do the load
2195 			foreach(idx, m; MonitorInfo.info) {
2196 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
2197 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
2198 					actualDpi_ = m.dpi;
2199 					// writeln("monitor ", idx);
2200 					if(changed && onDpiChanged)
2201 						onDpiChanged();
2202 					break;
2203 				}
2204 			}
2205 		}
2206 	}
2207 
2208 	/++
2209 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2210 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2211 
2212 		History:
2213 			Added November 26, 2021 (dub v10.4)
2214 
2215 		See_Also:
2216 			[actualDpi]
2217 	+/
2218 	void delegate() onDpiChanged;
2219 
2220 	version(X11) {
2221 		void recreateAfterDisconnect() {
2222 			if(!stateDiscarded) return;
2223 
2224 			if(_parent !is null && _parent.stateDiscarded)
2225 				_parent.recreateAfterDisconnect();
2226 
2227 			bool wasHidden = hidden;
2228 
2229 			activeScreenPainter = null; // should already be done but just to confirm
2230 
2231 			actualDpi_ = 0;
2232 			actualDpiLoadAttempted = false;
2233 			xRandrInfoLoadAttemped = false;
2234 
2235 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2236 
2237 			if(auto dh = dropHandler) {
2238 				dropHandler = null;
2239 				enableDragAndDrop(this, dh);
2240 			}
2241 
2242 			if(recreateAdditionalConnectionState)
2243 				recreateAdditionalConnectionState();
2244 
2245 			hidden = wasHidden;
2246 			stateDiscarded = false;
2247 		}
2248 
2249 		bool stateDiscarded;
2250 		void discardConnectionState() {
2251 			if(XDisplayConnection.display)
2252 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2253 			if(discardAdditionalConnectionState)
2254 				discardAdditionalConnectionState();
2255 			stateDiscarded = true;
2256 		}
2257 
2258 		void delegate() discardAdditionalConnectionState;
2259 		void delegate() recreateAdditionalConnectionState;
2260 
2261 	}
2262 
2263 	private DropHandler dropHandler;
2264 
2265 	SimpleWindow _parent;
2266 	bool beingOpenKeepsAppOpen = true;
2267 	/++
2268 		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.
2269 
2270 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2271 
2272 		Params:
2273 
2274 		width = the width of the window's client area, in pixels
2275 		height = the height of the window's client area, in pixels
2276 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2277 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2278 		resizable = [Resizability] has three options:
2279 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2280 			$(P `fixedSize` will not allow the user to resize the window.)
2281 			$(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.)
2282 		windowType = The type of window you want to make.
2283 		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.
2284 		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".
2285 	+/
2286 	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) {
2287 		claimGuiThread();
2288 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2289 		this._width = this._virtualWidth = width;
2290 		this._height = this._virtualHeight = height;
2291 		this.openglMode = opengl;
2292 		version(X11) {
2293 			// auto scale not implemented except with opengl and even there it is kinda weird
2294 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2295 				resizable = Resizability.fixedSize;
2296 		}
2297 		this.resizability = resizable;
2298 		this.windowType = windowType;
2299 		this.customizationFlags = customizationFlags;
2300 		this._title = (title is null ? "D Application" : title);
2301 		this._parent = parent;
2302 		impl.createWindow(width, height, this._title, opengl, parent);
2303 
2304 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2305 			beingOpenKeepsAppOpen = false;
2306 	}
2307 
2308 	/// ditto
2309 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2310 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2311 	}
2312 
2313 	/// Same as above, except using the `Size` struct instead of separate width and height.
2314 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2315 		this(size.width, size.height, title, opengl, resizable);
2316 	}
2317 
2318 	/// ditto
2319 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2320 		this(size, title, opengl, resizable);
2321 	}
2322 
2323 
2324 	/++
2325 		Creates a window based on the given [Image]. It's client area
2326 		width and height is equal to the image. (A window's client area
2327 		is the drawable space inside; it excludes the title bar, etc.)
2328 
2329 		Windows based on images will not be resizable and do not use OpenGL.
2330 
2331 		It will draw the image in upon creation, but this will be overwritten
2332 		upon any draws, including the initial window visible event.
2333 
2334 		You probably do not want to use this and it may be removed from
2335 		the library eventually, or I might change it to be a "permanent"
2336 		background image; one that is automatically drawn on it before any
2337 		other drawing event. idk.
2338 	+/
2339 	this(Image image, string title = null) {
2340 		this(image.width, image.height, title);
2341 		this.image = image;
2342 	}
2343 
2344 	/++
2345 		Wraps a native window handle with very little additional processing - notably no destruction
2346 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2347 		windows created through the low level API (so you can use platform-specific options and
2348 		other details SimpleWindow does not expose) available to the event loop wrappers.
2349 	+/
2350 	this(NativeWindowHandle nativeWindow) {
2351 		windowType = WindowTypes.minimallyWrapped;
2352 		version(Windows)
2353 			impl.hwnd = nativeWindow;
2354 		else version(X11) {
2355 			impl.window = nativeWindow;
2356 			if(nativeWindow)
2357 				display = XDisplayConnection.get(); // get initial display to not segfault
2358 		} else version(Emscripten) {
2359 			// FIXME
2360 		} else version(OSXCocoa) {
2361 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2362 		} else featureNotImplemented();
2363 		// FIXME: set the size correctly
2364 		_width = 1;
2365 		_height = 1;
2366 		if(nativeWindow)
2367 			nativeMapping[cast(void*) nativeWindow] = this;
2368 
2369 		beingOpenKeepsAppOpen = false;
2370 
2371 		if(nativeWindow)
2372 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2373 		_suppressDestruction = true; // so it doesn't try to close
2374 	}
2375 
2376 	/++
2377 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2378 		The delegate will be called when the window manager asks you to take focus.
2379 
2380 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2381 
2382 		History:
2383 			Added April 1, 2022 (dub v10.8)
2384 	+/
2385 	SimpleWindow delegate() setRequestedInputFocus;
2386 
2387 	/// Experimental, do not use yet
2388 	/++
2389 		Grabs exclusive input from the user until you release it with
2390 		[releaseInputGrab].
2391 
2392 
2393 		Note: it is extremely rude to do this without good reason.
2394 		Reasons may include doing some kind of mouse drag operation
2395 		or popping up a temporary menu that should get events and will
2396 		be dismissed at ease by the user clicking away.
2397 
2398 		Params:
2399 			keyboard = do you want to grab keyboard input?
2400 			mouse = grab mouse input?
2401 			confine = confine the mouse cursor to inside this window?
2402 
2403 		History:
2404 			Prior to March 11, 2021, grabbing the keyboard would always also
2405 			set the X input focus. Now, it only focuses if it is a non-transient
2406 			window and otherwise manages the input direction internally.
2407 
2408 			This means spurious focus/blur events will no longer be sent and the
2409 			application will not steal focus from other applications (which the
2410 			window manager may have rejected anyway).
2411 	+/
2412 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2413 		static if(UsingSimpledisplayX11) {
2414 			XSync(XDisplayConnection.get, 0);
2415 			if(keyboard) {
2416 				if(isTransient && _parent) {
2417 					/*
2418 					FIXME:
2419 						setting the keyboard focus is not actually that helpful, what I more likely want
2420 						is the events from the parent window to be sent over here if we're transient.
2421 					*/
2422 
2423 					_parent.inputProxy = this;
2424 				} else {
2425 
2426 					SimpleWindow setTo;
2427 					if(setRequestedInputFocus !is null)
2428 						setTo = setRequestedInputFocus();
2429 					if(setTo is null)
2430 						setTo = this;
2431 
2432 					// sdpyPrintDebugString("grabInput() ", setTo.impl.window;
2433 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2434 				}
2435 			}
2436 			if(mouse) {
2437 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2438 				EventMask.PointerMotionMask // FIXME: not efficient
2439 				| EventMask.ButtonPressMask
2440 				| EventMask.ButtonReleaseMask
2441 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2442 				)
2443 			{
2444 				XSync(XDisplayConnection.get, 0);
2445 				import core.stdc.stdio;
2446 				printf("Grab input failed %d\n", res);
2447 				//throw new Exception("Grab input failed");
2448 			} else {
2449 				// cool
2450 			}
2451 			}
2452 
2453 		} else version(Windows) {
2454 			// FIXME: keyboard?
2455 			SetCapture(impl.hwnd);
2456 			if(confine) {
2457 				RECT rcClip;
2458 				//RECT rcOldClip;
2459 				//GetClipCursor(&rcOldClip);
2460 				GetWindowRect(hwnd, &rcClip);
2461 				ClipCursor(&rcClip);
2462 			}
2463 		} else version(Emscripten) {
2464 			// nothing necessary
2465 		} else version(OSXCocoa) {
2466 			// throw new NotYetImplementedException();
2467 		} else static assert(0);
2468 	}
2469 
2470 	private Point imePopupLocation = Point(0, 0);
2471 
2472 	/++
2473 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2474 
2475 		Bugs:
2476 			Not implemented outside X11.
2477 	+/
2478 	void setIMEPopupLocation(Point location) {
2479 		static if(UsingSimpledisplayX11) {
2480 			imePopupLocation = location;
2481 			updateIMEPopupLocation();
2482 		} else {
2483 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2484 			// throw new NotYetImplementedException();
2485 		}
2486 	}
2487 
2488 	/// ditto
2489 	void setIMEPopupLocation(int x, int y) {
2490 		return setIMEPopupLocation(Point(x, y));
2491 	}
2492 
2493 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2494 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2495 	// receives a ConfigureNotify event
2496 	private void updateIMEPopupLocation() {
2497 		static if(UsingSimpledisplayX11) {
2498 			if (xic is null) {
2499 				return;
2500 			}
2501 
2502 			XPoint nspot;
2503 			nspot.x = cast(short) imePopupLocation.x;
2504 			nspot.y = cast(short) imePopupLocation.y;
2505 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2506 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2507 			XFree(preeditAttr);
2508 		}
2509 	}
2510 
2511 	private bool imeFocused = true;
2512 
2513 	/++
2514 		Tells the IME whether or not an input field is currently focused in the window.
2515 
2516 		Bugs:
2517 			Not implemented outside X11.
2518 	+/
2519 	void setIMEFocused(bool value) {
2520 		imeFocused = value;
2521 		updateIMEFocused();
2522 	}
2523 
2524 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2525 	private void updateIMEFocused() {
2526 		static if(UsingSimpledisplayX11) {
2527 			if (xic is null) {
2528 				return;
2529 			}
2530 
2531 			if (focused && imeFocused) {
2532 				XSetICFocus(xic);
2533 			} else {
2534 				XUnsetICFocus(xic);
2535 			}
2536 		}
2537 	}
2538 
2539 	/++
2540 		Returns the native window.
2541 
2542 		History:
2543 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2544 			to access it through the `impl` member (which is semi-supported
2545 			but platform specific and here it is simple enough to offer an accessor).
2546 
2547 		Bugs:
2548 			Not implemented outside Windows or X11.
2549 	+/
2550 	NativeWindowHandle nativeWindowHandle() {
2551 		version(X11)
2552 			return impl.window;
2553 		else version(Windows)
2554 			return impl.hwnd;
2555 		else
2556 			throw new NotYetImplementedException();
2557 	}
2558 
2559 	private bool isTransient() {
2560 		with(WindowTypes)
2561 		final switch(windowType) {
2562 			case normal, undecorated, eventOnly:
2563 			case nestedChild, minimallyWrapped:
2564 				return (customizationFlags & WindowFlags.transient) ? true : false;
2565 			case dropdownMenu, popupMenu, notification, dialog, tooltip, dnd, comboBoxDropdown:
2566 				return true;
2567 		}
2568 	}
2569 
2570 	private SimpleWindow inputProxy;
2571 
2572 	/++
2573 		Releases the grab acquired by [grabInput].
2574 	+/
2575 	void releaseInputGrab() {
2576 		static if(UsingSimpledisplayX11) {
2577 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2578 			if(_parent)
2579 				_parent.inputProxy = null;
2580 		} else version(Windows) {
2581 			ReleaseCapture();
2582 			ClipCursor(null);
2583 		} else version(OSXCocoa) {
2584 			// throw new NotYetImplementedException();
2585 		} else version(Emscripten) {
2586 			// nothing needed
2587 		} else static assert(0);
2588 	}
2589 
2590 	/++
2591 		Sets the input focus to this window.
2592 
2593 		You shouldn't call this very often - please let the user control the input focus.
2594 	+/
2595 	void focus() {
2596 		static if(UsingSimpledisplayX11) {
2597 			SimpleWindow setTo;
2598 			if(setRequestedInputFocus !is null)
2599 				setTo = setRequestedInputFocus();
2600 			if(setTo is null)
2601 				setTo = this;
2602 			// sdpyPrintDebugString("sdpy.focus() ", setTo.impl.window);
2603 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2604 		} else version(Windows) {
2605 			SetFocus(this.impl.hwnd);
2606 		} else version(Emscripten) {
2607 			throw new NotYetImplementedException();
2608 		} else version(OSXCocoa) {
2609 			throw new NotYetImplementedException();
2610 		} else static assert(0);
2611 	}
2612 
2613 	/++
2614 		Requests attention from the user for this window.
2615 
2616 
2617 		The typical result of this function is to change the color
2618 		of the taskbar icon, though it may be tweaked on specific
2619 		platforms.
2620 
2621 		It is meant to unobtrusively tell the user that something
2622 		relevant to them happened in the background and they should
2623 		check the window when they get a chance. Upon receiving the
2624 		keyboard focus, the window will automatically return to its
2625 		natural state.
2626 
2627 		If the window already has the keyboard focus, this function
2628 		may do nothing, because the user is presumed to already be
2629 		giving the window attention.
2630 
2631 		Implementation_note:
2632 
2633 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2634 		atom on X11 and the FlashWindow function on Windows.
2635 	+/
2636 	void requestAttention() {
2637 		if(_focused)
2638 			return;
2639 
2640 		version(Windows) {
2641 			FLASHWINFO info;
2642 			info.cbSize = info.sizeof;
2643 			info.hwnd = impl.hwnd;
2644 			info.dwFlags = FLASHW_TRAY;
2645 			info.uCount = 1;
2646 
2647 			FlashWindowEx(&info);
2648 
2649 		} else version(X11) {
2650 			demandingAttention = true;
2651 			demandAttention(this, true);
2652 		} else version(Emscripten) {
2653 			throw new NotYetImplementedException();
2654 		} else version(OSXCocoa) {
2655 			throw new NotYetImplementedException();
2656 		} else static assert(0);
2657 	}
2658 
2659 	private bool _focused;
2660 
2661 	version(X11) private bool demandingAttention;
2662 
2663 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2664 	/// You'll have to call `close()` manually if you set this delegate.
2665 	void delegate () closeQuery;
2666 
2667 	/// This will be called when window visibility was changed.
2668 	void delegate (bool becomesVisible) visibilityChanged;
2669 
2670 	/// This will be called when window becomes visible for the first time.
2671 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2672 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2673 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2674 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2675 	private bool _visibleForTheFirstTimeCalled;
2676 	void delegate () visibleForTheFirstTime;
2677 
2678 	/// Returns true if the window has been closed.
2679 	final @property bool closed() { return _closed; }
2680 
2681 	private final @property bool notClosed() { return !_closed; }
2682 
2683 	/// Returns true if the window is focused.
2684 	final @property bool focused() { return _focused; }
2685 
2686 	private bool _visible;
2687 	/// Returns true if the window is visible (mapped).
2688 	final @property bool visible() { return _visible; }
2689 
2690 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2691 	void close() {
2692 		if (!_closed) {
2693 			runInGuiThread( {
2694 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2695 				if (onClosing !is null) onClosing();
2696 				impl.closeWindow();
2697 				_closed = true;
2698 			} );
2699 		}
2700 	}
2701 
2702 	/++
2703 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2704 
2705 		History:
2706 			Overload added on March 7, 2021.
2707 	+/
2708 	void close() shared {
2709 		(cast() this).close();
2710 	}
2711 
2712 	/++
2713 
2714 	+/
2715 	void maximize() {
2716 		version(Windows)
2717 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2718 		else version(X11) {
2719 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2720 
2721 			// also note _NET_WM_STATE_FULLSCREEN
2722 		}
2723 
2724 	}
2725 
2726 	private bool _fullscreen;
2727 	version(Windows)
2728 	private WINDOWPLACEMENT g_wpPrev;
2729 
2730 	/// not fully implemented but planned for a future release
2731 	void fullscreen(bool yes) {
2732 		version(Windows) {
2733 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2734 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2735 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2736 				MONITORINFO mi;
2737 				mi.cbSize = MONITORINFO.sizeof;
2738 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2739 					GetMonitorInfo(MonitorFromWindow(hwnd,
2740 					               MONITOR_DEFAULTTOPRIMARY), &mi)) {
2741 					SetWindowLong(hwnd, GWL_STYLE,
2742 					              dwStyle & ~WS_OVERLAPPEDWINDOW);
2743 					SetWindowPos(hwnd, HWND_TOP,
2744 					             mi.rcMonitor.left, mi.rcMonitor.top,
2745 					             mi.rcMonitor.right - mi.rcMonitor.left,
2746 					             mi.rcMonitor.bottom - mi.rcMonitor.top,
2747 					             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2748 				}
2749 			} else {
2750 				SetWindowLong(hwnd, GWL_STYLE,
2751 				              dwStyle | WS_OVERLAPPEDWINDOW);
2752 				SetWindowPlacement(hwnd, &g_wpPrev);
2753 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2754 				             SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2755 				             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2756 			}
2757 
2758 		} else version(X11) {
2759 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2760 		}
2761 
2762 		_fullscreen = yes;
2763 
2764 	}
2765 
2766 	bool fullscreen() {
2767 		return _fullscreen;
2768 	}
2769 
2770 	/++
2771 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2772 
2773 	+/
2774 	void minimize() {
2775 		version(Windows)
2776 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2777 		//else version(X11)
2778 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2779 	}
2780 
2781 	/// Alias for `hidden = false`
2782 	void show() {
2783 		hidden = false;
2784 	}
2785 
2786 	/// Alias for `hidden = true`
2787 	void hide() {
2788 		hidden = true;
2789 	}
2790 
2791 	/// Hide cursor when it enters the window.
2792 	void hideCursor() {
2793 		version(OSXCocoa) throw new NotYetImplementedException(); else
2794 		if (!_closed) impl.hideCursor();
2795 	}
2796 
2797 	/// Don't hide cursor when it enters the window.
2798 	void showCursor() {
2799 		version(OSXCocoa) throw new NotYetImplementedException(); else
2800 		if (!_closed) impl.showCursor();
2801 	}
2802 
2803 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2804 	 *
2805 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2806 	 * control. Try to think for other approaches before using this function.
2807 	 *
2808 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2809 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2810 	 *       receive "mouse moved here" event.
2811 	 */
2812 	bool warpMouse (int x, int y) {
2813 		version(X11) {
2814 			if (!_closed) { impl.warpMouse(x, y); return true; }
2815 		} else version(Windows) {
2816 			if (!_closed) {
2817 				POINT point;
2818 				point.x = x;
2819 				point.y = y;
2820 				if(ClientToScreen(impl.hwnd, &point)) {
2821 					SetCursorPos(point.x, point.y);
2822 					return true;
2823 				}
2824 			}
2825 		}
2826 		return false;
2827 	}
2828 
2829 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2830 	void sendDummyEvent () {
2831 		version(X11) {
2832 			if (!_closed) { impl.sendDummyEvent(); }
2833 		}
2834 	}
2835 
2836 	/// Set window minimal size.
2837 	void setMinSize (int minwidth, int minheight) {
2838 		version(OSXCocoa) throw new NotYetImplementedException(); else
2839 		if (!_closed) impl.setMinSize(minwidth, minheight);
2840 	}
2841 
2842 	/// Set window maximal size.
2843 	void setMaxSize (int maxwidth, int maxheight) {
2844 		version(OSXCocoa) throw new NotYetImplementedException(); else
2845 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2846 	}
2847 
2848 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2849 	/// Currently only supported on X11.
2850 	void setResizeGranularity (int granx, int grany) {
2851 		version(OSXCocoa) throw new NotYetImplementedException(); else
2852 		if (!_closed) impl.setResizeGranularity(granx, grany);
2853 	}
2854 
2855 	/// Move window.
2856 	void move(int x, int y) {
2857 		version(OSXCocoa) throw new NotYetImplementedException(); else
2858 		if (!_closed) impl.move(x, y);
2859 	}
2860 
2861 	/// ditto
2862 	void move(Point p) {
2863 		version(OSXCocoa) throw new NotYetImplementedException(); else
2864 		if (!_closed) impl.move(p.x, p.y);
2865 	}
2866 
2867 	/++
2868 		Resize window.
2869 
2870 		Note that the width and height of the window are NOT instantly
2871 		updated - it waits for the window manager to approve the resize
2872 		request, which means you must return to the event loop before the
2873 		width and height are actually changed.
2874 	+/
2875 	void resize(int w, int h) {
2876 		if(!_closed && _fullscreen) fullscreen = false;
2877 		if (!_closed) impl.resize(w, h);
2878 	}
2879 
2880 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2881 	void moveResize (int x, int y, int w, int h) {
2882 		if(!_closed && _fullscreen) fullscreen = false;
2883 		if (!_closed) impl.moveResize(x, y, w, h);
2884 	}
2885 
2886 	private bool _hidden;
2887 
2888 	/// Returns true if the window is hidden.
2889 	final @property bool hidden() {
2890 		return _hidden;
2891 	}
2892 
2893 	/// Shows or hides the window based on the bool argument.
2894 	final @property void hidden(bool b) {
2895 		_hidden = b;
2896 		version(Windows) {
2897 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2898 		} else version(X11) {
2899 			if(b)
2900 				//XUnmapWindow(impl.display, impl.window);
2901 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2902 			else
2903 				XMapWindow(impl.display, impl.window);
2904 		} else version(OSXCocoa) {
2905 			impl.window.setIsVisible = !b;
2906 			if(!hidden)
2907 			impl.view.setNeedsDisplay(true);
2908 		} else version(Emscripten) {
2909 		} else static assert(0);
2910 	}
2911 
2912 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2913 	void opacity(double opacity) @property
2914 	in {
2915 		assert(opacity >= 0 && opacity <= 1);
2916 	} do {
2917 		version (Windows) {
2918 			impl.setOpacity(cast(ubyte)(255 * opacity));
2919 		} else version (X11) {
2920 			impl.setOpacity(cast(uint)(uint.max * opacity));
2921 		} else throw new NotYetImplementedException();
2922 	}
2923 
2924 	/++
2925 		Sets your event handlers, without entering the event loop. Useful if you
2926 		have multiple windows - set the handlers on each window, then only do
2927 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2928 
2929 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2930 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2931 		delegate signatures.
2932 	+/
2933 	void setEventHandlers(T...)(T eventHandlers) {
2934 		// FIXME: add more events
2935 		foreach(handler; eventHandlers) {
2936 			static if(__traits(compiles, handleKeyEvent = handler)) {
2937 				handleKeyEvent = handler;
2938 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2939 				handleCharEvent = handler;
2940 			} else static if(__traits(compiles, handlePulse = handler)) {
2941 				handlePulse = handler;
2942 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2943 				handleMouseEvent = handler;
2944 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2945 		}
2946 	}
2947 
2948 	/++
2949 		The event loop automatically returns when the window is closed
2950 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2951 		pulse timer is created. The event loop will block until an event
2952 		arrives or the pulse timer goes off.
2953 
2954 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2955 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2956 		[handleMouseEvent], based on the signature of delegates you provide.
2957 
2958 		Give one with no parameters to set a timer pulse handler. Give one that
2959 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2960 		and one that takes `dchar` for a char event handler. You can use as many
2961 		or as few handlers as you need for your application.
2962 
2963 		Bugs:
2964 
2965 		$(PITFALL
2966 			You should always have one event loop live for your application.
2967 			If you make two windows in sequence, the second call to eventLoop
2968 			might fail:
2969 
2970 			---
2971 			// don't do this!
2972 			auto window = new SimpleWindow();
2973 			window.eventLoop(0);
2974 
2975 			auto window2 = new SimpleWindow();
2976 			window2.eventLoop(0); // problematic! might crash
2977 			---
2978 
2979 			simpledisplay's current implementation assumes that final cleanup is
2980 			done when the event loop refcount reaches zero. So after the first
2981 			eventLoop returns, when there isn't already another one active, it assumes
2982 			the program will exit soon and cleans up.
2983 
2984 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2985 			it eventually, but in the mean time, there's an easy solution:
2986 
2987 			---
2988 			// do this
2989 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2990 
2991 			auto window = new SimpleWindow();
2992 			window.eventLoop(0);
2993 
2994 			auto window2 = new SimpleWindow();
2995 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2996 			---
2997 
2998 			By adding a top-level reference to the event loop, it ensures the final cleanup
2999 			is not performed until it goes out of scope too, letting the individual window loops
3000 			work without trouble despite the bug.
3001 		)
3002 
3003 		History:
3004 			The overload without `pulseTimeout` was added on December 8, 2021.
3005 
3006 			On December 9, 2021, the default blocking mode (which is now configurable
3007 			because [eventLoopWithBlockingMode] was added) switched from
3008 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
3009 			should almost never be noticeable to you since the typical simpledisplay
3010 			paradigm has been (and I still recommend) to have one `eventLoop` call.
3011 
3012 		See_Also:
3013 			[eventLoopWithBlockingMode]
3014 	+/
3015 	final int eventLoop(T...)(
3016 		long pulseTimeout,    /// set to zero if you don't want a pulse.
3017 		T eventHandlers) /// delegate list like std.concurrency.receive
3018 	{
3019 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
3020 	}
3021 
3022 	/// ditto
3023 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
3024 	{
3025 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
3026 	}
3027 
3028 	/++
3029 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
3030 
3031 		History:
3032 			Added December 8, 2021 (dub v10.5)
3033 
3034 			Previously, this implementation was right inside [eventLoop], but when I wanted
3035 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
3036 			just renamed it instead of adding as an overload. Besides, the new name makes it
3037 			easier to remember the order and avoids ambiguity between two int-like params anyway.
3038 
3039 		See_Also:
3040 			[SimpleWindow.eventLoop], [EventLoop]
3041 
3042 		Bugs:
3043 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
3044 	+/
3045 	final int eventLoopWithBlockingMode(T...)(
3046 		BlockingMode blockingMode, /// when you want this function to block until
3047 		long pulseTimeout,    /// set to zero if you don't want a pulse.
3048 		T eventHandlers) /// delegate list like std.concurrency.receive
3049 	{
3050 		setEventHandlers(eventHandlers);
3051 
3052 		version(with_eventloop) {
3053 			// delegates event loop to my other module
3054 			version(X11)
3055 				XFlush(display);
3056 
3057 			import arsd.eventloop;
3058 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
3059 			scope(exit) clearInterval(handle);
3060 
3061 			loop();
3062 			return 0;
3063 		} else {
3064 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
3065 
3066 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
3067 				return 0;
3068 
3069 			return el.run(
3070 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
3071 					null :
3072 					&this.notClosed
3073 			);
3074 		}
3075 	}
3076 
3077 	/++
3078 		This lets you draw on the window (or its backing buffer) using basic
3079 		2D primitives.
3080 
3081 		Be sure to call this in a limited scope because your changes will not
3082 		actually appear on the window until ScreenPainter's destructor runs.
3083 
3084 		Returns: an instance of [ScreenPainter], which has the drawing methods
3085 		on it to draw on this window.
3086 
3087 		Params:
3088 			manualInvalidations = if you set this to true, you will need to
3089 			set the invalid rectangle on the painter yourself. If false, it
3090 			assumes the whole window has been redrawn each time you draw.
3091 
3092 			Only invalidated rectangles are blitted back to the window when
3093 			the destructor runs. Doing this yourself can reduce flickering
3094 			of child windows.
3095 
3096 		History:
3097 			The `manualInvalidations` parameter overload was added on
3098 			December 30, 2021 (dub v10.5)
3099 	+/
3100 	ScreenPainter draw() {
3101 		return draw(false);
3102 	}
3103 	/// ditto
3104 	ScreenPainter draw(bool manualInvalidations) {
3105 		return impl.getPainter(manualInvalidations);
3106 	}
3107 
3108 	// This is here to implement the interface we use for various native handlers.
3109 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
3110 
3111 	// maps native window handles to SimpleWindow instances, if there are any
3112 	// you shouldn't need this, but it is public in case you do in a native event handler or something
3113 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
3114 	version(OSXCocoa)
3115 	public __gshared SimpleWindow[void*] nativeMapping;
3116 	else
3117 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
3118 
3119 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
3120 	private int _virtualWidth;
3121 	private int _virtualHeight;
3122 
3123 	/// Width of the window's drawable client area, in pixels.
3124 	@scriptable
3125 	final @property int width() const pure nothrow @safe @nogc {
3126 		if(resizability == Resizability.automaticallyScaleIfPossible)
3127 			return _virtualWidth;
3128 		else
3129 			return _width;
3130 	}
3131 
3132 	/// Height of the window's drawable client area, in pixels.
3133 	@scriptable
3134 	final @property int height() const pure nothrow @safe @nogc {
3135 		if(resizability == Resizability.automaticallyScaleIfPossible)
3136 			return _virtualHeight;
3137 		else
3138 			return _height;
3139 	}
3140 
3141 	/++
3142 		Returns the actual size of the window, bypassing the logical
3143 		illusions of [Resizability.automaticallyScaleIfPossible].
3144 
3145 		History:
3146 			Added November 11, 2022 (dub v10.10)
3147 	+/
3148 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
3149 		return Size(_width, _height);
3150 	}
3151 
3152 
3153 	private int _width;
3154 	private int _height;
3155 
3156 	// HACK: making the best of some copy constructor woes with refcounting
3157 	private ScreenPainterImplementation* activeScreenPainter_;
3158 
3159 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
3160 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
3161 
3162 	private OpenGlOptions openglMode;
3163 	private Resizability resizability;
3164 	private WindowTypes windowType;
3165 	private int customizationFlags;
3166 
3167 	/// `true` if OpenGL was initialized for this window.
3168 	@property bool isOpenGL () const pure nothrow @safe @nogc {
3169 		version(without_opengl)
3170 			return false;
3171 		else
3172 			return (openglMode == OpenGlOptions.yes);
3173 	}
3174 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
3175 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
3176 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
3177 
3178 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3179 	/// to call this, as it's not recommended to share window between threads.
3180 	void mtLock () {
3181 		version(X11) {
3182 			XLockDisplay(this.display);
3183 		}
3184 	}
3185 
3186 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3187 	/// to call this, as it's not recommended to share window between threads.
3188 	void mtUnlock () {
3189 		version(X11) {
3190 			XUnlockDisplay(this.display);
3191 		}
3192 	}
3193 
3194 	/// Emit a beep to get user's attention.
3195 	void beep () {
3196 		version(X11) {
3197 			XBell(this.display, 100);
3198 		} else version(Windows) {
3199 			MessageBeep(0xFFFFFFFF);
3200 		}
3201 	}
3202 
3203 
3204 
3205 	version(without_opengl) {} else {
3206 
3207 		/// 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`.
3208 		void delegate() redrawOpenGlScene;
3209 
3210 		/// This will allow you to change OpenGL vsync state.
3211 		final @property void vsync (bool wait) {
3212 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3213 			version(X11) {
3214 				setAsCurrentOpenGlContext();
3215 				glxSetVSync(display, impl.window, wait);
3216 			} else version(Windows) {
3217 				setAsCurrentOpenGlContext();
3218 				wglSetVSync(wait);
3219 			}
3220 		}
3221 
3222 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3223 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3224 		/// enough without waiting 'em to finish their frame business.
3225 		bool useGLFinish = true;
3226 
3227 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3228 		/// 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.
3229 		void redrawOpenGlSceneNow() {
3230 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3231 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3232 			if(redrawOpenGlScene is null)
3233 				return;
3234 
3235 			this.mtLock();
3236 			scope(exit) this.mtUnlock();
3237 
3238 			this.setAsCurrentOpenGlContext();
3239 
3240 			redrawOpenGlScene();
3241 
3242 			this.swapOpenGlBuffers();
3243 			// 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.
3244 			if (useGLFinish) glFinish();
3245 		}
3246 
3247 		private bool redrawOpenGlSceneSoonSet = false;
3248 		private static class RedrawOpenGlSceneEvent {
3249 			SimpleWindow w;
3250 			this(SimpleWindow w) { this.w = w; }
3251 		}
3252 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3253 		/++
3254 			Queues an opengl redraw as soon as the other pending events are cleared.
3255 		+/
3256 		void redrawOpenGlSceneSoon() {
3257 			if(redrawOpenGlScene is null)
3258 				return;
3259 
3260 			if(!redrawOpenGlSceneSoonSet) {
3261 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3262 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3263 				redrawOpenGlSceneSoonSet = true;
3264 			}
3265 			this.postEvent(redrawOpenGlSceneEvent, true);
3266 		}
3267 
3268 
3269 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3270 		void setAsCurrentOpenGlContext() {
3271 			assert(openglMode == OpenGlOptions.yes);
3272 			version(X11) {
3273 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3274 					throw new Exception("glXMakeCurrent");
3275 			} else version(Windows) {
3276 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3277 				if (!wglMakeCurrent(ghDC, ghRC))
3278 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3279 			}
3280 		}
3281 
3282 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3283 		/// This doesn't throw, returning success flag instead.
3284 		bool setAsCurrentOpenGlContextNT() nothrow {
3285 			assert(openglMode == OpenGlOptions.yes);
3286 			version(X11) {
3287 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3288 			} else version(Windows) {
3289 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3290 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3291 			}
3292 		}
3293 
3294 		/// 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.
3295 		/// This doesn't throw, returning success flag instead.
3296 		bool releaseCurrentOpenGlContext() nothrow {
3297 			assert(openglMode == OpenGlOptions.yes);
3298 			version(X11) {
3299 				return (glXMakeCurrent(display, 0, null) != 0);
3300 			} else version(Windows) {
3301 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3302 				return wglMakeCurrent(ghDC, null) ? true : false;
3303 			}
3304 		}
3305 
3306 		/++
3307 			simpledisplay always uses double buffering, usually automatically. This
3308 			manually swaps the OpenGL buffers. You should only use this if you are NOT
3309 			using the [redrawOpenGlScene] delegate.
3310 
3311 
3312 			You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it
3313 			for you after calling your `redrawOpenGlScene`. Please note that once you swap
3314 			buffers, the contents become undefined - the implementation, in the OpenGL driver
3315 			or the desktop compositor, may not actually just swap two buffers. The back buffer's
3316 			contents are $(B undefined) after calling this function.
3317 
3318 			See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers
3319 			and https://linux.die.net/man/3/glxswapbuffers
3320 
3321 			Remember that this may throw an exception, which you can catch in a multithreaded
3322 			application to keep your thread from dying from an unhandled exception.
3323 		+/
3324 		void swapOpenGlBuffers() {
3325 			assert(openglMode == OpenGlOptions.yes);
3326 			version(X11) {
3327 				if (!this._visible) return; // no need to do this if window is invisible
3328 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3329 				glXSwapBuffers(display, impl.window);
3330 			} else version(Windows) {
3331 				SwapBuffers(ghDC);
3332 			}
3333 		}
3334 	}
3335 
3336 	/++
3337 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3338 
3339 
3340 		---
3341 			auto window = new SimpleWindow(100, 100, "First title");
3342 			window.title = "A new title";
3343 		---
3344 
3345 		You may call this function at any time.
3346 	+/
3347 	@property void title(string title) {
3348 		_title = title;
3349 		impl.setTitle(title);
3350 	}
3351 
3352 	private string _title;
3353 
3354 	/// Gets the title
3355 	@property string title() {
3356 		if(_title is null)
3357 			_title = getRealTitle();
3358 		return _title;
3359 	}
3360 
3361 	/++
3362 		Get the title as set by the window manager.
3363 		May not match what you attempted to set.
3364 	+/
3365 	string getRealTitle() {
3366 		static if(is(typeof(impl.getTitle())))
3367 			return impl.getTitle();
3368 		else
3369 			return null;
3370 	}
3371 
3372 	// don't use this generally it is not yet really released
3373 	version(X11)
3374 	@property Image secret_icon() {
3375 		return secret_icon_inner;
3376 	}
3377 	private Image secret_icon_inner;
3378 
3379 
3380 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3381 	@property void icon(MemoryImage icon) {
3382 		if(icon is null)
3383 			return;
3384 		auto tci = icon.getAsTrueColorImage();
3385 		version(Windows) {
3386 			winIcon = new WindowsIcon(icon);
3387 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3388 		} else version(X11) {
3389 			secret_icon_inner = Image.fromMemoryImage(icon);
3390 			// FIXME: ensure this is correct
3391 			auto display = XDisplayConnection.get;
3392 			arch_ulong[] buffer;
3393 			buffer ~= icon.width;
3394 			buffer ~= icon.height;
3395 			foreach(c; tci.imageData.colors) {
3396 				arch_ulong b;
3397 				b |= c.a << 24;
3398 				b |= c.r << 16;
3399 				b |= c.g << 8;
3400 				b |= c.b;
3401 				buffer ~= b;
3402 			}
3403 
3404 			XChangeProperty(
3405 				display,
3406 				impl.window,
3407 				GetAtom!("_NET_WM_ICON", true)(display),
3408 				GetAtom!"CARDINAL"(display),
3409 				32 /* bits */,
3410 				0 /*PropModeReplace*/,
3411 				buffer.ptr,
3412 				cast(int) buffer.length);
3413 		} else version(OSXCocoa) {
3414 			throw new NotYetImplementedException();
3415 		} else version(Emscripten) {
3416 			throw new NotYetImplementedException();
3417 		} else static assert(0);
3418 	}
3419 
3420 	version(Windows)
3421 		private WindowsIcon winIcon;
3422 
3423 	bool _suppressDestruction;
3424 
3425 	~this() {
3426 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3427 		if(_suppressDestruction)
3428 			return;
3429 		impl.dispose();
3430 	}
3431 
3432 	private bool _closed;
3433 
3434 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3435 	/*
3436 	ScreenPainter drawTransiently() {
3437 		return impl.getPainter();
3438 	}
3439 	*/
3440 
3441 	/// Draws an image on the window. This is meant to provide quick look
3442 	/// of a static image generated elsewhere.
3443 	@property void image(Image i) {
3444 	/+
3445 		version(Windows) {
3446 			BITMAP bm;
3447 			HDC hdc = GetDC(hwnd);
3448 			HDC hdcMem = CreateCompatibleDC(hdc);
3449 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3450 
3451 			GetObject(i.handle, bm.sizeof, &bm);
3452 
3453 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3454 
3455 			SelectObject(hdcMem, hbmOld);
3456 			DeleteDC(hdcMem);
3457 			ReleaseDC(hwnd, hdc);
3458 
3459 			/*
3460 			RECT r;
3461 			r.right = i.width;
3462 			r.bottom = i.height;
3463 			InvalidateRect(hwnd, &r, false);
3464 			*/
3465 		} else
3466 		version(X11) {
3467 			if(!destroyed) {
3468 				if(i.usingXshm)
3469 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3470 				else
3471 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3472 			}
3473 		} else
3474 		version(OSXCocoa) {
3475 			draw().drawImage(Point(0, 0), i);
3476 			setNeedsDisplay(view, true);
3477 		} else static assert(0);
3478 	+/
3479 		auto painter = this.draw;
3480 		painter.drawImage(Point(0, 0), i);
3481 	}
3482 
3483 	/++
3484 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3485 
3486 		---
3487 		window.cursor = GenericCursor.Help;
3488 		// now the window mouse cursor is set to a generic help
3489 		---
3490 
3491 	+/
3492 	@property void cursor(MouseCursor cursor) {
3493 		version(OSXCocoa)
3494 			{} // featureNotImplemented();
3495 		else
3496 		if(this.impl.curHidden <= 0) {
3497 			static if(UsingSimpledisplayX11) {
3498 				auto ch = cursor.cursorHandle;
3499 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3500 			} else version(Windows) {
3501 				auto ch = cursor.cursorHandle;
3502 				impl.currentCursor = ch;
3503 				SetCursor(ch); // redraw without waiting for mouse movement to update
3504 			} else featureNotImplemented();
3505 		}
3506 
3507 	}
3508 
3509 	/// What follows are the event handlers. These are set automatically
3510 	/// by the eventLoop function, but are still public so you can change
3511 	/// them later. wasPressed == true means key down. false == key up.
3512 
3513 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3514 	void delegate(KeyEvent ke) handleKeyEvent;
3515 
3516 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3517 	void delegate(dchar c) handleCharEvent;
3518 
3519 	/// Handles a timer pulse. Settable through setEventHandlers.
3520 	void delegate() handlePulse;
3521 
3522 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3523 	void delegate(bool) onFocusChange;
3524 
3525 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3526 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3527 	void delegate() onClosing;
3528 
3529 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3530 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3531 	 * last minute cleanup. */
3532 	void delegate() onDestroyed;
3533 
3534 	static if (UsingSimpledisplayX11)
3535 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3536 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3537 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3538 	 *
3539 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3540 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3541 
3542 	//version(Windows)
3543 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3544 
3545 	private {
3546 		int lastMouseX = int.min;
3547 		int lastMouseY = int.min;
3548 		void mdx(ref MouseEvent ev) {
3549 			if(lastMouseX == int.min || lastMouseY == int.min) {
3550 				ev.dx = 0;
3551 				ev.dy = 0;
3552 			} else {
3553 				ev.dx = ev.x - lastMouseX;
3554 				ev.dy = ev.y - lastMouseY;
3555 			}
3556 
3557 			lastMouseX = ev.x;
3558 			lastMouseY = ev.y;
3559 		}
3560 	}
3561 
3562 	/// Mouse event handler. Settable through setEventHandlers.
3563 	void delegate(MouseEvent) handleMouseEvent;
3564 
3565 	/// use to redraw child widgets if you use system apis to add stuff
3566 	void delegate() paintingFinished;
3567 
3568 	void delegate() paintingFinishedDg() {
3569 		return paintingFinished;
3570 	}
3571 
3572 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3573 	/// for this to ever happen.
3574 	void delegate(int width, int height) windowResized;
3575 
3576 	/++
3577 		Platform specific - handle any native message this window gets.
3578 
3579 		Note: this is called *in addition to* other event handlers, unless you either:
3580 
3581 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3582 
3583 		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.
3584 
3585 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3586 
3587 		On X, it takes the form of `int delegate(XEvent)`.
3588 
3589 		History:
3590 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3591 
3592 			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.
3593 	+/
3594 	NativeEventHandler handleNativeEvent_;
3595 
3596 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3597 		return handleNativeEvent_;
3598 	}
3599 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3600 		handleNativeEvent_ = neh;
3601 	}
3602 
3603 	version(Windows)
3604 	// compatibility shim with the old deprecated way
3605 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3606 	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) {
3607 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3608 			auto ret = dg(h, m, w, l);
3609 			if(ret == 0)
3610 				r = 1;
3611 			return ret;
3612 		};
3613 	}
3614 
3615 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3616 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3617 	/// this instead and it will work the same way.
3618 	__gshared NativeEventHandler handleNativeGlobalEvent;
3619 
3620 //  private:
3621 	/// The native implementation is available, but you shouldn't use it unless you are
3622 	/// familiar with the underlying operating system, don't mind depending on it, and
3623 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3624 	/// do what you need to do with handleNativeEvent instead.
3625 	///
3626 	/// This is likely to eventually change to be just a struct holding platform-specific
3627 	/// handles instead of a template mixin at some point because I'm not happy with the
3628 	/// code duplication here (ironically).
3629 	mixin NativeSimpleWindowImplementation!() impl;
3630 
3631 	/**
3632 		This is in-process one-way (from anything to window) event sending mechanics.
3633 		It is thread-safe, so it can be used in multi-threaded applications to send,
3634 		for example, "wake up and repaint" events when thread completed some operation.
3635 		This will allow to avoid using timer pulse to check events with synchronization,
3636 		'cause event handler will be called in UI thread. You can stop guessing which
3637 		pulse frequency will be enough for your app.
3638 		Note that events handlers may be called in arbitrary order, i.e. last registered
3639 		handler can be called first, and vice versa.
3640 	*/
3641 public:
3642 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3643 	 * "spamming" window with events it can't cope with.
3644 	 * It is safe to call this from non-UI threads.
3645 	 */
3646 	@property bool eventQueueEmpty() () {
3647 		synchronized(this) {
3648 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3649 		}
3650 		return true;
3651 	}
3652 
3653 	/** Does our custom event queue contains at least one with the given type?
3654 	 * Can be used in simple cases to prevent "spamming" window with events
3655 	 * it can't cope with.
3656 	 * It is safe to call this from non-UI threads.
3657 	 */
3658 	@property bool eventQueued(ET:Object) () {
3659 		synchronized(this) {
3660 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3661 				if (!o.doProcess) {
3662 					if (cast(ET)(o.evt)) return true;
3663 				}
3664 			}
3665 		}
3666 		return false;
3667 	}
3668 
3669 	/++
3670 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3671 
3672 		History:
3673 			Added May 12, 2021
3674 	+/
3675 	void delegate(Exception e) nothrow eventUncaughtException;
3676 
3677 	/** Add listener for custom event. Can be used like this:
3678 	 *
3679 	 * ---------------------
3680 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3681 	 *   ...
3682 	 *   win.removeEventListener(eid);
3683 	 * ---------------------
3684 	 *
3685 	 * Returns: 0 on failure (should never happen, so ignore it)
3686 	 *
3687 	 * $(WARNING Don't use this method in object destructors!)
3688 	 *
3689 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3690 	 *           'cause if event handler id counter will overflow, you won't be able
3691 	 *           to register any more events.)
3692 	 */
3693 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3694 		if (dg is null) return 0; // ignore empty handlers
3695 		synchronized(this) {
3696 			//FIXME: abort on overflow?
3697 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3698 			EventHandlerEntry e;
3699 			e.dg = delegate (Object o) {
3700 				if (auto co = cast(ET)o) {
3701 					try {
3702 						dg(co);
3703 					} catch (Exception e) {
3704 						// sorry!
3705 						if(eventUncaughtException)
3706 							eventUncaughtException(e);
3707 					}
3708 					return true;
3709 				}
3710 				return false;
3711 			};
3712 			e.id = lastUsedHandlerId;
3713 			auto optr = eventHandlers.ptr;
3714 			eventHandlers ~= e;
3715 			if (eventHandlers.ptr !is optr) {
3716 				import core.memory : GC;
3717 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3718 			}
3719 			return lastUsedHandlerId;
3720 		}
3721 	}
3722 
3723 	/// Remove event listener. It is safe to pass invalid event id here.
3724 	/// $(WARNING Don't use this method in object destructors!)
3725 	void removeEventListener() (uint id) {
3726 		if (id == 0 || id > lastUsedHandlerId) return;
3727 		synchronized(this) {
3728 			foreach (immutable idx; 0..eventHandlers.length) {
3729 				if (eventHandlers[idx].id == id) {
3730 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3731 					eventHandlers[$-1].dg = null;
3732 					eventHandlers.length -= 1;
3733 					eventHandlers.assumeSafeAppend;
3734 					return;
3735 				}
3736 			}
3737 		}
3738 	}
3739 
3740 	/// Post event to queue. It is safe to call this from non-UI threads.
3741 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3742 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3743 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3744 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3745 		if (this.closed) return false; // closed windows can't handle events
3746 
3747 		// remove all events of type `ET`
3748 		void removeAllET () {
3749 			uint eidx = 0, ec = eventQueueUsed;
3750 			auto eptr = eventQueue.ptr;
3751 			while (eidx < ec) {
3752 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3753 				if (cast(ET)eptr.evt !is null) {
3754 					// i found her!
3755 					if (inCustomEventProcessor) {
3756 						// if we're in custom event processing loop, processor will clear it for us
3757 						eptr.evt = null;
3758 						++eidx;
3759 						++eptr;
3760 					} else {
3761 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3762 						ec = --eventQueueUsed;
3763 						// clear last event (it is already copied)
3764 						eventQueue.ptr[ec].evt = null;
3765 					}
3766 				} else {
3767 					++eidx;
3768 					++eptr;
3769 				}
3770 			}
3771 		}
3772 
3773 		if (evt is null) {
3774 			if (replace) { synchronized(this) removeAllET(); }
3775 			// ignore empty events, they can't be handled anyway
3776 			return false;
3777 		}
3778 
3779 		// add events even if no event FD/event object created yet
3780 		synchronized(this) {
3781 			if (replace) removeAllET();
3782 			if (eventQueueUsed == uint.max) return false; // just in case
3783 			if (eventQueueUsed < eventQueue.length) {
3784 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3785 			} else {
3786 				if (eventQueue.capacity == eventQueue.length) {
3787 					// need to reallocate; do a trick to ensure that old array is cleared
3788 					auto oarr = eventQueue;
3789 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3790 					// just in case, do yet another check
3791 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3792 					import core.memory : GC;
3793 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3794 				} else {
3795 					auto optr = eventQueue.ptr;
3796 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3797 					assert(eventQueue.ptr is optr);
3798 				}
3799 				++eventQueueUsed;
3800 				assert(eventQueueUsed == eventQueue.length);
3801 			}
3802 			if (!eventWakeUp()) {
3803 				// can't wake up event processor, so there is no reason to keep the event
3804 				assert(eventQueueUsed > 0);
3805 				eventQueue[--eventQueueUsed].evt = null;
3806 				return false;
3807 			}
3808 			return true;
3809 		}
3810 	}
3811 
3812 	/// Post event to queue. It is safe to call this from non-UI threads.
3813 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3814 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3815 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3816 		return postTimeout!ET(evt, 0, replace);
3817 	}
3818 
3819 private:
3820 	private import core.time : MonoTime;
3821 
3822 	version(Posix) {
3823 		__gshared int customEventFDRead = -1;
3824 		__gshared int customEventFDWrite = -1;
3825 		__gshared int customSignalFD = -1;
3826 	} else version(Windows) {
3827 		__gshared HANDLE customEventH = null;
3828 	}
3829 
3830 	// wake up event processor
3831 	static bool eventWakeUp () {
3832 		version(X11) {
3833 			import core.sys.posix.unistd : write;
3834 			ulong n = 1;
3835 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3836 			return true;
3837 		} else version(Windows) {
3838 			if (customEventH !is null) SetEvent(customEventH);
3839 			return true;
3840 		} else version(OSXCocoa) {
3841 			if(globalAppDelegate)
3842 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3843 			return true;
3844 		} else {
3845 			// not implemented for other OSes
3846 			return false;
3847 		}
3848 	}
3849 
3850 	static struct QueuedEvent {
3851 		Object evt;
3852 		bool timed = false;
3853 		MonoTime hittime = MonoTime.zero;
3854 		bool doProcess = false; // process event at the current iteration (internal flag)
3855 
3856 		this (Object aevt, uint toutmsecs) {
3857 			evt = aevt;
3858 			if (toutmsecs > 0) {
3859 				import core.time : msecs;
3860 				timed = true;
3861 				hittime = MonoTime.currTime+toutmsecs.msecs;
3862 			}
3863 		}
3864 	}
3865 
3866 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3867 	static struct EventHandlerEntry {
3868 		CustomEventHandler dg;
3869 		uint id;
3870 	}
3871 
3872 	uint lastUsedHandlerId;
3873 	EventHandlerEntry[] eventHandlers;
3874 	QueuedEvent[] eventQueue = null;
3875 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3876 	bool inCustomEventProcessor = false; // required to properly remove events
3877 
3878 	// process queued events and call custom event handlers
3879 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3880 	void processCustomEvents () @system {
3881 		bool hasSomethingToDo = false;
3882 		uint ecount;
3883 		bool ocep;
3884 		synchronized(this) {
3885 			ocep = inCustomEventProcessor;
3886 			inCustomEventProcessor = true;
3887 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3888 			auto ctt = MonoTime.currTime;
3889 			bool hasEmpty = false;
3890 			// mark events to process (this is required for `eventQueued()`)
3891 			foreach (ref qe; eventQueue[0..ecount]) {
3892 				if (qe.evt is null) { hasEmpty = true; continue; }
3893 				if (qe.timed) {
3894 					qe.doProcess = (qe.hittime <= ctt);
3895 				} else {
3896 					qe.doProcess = true;
3897 				}
3898 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3899 			}
3900 			if (!hasSomethingToDo) {
3901 				// remove empty events
3902 				if (hasEmpty) {
3903 					uint eidx = 0, ec = eventQueueUsed;
3904 					auto eptr = eventQueue.ptr;
3905 					while (eidx < ec) {
3906 						if (eptr.evt is null) {
3907 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3908 							ec = --eventQueueUsed;
3909 							eventQueue.ptr[ec].evt = null; // make GC life easier
3910 						} else {
3911 							++eidx;
3912 							++eptr;
3913 						}
3914 					}
3915 				}
3916 				inCustomEventProcessor = ocep;
3917 				return;
3918 			}
3919 		}
3920 		// process marked events
3921 		uint efree = 0; // non-processed events will be put at this index
3922 		EventHandlerEntry[] eh;
3923 		Object evt;
3924 		foreach (immutable eidx; 0..ecount) {
3925 			synchronized(this) {
3926 				if (!eventQueue[eidx].doProcess) {
3927 					// skip this event
3928 					assert(efree <= eidx);
3929 					if (efree != eidx) {
3930 						// copy this event to queue start
3931 						eventQueue[efree] = eventQueue[eidx];
3932 						eventQueue[eidx].evt = null; // just in case
3933 					}
3934 					++efree;
3935 					continue;
3936 				}
3937 				evt = eventQueue[eidx].evt;
3938 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3939 				if (evt is null) continue; // just in case
3940 				// try all handlers; this can be slow, but meh...
3941 				eh = eventHandlers;
3942 			}
3943 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3944 			evt = null;
3945 			eh = null;
3946 		}
3947 		synchronized(this) {
3948 			// move all unprocessed events to queue top; efree holds first "free index"
3949 			foreach (immutable eidx; ecount..eventQueueUsed) {
3950 				assert(efree <= eidx);
3951 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3952 				++efree;
3953 			}
3954 			eventQueueUsed = efree;
3955 			// wake up event processor on next event loop iteration if we have more queued events
3956 			// also, remove empty events
3957 			bool awaken = false;
3958 			uint eidx = 0, ec = eventQueueUsed;
3959 			auto eptr = eventQueue.ptr;
3960 			while (eidx < ec) {
3961 				if (eptr.evt is null) {
3962 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3963 					ec = --eventQueueUsed;
3964 					eventQueue.ptr[ec].evt = null; // make GC life easier
3965 				} else {
3966 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3967 					++eidx;
3968 					++eptr;
3969 				}
3970 			}
3971 			inCustomEventProcessor = ocep;
3972 		}
3973 	}
3974 
3975 	// for all windows in nativeMapping
3976 	package static void processAllCustomEvents () @system {
3977 
3978 		cleanupQueue.process();
3979 
3980 		justCommunication.processCustomEvents();
3981 
3982 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3983 			if (sw is null || sw.closed) continue;
3984 			sw.processCustomEvents();
3985 		}
3986 
3987 		runPendingRunInGuiThreadDelegates();
3988 	}
3989 
3990 	// 0: infinite (i.e. no scheduled events in queue)
3991 	uint eventQueueTimeoutMSecs () {
3992 		synchronized(this) {
3993 			if (eventQueueUsed == 0) return 0;
3994 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3995 			uint res = int.max;
3996 			auto ctt = MonoTime.currTime;
3997 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3998 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3999 				if (qe.doProcess) continue; // just in case
4000 				if (!qe.timed) return 1; // minimal
4001 				if (qe.hittime <= ctt) return 1; // minimal
4002 				auto tms = (qe.hittime-ctt).total!"msecs";
4003 				if (tms < 1) tms = 1; // safety net
4004 				if (tms >= int.max) tms = int.max-1; // and another safety net
4005 				if (res > tms) res = cast(uint)tms;
4006 			}
4007 			return (res >= int.max ? 0 : res);
4008 		}
4009 	}
4010 
4011 	// for all windows in nativeMapping
4012 	static uint eventAllQueueTimeoutMSecs () {
4013 		uint res = uint.max;
4014 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
4015 			if (sw is null || sw.closed) continue;
4016 			uint to = sw.eventQueueTimeoutMSecs();
4017 			if (to && to < res) {
4018 				res = to;
4019 				if (to == 1) break; // can't have less than this
4020 			}
4021 		}
4022 		return (res >= int.max ? 0 : res);
4023 	}
4024 
4025 	version(X11) {
4026 		ResizeEvent pendingResizeEvent;
4027 	}
4028 
4029 	/++
4030 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
4031 
4032 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
4033 		worth so you can disable it by setting this to `true`.
4034 
4035 		History:
4036 			Added November 13, 2022.
4037 	+/
4038 	public bool suppressAutoOpenglViewport = false;
4039 	private void updateOpenglViewportIfNeeded(int width, int height) {
4040 		if(suppressAutoOpenglViewport) return;
4041 
4042 		version(without_opengl) {} else
4043 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
4044 		// writeln(width, " ", height);
4045 			setAsCurrentOpenGlContextNT();
4046 			glViewport(0, 0, width, height);
4047 		}
4048 	}
4049 
4050 	// TODO: Implement on non-Windows platforms (where available).
4051 	private CornerStyle _fauxCornerStyle = CornerStyle.automatic;
4052 
4053 	/++
4054 		Style of the window's corners
4055 
4056 		$(WARNING
4057 			Currently only implemented on Windows targets.
4058 			Has no visual effect elsewhere.
4059 
4060 			Windows: Requires Windows 11 or later.
4061 		)
4062 
4063 		History:
4064 			Added September 09, 2024.
4065 	 +/
4066 	public CornerStyle cornerStyle() @trusted {
4067 		version(Windows) {
4068 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4069 			const apiResult = DwmGetWindowAttribute(
4070 				this.hwnd,
4071 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4072 				&dwmCorner,
4073 				typeof(dwmCorner).sizeof
4074 			);
4075 
4076 			if (apiResult != S_OK) {
4077 				// Unsupported?
4078 				if (apiResult == E_INVALIDARG) {
4079 					// Feature unsupported; Windows version probably too old.
4080 					// Requires Windows 11 (build 22000) or later.
4081 					return _fauxCornerStyle;
4082 				}
4083 
4084 				throw new WindowsApiException("DwmGetWindowAttribute", apiResult);
4085 			}
4086 
4087 			CornerStyle corner;
4088 			if (!dwmCorner.fromDWM(corner)) {
4089 				throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner);
4090 			}
4091 			return corner;
4092 		} else {
4093 			return _fauxCornerStyle;
4094 		}
4095 	}
4096 
4097 	/// ditto
4098 	public void cornerStyle(const CornerStyle corner) @trusted {
4099 		version(Windows) {
4100 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4101 			if (!corner.toDWM(dwmCorner)) {
4102 				assert(false, "This should have been impossible because of a final switch.");
4103 			}
4104 
4105 			const apiResult = DwmSetWindowAttribute(
4106 				this.hwnd,
4107 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4108 				&dwmCorner,
4109 				typeof(dwmCorner).sizeof
4110 			);
4111 
4112 			if (apiResult != S_OK) {
4113 				// Unsupported?
4114 				if (apiResult == E_INVALIDARG) {
4115 					// Feature unsupported; Windows version probably too old.
4116 					// Requires Windows 11 (build 22000) or later.
4117 					_fauxCornerStyle = corner;
4118 					return;
4119 				}
4120 
4121 				throw new WindowsApiException("DwmSetWindowAttribute", apiResult);
4122 			}
4123 		} else {
4124 			_fauxCornerStyle = corner;
4125 		}
4126 	}
4127 }
4128 
4129 version(OSXCocoa)
4130 	enum NSWindow NullWindow = null;
4131 else
4132 	enum NullWindow = NativeWindowHandle.init;
4133 
4134 /++
4135 	Magic pseudo-window for just posting events to a global queue.
4136 
4137 	Not entirely supported, I might delete it at any time.
4138 
4139 	Added Nov 5, 2021.
4140 +/
4141 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
4142 
4143 /* Drag and drop support { */
4144 version(X11) {
4145 
4146 } else version(Windows) {
4147 	import core.sys.windows.uuid;
4148 	import core.sys.windows.ole2;
4149 	import core.sys.windows.oleidl;
4150 	import core.sys.windows.objidl;
4151 	import core.sys.windows.wtypes;
4152 
4153 	pragma(lib, "ole32");
4154 	void initDnd() {
4155 		auto err = OleInitialize(null);
4156 		if(err != S_OK && err != S_FALSE)
4157 			throw new Exception("init");//err);
4158 	}
4159 }
4160 /* } End drag and drop support */
4161 
4162 
4163 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
4164 /// See [GenericCursor].
4165 class MouseCursor {
4166 	int osId;
4167 	bool isStockCursor;
4168 	private this(int osId) {
4169 		this.osId = osId;
4170 		this.isStockCursor = true;
4171 	}
4172 
4173 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
4174 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
4175 
4176 	version(Windows) {
4177 		HCURSOR cursor_;
4178 		HCURSOR cursorHandle() {
4179 			if(cursor_ is null)
4180 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
4181 			return cursor_;
4182 		}
4183 
4184 	} else static if(UsingSimpledisplayX11) {
4185 		Cursor cursor_ = None;
4186 		int xDisplaySequence;
4187 
4188 		Cursor cursorHandle() {
4189 			if(this.osId == None)
4190 				return None;
4191 
4192 			// we need to reload if we on a new X connection
4193 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
4194 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
4195 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
4196 			}
4197 			return cursor_;
4198 		}
4199 	}
4200 }
4201 
4202 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
4203 // https://tronche.com/gui/x/xlib/appendix/b/
4204 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
4205 /// 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.
4206 enum GenericCursorType {
4207 	Default, /// The default arrow pointer.
4208 	Wait, /// A cursor indicating something is loading and the user must wait.
4209 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
4210 	Help, /// A cursor indicating the user can get help about the pointer location.
4211 	Cross, /// A crosshair.
4212 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
4213 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
4214 	UpArrow, /// An arrow pointing straight up.
4215 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
4216 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
4217 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
4218 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
4219 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
4220 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
4221 
4222 }
4223 
4224 /*
4225 	X_plus == css cell == Windows ?
4226 */
4227 
4228 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
4229 static struct GenericCursor {
4230 	static:
4231 	///
4232 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
4233 		static MouseCursor mc;
4234 
4235 		auto type = __traits(getMember, GenericCursorType, str);
4236 
4237 		if(mc is null) {
4238 
4239 			version(Windows) {
4240 				int osId;
4241 				final switch(type) {
4242 					case GenericCursorType.Default: osId = IDC_ARROW; break;
4243 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
4244 					case GenericCursorType.Hand: osId = IDC_HAND; break;
4245 					case GenericCursorType.Help: osId = IDC_HELP; break;
4246 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
4247 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
4248 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
4249 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
4250 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
4251 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
4252 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
4253 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
4254 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
4255 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
4256 				}
4257 			} else static if(UsingSimpledisplayX11) {
4258 				int osId;
4259 				final switch(type) {
4260 					case GenericCursorType.Default: osId = None; break;
4261 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
4262 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
4263 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
4264 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
4265 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
4266 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
4267 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
4268 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
4269 
4270 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
4271 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
4272 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
4273 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
4274 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
4275 				}
4276 
4277 			} else {
4278 				int osId;
4279 				// featureNotImplemented();
4280 			}
4281 
4282 			mc = new MouseCursor(osId);
4283 		}
4284 		return mc;
4285 	}
4286 }
4287 
4288 
4289 /++
4290 	If you want to get more control over the event loop, you can use this.
4291 
4292 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4293 	to `EventLoop.get.run`.
4294 +/
4295 struct EventLoop {
4296 	@disable this();
4297 
4298 	/// Gets a reference to an existing event loop
4299 	static EventLoop get() {
4300 		return EventLoop(0, null);
4301 	}
4302 
4303 	static void quitApplication() {
4304 		static if(use_arsd_core) {
4305 			import arsd.core;
4306 			ICoreEventLoop.exitApplication();
4307 		}
4308 		EventLoop.get().exit();
4309 	}
4310 
4311 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4312 
4313 	/// Construct an application-global event loop for yourself
4314 	/// See_Also: [SimpleWindow.setEventHandlers]
4315 	this(long pulseTimeout, void delegate() handlePulse) {
4316 		synchronized(monitor) {
4317 			if(impl is null) {
4318 				claimGuiThread();
4319 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4320 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4321 			} else {
4322 				if(pulseTimeout) {
4323 					impl.pulseTimeout = pulseTimeout;
4324 					impl.handlePulse = handlePulse;
4325 				}
4326 			}
4327 			impl.refcount++;
4328 		}
4329 	}
4330 
4331 	~this() {
4332 		if(impl is null)
4333 			return;
4334 		impl.refcount--;
4335 		if(impl.refcount == 0) {
4336 			impl.dispose();
4337 			if(thisIsGuiThread)
4338 				guiThreadFinalize();
4339 		}
4340 
4341 	}
4342 
4343 	this(this) {
4344 		if(impl is null)
4345 			return;
4346 		impl.refcount++;
4347 	}
4348 
4349 	/// Runs the event loop until the whileCondition, if present, returns false
4350 	int run(bool delegate() whileCondition = null) {
4351 		assert(impl !is null);
4352 		impl.notExited = true;
4353 		return impl.run(whileCondition);
4354 	}
4355 
4356 	/// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program)
4357 	void exit() {
4358 		assert(impl !is null);
4359 		impl.notExited = false;
4360 
4361 		static if(use_arsd_core) {
4362 			import arsd.core;
4363 			ICoreEventLoop.exitApplication();
4364 		}
4365 	}
4366 
4367 	version(linux)
4368 	ref void delegate(int) signalHandler() {
4369 		assert(impl !is null);
4370 		return impl.signalHandler;
4371 	}
4372 
4373 	__gshared static EventLoopImpl* impl;
4374 }
4375 
4376 version(linux)
4377 	void delegate(int, int) globalHupHandler;
4378 
4379 version(Posix)
4380 	void makeNonBlocking(int fd) {
4381 		import fcntl = core.sys.posix.fcntl;
4382 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4383 		if(flags == -1)
4384 			throw new Exception("fcntl get");
4385 		flags |= fcntl.O_NONBLOCK;
4386 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4387 		if(s == -1)
4388 			throw new Exception("fcntl set");
4389 	}
4390 
4391 struct EventLoopImpl {
4392 	int refcount;
4393 
4394 	bool notExited = true;
4395 
4396 	version(Emscripten) {
4397 		void delegate(int) signalHandler;
4398 		static import unix = core.sys.posix.unistd;
4399 		static import err = core.stdc.errno;
4400 	} else
4401 	version(linux) {
4402 		static import ep = core.sys.linux.epoll;
4403 		static import unix = core.sys.posix.unistd;
4404 		static import err = core.stdc.errno;
4405 		import core.sys.linux.timerfd;
4406 
4407 		void delegate(int) signalHandler;
4408 	}
4409 
4410 	version(X11) {
4411 		int pulseFd = -1;
4412 		version(Emscripten) {} else
4413 		version(linux) ep.epoll_event[16] events = void;
4414 	} else version(Windows) {
4415 		Timer pulser;
4416 		HANDLE[] handles;
4417 	}
4418 
4419 
4420 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4421 	/// to call this, as it's not recommended to share window between threads.
4422 	void mtLock () {
4423 		version(X11) {
4424 			XLockDisplay(this.display);
4425 		}
4426 	}
4427 
4428 	version(X11)
4429 	auto display() { return XDisplayConnection.get; }
4430 
4431 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4432 	/// to call this, as it's not recommended to share window between threads.
4433 	void mtUnlock () {
4434 		version(X11) {
4435 			XUnlockDisplay(this.display);
4436 		}
4437 	}
4438 
4439 	version(with_eventloop)
4440 	void initialize(long pulseTimeout) {}
4441 	else
4442 	void initialize(long pulseTimeout) @system {
4443 		version(Windows) {
4444 			if(pulseTimeout && handlePulse !is null)
4445 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4446 
4447 			if (customEventH is null) {
4448 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4449 				if (customEventH !is null) {
4450 					handles ~= customEventH;
4451 				} else {
4452 					// this is something that should not be; better be safe than sorry
4453 					throw new Exception("can't create eventfd for custom event processing");
4454 				}
4455 			}
4456 
4457 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4458 		}
4459 
4460 		version(Emscripten) {
4461 
4462 		} else version(linux) {
4463 			prepareEventLoop();
4464 			{
4465 				auto display = XDisplayConnection.get;
4466 				// adding Xlib file
4467 				ep.epoll_event ev = void;
4468 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4469 				ev.events = ep.EPOLLIN;
4470 				ev.data.fd = display.fd;
4471 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4472 					throw new Exception("add x fd");// ~ to!string(epollFd));
4473 				displayFd = display.fd;
4474 			}
4475 
4476 			if(pulseTimeout && handlePulse !is null) {
4477 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4478 				if(pulseFd == -1)
4479 					throw new Exception("pulse timer create failed");
4480 
4481 				itimerspec value;
4482 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4483 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4484 
4485 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4486 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4487 
4488 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4489 					throw new Exception("couldn't make pulse timer");
4490 
4491 				ep.epoll_event ev = void;
4492 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4493 				ev.events = ep.EPOLLIN;
4494 				ev.data.fd = pulseFd;
4495 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4496 			}
4497 
4498 			// eventfd for custom events
4499 			if (customEventFDWrite == -1) {
4500 				customEventFDWrite = eventfd(0, 0);
4501 				customEventFDRead = customEventFDWrite;
4502 				if (customEventFDRead >= 0) {
4503 					ep.epoll_event ev = void;
4504 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4505 					ev.events = ep.EPOLLIN;
4506 					ev.data.fd = customEventFDRead;
4507 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4508 				} else {
4509 					// this is something that should not be; better be safe than sorry
4510 					throw new Exception("can't create eventfd for custom event processing");
4511 				}
4512 			}
4513 
4514 			if (customSignalFD == -1) {
4515 				import core.sys.linux.sys.signalfd;
4516 
4517 				sigset_t sigset;
4518 				auto err = sigemptyset(&sigset);
4519 				assert(!err);
4520 				err = sigaddset(&sigset, SIGINT);
4521 				assert(!err);
4522 				err = sigaddset(&sigset, SIGHUP);
4523 				assert(!err);
4524 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4525 				assert(!err);
4526 
4527 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4528 				assert(customSignalFD != -1);
4529 
4530 				ep.epoll_event ev = void;
4531 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4532 				ev.events = ep.EPOLLIN;
4533 				ev.data.fd = customSignalFD;
4534 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4535 			}
4536 		} else version(Posix) {
4537 			prepareEventLoop();
4538 			if (customEventFDRead == -1) {
4539 				int[2] bfr;
4540 				import core.sys.posix.unistd;
4541 				auto ret = pipe(bfr);
4542 				if(ret == -1) throw new Exception("pipe");
4543 				customEventFDRead = bfr[0];
4544 				customEventFDWrite = bfr[1];
4545 			}
4546 
4547 		}
4548 
4549 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4550 
4551 		version(linux) {
4552 			this.mtLock();
4553 			scope(exit) this.mtUnlock();
4554 			version(X11)
4555 				XPending(display); // no, really
4556 		}
4557 
4558 		disposed = false;
4559 	}
4560 
4561 	bool disposed = true;
4562 	version(X11)
4563 		int displayFd = -1;
4564 
4565 	version(with_eventloop)
4566 	void dispose() {}
4567 	else
4568 	void dispose() @system {
4569 		disposed = true;
4570 		version(X11) {
4571 			if(pulseFd != -1) {
4572 				import unix = core.sys.posix.unistd;
4573 				unix.close(pulseFd);
4574 				pulseFd = -1;
4575 			}
4576 
4577 				version(Emscripten) {} else
4578 				version(linux)
4579 				if(displayFd != -1) {
4580 					// 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
4581 					ep.epoll_event ev = void;
4582 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4583 					ev.events = ep.EPOLLIN;
4584 					ev.data.fd = displayFd;
4585 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4586 					displayFd = -1;
4587 				}
4588 
4589 		} else version(Windows) {
4590 			if(pulser !is null) {
4591 				pulser.destroy();
4592 				pulser = null;
4593 			}
4594 			if (customEventH !is null) {
4595 				CloseHandle(customEventH);
4596 				customEventH = null;
4597 			}
4598 		}
4599 	}
4600 
4601 	this(long pulseTimeout, void delegate() handlePulse) {
4602 		this.pulseTimeout = pulseTimeout;
4603 		this.handlePulse = handlePulse;
4604 		initialize(pulseTimeout);
4605 	}
4606 
4607 	private long pulseTimeout;
4608 	void delegate() handlePulse;
4609 
4610 	~this() {
4611 		dispose();
4612 	}
4613 
4614 	version(Posix)
4615 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4616 	version(Posix)
4617 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4618 	version(linux)
4619 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4620 	version(Windows)
4621 	ref auto customEventH() { return SimpleWindow.customEventH; }
4622 
4623 	version(X11) {
4624 		bool doXPending() {
4625 			bool done = false;
4626 
4627 			this.mtLock();
4628 			scope(exit) this.mtUnlock();
4629 			//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4630 			while(!done && XPending(display)) {
4631 				done = doXNextEvent(this.display);
4632 			}
4633 
4634 			return done;
4635 		}
4636 		void doXNextEventVoid() {
4637 			doXPending();
4638 		}
4639 	}
4640 
4641 	version(with_eventloop) {
4642 		int loopHelper(bool delegate() whileCondition) {
4643 			// FIXME: whileCondition
4644 			import arsd.eventloop;
4645 			loop();
4646 			return 0;
4647 		}
4648 	} else
4649 	int loopHelper(bool delegate() whileCondition) {
4650 		version(X11) {
4651 			bool done = false;
4652 
4653 			XFlush(display);
4654 			insideXEventLoop = true;
4655 			scope(exit) insideXEventLoop = false;
4656 
4657 			static if(use_arsd_core) {
4658 				import arsd.core;
4659 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4660 
4661 				static bool loopInitialized = false;
4662 				if(!loopInitialized) {
4663 					el.addDelegateOnLoopIteration(&doXNextEventVoid, 3);
4664 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3);
4665 
4666 					if(customSignalFD != -1)
4667 					cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() {
4668 						version(linux) {
4669 							import core.sys.linux.sys.signalfd;
4670 							import core.sys.posix.unistd : read;
4671 							signalfd_siginfo info;
4672 							read(customSignalFD, &info, info.sizeof);
4673 
4674 							auto sig = info.ssi_signo;
4675 
4676 							if(EventLoop.get.signalHandler !is null) {
4677 								EventLoop.get.signalHandler()(sig);
4678 							} else {
4679 								EventLoop.get.exit();
4680 							}
4681 						}
4682 					}));
4683 
4684 					if(display.fd != -1)
4685 					cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() {
4686 						this.mtLock();
4687 						scope(exit) this.mtUnlock();
4688 						while(!done && XPending(display)) {
4689 							done = doXNextEvent(this.display);
4690 						}
4691 					}));
4692 
4693 					if(pulseFd != -1)
4694 					cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() {
4695 						long expirationCount;
4696 						// 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...
4697 
4698 						handlePulse();
4699 
4700 						// read just to clear the buffer so poll doesn't trigger again
4701 						// BTW I read AFTER the pulse because if the pulse handler takes
4702 						// a lot of time to execute, we don't want the app to get stuck
4703 						// in a loop of timer hits without a chance to do anything else
4704 						//
4705 						// IOW handlePulse happens at most once per pulse interval.
4706 						unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4707 					}));
4708 
4709 					if(customEventFDRead != -1)
4710 					cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() {
4711 						// we have some custom events; process 'em
4712 						import core.sys.posix.unistd : read;
4713 						ulong n;
4714 						read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4715 						//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4716 						//SimpleWindow.processAllCustomEvents();
4717 					}));
4718 
4719 					// FIXME: posix fds
4720 					// FIXME up?
4721 
4722 
4723 					loopInitialized = true;
4724 				}
4725 
4726 				if(whileCondition is null)
4727 					whileCondition = () => true;
4728 
4729 				el.run(() => !whileCondition());
4730 			} else version(linux) {
4731 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4732 					bool forceXPending = false;
4733 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4734 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4735 					{
4736 						this.mtLock();
4737 						scope(exit) this.mtUnlock();
4738 						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
4739 					}
4740 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4741 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4742 					if(nfds == -1) {
4743 						if(err.errno == err.EINTR) {
4744 							//if(forceXPending) goto xpending;
4745 							continue; // interrupted by signal, just try again
4746 						}
4747 						throw new Exception("epoll wait failure");
4748 					}
4749 					// writeln(nfds, " ", events[0].data.fd);
4750 
4751 					SimpleWindow.processAllCustomEvents(); // anyway
4752 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4753 					foreach(idx; 0 .. nfds) {
4754 						if(done) break;
4755 						auto fd = events[idx].data.fd;
4756 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4757 						auto flags = events[idx].events;
4758 						if(flags & ep.EPOLLIN) {
4759 							if (fd == customSignalFD) {
4760 								version(linux) {
4761 									import core.sys.linux.sys.signalfd;
4762 									import core.sys.posix.unistd : read;
4763 									signalfd_siginfo info;
4764 									read(customSignalFD, &info, info.sizeof);
4765 
4766 									auto sig = info.ssi_signo;
4767 
4768 									if(EventLoop.get.signalHandler !is null) {
4769 										EventLoop.get.signalHandler()(sig);
4770 									} else {
4771 										EventLoop.get.exit();
4772 									}
4773 								}
4774 							} else if(fd == display.fd) {
4775 								version(sdddd) { writeln("X EVENT PENDING!"); }
4776 								this.mtLock();
4777 								scope(exit) this.mtUnlock();
4778 								while(!done && XPending(display)) {
4779 									done = doXNextEvent(this.display);
4780 								}
4781 								forceXPending = false;
4782 							} else if(fd == pulseFd) {
4783 								long expirationCount;
4784 								// 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...
4785 
4786 								handlePulse();
4787 
4788 								// read just to clear the buffer so poll doesn't trigger again
4789 								// BTW I read AFTER the pulse because if the pulse handler takes
4790 								// a lot of time to execute, we don't want the app to get stuck
4791 								// in a loop of timer hits without a chance to do anything else
4792 								//
4793 								// IOW handlePulse happens at most once per pulse interval.
4794 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4795 								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
4796 							} else if (fd == customEventFDRead) {
4797 								// we have some custom events; process 'em
4798 								import core.sys.posix.unistd : read;
4799 								ulong n;
4800 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4801 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4802 								//SimpleWindow.processAllCustomEvents();
4803 
4804 								forceXPending = true;
4805 							} else {
4806 								// some other timer
4807 								version(sdddd) { writeln("unknown fd: ", fd); }
4808 
4809 								if(Timer* t = fd in Timer.mapping)
4810 									(*t).trigger();
4811 
4812 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4813 									(*pfr).ready(flags);
4814 
4815 								// we don't know what the user did in this timer, so we need to assume that
4816 								// there's X data to be flushed and potentially processed
4817 								forceXPending = true;
4818 
4819 								// or i might add support for other FDs too
4820 								// but for now it is just timer
4821 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4822 							}
4823 						}
4824 						if(flags & ep.EPOLLHUP) {
4825 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4826 								(*pfr).hup(flags);
4827 							if(globalHupHandler)
4828 								globalHupHandler(fd, flags);
4829 						}
4830 						/+
4831 						} else {
4832 							// not interested in OUT, we are just reading here.
4833 							//
4834 							// error or hup might also be reported
4835 							// but it shouldn't here since we are only
4836 							// using a few types of FD and Xlib will report
4837 							// if it dies.
4838 							// so instead of thoughtfully handling it, I'll
4839 							// just throw. for now at least
4840 
4841 							throw new Exception("epoll did something else");
4842 						}
4843 						+/
4844 					}
4845 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4846 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4847 					xpending:
4848 					if (!done && forceXPending) {
4849 						done = doXPending();
4850 					}
4851 				}
4852 			} else {
4853 				// Generic fallback: yes to simple pulse support,
4854 				// but NO timer support!
4855 
4856 				// FIXME: we could probably support the POSIX timer_create
4857 				// signal-based option, but I'm in no rush to write it since
4858 				// I prefer the fd-based functions.
4859 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4860 
4861 					import core.sys.posix.poll;
4862 
4863 					pollfd[] pfds;
4864 					pollfd[32] pfdsBuffer;
4865 					auto len = PosixFdReader.mapping.length + 2;
4866 					// FIXME: i should just reuse the buffer
4867 					if(len < pfdsBuffer.length)
4868 						pfds = pfdsBuffer[0 .. len];
4869 					else
4870 						pfds = new pollfd[](len);
4871 
4872 					pfds[0].fd = display.fd;
4873 					pfds[0].events = POLLIN;
4874 					pfds[0].revents = 0;
4875 
4876 					int slot = 1;
4877 
4878 					if(customEventFDRead != -1) {
4879 						pfds[slot].fd = customEventFDRead;
4880 						pfds[slot].events = POLLIN;
4881 						pfds[slot].revents = 0;
4882 
4883 						slot++;
4884 					}
4885 
4886 					foreach(fd, obj; PosixFdReader.mapping) {
4887 						if(!obj.enabled) continue;
4888 						pfds[slot].fd = fd;
4889 						pfds[slot].events = POLLIN;
4890 						pfds[slot].revents = 0;
4891 
4892 						slot++;
4893 					}
4894 
4895 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4896 					if(ret == -1) throw new Exception("poll");
4897 
4898 					if(ret == 0) {
4899 						// FIXME it may not necessarily time out if events keep coming
4900 						if(handlePulse !is null)
4901 							handlePulse();
4902 					} else {
4903 						foreach(s; 0 .. slot) {
4904 							if(pfds[s].revents == 0) continue;
4905 
4906 							if(pfds[s].fd == display.fd) {
4907 								while(!done && XPending(display)) {
4908 									this.mtLock();
4909 									scope(exit) this.mtUnlock();
4910 									done = doXNextEvent(this.display);
4911 								}
4912 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4913 
4914 								import core.sys.posix.unistd : read;
4915 								ulong n;
4916 								read(customEventFDRead, &n, n.sizeof);
4917 								SimpleWindow.processAllCustomEvents();
4918 							} else {
4919 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4920 								if(pfds[s].revents & POLLNVAL) {
4921 									obj.dispose();
4922 								} else {
4923 									obj.ready(pfds[s].revents);
4924 								}
4925 							}
4926 
4927 							ret--;
4928 							if(ret == 0) break;
4929 						}
4930 					}
4931 				}
4932 			}
4933 		} else
4934 		version(Windows) {
4935 
4936 			static if(use_arsd_core) {
4937 				import arsd.core;
4938 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4939 				static bool loopInitialized = false;
4940 				if(!loopInitialized) {
4941 					el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3);
4942 					el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 3);
4943 					loopInitialized = true;
4944 				}
4945 				el.run(() => !whileCondition());
4946 			} else {
4947 				int ret = -1;
4948 				MSG message;
4949 				while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4950 					eventLoopRound++;
4951 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4952 					auto waitResult = MsgWaitForMultipleObjectsEx(
4953 						cast(int) handles.length, handles.ptr,
4954 						(wto == 0 ? INFINITE : wto), /* timeout */
4955 						0x04FF, /* QS_ALLINPUT */
4956 						0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4957 
4958 					SimpleWindow.processAllCustomEvents(); // anyway
4959 					enum WAIT_OBJECT_0 = 0;
4960 					if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4961 						auto h = handles[waitResult - WAIT_OBJECT_0];
4962 						if(auto e = h in WindowsHandleReader.mapping) {
4963 							(*e).ready();
4964 						}
4965 					} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4966 						// message ready
4967 						int count;
4968 						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
4969 							ret = GetMessage(&message, null, 0, 0);
4970 							if(ret == -1)
4971 								throw new WindowsApiException("GetMessage", GetLastError());
4972 							TranslateMessage(&message);
4973 							DispatchMessage(&message);
4974 
4975 							count++;
4976 							if(count > 10)
4977 								break; // take the opportunity to catch up on other events
4978 
4979 							if(ret == 0) { // WM_QUIT
4980 								EventLoop.quitApplication();
4981 								break;
4982 							}
4983 						}
4984 					} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4985 						SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4986 					} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4987 						// timeout, should never happen since we aren't using it
4988 					} else if(waitResult == 0xFFFFFFFF) {
4989 							// failed
4990 							throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4991 					} else {
4992 						// idk....
4993 					}
4994 				}
4995 			}
4996 
4997 			// return message.wParam;
4998 			return 0;
4999 		} version (OSXCocoa) {
5000 
5001 			static assert(use_arsd_core);
5002 
5003 			/+
5004 			if (handlePulse !is null && pulseTimeout != 0) {
5005 				NSTimer timer = NSTimer.schedule(pulseTimeout*1e-3,
5006 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
5007 					null, true);
5008 
5009 
5010 			if(timer)
5011 				timer.invalidate();
5012 			}
5013 			+/
5014 
5015 			import arsd.core;
5016 			auto el = getThisThreadEventLoop(EventLoopType.Ui);
5017 			static bool loopInitialized = false;
5018 			if(!loopInitialized) {
5019 				el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3);
5020 				loopInitialized = true;
5021 				sdpyPrintDebugString("one");
5022 				NSApp.run();
5023 				sdpyPrintDebugString("here");
5024 			}
5025 
5026 				sdpyPrintDebugString("arsd.core loop starting");
5027 			el.run(() => !whileCondition());
5028 
5029 				sdpyPrintDebugString("kiio all done");
5030 
5031 			return 0;
5032 		} else {
5033 			return 0;
5034 		}
5035 	}
5036 
5037 	int run(bool delegate() whileCondition = null) {
5038 		if(disposed)
5039 			initialize(this.pulseTimeout);
5040 
5041 		version(X11) {
5042 			try {
5043 				return loopHelper(whileCondition);
5044 			} catch(XDisconnectException e) {
5045 				if(e.userRequested) {
5046 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
5047 						item.discardConnectionState();
5048 					XCloseDisplay(XDisplayConnection.display);
5049 				}
5050 
5051 				XDisplayConnection.display = null;
5052 
5053 				this.dispose();
5054 
5055 				throw e;
5056 			}
5057 		} else {
5058 			return loopHelper(whileCondition);
5059 		}
5060 	}
5061 }
5062 
5063 
5064 /++
5065 	Provides an icon on the system notification area (also known as the system tray).
5066 
5067 
5068 	If a notification area is not available with the NotificationIcon object is created,
5069 	it will silently succeed and simply attempt to create one when an area becomes available.
5070 
5071 
5072 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
5073 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
5074 	with true color was added at that time. I was just too lazy to write the fallback.
5075 
5076 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
5077 	you use arsd 10.x when targeting Windows XP.
5078 +/
5079 version(Emscripten) {} else
5080 version(OSXCocoa) {} else // NotYetImplementedException
5081 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
5082 
5083 	version(X11) {
5084 		void recreateAfterDisconnect() {
5085 			stateDiscarded = false;
5086 			clippixmap = None;
5087 			throw new Exception("NOT IMPLEMENTED");
5088 		}
5089 
5090 		bool stateDiscarded;
5091 		void discardConnectionState() {
5092 			stateDiscarded = true;
5093 		}
5094 	}
5095 
5096 
5097 	version(X11) {
5098 		Image img;
5099 
5100 		NativeEventHandler getNativeEventHandler() {
5101 			return delegate int(XEvent e) {
5102 				switch(e.type) {
5103 					case EventType.Expose:
5104 					//case EventType.VisibilityNotify:
5105 						redraw();
5106 					break;
5107 					case EventType.ClientMessage:
5108 						version(sddddd) {
5109 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
5110 						writeln("\t", e.xclient.format);
5111 						writeln("\t", e.xclient.data.l);
5112 						}
5113 					break;
5114 					case EventType.ButtonPress:
5115 						auto event = e.xbutton;
5116 						if (onClick !is null || onClickEx !is null) {
5117 							MouseButton mb = cast(MouseButton)0;
5118 							switch (event.button) {
5119 								case 1: mb = MouseButton.left; break; // left
5120 								case 2: mb = MouseButton.middle; break; // middle
5121 								case 3: mb = MouseButton.right; break; // right
5122 								case 4: mb = MouseButton.wheelUp; break; // scroll up
5123 								case 5: mb = MouseButton.wheelDown; break; // scroll down
5124 								case 6: break; // scroll left...
5125 								case 7: break; // scroll right...
5126 								case 8: mb = MouseButton.backButton; break;
5127 								case 9: mb = MouseButton.forwardButton; break;
5128 								default:
5129 							}
5130 							if (mb) {
5131 								try { onClick()(mb); } catch (Exception) {}
5132 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
5133 							}
5134 						}
5135 					break;
5136 					case EventType.EnterNotify:
5137 						if (onEnter !is null) {
5138 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
5139 						}
5140 						break;
5141 					case EventType.LeaveNotify:
5142 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
5143 						break;
5144 					case EventType.DestroyNotify:
5145 						active = false;
5146 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
5147 					break;
5148 					case EventType.ConfigureNotify:
5149 						auto event = e.xconfigure;
5150 						this.width = event.width;
5151 						this.height = event.height;
5152 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
5153 						redraw();
5154 					break;
5155 					default: return 1;
5156 				}
5157 				return 1;
5158 			};
5159 		}
5160 
5161 		/* private */ void hideBalloon() {
5162 			balloon.close();
5163 			version(with_timer)
5164 				timer.destroy();
5165 			balloon = null;
5166 			version(with_timer)
5167 				timer = null;
5168 		}
5169 
5170 		void redraw() {
5171 			if (!active) return;
5172 
5173 			auto display = XDisplayConnection.get;
5174 			GC gc;
5175 
5176 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
5177 
5178 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
5179 				Visual *visual;
5180 				XVisualInfo vis_info;
5181 				XSetWindowAttributes win_attr;
5182 				c_ulong win_mask;
5183 
5184 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
5185 					assert(0);
5186 					// return 1;
5187 				}
5188 
5189 				visual = vis_info.visual;
5190 
5191 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
5192 				win_attr.background_pixel = 0;
5193 				win_attr.border_pixel = 0;
5194 
5195 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
5196 
5197 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
5198 
5199 				return 0;
5200 			}
5201 
5202 			if(useAlpha)
5203 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
5204 			else
5205 				gc = DefaultGC(display, DefaultScreen(display));
5206 
5207 			XClearWindow(display, nativeHandle);
5208 
5209 			if(!useAlpha && img !is null)
5210 				XSetClipMask(display, gc, clippixmap);
5211 
5212 			/+
5213 			XSetForeground(display, gc,
5214 				cast(uint) 0 << 16 |
5215 				cast(uint) 0 << 8 |
5216 				cast(uint) 0);
5217 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
5218 			+/
5219 
5220 			if (img is null) {
5221 				XSetForeground(display, gc,
5222 					cast(uint) 0 << 16 |
5223 					cast(uint) 127 << 8 |
5224 					cast(uint) 0);
5225 				XFillArc(display, nativeHandle,
5226 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
5227 			} else {
5228 				int dx = 0;
5229 				int dy = 0;
5230 				if(width > img.width)
5231 					dx = (width - img.width) / 2;
5232 				if(height > img.height)
5233 					dy = (height - img.height) / 2;
5234 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
5235 				XSetClipOrigin(display, gc, dx, dy);
5236 
5237 				int max(int a, int b) {
5238 					if(a > b) return a; else return b;
5239 				}
5240 
5241 				if (img.usingXshm)
5242 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
5243 				else
5244 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
5245 			}
5246 			XSetClipMask(display, gc, None);
5247 			flushGui();
5248 		}
5249 
5250 		static Window getTrayOwner() {
5251 			auto display = XDisplayConnection.get;
5252 			auto i = cast(int) DefaultScreen(display);
5253 			if(i < 10 && i >= 0) {
5254 				static Atom atom;
5255 				if(atom == None)
5256 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
5257 				return XGetSelectionOwner(display, atom);
5258 			}
5259 			return None;
5260 		}
5261 
5262 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
5263 			auto to = getTrayOwner();
5264 			auto display = XDisplayConnection.get;
5265 			XEvent ev;
5266 			ev.xclient.type = EventType.ClientMessage;
5267 			ev.xclient.window = to;
5268 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
5269 			ev.xclient.format = 32;
5270 			ev.xclient.data.l[0] = CurrentTime;
5271 			ev.xclient.data.l[1] = message;
5272 			ev.xclient.data.l[2] = d1;
5273 			ev.xclient.data.l[3] = d2;
5274 			ev.xclient.data.l[4] = d3;
5275 
5276 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
5277 		}
5278 
5279 		private static NotificationAreaIcon[] activeIcons;
5280 
5281 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
5282 		private void newManager() {
5283 			close();
5284 			createXWin();
5285 
5286 			if(this.clippixmap)
5287 				XFreePixmap(XDisplayConnection.get, clippixmap);
5288 			if(this.originalMemoryImage)
5289 				this.icon = this.originalMemoryImage;
5290 			else if(this.img)
5291 				this.icon = this.img;
5292 		}
5293 
5294 		private bool useAlpha = false;
5295 
5296 		private void createXWin () {
5297 			// create window
5298 			auto display = XDisplayConnection.get;
5299 
5300 			// to check for MANAGER on root window to catch new/changed tray owners
5301 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
5302 			// so if a thing does appear, we can handle it
5303 			foreach(ai; activeIcons)
5304 				if(ai is this)
5305 					goto alreadythere;
5306 			activeIcons ~= this;
5307 			alreadythere:
5308 
5309 			// and check for an existing tray
5310 			auto trayOwner = getTrayOwner();
5311 			if(trayOwner == None)
5312 				return;
5313 				//throw new Exception("No notification area found");
5314 
5315 			Visual* v = cast(Visual*) CopyFromParent;
5316 
5317 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
5318 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
5319 			// a resize event later.
5320 			width = 22;
5321 			height = 22;
5322 
5323 			// if they system gave us a 32 bit visual we need to switch to it too
5324 			int depth = 24;
5325 
5326 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
5327 			if(visualProp !is null) {
5328 				c_ulong[] info = cast(c_ulong[]) visualProp;
5329 				if(info.length == 1) {
5330 					auto vid = info[0];
5331 					int returned;
5332 					XVisualInfo t;
5333 					t.visualid = vid;
5334 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
5335 					if(got !is null) {
5336 						if(returned == 1) {
5337 							v = got.visual;
5338 							depth = got.depth;
5339 							// writeln("using special visual ", got.depth);
5340 							// writeln(depth);
5341 						}
5342 						XFree(got);
5343 					}
5344 				}
5345 			}
5346 
5347 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
5348 			XSetWindowAttributes attr;
5349 			attr.background_pixel = 0;
5350 			attr.border_pixel = 0;
5351 			attr.override_redirect = 0;
5352 			if(v !is cast(Visual*) CopyFromParent) {
5353 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
5354 				CWFlags |= CWColormap;
5355 				if(depth == 32)
5356 					useAlpha = true;
5357 				else
5358 					goto plain;
5359 			} else {
5360 				plain:
5361 				attr.background_pixmap = 1 /* ParentRelative */;
5362 				CWFlags |= CWBackPixmap;
5363 			}
5364 
5365 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
5366 
5367 			assert(nativeWindow);
5368 
5369 			if(!useAlpha)
5370 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
5371 
5372 			nativeHandle = nativeWindow;
5373 
5374 			///+
5375 			arch_ulong[2] info;
5376 			info[0] = 0;
5377 			info[1] = 1;
5378 
5379 			string title = this.name is null ? "simpledisplay.d program" : this.name;
5380 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
5381 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
5382 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
5383 
5384 			XChangeProperty(
5385 				display,
5386 				nativeWindow,
5387 				GetAtom!("_XEMBED_INFO", true)(display),
5388 				GetAtom!("_XEMBED_INFO", true)(display),
5389 				32 /* bits */,
5390 				0 /*PropModeReplace*/,
5391 				info.ptr,
5392 				2);
5393 
5394 			import core.sys.posix.unistd;
5395 			arch_ulong pid = getpid();
5396 
5397 			// XSetCommand(display, nativeWindow, ["sdpy".ptr].ptr, 1);
5398 
5399 			XChangeProperty(
5400 				display,
5401 				nativeWindow,
5402 				GetAtom!("_NET_WM_PID", true)(display),
5403 				XA_CARDINAL,
5404 				32 /* bits */,
5405 				0 /*PropModeReplace*/,
5406 				&pid,
5407 				1);
5408 
5409 			updateNetWmIcon();
5410 
5411 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
5412 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
5413 				XClassHint klass;
5414 				XWMHints wh;
5415 				XSizeHints size;
5416 				klass.res_name = sdpyWindowClassStr;
5417 				klass.res_class = sdpyWindowClassStr;
5418 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
5419 			}
5420 
5421 				// believe it or not, THIS is what xfce needed for the 9999 issue
5422 				XSizeHints sh;
5423 				c_long spr;
5424 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
5425 				sh.flags |= PMaxSize | PMinSize;
5426 				// FIXME maybe nicer resizing
5427 				sh.min_width = 16;
5428 				sh.min_height = 16;
5429 				sh.max_width = 22;
5430 				sh.max_height = 22;
5431 				XSetWMNormalHints(display, nativeWindow, &sh);
5432 
5433 
5434 			//+/
5435 
5436 
5437 			XSelectInput(display, nativeWindow,
5438 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
5439 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
5440 
5441 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5442 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5443 
5444 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5445 			active = true;
5446 		}
5447 
5448 		void updateNetWmIcon() {
5449 			if(img is null) return;
5450 			auto display = XDisplayConnection.get;
5451 			// FIXME: ensure this is correct
5452 			arch_ulong[] buffer;
5453 			auto imgMi = img.toTrueColorImage;
5454 			buffer ~= imgMi.width;
5455 			buffer ~= imgMi.height;
5456 			foreach(c; imgMi.imageData.colors) {
5457 				arch_ulong b;
5458 				b |= c.a << 24;
5459 				b |= c.r << 16;
5460 				b |= c.g << 8;
5461 				b |= c.b;
5462 				buffer ~= b;
5463 			}
5464 
5465 			XChangeProperty(
5466 				display,
5467 				nativeHandle,
5468 				GetAtom!"_NET_WM_ICON"(display),
5469 				GetAtom!"CARDINAL"(display),
5470 				32 /* bits */,
5471 				0 /*PropModeReplace*/,
5472 				buffer.ptr,
5473 				cast(int) buffer.length);
5474 		}
5475 
5476 
5477 
5478 		private SimpleWindow balloon;
5479 		version(with_timer)
5480 		private Timer timer;
5481 
5482 		private Window nativeHandle;
5483 		private Pixmap clippixmap = None;
5484 		private int width = 16;
5485 		private int height = 16;
5486 		private bool active = false;
5487 
5488 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5489 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5490 		void delegate () onLeave; /// X11 only.
5491 
5492 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5493 
5494 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5495 		void getWindowRect (out int x, out int y, out int width, out int height) {
5496 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5497 			Window dummyw;
5498 			auto dpy = XDisplayConnection.get;
5499 			//XWindowAttributes xwa;
5500 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5501 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5502 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5503 			width = this.width;
5504 			height = this.height;
5505 		}
5506 	}
5507 
5508 	/+
5509 		What I actually want from this:
5510 
5511 		* set / change: icon, tooltip
5512 		* handle: mouse click, right click
5513 		* show: notification bubble.
5514 	+/
5515 
5516 	version(Windows) {
5517 		WindowsIcon win32Icon;
5518 		HWND hwnd;
5519 
5520 		NOTIFYICONDATAW data;
5521 
5522 		NativeEventHandler getNativeEventHandler() {
5523 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5524 				if(msg == WM_USER) {
5525 					auto event = LOWORD(lParam);
5526 					auto iconId = HIWORD(lParam);
5527 					//auto x = GET_X_LPARAM(wParam);
5528 					//auto y = GET_Y_LPARAM(wParam);
5529 					switch(event) {
5530 						case WM_LBUTTONDOWN:
5531 							onClick()(MouseButton.left);
5532 						break;
5533 						case WM_RBUTTONDOWN:
5534 							onClick()(MouseButton.right);
5535 						break;
5536 						case WM_MBUTTONDOWN:
5537 							onClick()(MouseButton.middle);
5538 						break;
5539 						case WM_MOUSEMOVE:
5540 							// sent, we could use it.
5541 						break;
5542 						case WM_MOUSEWHEEL:
5543 							// NOT SENT
5544 						break;
5545 						//case NIN_KEYSELECT:
5546 						//case NIN_SELECT:
5547 						//break;
5548 						default: {}
5549 					}
5550 				}
5551 				return 0;
5552 			};
5553 		}
5554 
5555 		enum NIF_SHOWTIP = 0x00000080;
5556 
5557 		private static struct NOTIFYICONDATAW {
5558 			DWORD cbSize;
5559 			HWND  hWnd;
5560 			UINT  uID;
5561 			UINT  uFlags;
5562 			UINT  uCallbackMessage;
5563 			HICON hIcon;
5564 			WCHAR[128] szTip;
5565 			DWORD dwState;
5566 			DWORD dwStateMask;
5567 			WCHAR[256] szInfo;
5568 			union {
5569 				UINT uTimeout;
5570 				UINT uVersion;
5571 			}
5572 			WCHAR[64] szInfoTitle;
5573 			DWORD dwInfoFlags;
5574 			GUID  guidItem;
5575 			HICON hBalloonIcon;
5576 		}
5577 
5578 	}
5579 
5580 	/++
5581 		Note that on Windows, only left, right, and middle buttons are sent.
5582 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5583 		program is meant to be used on Windows too.
5584 	+/
5585 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5586 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5587 		// but on X, we need an Image, so its canonical ctor is there. They should
5588 		// forward to each other though.
5589 		version(X11) {
5590 			this.name = name;
5591 			this.onClick = onClick;
5592 			createXWin();
5593 			this.icon = icon;
5594 		} else version(Windows) {
5595 			this.onClick = onClick;
5596 			this.win32Icon = new WindowsIcon(icon);
5597 
5598 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5599 
5600 			static bool registered = false;
5601 			if(!registered) {
5602 				WNDCLASSEX wc;
5603 				wc.cbSize = wc.sizeof;
5604 				wc.hInstance = hInstance;
5605 				wc.lpfnWndProc = &WndProc;
5606 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5607 				if(!RegisterClassExW(&wc))
5608 					throw new WindowsApiException("RegisterClass", GetLastError());
5609 				registered = true;
5610 			}
5611 
5612 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5613 			if(hwnd is null)
5614 				throw new WindowsApiException("CreateWindow", GetLastError());
5615 
5616 			data.cbSize = data.sizeof;
5617 			data.hWnd = hwnd;
5618 			data.uID = cast(uint) cast(void*) this;
5619 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5620 				// NIF_INFO means show balloon
5621 			data.uCallbackMessage = WM_USER;
5622 			data.hIcon = this.win32Icon.hIcon;
5623 			data.szTip = ""; // FIXME
5624 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5625 			data.dwStateMask = NIS_HIDDEN; // windows vista
5626 
5627 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5628 
5629 
5630 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5631 
5632 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5633 		} else version(OSXCocoa) {
5634 			throw new NotYetImplementedException();
5635 		} else static assert(0);
5636 	}
5637 
5638 	/// ditto
5639 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5640 		version(X11) {
5641 			this.onClick = onClick;
5642 			this.name = name;
5643 			createXWin();
5644 			this.icon = icon;
5645 		} else version(Windows) {
5646 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5647 		} else version(OSXCocoa) {
5648 			throw new NotYetImplementedException();
5649 		} else static assert(0);
5650 	}
5651 
5652 	version(X11) {
5653 		/++
5654 			X-specific extension (for now at least)
5655 		+/
5656 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5657 			this.onClickEx = onClickEx;
5658 			createXWin();
5659 			if (icon !is null) this.icon = icon;
5660 		}
5661 
5662 		/// ditto
5663 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5664 			this.onClickEx = onClickEx;
5665 			createXWin();
5666 			this.icon = icon;
5667 		}
5668 	}
5669 
5670 	private void delegate (MouseButton button) onClick_;
5671 
5672 	///
5673 	@property final void delegate(MouseButton) onClick() {
5674 		if(onClick_ is null)
5675 			onClick_ = delegate void(MouseButton) {};
5676 		return onClick_;
5677 	}
5678 
5679 	/// ditto
5680 	@property final void onClick(void delegate(MouseButton) handler) {
5681 		// I made this a property setter so we can wrap smaller arg
5682 		// delegates and just forward all to onClickEx or something.
5683 		onClick_ = handler;
5684 	}
5685 
5686 
5687 	string name_;
5688 	@property void name(string n) {
5689 		name_ = n;
5690 	}
5691 
5692 	@property string name() {
5693 		return name_;
5694 	}
5695 
5696 	private MemoryImage originalMemoryImage;
5697 
5698 	///
5699 	@property void icon(MemoryImage i) {
5700 		version(X11) {
5701 			this.originalMemoryImage = i;
5702 			if (!active) return;
5703 			if (i !is null) {
5704 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5705 				if(!useAlpha)
5706 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5707 				// writeln("using pixmap ", clippixmap);
5708 				updateNetWmIcon();
5709 				redraw();
5710 			} else {
5711 				if (this.img !is null) {
5712 					this.img = null;
5713 					redraw();
5714 				}
5715 			}
5716 		} else version(Windows) {
5717 			this.win32Icon = new WindowsIcon(i);
5718 
5719 			data.uFlags = NIF_ICON;
5720 			data.hIcon = this.win32Icon.hIcon;
5721 
5722 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5723 		} else version(OSXCocoa) {
5724 			throw new NotYetImplementedException();
5725 		} else static assert(0);
5726 	}
5727 
5728 	/// ditto
5729 	@property void icon (Image i) {
5730 		version(X11) {
5731 			if (!active) return;
5732 			if (i !is img) {
5733 				originalMemoryImage = null;
5734 				img = i;
5735 				redraw();
5736 			}
5737 		} else version(Windows) {
5738 			this.icon(i is null ? null : i.toTrueColorImage());
5739 		} else version(OSXCocoa) {
5740 			throw new NotYetImplementedException();
5741 		} else static assert(0);
5742 	}
5743 
5744 	/++
5745 		Shows a balloon notification. You can only show one balloon at a time, if you call
5746 		it twice while one is already up, the first balloon will be replaced.
5747 
5748 
5749 		The user is free to block notifications and they will automatically disappear after
5750 		a timeout period.
5751 
5752 		Params:
5753 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5754 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5755 			icon = the icon to display with the notification. If null, it uses your existing icon.
5756 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5757 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5758 	+/
5759 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5760 		bool useCustom = true;
5761 		version(libnotify) {
5762 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5763 			try {
5764 				if(!active) return;
5765 
5766 				if(libnotify is null) {
5767 					libnotify = new C_DynamicLibrary("libnotify.so");
5768 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5769 				}
5770 
5771 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5772 
5773 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5774 
5775 				if(onclick) {
5776 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5777 					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);
5778 					libnotify_action_delegates_count++;
5779 				}
5780 
5781 				// FIXME icon
5782 
5783 				// set hint image-data
5784 				// set default action for onclick
5785 
5786 				void* error;
5787 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5788 
5789 				useCustom = false;
5790 			} catch(Exception e) {
5791 
5792 			}
5793 		}
5794 
5795 		version(X11) {
5796 		if(useCustom) {
5797 			if(!active) return;
5798 			if(balloon) {
5799 				hideBalloon();
5800 			}
5801 			// I know there are two specs for this, but one is never
5802 			// implemented by any window manager I have ever seen, and
5803 			// the other is a bloated mess and too complicated for simpledisplay...
5804 			// so doing my own little window instead.
5805 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5806 
5807 			int x, y, width, height;
5808 			getWindowRect(x, y, width, height);
5809 
5810 			int bx = x - balloon.width;
5811 			int by = y - balloon.height;
5812 			if(bx < 0)
5813 				bx = x + width + balloon.width;
5814 			if(by < 0)
5815 				by = y + height;
5816 
5817 			// just in case, make sure it is actually on scren
5818 			if(bx < 0)
5819 				bx = 0;
5820 			if(by < 0)
5821 				by = 0;
5822 
5823 			balloon.move(bx, by);
5824 			auto painter = balloon.draw();
5825 			painter.fillColor = Color(220, 220, 220);
5826 			painter.outlineColor = Color.black;
5827 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5828 			auto iconWidth = icon is null ? 0 : icon.width;
5829 			if(icon)
5830 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5831 			iconWidth += 6; // margin around the icon
5832 
5833 			// draw a close button
5834 			painter.outlineColor = Color(44, 44, 44);
5835 			painter.fillColor = Color(255, 255, 255);
5836 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5837 			painter.pen = Pen(Color.black, 3);
5838 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5839 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5840 			painter.pen = Pen(Color.black, 1);
5841 			painter.fillColor = Color(220, 220, 220);
5842 
5843 			// Draw the title and message
5844 			painter.drawText(Point(4 + iconWidth, 4), title);
5845 			painter.drawLine(
5846 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5847 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5848 			);
5849 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5850 
5851 			balloon.setEventHandlers(
5852 				(MouseEvent ev) {
5853 					if(ev.type == MouseEventType.buttonPressed) {
5854 						if(ev.x > balloon.width - 16 && ev.y < 16)
5855 							hideBalloon();
5856 						else if(onclick)
5857 							onclick();
5858 					}
5859 				}
5860 			);
5861 			balloon.show();
5862 
5863 			version(with_timer)
5864 			timer = new Timer(timeout, &hideBalloon);
5865 			else {} // FIXME
5866 		}
5867 		} else version(Windows) {
5868 			enum NIF_INFO = 0x00000010;
5869 
5870 			data.uFlags = NIF_INFO;
5871 
5872 			// FIXME: go back to the last valid unicode code point
5873 			if(title.length > 40)
5874 				title = title[0 .. 40];
5875 			if(message.length > 220)
5876 				message = message[0 .. 220];
5877 
5878 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5879 			enum NIIF_LARGE_ICON  = 0x00000020;
5880 			enum NIIF_NOSOUND = 0x00000010;
5881 			enum NIIF_USER = 0x00000004;
5882 			enum NIIF_ERROR = 0x00000003;
5883 			enum NIIF_WARNING = 0x00000002;
5884 			enum NIIF_INFO = 0x00000001;
5885 			enum NIIF_NONE = 0;
5886 
5887 			WCharzBuffer t = WCharzBuffer(title);
5888 			WCharzBuffer m = WCharzBuffer(message);
5889 
5890 			t.copyInto(data.szInfoTitle);
5891 			m.copyInto(data.szInfo);
5892 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5893 
5894 			if(icon !is null) {
5895 				auto i = new WindowsIcon(icon);
5896 				data.hBalloonIcon = i.hIcon;
5897 				data.dwInfoFlags |= NIIF_USER;
5898 			}
5899 
5900 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5901 		} else version(OSXCocoa) {
5902 			throw new NotYetImplementedException();
5903 		} else static assert(0);
5904 	}
5905 
5906 	///
5907 	//version(Windows)
5908 	void show() {
5909 		version(X11) {
5910 			if(!hidden)
5911 				return;
5912 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5913 			hidden = false;
5914 		} else version(Windows) {
5915 			data.uFlags = NIF_STATE;
5916 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5917 			data.dwStateMask = NIS_HIDDEN; // windows vista
5918 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5919 		} else version(OSXCocoa) {
5920 			throw new NotYetImplementedException();
5921 		} else static assert(0);
5922 	}
5923 
5924 	version(X11)
5925 		bool hidden = false;
5926 
5927 	///
5928 	//version(Windows)
5929 	void hide() {
5930 		version(X11) {
5931 			if(hidden)
5932 				return;
5933 			hidden = true;
5934 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5935 		} else version(Windows) {
5936 			data.uFlags = NIF_STATE;
5937 			data.dwState = NIS_HIDDEN; // windows vista
5938 			data.dwStateMask = NIS_HIDDEN; // windows vista
5939 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5940 		} else version(OSXCocoa) {
5941 			throw new NotYetImplementedException();
5942 		} else static assert(0);
5943 	}
5944 
5945 	///
5946 	void close () {
5947 		version(X11) {
5948 			if (active) {
5949 				active = false; // event handler will set this too, but meh
5950 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5951 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5952 				flushGui();
5953 			}
5954 		} else version(Windows) {
5955 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5956 		} else version(OSXCocoa) {
5957 			throw new NotYetImplementedException();
5958 		} else static assert(0);
5959 	}
5960 
5961 	~this() {
5962 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5963 		version(X11)
5964 			if(clippixmap != None)
5965 				XFreePixmap(XDisplayConnection.get, clippixmap);
5966 		close();
5967 	}
5968 }
5969 
5970 version(X11)
5971 /// Call `XFreePixmap` on the return value.
5972 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5973 	char[] data = new char[](i.width * i.height / 8 + 2);
5974 	data[] = 0;
5975 
5976 	int bitOffset = 0;
5977 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5978 		ubyte v = c.a > 128 ? 1 : 0;
5979 		data[bitOffset / 8] |= v << (bitOffset%8);
5980 		bitOffset++;
5981 	}
5982 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5983 	return handle;
5984 }
5985 
5986 
5987 // basic functions to make timers
5988 /**
5989 	A timer that will trigger your function on a given interval.
5990 
5991 
5992 	You create a timer with an interval and a callback. It will continue
5993 	to fire on the interval until it is destroyed.
5994 
5995 	There are currently no one-off timers (instead, just create one and
5996 	destroy it when it is triggered) nor are there pause/resume functions -
5997 	the timer must again be destroyed and recreated if you want to pause it.
5998 
5999 	---
6000 	auto timer = new Timer(50, { it happened!; });
6001 	timer.destroy();
6002 	---
6003 
6004 	Timers can only be expected to fire when the event loop is running and only
6005 	once per iteration through the event loop.
6006 
6007 	History:
6008 		Prior to December 9, 2020, a timer pulse set too high with a handler too
6009 		slow could lock up the event loop. It now guarantees other things will
6010 		get a chance to run between timer calls, even if that means not keeping up
6011 		with the requested interval.
6012 */
6013 version(with_timer) {
6014 static if(use_arsd_core) {
6015 	alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api
6016 } else
6017 class Timer {
6018 // FIXME: needs pause and unpause
6019 	// FIXME: I might add overloads for ones that take a count of
6020 	// how many elapsed since last time (on Windows, it will divide
6021 	// the ticks thing given, on Linux it is just available) and
6022 	// maybe one that takes an instance of the Timer itself too
6023 	/// Create a timer with a callback when it triggers.
6024 	this(int intervalInMilliseconds, void delegate() onPulse) @trusted {
6025 		assert(onPulse !is null);
6026 
6027 		this.intervalInMilliseconds = intervalInMilliseconds;
6028 		this.onPulse = onPulse;
6029 
6030 		version(Windows) {
6031 			/*
6032 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6033 			if(handle == 0)
6034 				throw new WindowsApiException("SetTimer", GetLastError());
6035 			*/
6036 
6037 			// thanks to Archival 998 for the WaitableTimer blocks
6038 			handle = CreateWaitableTimer(null, false, null);
6039 			long initialTime = -intervalInMilliseconds;
6040 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6041 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
6042 
6043 			mapping[handle] = this;
6044 
6045 		} else version(Emscripten) {
6046 		} else version(linux) {
6047 			static import ep = core.sys.linux.epoll;
6048 
6049 			import core.sys.linux.timerfd;
6050 
6051 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
6052 			if(fd == -1)
6053 				throw new Exception("timer create failed");
6054 
6055 			mapping[fd] = this;
6056 
6057 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6058 
6059 			if(timerfd_settime(fd, 0, &value, null) == -1)
6060 				throw new Exception("couldn't make pulse timer");
6061 
6062 			version(with_eventloop) {
6063 				import arsd.eventloop;
6064 				addFileEventListeners(fd, &trigger, null, null);
6065 			} else {
6066 				prepareEventLoop();
6067 
6068 				ep.epoll_event ev = void;
6069 				ev.events = ep.EPOLLIN;
6070 				ev.data.fd = fd;
6071 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6072 			}
6073 		} else featureNotImplemented();
6074 	}
6075 
6076 	private int intervalInMilliseconds;
6077 
6078 	// just cuz I sometimes call it this.
6079 	alias dispose = destroy;
6080 
6081 	/// Stop and destroy the timer object.
6082 	void destroy() {
6083 		version(Windows) {
6084 			staticDestroy(handle);
6085 			handle = null;
6086 		} else version(linux) {
6087 			staticDestroy(fd);
6088 			fd = -1;
6089 		} else featureNotImplemented();
6090 	}
6091 
6092 	version(Windows)
6093 	static void staticDestroy(HANDLE handle) {
6094 		if(handle) {
6095 			// KillTimer(null, handle);
6096 			CancelWaitableTimer(cast(void*)handle);
6097 			mapping.remove(handle);
6098 			CloseHandle(handle);
6099 		}
6100 	}
6101 	else version(Emscripten)
6102 	static void staticDestroy(int fd) @system {
6103 		assert(0);
6104 	}
6105 	else version(linux)
6106 	static void staticDestroy(int fd) @system {
6107 		if(fd != -1) {
6108 			import unix = core.sys.posix.unistd;
6109 			static import ep = core.sys.linux.epoll;
6110 
6111 			version(with_eventloop) {
6112 				import arsd.eventloop;
6113 				removeFileEventListeners(fd);
6114 			} else {
6115 				ep.epoll_event ev = void;
6116 				ev.events = ep.EPOLLIN;
6117 				ev.data.fd = fd;
6118 
6119 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6120 			}
6121 			unix.close(fd);
6122 			mapping.remove(fd);
6123 		}
6124 	}
6125 
6126 	~this() {
6127 		version(Windows) { if(handle)
6128 			cleanupQueue.queue!staticDestroy(handle);
6129 		} else version(linux) { if(fd != -1)
6130 			cleanupQueue.queue!staticDestroy(fd);
6131 		}
6132 	}
6133 
6134 	void changeTime(int intervalInMilliseconds)
6135 	{
6136 		this.intervalInMilliseconds = intervalInMilliseconds;
6137 		version(Windows)
6138 		{
6139 			if(handle)
6140 			{
6141 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6142 				long initialTime = -intervalInMilliseconds;
6143 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6144 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
6145 			}
6146 		} else version(linux) {
6147 			import core.sys.linux.timerfd;
6148 
6149 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6150 			if(timerfd_settime(fd, 0, &value, null) == -1) {
6151 				throw new Exception("couldn't change pulse timer");
6152 			}
6153 		} else {
6154 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
6155 		}
6156 	}
6157 
6158 
6159 	private:
6160 
6161 	void delegate() onPulse;
6162 
6163 	int lastEventLoopRoundTriggered;
6164 
6165 	version(linux) {
6166 		static auto makeItimerspec(int intervalInMilliseconds) {
6167 			import core.sys.linux.timerfd;
6168 
6169 			itimerspec value;
6170 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6171 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6172 
6173 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6174 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6175 
6176 			return value;
6177 		}
6178 	}
6179 
6180 	void trigger() {
6181 		version(linux) {
6182 			import unix = core.sys.posix.unistd;
6183 			long val;
6184 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
6185 		} else version(Windows) {
6186 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
6187 				return; // never try to actually run faster than the event loop
6188 			lastEventLoopRoundTriggered = eventLoopRound;
6189 		} else featureNotImplemented();
6190 
6191 		onPulse();
6192 	}
6193 
6194 	version(Windows)
6195 	void rearm() {
6196 
6197 	}
6198 
6199 	version(Windows)
6200 		extern(Windows)
6201 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
6202 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
6203 			if(Timer* t = timer in mapping) {
6204 				try
6205 				(*t).trigger();
6206 				catch(Exception e) { sdpy_abort(e); assert(0); }
6207 			}
6208 		}
6209 
6210 	version(Windows) {
6211 		//UINT_PTR handle;
6212 		//static Timer[UINT_PTR] mapping;
6213 		HANDLE handle;
6214 		__gshared Timer[HANDLE] mapping;
6215 	} else version(linux) {
6216 		int fd = -1;
6217 		__gshared Timer[int] mapping;
6218 	} else version(OSXCocoa) {
6219 	} else static assert(0, "timer not supported");
6220 }
6221 }
6222 
6223 version(Windows)
6224 private int eventLoopRound;
6225 
6226 version(Windows)
6227 /// 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
6228 class WindowsHandleReader {
6229 	///
6230 	this(void delegate() onReady, HANDLE handle) {
6231 		this.onReady = onReady;
6232 		this.handle = handle;
6233 
6234 		mapping[handle] = this;
6235 
6236 		enable();
6237 	}
6238 
6239 	static if(use_arsd_core)
6240 		ICoreEventLoop.UnregisterToken unregisterToken;
6241 
6242 	///
6243 	void enable() {
6244 		static if(use_arsd_core) {
6245 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready));
6246 		} else {
6247 			auto el = EventLoop.get().impl;
6248 			el.handles ~= handle;
6249 		}
6250 	}
6251 
6252 	///
6253 	void disable() {
6254 		static if(use_arsd_core) {
6255 			unregisterToken.unregister();
6256 		} else {
6257 			auto el = EventLoop.get().impl;
6258 			for(int i = 0; i < el.handles.length; i++) {
6259 				if(el.handles[i] is handle) {
6260 					el.handles[i] = el.handles[$-1];
6261 					el.handles = el.handles[0 .. $-1];
6262 					return;
6263 				}
6264 			}
6265 		}
6266 	}
6267 
6268 	void dispose() {
6269 		disable();
6270 		if(handle)
6271 			mapping.remove(handle);
6272 		handle = null;
6273 	}
6274 
6275 	void ready() {
6276 		if(onReady)
6277 			onReady();
6278 	}
6279 
6280 	HANDLE handle;
6281 	void delegate() onReady;
6282 
6283 	__gshared WindowsHandleReader[HANDLE] mapping;
6284 }
6285 
6286 version(Posix)
6287 /// Lets you add files to the event loop for reading. Use at your own risk.
6288 class PosixFdReader {
6289 	///
6290 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6291 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
6292 	}
6293 
6294 	///
6295 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6296 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
6297 	}
6298 
6299 	///
6300 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6301 		this.onReady = onReady;
6302 		this.fd = fd;
6303 		this.captureWrites = captureWrites;
6304 		this.captureReads = captureReads;
6305 
6306 		mapping[fd] = this;
6307 
6308 		version(with_eventloop) {
6309 			import arsd.eventloop;
6310 			addFileEventListeners(fd, &readyel);
6311 		} else {
6312 			enable();
6313 		}
6314 	}
6315 
6316 	bool captureReads;
6317 	bool captureWrites;
6318 
6319 	static if(use_arsd_core) {
6320 		import arsd.core;
6321 		ICoreEventLoop.UnregisterToken unregisterToken;
6322 	}
6323 
6324 	version(with_eventloop) {} else
6325 	///
6326 	void enable() @system {
6327 		enabled = true;
6328 
6329 		static if(use_arsd_core) {
6330 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper(
6331 				() { onReady(fd, true, false); }
6332 			));
6333 			// FIXME: what if it is writeable?
6334 
6335 		} else version(linux) {
6336 			prepareEventLoop();
6337 			static import ep = core.sys.linux.epoll;
6338 			ep.epoll_event ev = void;
6339 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6340 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
6341 			ev.data.fd = fd;
6342 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6343 		} else {
6344 
6345 		}
6346 	}
6347 
6348 	version(with_eventloop) {} else
6349 	///
6350 	void disable() @system {
6351 		enabled = false;
6352 
6353 		static if(use_arsd_core) {
6354 			unregisterToken.unregister();
6355 		} else
6356 		version(linux) {
6357 			prepareEventLoop();
6358 			static import ep = core.sys.linux.epoll;
6359 			ep.epoll_event ev = void;
6360 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6361 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
6362 			ev.data.fd = fd;
6363 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6364 		}
6365 	}
6366 
6367 	version(with_eventloop) {} else
6368 	///
6369 	void dispose() {
6370 		if(enabled)
6371 			disable();
6372 		if(fd != -1)
6373 			mapping.remove(fd);
6374 		fd = -1;
6375 	}
6376 
6377 	void delegate(int, bool, bool) onReady;
6378 
6379 	version(with_eventloop)
6380 	void readyel() {
6381 		onReady(fd, true, true);
6382 	}
6383 
6384 	void ready(uint flags) {
6385 		version(Emscripten) {
6386 			assert(0);
6387 		} else version(linux) {
6388 			static import ep = core.sys.linux.epoll;
6389 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
6390 		} else {
6391 			import core.sys.posix.poll;
6392 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
6393 		}
6394 	}
6395 
6396 	void hup(uint flags) {
6397 		if(onHup)
6398 			onHup();
6399 	}
6400 
6401 	void delegate() onHup;
6402 
6403 	int fd = -1;
6404 	private bool enabled;
6405 	__gshared PosixFdReader[int] mapping;
6406 }
6407 
6408 // basic functions to access the clipboard
6409 /+
6410 
6411 
6412 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
6413 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
6414 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6415 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
6416 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
6417 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6418 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
6419 
6420 +/
6421 
6422 /++
6423 	this does a delegate because it is actually an async call on X...
6424 	the receiver may never be called if the clipboard is empty or unavailable
6425 	gets plain text from the clipboard.
6426 +/
6427 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system {
6428 	version(Windows) {
6429 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6430 		if(OpenClipboard(hwndOwner) == 0)
6431 			throw new WindowsApiException("OpenClipboard", GetLastError());
6432 		scope(exit)
6433 			CloseClipboard();
6434 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
6435 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
6436 
6437 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
6438 				scope(exit)
6439 					GlobalUnlock(dataHandle);
6440 
6441 				// FIXME: CR/LF conversions
6442 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
6443 				int len = 0;
6444 				auto d = data;
6445 				while(*d) {
6446 					d++;
6447 					len++;
6448 				}
6449 				string s;
6450 				s.reserve(len);
6451 				foreach(dchar ch; data[0 .. len]) {
6452 					s ~= ch;
6453 				}
6454 				receiver(s);
6455 			}
6456 		}
6457 	} else version(X11) {
6458 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6459 	} else version(OSXCocoa) {
6460 		throw new NotYetImplementedException();
6461 	} else version(Emscripten) {
6462 		throw new NotYetImplementedException();
6463 	} else static assert(0);
6464 }
6465 
6466 // FIXME: a clipboard listener might be cool btw
6467 
6468 /++
6469 	this does a delegate because it is actually an async call on X...
6470 	the receiver may never be called if the clipboard is empty or unavailable
6471 	gets image from the clipboard.
6472 
6473 	templated because it introduces an optional dependency on arsd.bmp
6474 +/
6475 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6476 	version(Windows) {
6477 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6478 		if(OpenClipboard(hwndOwner) == 0)
6479 			throw new WindowsApiException("OpenClipboard", GetLastError());
6480 		scope(exit)
6481 			CloseClipboard();
6482 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6483 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6484 				scope(exit)
6485 					GlobalUnlock(dataHandle);
6486 
6487 				auto len = GlobalSize(dataHandle);
6488 
6489 				import arsd.bmp;
6490 				auto img = readBmp(data[0 .. len], false);
6491 				receiver(img);
6492 			}
6493 		}
6494 	} else version(X11) {
6495 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6496 	} else version(OSXCocoa) {
6497 		throw new NotYetImplementedException();
6498 	} else version(Emscripten) {
6499 		throw new NotYetImplementedException();
6500 	} else static assert(0);
6501 }
6502 
6503 /// Copies some text to the clipboard.
6504 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6505 	assert(clipboardOwner !is null);
6506 	version(Windows) {
6507 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6508 			throw new WindowsApiException("OpenClipboard", GetLastError());
6509 		scope(exit)
6510 			CloseClipboard();
6511 		EmptyClipboard();
6512 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6513 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6514 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6515 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6516 			auto slice = data[0 .. sz];
6517 			scope(failure)
6518 				GlobalUnlock(handle);
6519 
6520 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6521 
6522 			GlobalUnlock(handle);
6523 			SetClipboardData(CF_UNICODETEXT, handle);
6524 		}
6525 	} else version(X11) {
6526 		// we set BOTH clipboard and primary on an explicit action
6527 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6528 		setX11Selection!"PRIMARY"(clipboardOwner, text);
6529 	} else version(OSXCocoa) {
6530 		throw new NotYetImplementedException();
6531 	} else version(Emscripten) {
6532 		throw new NotYetImplementedException();
6533 	} else static assert(0);
6534 }
6535 
6536 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6537 	assert(clipboardOwner !is null);
6538 	version(Windows) {
6539 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6540 			throw new WindowsApiException("OpenClipboard", GetLastError());
6541 		scope(exit)
6542 			CloseClipboard();
6543 		EmptyClipboard();
6544 
6545 
6546 		import arsd.bmp;
6547 		ubyte[] mdata;
6548 		mdata.reserve(img.width * img.height);
6549 		void sink(ubyte b) {
6550 			mdata ~= b;
6551 		}
6552 		writeBmpIndirect(img, &sink, false);
6553 
6554 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6555 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6556 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6557 			auto slice = data[0 .. mdata.length];
6558 			scope(failure)
6559 				GlobalUnlock(handle);
6560 
6561 			slice[] = mdata[];
6562 
6563 			GlobalUnlock(handle);
6564 			SetClipboardData(CF_DIB, handle);
6565 		}
6566 	} else version(X11) {
6567 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6568 			mixin X11SetSelectionHandler_Basics;
6569 			private const(ubyte)[] mdata;
6570 			private const(ubyte)[] mdata_original;
6571 			this(MemoryImage img) {
6572 				import arsd.bmp;
6573 
6574 				mdata.reserve(img.width * img.height);
6575 				void sink(ubyte b) {
6576 					mdata ~= b;
6577 				}
6578 				writeBmpIndirect(img, &sink, true);
6579 
6580 				mdata_original = mdata;
6581 			}
6582 
6583 			Atom[] availableFormats() {
6584 				auto display = XDisplayConnection.get;
6585 				return [
6586 					GetAtom!"image/bmp"(display),
6587 					GetAtom!"TARGETS"(display)
6588 				];
6589 			}
6590 
6591 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6592 				if(mdata.length < data.length) {
6593 					data[0 .. mdata.length] = mdata[];
6594 					auto ret = data[0 .. mdata.length];
6595 					mdata = mdata[$..$];
6596 					return ret;
6597 				} else {
6598 					data[] = mdata[0 .. data.length];
6599 					mdata = mdata[data.length .. $];
6600 					return data[];
6601 				}
6602 			}
6603 
6604 			void done() {
6605 				mdata = mdata_original;
6606 			}
6607 		}
6608 
6609 		auto handler = new X11SetSelectionHandler_Image(img);
6610 		setX11Selection!"PRIMARY"(clipboardOwner, handler);
6611 		setX11Selection!"CLIPBOARD"(clipboardOwner, handler);
6612 	} else version(OSXCocoa) {
6613 		throw new NotYetImplementedException();
6614 	} else version(Emscripten) {
6615 		throw new NotYetImplementedException();
6616 	} else static assert(0);
6617 }
6618 
6619 
6620 version(X11) {
6621 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6622 
6623 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6624 
6625 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6626 	/// Platform-specific for X11.
6627 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6628 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6629 		__gshared static Atom a;
6630 		if(!a) {
6631 			a = XInternAtom(display, name, !create);
6632 			// FIXME: might need to synchronize this and attach it to the actual object
6633 			interredAtoms ~= &a;
6634 		}
6635 		if(a == None)
6636 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6637 		return a;
6638 	}
6639 
6640 	/// Platform-specific for X11 - gets atom names as a string.
6641 	string getAtomName(Atom atom, Display* display) {
6642 		auto got = XGetAtomName(display, atom);
6643 		scope(exit) XFree(got);
6644 		import core.stdc.string;
6645 		string s = got[0 .. strlen(got)].idup;
6646 		return s;
6647 	}
6648 
6649 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6650 	void setPrimarySelection(SimpleWindow window, string text) {
6651 		setX11Selection!"PRIMARY"(window, text);
6652 	}
6653 
6654 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6655 	void setSecondarySelection(SimpleWindow window, string text) {
6656 		setX11Selection!"SECONDARY"(window, text);
6657 	}
6658 
6659 	interface X11SetSelectionHandler {
6660 		// should include TARGETS right now
6661 		Atom[] availableFormats();
6662 		// Return the slice of data you filled, empty slice if done.
6663 		// this is to support the incremental thing
6664 		ubyte[] getData(Atom format, return scope ubyte[] data);
6665 
6666 		void done();
6667 
6668 		void handleRequest(XEvent);
6669 
6670 		bool matchesIncr(Window, Atom);
6671 		void sendMoreIncr(XPropertyEvent*);
6672 	}
6673 
6674 	mixin template X11SetSelectionHandler_Basics() {
6675 		Window incrWindow;
6676 		Atom incrAtom;
6677 		Atom selectionAtom;
6678 		Atom formatAtom;
6679 		ubyte[] toSend;
6680 		bool matchesIncr(Window w, Atom a) {
6681 			return incrAtom && incrAtom == a && w == incrWindow;
6682 		}
6683 		void sendMoreIncr(XPropertyEvent* event) {
6684 			auto display = XDisplayConnection.get;
6685 
6686 			XChangeProperty (display,
6687 				incrWindow,
6688 				incrAtom,
6689 				formatAtom,
6690 				8 /* bits */, PropModeReplace,
6691 				toSend.ptr, cast(int) toSend.length);
6692 
6693 			if(toSend.length != 0) {
6694 				toSend = this.getData(formatAtom, toSend[]);
6695 			} else {
6696 				this.done();
6697 				incrWindow = None;
6698 				incrAtom = None;
6699 				selectionAtom = None;
6700 				formatAtom = None;
6701 				toSend = null;
6702 			}
6703 		}
6704 		void handleRequest(XEvent ev) {
6705 
6706 			auto display = XDisplayConnection.get;
6707 
6708 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6709 			XSelectionEvent selectionEvent;
6710 			selectionEvent.type = EventType.SelectionNotify;
6711 			selectionEvent.display = event.display;
6712 			selectionEvent.requestor = event.requestor;
6713 			selectionEvent.selection = event.selection;
6714 			selectionEvent.time = event.time;
6715 			selectionEvent.target = event.target;
6716 
6717 			bool supportedType() {
6718 				foreach(t; this.availableFormats())
6719 					if(t == event.target)
6720 						return true;
6721 				return false;
6722 			}
6723 
6724 			if(event.property == None) {
6725 				selectionEvent.property = event.target;
6726 
6727 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6728 				XFlush(display);
6729 			} if(event.target == GetAtom!"TARGETS"(display)) {
6730 				/* respond with the supported types */
6731 				auto tlist = this.availableFormats();
6732 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6733 				selectionEvent.property = event.property;
6734 
6735 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6736 				XFlush(display);
6737 			} else if(supportedType()) {
6738 				auto buffer = new ubyte[](1024 * 64);
6739 				auto toSend = this.getData(event.target, buffer[]);
6740 
6741 				if(toSend.length < 32 * 1024) {
6742 					// small enough to send directly...
6743 					selectionEvent.property = event.property;
6744 					XChangeProperty (display,
6745 						selectionEvent.requestor,
6746 						selectionEvent.property,
6747 						event.target,
6748 						8 /* bits */, 0 /* PropModeReplace */,
6749 						toSend.ptr, cast(int) toSend.length);
6750 
6751 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6752 					XFlush(display);
6753 				} else {
6754 					// large, let's send incrementally
6755 					arch_ulong l = toSend.length;
6756 
6757 					// if I wanted other events from this window don't want to clear that out....
6758 					XWindowAttributes xwa;
6759 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6760 
6761 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6762 
6763 					incrWindow = event.requestor;
6764 					incrAtom = event.property;
6765 					formatAtom = event.target;
6766 					selectionAtom = event.selection;
6767 					this.toSend = toSend;
6768 
6769 					selectionEvent.property = event.property;
6770 					XChangeProperty (display,
6771 						selectionEvent.requestor,
6772 						selectionEvent.property,
6773 						GetAtom!"INCR"(display),
6774 						32 /* bits */, PropModeReplace,
6775 						&l, 1);
6776 
6777 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6778 					XFlush(display);
6779 				}
6780 				//if(after)
6781 					//after();
6782 			} else {
6783 				debug(sdpy_clip) {
6784 					writeln("Unsupported data ", getAtomName(event.target, display));
6785 				}
6786 				selectionEvent.property = None; // I don't know how to handle this type...
6787 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6788 				XFlush(display);
6789 			}
6790 		}
6791 	}
6792 
6793 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6794 		mixin X11SetSelectionHandler_Basics;
6795 		private const(ubyte)[] text;
6796 		private const(ubyte)[] text_original;
6797 		this(string text) {
6798 			this.text = cast(const ubyte[]) text;
6799 			this.text_original = this.text;
6800 		}
6801 		Atom[] availableFormats() {
6802 			auto display = XDisplayConnection.get;
6803 			return [
6804 				GetAtom!"UTF8_STRING"(display),
6805 				GetAtom!"text/plain"(display),
6806 				XA_STRING,
6807 				GetAtom!"TARGETS"(display)
6808 			];
6809 		}
6810 
6811 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6812 			if(text.length < data.length) {
6813 				data[0 .. text.length] = text[];
6814 				return data[0 .. text.length];
6815 			} else {
6816 				data[] = text[0 .. data.length];
6817 				text = text[data.length .. $];
6818 				return data[];
6819 			}
6820 		}
6821 
6822 		void done() {
6823 			text = text_original;
6824 		}
6825 	}
6826 
6827 	/// 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?!)
6828 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6829 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6830 	}
6831 
6832 	private __gshared bool mightShortCircuitClipboard;
6833 
6834 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6835 		assert(window !is null);
6836 
6837 		auto display = XDisplayConnection.get();
6838 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6839 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6840 		else Atom a = GetAtom!atomName(display);
6841 
6842 		if(mightShortCircuitClipboard)
6843 		if(auto ptr = a in window.impl.setSelectionHandlers) {
6844 			// we already have it, don't even need to inform the X server
6845 			// sdpyPrintDebugString("short circuit in set");
6846 			*ptr = data;
6847 			return;
6848 		}
6849 
6850 		// we don't have it, tell X we want it
6851 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6852 		window.impl.setSelectionHandlers[a] = data;
6853 		mightShortCircuitClipboard = true;
6854 	}
6855 
6856 	/+
6857 	/++
6858 		History:
6859 			Added September 28, 2024
6860 	+/
6861 	bool hasX11Selection(string atomName)(SimpleWindow window) {
6862 		auto display = XDisplayConnection.get();
6863 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6864 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6865 		else Atom a = GetAtom!atomName(display);
6866 
6867 		if(a in window.impl.setSelectionHandlers)
6868 			return true;
6869 		else
6870 			return false;
6871 	}
6872 	+/
6873 
6874 	///
6875 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6876 		getX11Selection!"PRIMARY"(window, handler);
6877 	}
6878 
6879 	// added July 28, 2020
6880 	// undocumented as experimental tho
6881 	interface X11GetSelectionHandler {
6882 		void handleData(Atom target, in ubyte[] data);
6883 		Atom findBestFormat(Atom[] answer);
6884 
6885 		void prepareIncremental(Window, Atom);
6886 		bool matchesIncr(Window, Atom);
6887 		void handleIncrData(Atom, in ubyte[] data);
6888 	}
6889 
6890 	mixin template X11GetSelectionHandler_Basics() {
6891 		Window incrWindow;
6892 		Atom incrAtom;
6893 
6894 		void prepareIncremental(Window w, Atom a) {
6895 			incrWindow = w;
6896 			incrAtom = a;
6897 		}
6898 		bool matchesIncr(Window w, Atom a) {
6899 			return incrWindow == w && incrAtom == a;
6900 		}
6901 
6902 		Atom incrFormatAtom;
6903 		ubyte[] incrData;
6904 		void handleIncrData(Atom format, in ubyte[] data) {
6905 			incrFormatAtom = format;
6906 
6907 			if(data.length)
6908 				incrData ~= data;
6909 			else
6910 				handleData(incrFormatAtom, incrData);
6911 
6912 		}
6913 	}
6914 
6915 	static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6916 		this(void delegate(in char[]) handler) {
6917 			this.handler = handler;
6918 		}
6919 
6920 		mixin X11GetSelectionHandler_Basics;
6921 
6922 		void delegate(in char[]) handler;
6923 
6924 		void handleData(Atom target, in ubyte[] data) {
6925 			// import std.stdio; writeln(target, " ", data);
6926 			if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6927 				handler(cast(const char[]) data);
6928 			else if(target == None && data is null)
6929 				handler(null); // no suitable selection exists
6930 		}
6931 
6932 		Atom findBestFormat(Atom[] answer) {
6933 			Atom best = None;
6934 			foreach(option; answer) {
6935 				if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6936 					best = option;
6937 					break;
6938 				} else if(option == XA_STRING) {
6939 					best = option;
6940 				} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6941 					best = option;
6942 				}
6943 			}
6944 			return best;
6945 		}
6946 	}
6947 
6948 	///
6949 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6950 		assert(window !is null);
6951 
6952 		auto display = XDisplayConnection.get();
6953 
6954 		static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY;
6955 		else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY;
6956 		else Atom atom = GetAtom!atomName(display);
6957 
6958 		if(mightShortCircuitClipboard)
6959 		if(auto ptr = atom in window.impl.setSelectionHandlers) {
6960 			if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) {
6961 				// we already have it! short circuit everything
6962 
6963 				// sdpyPrintDebugString("short circuit in get");
6964 				handler(cast(char[]) txt.text_original);
6965 				return;
6966 			}
6967 		}
6968 
6969 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6970 
6971 		auto target = GetAtom!"TARGETS"(display);
6972 
6973 		// SDD_DATA is "simpledisplay.d data"
6974 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6975 	}
6976 
6977 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6978 	/// only supports bmps. using this function will import arsd.bmp.
6979 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6980 		assert(window !is null);
6981 
6982 		auto display = XDisplayConnection.get();
6983 		auto atom = GetAtom!atomName(display);
6984 
6985 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6986 			this(void delegate(MemoryImage) handler) {
6987 				this.handler = handler;
6988 			}
6989 
6990 			mixin X11GetSelectionHandler_Basics;
6991 
6992 			void delegate(MemoryImage) handler;
6993 
6994 			void handleData(Atom target, in ubyte[] data) {
6995 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6996 					import arsd.bmp;
6997 					handler(readBmp(data));
6998 				}
6999 			}
7000 
7001 			Atom findBestFormat(Atom[] answer) {
7002 				Atom best = None;
7003 				foreach(option; answer) {
7004 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
7005 						best = option;
7006 					}
7007 				}
7008 				return best;
7009 			}
7010 
7011 		}
7012 
7013 
7014 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
7015 
7016 		auto target = GetAtom!"TARGETS"(display);
7017 
7018 		// SDD_DATA is "simpledisplay.d data"
7019 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
7020 	}
7021 
7022 
7023 	///
7024 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
7025 		Atom actualType;
7026 		int actualFormat;
7027 		arch_ulong actualItems;
7028 		arch_ulong bytesRemaining;
7029 		void* data;
7030 
7031 		auto display = XDisplayConnection.get();
7032 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
7033 			if(actualFormat == 0)
7034 				return null;
7035 			else {
7036 				int byteLength;
7037 				if(actualFormat == 32) {
7038 					// 32 means it is a C long... which is variable length
7039 					actualFormat = cast(int) arch_long.sizeof * 8;
7040 				}
7041 
7042 				// then it is just a bit count
7043 				byteLength = cast(int) (actualItems * actualFormat / 8);
7044 
7045 				auto d = new ubyte[](byteLength);
7046 				d[] = cast(ubyte[]) data[0 .. byteLength];
7047 				XFree(data);
7048 				return d;
7049 			}
7050 		}
7051 		return null;
7052 	}
7053 
7054 	/* defined in the systray spec */
7055 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
7056 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
7057 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
7058 
7059 
7060 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
7061 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
7062 	public class GlobalHotkey {
7063 		KeyEvent key;
7064 		void delegate () handler;
7065 
7066 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
7067 
7068 		/// Create from initialzed KeyEvent object
7069 		this (KeyEvent akey, void delegate () ahandler=null) {
7070 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
7071 			key = akey;
7072 			handler = ahandler;
7073 		}
7074 
7075 		/// Create from emacs-like key name ("C-M-Y", etc.)
7076 		this (const(char)[] akey, void delegate () ahandler=null) {
7077 			key = KeyEvent.parse(akey);
7078 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
7079 			handler = ahandler;
7080 		}
7081 
7082 	}
7083 
7084 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7085 		//conwriteln("failed to grab key");
7086 		GlobalHotkeyManager.ghfailed = true;
7087 		return 0;
7088 	}
7089 
7090 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7091 		Image.impl.xshmfailed = true;
7092 		return 0;
7093 	}
7094 
7095 	private __gshared int errorHappened;
7096 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7097 		import core.stdc.stdio;
7098 		char[265] buffer;
7099 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
7100 		debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, cast(long) evt.serial, evt.request_code, evt.minor_code, cast(long) evt.resourceid);
7101 		errorHappened = true;
7102 		return 0;
7103 	}
7104 
7105 	/++
7106 		Global hotkey manager. It contains static methods to manage global hotkeys.
7107 
7108 		---
7109 		 try {
7110 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
7111 		} catch (Exception e) {
7112 			conwriteln("ERROR registering hotkey!");
7113 		}
7114 		EventLoop.get.run();
7115 		---
7116 
7117 		The key strings are based on Emacs. In practical terms,
7118 		`M` means `alt` and `H` means the Windows logo key. `C`
7119 		is `ctrl`.
7120 
7121 		$(WARNING
7122 			This is X-specific right now. If you are on
7123 			Windows, try [registerHotKey] instead.
7124 
7125 			We will probably merge these into a single
7126 			interface later.
7127 		)
7128 	+/
7129 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
7130 		version(X11) {
7131 			void recreateAfterDisconnect() {
7132 				throw new Exception("NOT IMPLEMENTED");
7133 			}
7134 			void discardConnectionState() {
7135 				throw new Exception("NOT IMPLEMENTED");
7136 			}
7137 		}
7138 
7139 		private static immutable uint[8] masklist = [ 0,
7140 			KeyOrButtonMask.LockMask,
7141 			KeyOrButtonMask.Mod2Mask,
7142 			KeyOrButtonMask.Mod3Mask,
7143 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
7144 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
7145 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7146 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7147 		];
7148 		private __gshared GlobalHotkeyManager ghmanager;
7149 		private __gshared bool ghfailed = false;
7150 
7151 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
7152 			if (modmask == 0) return false;
7153 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
7154 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
7155 			return true;
7156 		}
7157 
7158 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
7159 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
7160 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
7161 			return modmask;
7162 		}
7163 
7164 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
7165 			uint keycode = cast(uint)ke.key;
7166 			auto dpy = XDisplayConnection.get;
7167 			return XKeysymToKeycode(dpy, keycode);
7168 		}
7169 
7170 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
7171 
7172 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
7173 
7174 		NativeEventHandler getNativeEventHandler () {
7175 			return delegate int (XEvent e) {
7176 				if (e.type != EventType.KeyPress) return 1;
7177 				auto kev = cast(const(XKeyEvent)*)&e;
7178 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
7179 				if (auto ghkp = hash in globalHotkeyList) {
7180 					try {
7181 						ghkp.doHandle();
7182 					} catch (Exception e) {
7183 						import core.stdc.stdio : stderr, fprintf;
7184 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
7185 					}
7186 				}
7187 				return 1;
7188 			};
7189 		}
7190 
7191 		private this () {
7192 			auto dpy = XDisplayConnection.get;
7193 			auto root = RootWindow(dpy, DefaultScreen(dpy));
7194 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
7195 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
7196 		}
7197 
7198 		/// Register new global hotkey with initialized `GlobalHotkey` object.
7199 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
7200 		static void register (GlobalHotkey gh) {
7201 			if (gh is null) return;
7202 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
7203 
7204 			auto dpy = XDisplayConnection.get;
7205 			immutable keycode = keyEvent2KeyCode(gh.key);
7206 
7207 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
7208 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
7209 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
7210 			XSync(dpy, 0/*False*/);
7211 
7212 			Window root = RootWindow(dpy, DefaultScreen(dpy));
7213 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7214 			ghfailed = false;
7215 			foreach (immutable uint ormask; masklist[]) {
7216 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
7217 			}
7218 			XSync(dpy, 0/*False*/);
7219 			XSetErrorHandler(savedErrorHandler);
7220 
7221 			if (ghfailed) {
7222 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7223 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
7224 				XSync(dpy, 0/*False*/);
7225 				XSetErrorHandler(savedErrorHandler);
7226 				throw new Exception("cannot register global hotkey");
7227 			}
7228 
7229 			globalHotkeyList[hash] = gh;
7230 		}
7231 
7232 		/// Ditto
7233 		static void register (const(char)[] akey, void delegate () ahandler) {
7234 			register(new GlobalHotkey(akey, ahandler));
7235 		}
7236 
7237 		private static void removeByHash (ulong hash) {
7238 			if (auto ghp = hash in globalHotkeyList) {
7239 				auto dpy = XDisplayConnection.get;
7240 				immutable keycode = keyEvent2KeyCode(ghp.key);
7241 				Window root = RootWindow(dpy, DefaultScreen(dpy));
7242 				XSync(dpy, 0/*False*/);
7243 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7244 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
7245 				XSync(dpy, 0/*False*/);
7246 				XSetErrorHandler(savedErrorHandler);
7247 				globalHotkeyList.remove(hash);
7248 			}
7249 		}
7250 
7251 		/// Register new global hotkey with previously used `GlobalHotkey` object.
7252 		/// It is safe to unregister unknown or invalid hotkey.
7253 		static void unregister (GlobalHotkey gh) {
7254 			//TODO: add second AA for faster search? prolly doesn't worth it.
7255 			if (gh is null) return;
7256 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
7257 				if (kv.value is gh) {
7258 					removeByHash(kv.key);
7259 					return;
7260 				}
7261 			}
7262 		}
7263 
7264 		/// Ditto.
7265 		static void unregister (const(char)[] key) {
7266 			auto kev = KeyEvent.parse(key);
7267 			immutable keycode = keyEvent2KeyCode(kev);
7268 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
7269 		}
7270 	}
7271 }
7272 
7273 version(Windows) {
7274 	/++
7275 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
7276 
7277 		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).
7278 	+/
7279 	void sendSyntheticInput(wstring s) {
7280 			INPUT[] inputs;
7281 			inputs.reserve(s.length * 2);
7282 
7283 			foreach(wchar c; s) {
7284 				INPUT input;
7285 				input.type = INPUT_KEYBOARD;
7286 				input.ki.wScan = c;
7287 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7288 				inputs ~= input;
7289 
7290 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7291 				inputs ~= input;
7292 			}
7293 
7294 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7295 				throw new WindowsApiException("SendInput", GetLastError());
7296 			}
7297 
7298 	}
7299 
7300 
7301 	// global hotkey helper function
7302 
7303 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
7304 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system {
7305 		__gshared int hotkeyId = 0;
7306 		int id = ++hotkeyId;
7307 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
7308 			throw new Exception("RegisterHotKey");
7309 
7310 		__gshared void delegate()[WPARAM][HWND] handlers;
7311 
7312 		handlers[window.impl.hwnd][id] = handler;
7313 
7314 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
7315 
7316 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7317 			switch(msg) {
7318 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
7319 				case WM_HOTKEY:
7320 					if(auto list = hwnd in handlers) {
7321 						if(auto h = wParam in *list) {
7322 							(*h)();
7323 							return 0;
7324 						}
7325 					}
7326 				goto default;
7327 				default:
7328 			}
7329 			if(oldHandler)
7330 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
7331 			return 1; // pass it on
7332 		};
7333 
7334 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
7335 			oldHandler = window.handleNativeEvent;
7336 			window.handleNativeEvent = nativeEventHandler;
7337 		}
7338 
7339 		return id;
7340 	}
7341 
7342 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
7343 	void unregisterHotKey(SimpleWindow window, int id) {
7344 		if(!UnregisterHotKey(window.impl.hwnd, id))
7345 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
7346 	}
7347 }
7348 
7349 version (X11) {
7350 	pragma(lib, "dl");
7351 	import core.sys.posix.dlfcn;
7352 }
7353 
7354 /++
7355 	Allows for sending synthetic input to the X server via the Xtst
7356 	extension or on Windows using SendInput.
7357 
7358 	Please remember user input is meant to be user - don't use this
7359 	if you have some other alternative!
7360 
7361 	History:
7362 		Added May 17, 2020 with the X implementation.
7363 
7364 		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.)
7365 	Bugs:
7366 		All methods on OSX Cocoa will throw not yet implemented exceptions.
7367 +/
7368 struct SyntheticInput {
7369 	@disable this();
7370 
7371 	private int* refcount;
7372 
7373 	version(X11) {
7374 		private void* lib;
7375 
7376 		private extern(C) {
7377 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
7378 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
7379 		}
7380 	}
7381 
7382 	/// The dummy param must be 0.
7383 	this(int dummy) {
7384 		version(X11) {
7385 			lib = dlopen("libXtst.so", RTLD_NOW);
7386 			if(lib is null)
7387 				throw new Exception("cannot load xtest lib extension");
7388 			scope(failure)
7389 				dlclose(lib);
7390 
7391 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
7392 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
7393 
7394 			if(XTestFakeKeyEvent is null)
7395 				throw new Exception("No XTestFakeKeyEvent");
7396 			if(XTestFakeButtonEvent is null)
7397 				throw new Exception("No XTestFakeButtonEvent");
7398 		}
7399 
7400 		refcount = new int;
7401 		*refcount = 1;
7402 	}
7403 
7404 	this(this) {
7405 		if(refcount)
7406 			*refcount += 1;
7407 	}
7408 
7409 	~this() {
7410 		if(refcount) {
7411 			*refcount -= 1;
7412 			if(*refcount == 0)
7413 				// I commented this because if I close the lib before
7414 				// XCloseDisplay, it is liable to segfault... so just
7415 				// gonna keep it loaded if it is loaded, no big deal
7416 				// anyway.
7417 				{} // dlclose(lib);
7418 		}
7419 	}
7420 
7421 	/++
7422 		Simulates typing a string into the keyboard.
7423 
7424 		Bugs:
7425 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
7426 
7427 			Not implemented except on Windows and X11.
7428 	+/
7429 	void sendSyntheticInput(string s) {
7430 		version(Windows) {
7431 			INPUT[] inputs;
7432 			inputs.reserve(s.length * 2);
7433 
7434 			auto ei = GetMessageExtraInfo();
7435 
7436 			foreach(wchar c; s) {
7437 				INPUT input;
7438 				input.type = INPUT_KEYBOARD;
7439 				input.ki.wScan = c;
7440 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7441 				input.ki.dwExtraInfo = ei;
7442 				inputs ~= input;
7443 
7444 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7445 				inputs ~= input;
7446 			}
7447 
7448 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7449 				throw new WindowsApiException("SendInput", GetLastError());
7450 			}
7451 		} else version(X11) {
7452 			int delay = 0;
7453 			foreach(ch; s) {
7454 				pressKey(cast(Key) ch, true, delay);
7455 				pressKey(cast(Key) ch, false, delay);
7456 				delay += 5;
7457 			}
7458 		} else throw new NotYetImplementedException();
7459 	}
7460 
7461 	/++
7462 		Sends a fake press or release key event.
7463 
7464 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7465 
7466 		Bugs:
7467 			The `delay` parameter is not implemented yet on Windows.
7468 
7469 			Not implemented except on Windows and X11.
7470 	+/
7471 	void pressKey(Key key, bool pressed, int delay = 0) {
7472 		version(Windows) {
7473 			INPUT input;
7474 			input.type = INPUT_KEYBOARD;
7475 			input.ki.wVk = cast(ushort) key;
7476 
7477 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
7478 			input.ki.dwExtraInfo = GetMessageExtraInfo();
7479 
7480 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7481 				throw new WindowsApiException("SendInput", GetLastError());
7482 			}
7483 		} else version(X11) {
7484 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
7485 		} else throw new NotYetImplementedException();
7486 	}
7487 
7488 	/++
7489 		Sends a fake mouse button press or release event.
7490 
7491 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7492 
7493 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
7494 
7495 		Bugs:
7496 			The `delay` parameter is not implemented yet on Windows.
7497 
7498 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
7499 
7500 			All arguments will throw NotYetImplementedException on OSX Cocoa.
7501 	+/
7502 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
7503 		version(Windows) {
7504 			INPUT input;
7505 			input.type = INPUT_MOUSE;
7506 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7507 
7508 			// input.mi.mouseData for a wheel event
7509 
7510 			switch(button) {
7511 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
7512 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
7513 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
7514 				case MouseButton.wheelUp:
7515 				case MouseButton.wheelDown:
7516 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
7517 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
7518 				break;
7519 				case MouseButton.backButton: throw new NotYetImplementedException();
7520 				case MouseButton.forwardButton: throw new NotYetImplementedException();
7521 				default:
7522 			}
7523 
7524 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7525 				throw new WindowsApiException("SendInput", GetLastError());
7526 			}
7527 		} else version(X11) {
7528 			int btn;
7529 
7530 			switch(button) {
7531 				case MouseButton.left: btn = 1; break;
7532 				case MouseButton.middle: btn = 2; break;
7533 				case MouseButton.right: btn = 3; break;
7534 				case MouseButton.wheelUp: btn = 4; break;
7535 				case MouseButton.wheelDown: btn = 5; break;
7536 				case MouseButton.backButton: btn = 8; break;
7537 				case MouseButton.forwardButton: btn = 9; break;
7538 				default:
7539 			}
7540 
7541 			assert(btn);
7542 
7543 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7544 		} else throw new NotYetImplementedException();
7545 	}
7546 
7547 	///
7548 	static void moveMouseArrowBy(int dx, int dy) {
7549 		version(Windows) {
7550 			INPUT input;
7551 			input.type = INPUT_MOUSE;
7552 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7553 			input.mi.dx = dx;
7554 			input.mi.dy = dy;
7555 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7556 
7557 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7558 				throw new WindowsApiException("SendInput", GetLastError());
7559 			}
7560 		} else version(X11) {
7561 			auto disp = XDisplayConnection.get();
7562 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7563 			XFlush(disp);
7564 		} else throw new NotYetImplementedException();
7565 	}
7566 
7567 	///
7568 	static void moveMouseArrowTo(int x, int y) {
7569 		version(Windows) {
7570 			INPUT input;
7571 			input.type = INPUT_MOUSE;
7572 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7573 			input.mi.dx = x;
7574 			input.mi.dy = y;
7575 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7576 
7577 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7578 				throw new WindowsApiException("SendInput", GetLastError());
7579 			}
7580 		} else version(X11) {
7581 			auto disp = XDisplayConnection.get();
7582 			auto root = RootWindow(disp, DefaultScreen(disp));
7583 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7584 			XFlush(disp);
7585 		} else throw new NotYetImplementedException();
7586 	}
7587 }
7588 
7589 
7590 
7591 /++
7592 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7593 
7594 	See_Also:
7595 	$(LIST
7596 		*[ScreenPainter]
7597 		*[ScreenPainter.rasterOp]
7598 	)
7599 +/
7600 enum RasterOp {
7601 	normal, /// Replaces the pixel.
7602 	xor, /// Uses bitwise xor to draw.
7603 }
7604 
7605 // being phobos-free keeps the size WAY down
7606 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7607 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7608 package(arsd) const(wchar)* toWStringz(string s) {
7609 	wstring r;
7610 	foreach(dchar c; s)
7611 		r ~= c;
7612 	r ~= '\0';
7613 	return r.ptr;
7614 }
7615 private string[] split(in void[] a, char c) {
7616 		string[] ret;
7617 		size_t previous = 0;
7618 		foreach(i, char ch; cast(ubyte[]) a) {
7619 			if(ch == c) {
7620 				ret ~= cast(string) a[previous .. i];
7621 				previous = i + 1;
7622 			}
7623 		}
7624 		if(previous != a.length)
7625 			ret ~= cast(string) a[previous .. $];
7626 		return ret;
7627 	}
7628 
7629 version(without_opengl) {
7630 	enum OpenGlOptions {
7631 		no,
7632 	}
7633 } else {
7634 	/++
7635 		Determines if you want an OpenGL context created on the new window.
7636 
7637 
7638 		See more: [#topics-3d|in the 3d topic].
7639 
7640 		---
7641 		import arsd.simpledisplay;
7642 		void main() {
7643 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7644 
7645 			// Set up the matrix
7646 			window.setAsCurrentOpenGlContext(); // make this window active
7647 
7648 			// This is called on each frame, we will draw our scene
7649 			window.redrawOpenGlScene = delegate() {
7650 
7651 			};
7652 
7653 			window.eventLoop(0);
7654 		}
7655 		---
7656 	+/
7657 	enum OpenGlOptions {
7658 		no, /// No OpenGL context is created
7659 		yes, /// Yes, create an OpenGL context
7660 	}
7661 
7662 	version(X11) {
7663 		static if (!SdpyIsUsingIVGLBinds) {
7664 
7665 
7666 			struct __GLXFBConfigRec {}
7667 			alias GLXFBConfig = __GLXFBConfigRec*;
7668 
7669 			//pragma(lib, "GL");
7670 			//pragma(lib, "GLU");
7671 			interface GLX {
7672 			extern(C) nothrow @nogc {
7673 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7674 						const int *attrib_list);
7675 
7676 				 void glXCopyContext(Display *dpy, GLXContext src,
7677 						GLXContext dst, arch_ulong mask);
7678 
7679 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7680 						GLXContext share_list, Bool direct);
7681 
7682 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7683 						Pixmap pixmap);
7684 
7685 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7686 
7687 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7688 
7689 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7690 						int attrib, int *value);
7691 
7692 				 GLXContext glXGetCurrentContext();
7693 
7694 				 GLXDrawable glXGetCurrentDrawable();
7695 
7696 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7697 
7698 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7699 						GLXContext ctx);
7700 
7701 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7702 
7703 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7704 
7705 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7706 
7707 				 void glXUseXFont(Font font, int first, int count, int list_base);
7708 
7709 				 void glXWaitGL();
7710 
7711 				 void glXWaitX();
7712 
7713 
7714 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7715 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7716 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7717 
7718 				char* glXQueryExtensionsString (Display*, int);
7719 				void* glXGetProcAddress (const(char)*);
7720 
7721 			}
7722 			}
7723 
7724 			version(OSX)
7725 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7726 			else
7727 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7728 			shared static this() {
7729 				glx.loadDynamicLibrary();
7730 			}
7731 
7732 			alias glbindGetProcAddress = glXGetProcAddress;
7733 		}
7734 	} else version(Windows) {
7735 		/* it is done below by interface GL */
7736 	} else
7737 		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.");
7738 }
7739 
7740 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7741 alias Resizablity = Resizability;
7742 
7743 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7744 enum Resizability {
7745 	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.
7746 	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.
7747 	/++
7748 		$(PITFALL
7749 			Planned for the future but not implemented.
7750 		)
7751 
7752 		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.
7753 
7754 		History:
7755 			Added November 11, 2022, but not yet implemented and may not be for some time.
7756 	+/
7757 	/*@__future*/ allowResizingMaintainingAspectRatio,
7758 	/++
7759 		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.
7760 
7761 		History:
7762 			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.
7763 
7764 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7765 	+/
7766 	automaticallyScaleIfPossible,
7767 }
7768 /// ditto
7769 alias Resizeability = Resizability;
7770 
7771 
7772 /++
7773 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7774 +/
7775 enum TextAlignment : uint {
7776 	Left = 0, ///
7777 	Center = 1, ///
7778 	Right = 2, ///
7779 
7780 	VerticalTop = 0, ///
7781 	VerticalCenter = 4, ///
7782 	VerticalBottom = 8, ///
7783 }
7784 
7785 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7786 alias Rectangle = arsd.color.Rectangle;
7787 
7788 
7789 /++
7790 	Keyboard press and release events.
7791 +/
7792 struct KeyEvent {
7793 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7794 	Key key;
7795 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7796 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7797 
7798 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7799 
7800 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7801 
7802 	SimpleWindow window; /// associated Window
7803 
7804 	/++
7805 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7806 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7807 		to predict if char events are actually coming..
7808 
7809 		Only available on X systems since this information is not given ahead of time elsewhere.
7810 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7811 
7812 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7813 		and potential quirks I'd recommend avoiding it.
7814 
7815 		History:
7816 			Added April 26, 2021 (dub v9.5)
7817 	+/
7818 	version(X11)
7819 		dchar[] charsPossible;
7820 
7821 	// convert key event to simplified string representation a-la emacs
7822 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7823 		uint dpos = 0;
7824 		void put (const(char)[] s...) nothrow @trusted {
7825 			static if (growdest) {
7826 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7827 			} else {
7828 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7829 			}
7830 		}
7831 
7832 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7833 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7834 		}
7835 
7836 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7837 
7838 		// put modifiers
7839 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7840 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7841 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7842 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7843 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7844 
7845 		if (this.key) {
7846 			foreach (string kn; __traits(allMembers, Key)) {
7847 				if (this.key == __traits(getMember, Key, kn)) {
7848 					// HACK!
7849 					static if (kn == "N0") put("0");
7850 					else static if (kn == "N1") put("1");
7851 					else static if (kn == "N2") put("2");
7852 					else static if (kn == "N3") put("3");
7853 					else static if (kn == "N4") put("4");
7854 					else static if (kn == "N5") put("5");
7855 					else static if (kn == "N6") put("6");
7856 					else static if (kn == "N7") put("7");
7857 					else static if (kn == "N8") put("8");
7858 					else static if (kn == "N9") put("9");
7859 					else put(kn);
7860 					return dest[0..dpos];
7861 				}
7862 			}
7863 			put("Unknown");
7864 		} else {
7865 			if (dpos && dest[dpos-1] == '+') --dpos;
7866 		}
7867 		return dest[0..dpos];
7868 	}
7869 
7870 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7871 
7872 	/** Parse string into key name with modifiers. It accepts things like:
7873 	 *
7874 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7875 	 *
7876 	 * Ctrl+Win+1 -- windows style
7877 	 *
7878 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7879 	 *
7880 	 * Ctrl Win 1 -- and space
7881 	 *
7882 	 * and even "Win + 1 + Ctrl".
7883 	 */
7884 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7885 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7886 
7887 		// remove trailing spaces
7888 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7889 
7890 		// tokens delimited by blank, '+', or '-'
7891 		// null on eol
7892 		const(char)[] getToken () nothrow @trusted @nogc {
7893 			// remove leading spaces and delimiters
7894 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7895 			if (name.length == 0) return null; // oops, no more tokens
7896 			// get token
7897 			size_t epos = 0;
7898 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7899 			assert(epos > 0 && epos <= name.length);
7900 			auto res = name[0..epos];
7901 			name = name[epos..$];
7902 			return res;
7903 		}
7904 
7905 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7906 			if (s0.length != s1.length) return false;
7907 			foreach (immutable ci, char c0; s0) {
7908 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7909 				char c1 = s1[ci];
7910 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7911 				if (c0 != c1) return false;
7912 			}
7913 			return true;
7914 		}
7915 
7916 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7917 		if (updown !is null) *updown = -1;
7918 		KeyEvent res;
7919 		res.key = cast(Key)0; // just in case
7920 		const(char)[] tk, tkn; // last token
7921 		bool allowEmascStyle = true;
7922 		bool ignoreModifiers = false;
7923 		tokenloop: for (;;) {
7924 			tk = tkn;
7925 			tkn = getToken();
7926 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7927 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7928 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7929 			if (allowEmascStyle && tkn.length != 0) {
7930 				if (tk.length == 1) {
7931 					char mdc = tk[0];
7932 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7933 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7934 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7935 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7936 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7937 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7938 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7939 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7940 				}
7941 			}
7942 			allowEmascStyle = false;
7943 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7944 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7945 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7946 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7947 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7948 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7949 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7950 			if (tk.length == 0) continue;
7951 			// try key name
7952 			if (res.key == 0) {
7953 				// little hack
7954 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7955 					final switch (tk[0]) {
7956 						case '0': tk = "N0"; break;
7957 						case '1': tk = "N1"; break;
7958 						case '2': tk = "N2"; break;
7959 						case '3': tk = "N3"; break;
7960 						case '4': tk = "N4"; break;
7961 						case '5': tk = "N5"; break;
7962 						case '6': tk = "N6"; break;
7963 						case '7': tk = "N7"; break;
7964 						case '8': tk = "N8"; break;
7965 						case '9': tk = "N9"; break;
7966 					}
7967 				}
7968 				foreach (string kn; __traits(allMembers, Key)) {
7969 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7970 				}
7971 			}
7972 			// unknown or duplicate key name, get out of here
7973 			break;
7974 		}
7975 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7976 		return res; // something
7977 	}
7978 
7979 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7980 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7981 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7982 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7983 		}
7984 		bool ignoreMods;
7985 		int updown;
7986 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7987 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7988 		if (this.key != ke.key) {
7989 			// things like "ctrl+alt" are complicated
7990 			uint tkm = this.modifierState&modmask;
7991 			uint kkm = ke.modifierState&modmask;
7992 			Key tk = this.key;
7993 			// ke
7994 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7995 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7996 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7997 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7998 			// this
7999 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
8000 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
8001 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
8002 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
8003 			return (tk == ke.key && tkm == kkm);
8004 		}
8005 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
8006 	}
8007 }
8008 
8009 /// Sets the application name.
8010 @property string ApplicationName(string name) {
8011 	return _applicationName = name;
8012 }
8013 
8014 string _applicationName;
8015 
8016 /// ditto
8017 @property string ApplicationName() {
8018 	if(_applicationName is null) {
8019 		import core.runtime;
8020 		return Runtime.args[0];
8021 	}
8022 	return _applicationName;
8023 }
8024 
8025 
8026 /// Type of a [MouseEvent].
8027 enum MouseEventType : int {
8028 	motion = 0, /// The mouse moved inside the window
8029 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
8030 	buttonReleased = 2, /// A mouse button was released
8031 }
8032 
8033 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
8034 /++
8035 	Listen for this on your event listeners if you are interested in mouse action.
8036 
8037 	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.
8038 
8039 	Examples:
8040 
8041 	This will draw boxes on the window with the mouse as you hold the left button.
8042 	---
8043 	import arsd.simpledisplay;
8044 
8045 	void main() {
8046 		auto window = new SimpleWindow();
8047 
8048 		window.eventLoop(0,
8049 			(MouseEvent ev) {
8050 				if(ev.modifierState & ModifierState.leftButtonDown) {
8051 					auto painter = window.draw();
8052 					painter.fillColor = Color.red;
8053 					painter.outlineColor = Color.black;
8054 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
8055 				}
8056 			}
8057 		);
8058 	}
8059 	---
8060 +/
8061 struct MouseEvent {
8062 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
8063 
8064 	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.
8065 	int y; /// Current Y position of the cursor when the event fired.
8066 
8067 	int dx; /// Change in X position since last report
8068 	int dy; /// Change in Y position since last report
8069 
8070 	MouseButton button; /// See [MouseButton]
8071 	int modifierState; /// See [ModifierState]
8072 
8073 	version(X11)
8074 		private Time timestamp;
8075 
8076 	/// Returns a linear representation of mouse button,
8077 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
8078 	///
8079 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
8080 	@property ubyte buttonLinear() const {
8081 		import core.bitop;
8082 		if(button == 0)
8083 			return 0;
8084 		return (bsf(button) + 1) & 0b1111;
8085 	}
8086 
8087 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
8088 
8089 	SimpleWindow window; /// The window in which the event happened.
8090 
8091 	Point globalCoordinates() {
8092 		Point p;
8093 		if(window is null)
8094 			throw new Exception("wtf");
8095 		static if(UsingSimpledisplayX11) {
8096 			Window child;
8097 			XTranslateCoordinates(
8098 				XDisplayConnection.get,
8099 				window.impl.window,
8100 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
8101 				x, y, &p.x, &p.y, &child);
8102 			return p;
8103 		} else version(Windows) {
8104 			POINT[1] points;
8105 			points[0].x = x;
8106 			points[0].y = y;
8107 			MapWindowPoints(
8108 				window.impl.hwnd,
8109 				null,
8110 				points.ptr,
8111 				points.length
8112 			);
8113 			p.x = points[0].x;
8114 			p.y = points[0].y;
8115 
8116 			return p;
8117 		} else version(OSXCocoa) {
8118 			auto rect = window.window.frame;
8119 			// FIXME: mapped right?
8120 			return Point(cast(int) rect.origin.x + x, cast(int) rect.origin.y + y);
8121 		} else version(Emscripten) {
8122 			throw new NotYetImplementedException();
8123 		} else static assert(0);
8124 	}
8125 
8126 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
8127 
8128 	/**
8129 	can contain emacs-like modifier prefix
8130 	case-insensitive names:
8131 		lmbX/leftX
8132 		rmbX/rightX
8133 		mmbX/middleX
8134 		wheelX
8135 		motion (no prefix allowed)
8136 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
8137 	*/
8138 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
8139 		if (str.length == 0) return false; // just in case
8140 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
8141 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
8142 		auto anchor = str;
8143 		uint mods = 0; // uint.max == any
8144 		// interesting bits in kmod
8145 		uint kmodmask =
8146 			ModifierState.shift|
8147 			ModifierState.ctrl|
8148 			ModifierState.alt|
8149 			ModifierState.windows|
8150 			ModifierState.leftButtonDown|
8151 			ModifierState.middleButtonDown|
8152 			ModifierState.rightButtonDown|
8153 			0;
8154 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
8155 		bool wasButtons = false;
8156 		while (str.length) {
8157 			if (str.ptr[0] <= ' ') {
8158 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
8159 				continue;
8160 			}
8161 			// one-letter modifier?
8162 			if (str.length >= 2 && str.ptr[1] == '-') {
8163 				switch (str.ptr[0]) {
8164 					case '*': // "any" modifier (cannot be undone)
8165 						mods = mods.max;
8166 						break;
8167 					case 'C': case 'c': // emacs "ctrl"
8168 						if (mods != mods.max) mods |= ModifierState.ctrl;
8169 						break;
8170 					case 'M': case 'm': // emacs "meta"
8171 						if (mods != mods.max) mods |= ModifierState.alt;
8172 						break;
8173 					case 'S': case 's': // emacs "shift"
8174 						if (mods != mods.max) mods |= ModifierState.shift;
8175 						break;
8176 					case 'H': case 'h': // emacs "hyper" (aka winkey)
8177 						if (mods != mods.max) mods |= ModifierState.windows;
8178 						break;
8179 					default:
8180 						return false; // unknown modifier
8181 				}
8182 				str = str[2..$];
8183 				continue;
8184 			}
8185 			// word
8186 			char[16] buf = void; // locased
8187 			auto wep = 0;
8188 			while (str.length) {
8189 				immutable char ch = str.ptr[0];
8190 				if (ch <= ' ' || ch == '-') break;
8191 				str = str[1..$];
8192 				if (wep > buf.length) return false; // too long
8193 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8194 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8195 				else return false; // invalid char
8196 			}
8197 			if (wep == 0) return false; // just in case
8198 			uint bnum;
8199 			enum UpDown { None = -1, Up, Down, Any }
8200 			auto updown = UpDown.None; // 0: up; 1: down
8201 			switch (buf[0..wep]) {
8202 				// left button
8203 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
8204 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
8205 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
8206 				case "lmb": case "left": bnum = 0; break;
8207 				// middle button
8208 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
8209 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
8210 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
8211 				case "mmb": case "middle": bnum = 1; break;
8212 				// right button
8213 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
8214 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
8215 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
8216 				case "rmb": case "right": bnum = 2; break;
8217 				// wheel
8218 				case "wheelup": updown = UpDown.Up; goto case "wheel";
8219 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
8220 				case "wheelany": updown = UpDown.Any; goto case "wheel";
8221 				case "wheel": bnum = 3; break;
8222 				// motion
8223 				case "motion": bnum = 7; break;
8224 				// unknown
8225 				default: return false;
8226 			}
8227 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8228 			// parse possible "-up" or "-down"
8229 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
8230 				wep = 0;
8231 				foreach (immutable idx, immutable char ch; str[1..$]) {
8232 					if (ch <= ' ' || ch == '-') break;
8233 					assert(idx == wep); // for now; trick
8234 					if (wep > buf.length) { wep = 0; break; } // too long
8235 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8236 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8237 					else { wep = 0; break; } // invalid char
8238 				}
8239 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
8240 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
8241 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
8242 				// remove parsed part
8243 				if (updown != UpDown.None) str = str[wep+1..$];
8244 			}
8245 			if (updown == UpDown.None) {
8246 				updown = UpDown.Down;
8247 			}
8248 			wasButtons = wasButtons || (bnum <= 2);
8249 			//assert(updown != UpDown.None);
8250 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8251 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
8252 			if (lastButt != lastButt.max) {
8253 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
8254 				if (mods != mods.max) {
8255 					uint butbit = 0;
8256 					final switch (lastButt&0x03) {
8257 						case 0: butbit = ModifierState.leftButtonDown; break;
8258 						case 1: butbit = ModifierState.middleButtonDown; break;
8259 						case 2: butbit = ModifierState.rightButtonDown; break;
8260 					}
8261 					     if (lastButt&Flag.Down) mods |= butbit;
8262 					else if (lastButt&Flag.Up) mods &= ~butbit;
8263 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
8264 				}
8265 			}
8266 			// remember last button
8267 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
8268 		}
8269 		// no button -- nothing to do
8270 		if (lastButt == lastButt.max) return false;
8271 		// done parsing, check if something's left
8272 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
8273 		// remove action button from mask
8274 		if ((lastButt&0xff) < 3) {
8275 			final switch (lastButt&0x03) {
8276 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
8277 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
8278 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
8279 			}
8280 		}
8281 		// special case: "Motion" means "ignore buttons"
8282 		if ((lastButt&0xff) == 7 && !wasButtons) {
8283 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
8284 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
8285 		}
8286 		uint kmod = event.modifierState&kmodmask;
8287 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
8288 		// check modifier state
8289 		if (mods != mods.max) {
8290 			if (kmod != mods) return false;
8291 		}
8292 		// now check type
8293 		if ((lastButt&0xff) == 7) {
8294 			// motion
8295 			if (event.type != MouseEventType.motion) return false;
8296 		} else if ((lastButt&0xff) == 3) {
8297 			// wheel
8298 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
8299 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
8300 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
8301 			return false;
8302 		} else {
8303 			// buttons
8304 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
8305 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
8306 			{
8307 				return false;
8308 			}
8309 			// button number
8310 			switch (lastButt&0x03) {
8311 				case 0: if (event.button != MouseButton.left) return false; break;
8312 				case 1: if (event.button != MouseButton.middle) return false; break;
8313 				case 2: if (event.button != MouseButton.right) return false; break;
8314 				default: return false;
8315 			}
8316 		}
8317 		return true;
8318 	}
8319 }
8320 
8321 version(arsd_mevent_strcmp_test) unittest {
8322 	MouseEvent event;
8323 	event.type = MouseEventType.buttonPressed;
8324 	event.button = MouseButton.left;
8325 	event.modifierState = ModifierState.ctrl;
8326 	assert(event == "C-LMB");
8327 	assert(event != "C-LMBUP");
8328 	assert(event != "C-LMB-UP");
8329 	assert(event != "C-S-LMB");
8330 	assert(event == "*-LMB");
8331 	assert(event != "*-LMB-UP");
8332 
8333 	event.type = MouseEventType.buttonReleased;
8334 	assert(event != "C-LMB");
8335 	assert(event == "C-LMBUP");
8336 	assert(event == "C-LMB-UP");
8337 	assert(event != "C-S-LMB");
8338 	assert(event != "*-LMB");
8339 	assert(event == "*-LMB-UP");
8340 
8341 	event.button = MouseButton.right;
8342 	event.modifierState |= ModifierState.shift;
8343 	event.type = MouseEventType.buttonPressed;
8344 	assert(event != "C-LMB");
8345 	assert(event != "C-LMBUP");
8346 	assert(event != "C-LMB-UP");
8347 	assert(event != "C-S-LMB");
8348 	assert(event != "*-LMB");
8349 	assert(event != "*-LMB-UP");
8350 
8351 	assert(event != "C-RMB");
8352 	assert(event != "C-RMBUP");
8353 	assert(event != "C-RMB-UP");
8354 	assert(event == "C-S-RMB");
8355 	assert(event == "*-RMB");
8356 	assert(event != "*-RMB-UP");
8357 }
8358 
8359 /// This gives a few more options to drawing lines and such
8360 struct Pen {
8361 	Color color; /// the foreground color
8362 	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.
8363 	Style style; /// See [Style]
8364 /+
8365 // From X.h
8366 
8367 #define LineSolid		0
8368 #define LineOnOffDash		1
8369 #define LineDoubleDash		2
8370        LineDou-        The full path of the line is drawn, but the
8371        bleDash         even dashes are filled differently from the
8372                        odd dashes (see fill-style) with CapButt
8373                        style used where even and odd dashes meet.
8374 
8375 
8376 
8377 /* capStyle */
8378 
8379 #define CapNotLast		0
8380 #define CapButt			1
8381 #define CapRound		2
8382 #define CapProjecting		3
8383 
8384 /* joinStyle */
8385 
8386 #define JoinMiter		0
8387 #define JoinRound		1
8388 #define JoinBevel		2
8389 
8390 /* fillStyle */
8391 
8392 #define FillSolid		0
8393 #define FillTiled		1
8394 #define FillStippled		2
8395 #define FillOpaqueStippled	3
8396 
8397 
8398 +/
8399 	/// Style of lines drawn
8400 	enum Style {
8401 		Solid, /// a solid line
8402 		Dashed, /// a dashed line
8403 		Dotted, /// a dotted line
8404 	}
8405 }
8406 
8407 
8408 /++
8409 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
8410 
8411 
8412 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
8413 
8414 	$(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.)
8415 
8416 	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.
8417 
8418 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
8419 
8420 	$(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.
8421 
8422 	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!
8423 
8424 	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!)
8425 
8426 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
8427 
8428 	---
8429 		auto image = new Image(256, 256);
8430 		scope(exit) destroy(image);
8431 	---
8432 
8433 	As long as you don't hold on to it outside the scope.
8434 
8435 	I might change it to be an owned pointer at some point in the future.
8436 
8437 	)
8438 
8439 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
8440 	you can also often get a fair amount of speedup by getting the raw data format and
8441 	writing some custom code.
8442 
8443 	FIXME INSERT EXAMPLES HERE
8444 
8445 
8446 +/
8447 final class Image {
8448 	///
8449 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
8450 		this.width = width;
8451 		this.height = height;
8452 		this.enableAlpha = enableAlpha;
8453 
8454 		impl.createImage(width, height, forcexshm, enableAlpha);
8455 	}
8456 
8457 	///
8458 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
8459 		this(size.width, size.height, forcexshm, enableAlpha);
8460 	}
8461 
8462 	private bool suppressDestruction;
8463 
8464 	version(X11)
8465 	this(XImage* handle) {
8466 		this.handle = handle;
8467 		this.rawData = cast(ubyte*) handle.data;
8468 		this.width = handle.width;
8469 		this.height = handle.height;
8470 		this.enableAlpha = handle.depth == 32;
8471 		suppressDestruction = true;
8472 	}
8473 
8474 	~this() {
8475 		if(suppressDestruction) return;
8476 		impl.dispose();
8477 	}
8478 
8479 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
8480 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
8481 	pure const @system nothrow {
8482 		/*
8483 			To use these to draw a blue rectangle with size WxH at position X,Y...
8484 
8485 			// make certain that it will fit before we proceed
8486 			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!
8487 
8488 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
8489 			// (though calculating them isn't really that expensive).
8490 			auto nextLineAdjustment = img.adjustmentForNextLine();
8491 			auto offR = img.redByteOffset();
8492 			auto offB = img.blueByteOffset();
8493 			auto offG = img.greenByteOffset();
8494 			auto bpp = img.bytesPerPixel();
8495 
8496 			auto data = img.getDataPointer();
8497 
8498 			// figure out the starting byte offset
8499 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
8500 
8501 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
8502 
8503 			// and now our drawing loop for the rectangle
8504 			foreach(y; 0 .. H) {
8505 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
8506 				foreach(x; 0 .. W) {
8507 					// write our color
8508 					data[offR] = 0;
8509 					data[offG] = 0;
8510 					data[offB] = 255;
8511 
8512 					data += bpp; // moving to the next pixel is just an addition...
8513 				}
8514 				startOfLine += nextLineAdjustment;
8515 			}
8516 
8517 
8518 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
8519 
8520 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
8521 			can be made into a bitmask or something so we can write them as *uint...
8522 		*/
8523 
8524 		///
8525 		int offsetForTopLeftPixel() {
8526 			version(X11) {
8527 				return 0;
8528 			} else version(Windows) {
8529 				if(enableAlpha) {
8530 					return (width * 4) * (height - 1);
8531 				} else {
8532 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
8533 				}
8534 			} else version(OSXCocoa) {
8535 				return 0 ; //throw new NotYetImplementedException();
8536 			} else version(Emscripten) {
8537 				return 0;
8538 			} else static assert(0, "fill in this info for other OSes");
8539 		}
8540 
8541 		///
8542 		int offsetForPixel(int x, int y) {
8543 			version(X11) {
8544 				auto offset = (y * width + x) * 4;
8545 				return offset;
8546 			} else version(Windows) {
8547 				if(enableAlpha) {
8548 					auto itemsPerLine = width * 4;
8549 					// remember, bmps are upside down
8550 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8551 					return offset;
8552 				} else {
8553 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8554 					// remember, bmps are upside down
8555 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8556 					return offset;
8557 				}
8558 			} else version(OSXCocoa) {
8559 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8560 			} else version(Emscripten) {
8561 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8562 			} else static assert(0, "fill in this info for other OSes");
8563 		}
8564 
8565 		///
8566 		int adjustmentForNextLine() {
8567 			version(X11) {
8568 				return width * 4;
8569 			} else version(Windows) {
8570 				// windows bmps are upside down, so the adjustment is actually negative
8571 				if(enableAlpha)
8572 					return - (cast(int) width * 4);
8573 				else
8574 					return -((cast(int) width * 3 + 3) / 4) * 4;
8575 			} else version(OSXCocoa) {
8576 				return width * 4 ; //throw new NotYetImplementedException();
8577 			} else version(Emscripten) {
8578 				return width * 4 ; //throw new NotYetImplementedException();
8579 			} else static assert(0, "fill in this info for other OSes");
8580 		}
8581 
8582 		/// once you have the position of a pixel, use these to get to the proper color
8583 		int redByteOffset() {
8584 			version(X11) {
8585 				return 2;
8586 			} else version(Windows) {
8587 				return 2;
8588 			} else version(OSXCocoa) {
8589 				return 2 ; //throw new NotYetImplementedException();
8590 			} else version(Emscripten) {
8591 				return 2 ; //throw new NotYetImplementedException();
8592 			} else static assert(0, "fill in this info for other OSes");
8593 		}
8594 
8595 		///
8596 		int greenByteOffset() {
8597 			version(X11) {
8598 				return 1;
8599 			} else version(Windows) {
8600 				return 1;
8601 			} else version(OSXCocoa) {
8602 				return 1 ; //throw new NotYetImplementedException();
8603 			} else version(Emscripten) {
8604 				return 1 ; //throw new NotYetImplementedException();
8605 			} else static assert(0, "fill in this info for other OSes");
8606 		}
8607 
8608 		///
8609 		int blueByteOffset() {
8610 			version(X11) {
8611 				return 0;
8612 			} else version(Windows) {
8613 				return 0;
8614 			} else version(OSXCocoa) {
8615 				return 0 ; //throw new NotYetImplementedException();
8616 			} else version(Emscripten) {
8617 				return 0 ; //throw new NotYetImplementedException();
8618 			} else static assert(0, "fill in this info for other OSes");
8619 		}
8620 
8621 		/// Only valid if [enableAlpha] is true
8622 		int alphaByteOffset() {
8623 			version(X11) {
8624 				return 3;
8625 			} else version(Windows) {
8626 				return 3;
8627 			} else version(OSXCocoa) {
8628 				return 3; //throw new NotYetImplementedException();
8629 			} else version(Emscripten) {
8630 				return 3 ; //throw new NotYetImplementedException();
8631 			} else static assert(0, "fill in this info for other OSes");
8632 		}
8633 	}
8634 
8635 	///
8636 	final void putPixel(int x, int y, Color c) {
8637 		if(x < 0 || x >= width)
8638 			return;
8639 		if(y < 0 || y >= height)
8640 			return;
8641 
8642 		impl.setPixel(x, y, c);
8643 	}
8644 
8645 	///
8646 	final Color getPixel(int x, int y) {
8647 		if(x < 0 || x >= width)
8648 			return Color.transparent;
8649 		if(y < 0 || y >= height)
8650 			return Color.transparent;
8651 
8652 		version(OSXCocoa) throw new NotYetImplementedException(); else
8653 		return impl.getPixel(x, y);
8654 	}
8655 
8656 	///
8657 	final void opIndexAssign(Color c, int x, int y) {
8658 		putPixel(x, y, c);
8659 	}
8660 
8661 	///
8662 	TrueColorImage toTrueColorImage() {
8663 		auto tci = new TrueColorImage(width, height);
8664 		convertToRgbaBytes(tci.imageData.bytes);
8665 		return tci;
8666 	}
8667 
8668 	///
8669 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8670 		auto tci = i.getAsTrueColorImage();
8671 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8672 		static if(UsingSimpledisplayX11)
8673 			img.premultiply = premultiply;
8674 		img.setRgbaBytes(tci.imageData.bytes);
8675 		return img;
8676 	}
8677 
8678 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8679 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8680 	/// if you pass null, it will allocate a new one.
8681 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8682 		if(where is null)
8683 			where = new ubyte[this.width*this.height*4];
8684 		convertToRgbaBytes(where);
8685 		return where;
8686 	}
8687 
8688 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8689 	void setRgbaBytes(in ubyte[] from ) {
8690 		assert(from.length == this.width * this.height * 4);
8691 		setFromRgbaBytes(from);
8692 	}
8693 
8694 	// FIXME: make properly cross platform by getting rgba right
8695 
8696 	/// warning: this is not portable across platforms because the data format can change
8697 	ubyte* getDataPointer() {
8698 		return impl.rawData;
8699 	}
8700 
8701 	/// for use with getDataPointer
8702 	final int bytesPerLine() const pure @safe nothrow {
8703 		version(Windows)
8704 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8705 		else version(X11)
8706 			return 4 * width;
8707 		else version(OSXCocoa)
8708 			return 4 * width;
8709 		else static assert(0);
8710 	}
8711 
8712 	/// for use with getDataPointer
8713 	final int bytesPerPixel() const pure @safe nothrow {
8714 		version(Windows)
8715 			return enableAlpha ? 4 : 3;
8716 		else version(X11)
8717 			return 4;
8718 		else version(OSXCocoa)
8719 			return 4;
8720 		else static assert(0);
8721 	}
8722 
8723 	///
8724 	immutable int width;
8725 
8726 	///
8727 	immutable int height;
8728 
8729 	///
8730 	immutable bool enableAlpha;
8731     //private:
8732 	mixin NativeImageImplementation!() impl;
8733 }
8734 
8735 /++
8736 	A convenience function to pop up a window displaying the image.
8737 	If you pass a win, it will draw the image in it. Otherwise, it will
8738 	create a window with the size of the image and run its event loop, closing
8739 	when a key is pressed.
8740 
8741 	History:
8742 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8743 		always block until the application quit which could cause bizarre behavior
8744 		inside a more complex application. Now, the default is to block until
8745 		this window closes if it is the only event loop running, and otherwise,
8746 		not to block at all and just pop up the display window asynchronously.
8747 +/
8748 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8749 	if(win is null) {
8750 		win = new SimpleWindow(image);
8751 		{
8752 			auto p = win.draw;
8753 			p.drawImage(Point(0, 0), image);
8754 		}
8755 		win.eventLoopWithBlockingMode(
8756 			bm, 0,
8757 			(KeyEvent ev) {
8758 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8759 			} );
8760 	} else {
8761 		win.image = image;
8762 	}
8763 }
8764 
8765 enum FontWeight : int {
8766 	dontcare = 0,
8767 	thin = 100,
8768 	extralight = 200,
8769 	light = 300,
8770 	regular = 400,
8771 	medium = 500,
8772 	semibold = 600,
8773 	bold = 700,
8774 	extrabold = 800,
8775 	heavy = 900
8776 }
8777 
8778 /++
8779 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8780 
8781 	History:
8782 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8783 +/
8784 interface MeasurableFont {
8785 	/++
8786 		History:
8787 			Added April 12, 2025
8788 	+/
8789 	//version(OSXCocoa)
8790 		alias fnum = float;
8791 	//else
8792 		//alias fnum = int;
8793 
8794 
8795 	/++
8796 		Returns true if it is a monospace font, meaning each of the
8797 		glyphs (at least the ascii characters) have matching width
8798 		and no kerning, so you can determine the display width of some
8799 		strings by simply multiplying the string width by [averageWidth].
8800 
8801 		(Please note that multiply doesn't $(I actually) work in general,
8802 		consider characters like tab and newline, but it does sometimes.)
8803 	+/
8804 	bool isMonospace();
8805 
8806 	/++
8807 		The average width of glyphs in the font, traditionally equal to the
8808 		width of the lowercase x. Can be used to estimate bounding boxes,
8809 		especially if the font [isMonospace].
8810 
8811 		Given in pixels.
8812 	+/
8813 	fnum averageWidth();
8814 	/++
8815 		The height of the bounding box of a line.
8816 	+/
8817 	fnum height();
8818 	/++
8819 		The maximum ascent of a glyph above the baseline.
8820 
8821 		Given in pixels.
8822 	+/
8823 	fnum ascent();
8824 	/++
8825 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8826 
8827 		Given in pixels.
8828 	+/
8829 	fnum descent();
8830 	/++
8831 		The display width of the given string, and if you provide a window, it will use it to
8832 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8833 
8834 		Given in pixels.
8835 	+/
8836 	fnum stringWidth(scope const(char)[] s, SimpleWindow window = null);
8837 
8838 }
8839 
8840 int castFnumToCnum(MeasurableFont.fnum i) {
8841 	static if(is(MeasurableFont.fnum : long))
8842 		return cast(int) i;
8843 	else
8844 		return cast(int) (i + 0.9);
8845 }
8846 
8847 // FIXME: i need a font cache and it needs to handle disconnects.
8848 
8849 /++
8850 	Represents a font loaded off the operating system or the X server.
8851 
8852 
8853 	While the api here is unified cross platform, the fonts are not necessarily
8854 	available, even across machines of the same platform, so be sure to always check
8855 	for null (using [isNull]) and have a fallback plan.
8856 
8857 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8858 
8859 	Worst case, a null font will automatically fall back to the default font loaded
8860 	for your system.
8861 +/
8862 class OperatingSystemFont : MeasurableFont {
8863 	// FIXME: when the X Connection is lost, these need to be invalidated!
8864 	// that means I need to store the original stuff again to reconstruct it too.
8865 
8866 	version(Emscripten) {
8867 		void* font;
8868 	} else version(X11) {
8869 		XFontStruct* font;
8870 		XFontSet fontset;
8871 
8872 		version(with_xft) {
8873 			XftFont* xftFont;
8874 			bool isXft;
8875 		}
8876 	} else version(Windows) {
8877 		HFONT font;
8878 		int width_;
8879 		int height_;
8880 	} else version(OSXCocoa) {
8881 		NSFont font;
8882 	} else static assert(0);
8883 
8884 	/++
8885 		Constructs the class and immediately calls [load].
8886 	+/
8887 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8888 		load(name, size, weight, italic);
8889 	}
8890 
8891 	/++
8892 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8893 
8894 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8895 
8896 		History:
8897 			Added January 24, 2021.
8898 	+/
8899 	this() {
8900 		// this space intentionally left blank
8901 	}
8902 
8903 	/++
8904 		Constructs a copy of the given font object.
8905 
8906 		History:
8907 			Added January 7, 2023.
8908 	+/
8909 	this(OperatingSystemFont font) {
8910 		if(font is null || font.loadedInfo is LoadedInfo.init)
8911 			loadDefault();
8912 		else
8913 			load(font.loadedInfo.tupleof);
8914 	}
8915 
8916 	/++
8917 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8918 
8919 		History:
8920 			Added November 13, 2020.
8921 	+/
8922 	version(with_xft)
8923 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8924 		unload();
8925 
8926 		if(!XftLibrary.attempted) {
8927 			XftLibrary.loadDynamicLibrary();
8928 		}
8929 
8930 		if(!XftLibrary.loadSuccessful)
8931 			return false;
8932 
8933 		auto display = XDisplayConnection.get;
8934 
8935 		char[256] nameBuffer = void;
8936 		int nbp = 0;
8937 
8938 		void add(in char[] a) {
8939 			nameBuffer[nbp .. nbp + a.length] = a[];
8940 			nbp += a.length;
8941 		}
8942 		add(name);
8943 
8944 		if(size) {
8945 			add(":size=");
8946 			add(toInternal!string(size));
8947 		}
8948 		if(weight != FontWeight.dontcare && weight != 400) {
8949 			if(weight < 400)
8950 				add(":style=Light");
8951 			else
8952 				add(":style=Bold");
8953 			add(":weight=");
8954 			add(weightToString(weight));
8955 		}
8956 		if(italic) {
8957 			if(weight == FontWeight.dontcare)
8958 				add(":style=Italic");
8959 			add(":slant=100");
8960 		}
8961 
8962 		nameBuffer[nbp] = 0;
8963 
8964 		this.xftFont = XftFontOpenName(
8965 			display,
8966 			DefaultScreen(display),
8967 			nameBuffer.ptr
8968 		);
8969 
8970 		this.isXft = true;
8971 
8972 		if(xftFont !is null) {
8973 			isMonospace_ = stringWidth("x") == stringWidth("M");
8974 			ascent_ = xftFont.ascent;
8975 			descent_ = xftFont.descent;
8976 		}
8977 
8978 		return !isNull();
8979 	}
8980 
8981 	/++
8982 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8983 
8984 
8985 		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.
8986 
8987 		If `pattern` is null, it returns all available font families.
8988 
8989 		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.
8990 
8991 		The format of the pattern is platform-specific.
8992 
8993 		History:
8994 			Added May 1, 2021 (dub v9.5)
8995 	+/
8996 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8997 		version(Windows) {
8998 			auto hdc = GetDC(null);
8999 			scope(exit) ReleaseDC(null, hdc);
9000 			LOGFONT logfont;
9001 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
9002 				auto localHandler = *(cast(typeof(handler)*) p);
9003 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
9004 			}
9005 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
9006 		} else version(X11) {
9007 			//import core.stdc.stdio;
9008 			bool done = false;
9009 			version(with_xft) {
9010 				if(!XftLibrary.attempted) {
9011 					XftLibrary.loadDynamicLibrary();
9012 				}
9013 
9014 				if(!XftLibrary.loadSuccessful)
9015 					goto skipXft;
9016 
9017 				if(!FontConfigLibrary.attempted)
9018 					FontConfigLibrary.loadDynamicLibrary();
9019 				if(!FontConfigLibrary.loadSuccessful)
9020 					goto skipXft;
9021 
9022 				{
9023 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
9024 					if(got is null)
9025 						goto skipXft;
9026 					scope(exit) FcFontSetDestroy(got);
9027 
9028 					auto fontPatterns = got.fonts[0 .. got.nfont];
9029 					foreach(candidate; fontPatterns) {
9030 						char* where, whereStyle;
9031 
9032 						char* pmg = FcNameUnparse(candidate);
9033 
9034 						//FcPatternGetString(candidate, "family", 0, &where);
9035 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
9036 						//if(where && whereStyle) {
9037 						if(pmg) {
9038 							if(!handler(pmg.sliceCString))
9039 								return;
9040 							//printf("%s || %s %s\n", pmg, where, whereStyle);
9041 						}
9042 					}
9043 				}
9044 			}
9045 
9046 			skipXft:
9047 
9048 			if(pattern is null)
9049 				pattern = "*";
9050 
9051 			int count;
9052 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
9053 			scope(exit) XFreeFontNames(coreFontsRaw);
9054 
9055 			auto coreFonts = coreFontsRaw[0 .. count];
9056 
9057 			foreach(font; coreFonts) {
9058 				char[128] tmp;
9059 				tmp[0 ..5] = "core:";
9060 				auto cf = font.sliceCString;
9061 				if(5 + cf.length > tmp.length)
9062 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
9063 				tmp[5 .. 5 + cf.length] = cf;
9064 				if(!handler(tmp[0 .. 5 + cf.length]))
9065 					return;
9066 			}
9067 		}
9068 	}
9069 
9070 	/++
9071 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
9072 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
9073 
9074 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
9075 		underlying system doesn't support returning the raw bytes.
9076 
9077 		History:
9078 			Added September 10, 2021 (dub v10.3)
9079 	+/
9080 	ubyte[] getTtfBytes() {
9081 		if(isNull)
9082 			return null;
9083 
9084 		version(Windows) {
9085 			auto dc = GetDC(null);
9086 			auto orig = SelectObject(dc, font);
9087 
9088 			scope(exit) {
9089 				SelectObject(dc, orig);
9090 				ReleaseDC(null, dc);
9091 			}
9092 
9093 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
9094 			if(res == GDI_ERROR)
9095 				return null;
9096 
9097 			ubyte[] buffer = new ubyte[](res);
9098 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
9099 			if(res == GDI_ERROR)
9100 				return null; // wtf really tbh
9101 
9102 			return buffer;
9103 		} else version(with_xft) {
9104 			if(isXft && xftFont) {
9105 				if(!FontConfigLibrary.attempted)
9106 					FontConfigLibrary.loadDynamicLibrary();
9107 				if(!FontConfigLibrary.loadSuccessful)
9108 					return null;
9109 
9110 				char* file;
9111 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
9112 					if (file !is null && file[0]) {
9113 						import core.stdc.stdio;
9114 						auto fp = fopen(file, "rb");
9115 						if(fp is null)
9116 							return null;
9117 						scope(exit)
9118 							fclose(fp);
9119 						fseek(fp, 0, SEEK_END);
9120 						ubyte[] buffer = new ubyte[](ftell(fp));
9121 						fseek(fp, 0, SEEK_SET);
9122 
9123 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
9124 						if(got != buffer.length)
9125 							return null;
9126 
9127 						return buffer;
9128 					}
9129 				}
9130 			}
9131 			return null;
9132 		} else throw new NotYetImplementedException();
9133 	}
9134 
9135 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
9136 
9137 	private string weightToString(FontWeight weight) {
9138 		with(FontWeight)
9139 		final switch(weight) {
9140 			case dontcare: return "*";
9141 			case thin: return "extralight";
9142 			case extralight: return "extralight";
9143 			case light: return "light";
9144 			case regular: return "regular";
9145 			case medium: return "medium";
9146 			case semibold: return "demibold";
9147 			case bold: return "bold";
9148 			case extrabold: return "demibold";
9149 			case heavy: return "black";
9150 		}
9151 	}
9152 
9153 	/++
9154 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
9155 
9156 		History:
9157 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9158 	+/
9159 	version(X11)
9160 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9161 		unload();
9162 
9163 		string xfontstr;
9164 
9165 		if(name.length > 3 && name[0 .. 3] == "-*-") {
9166 			// this is kinda a disgusting hack but if the user sends an exact
9167 			// string I'd like to honor it...
9168 			xfontstr = name;
9169 		} else {
9170 			string weightstr = weightToString(weight);
9171 			string sizestr;
9172 			if(size == 0)
9173 				sizestr = "*";
9174 			else
9175 				sizestr = toInternal!string(size);
9176 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
9177 		}
9178 
9179 		// writeln(xfontstr);
9180 
9181 		auto display = XDisplayConnection.get;
9182 
9183 		font = XLoadQueryFont(display, xfontstr.ptr);
9184 		if(font is null)
9185 			return false;
9186 
9187 		char** lol;
9188 		int lol2;
9189 		char* lol3;
9190 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
9191 
9192 		prepareFontInfo();
9193 
9194 		return !isNull();
9195 	}
9196 
9197 	version(X11)
9198 	private void prepareFontInfo() {
9199 		if(font !is null) {
9200 			isMonospace_ = stringWidth("l") == stringWidth("M");
9201 			ascent_ = font.max_bounds.ascent;
9202 			descent_ = font.max_bounds.descent;
9203 		}
9204 	}
9205 
9206 	version(OSXCocoa)
9207 	private void prepareFontInfo() {
9208 		if(font !is null) {
9209 			isMonospace_ = font.isFixedPitch;
9210 			ascent_ = cast(int) font.ascender;
9211 			descent_ = cast(int) - font.descender;
9212 		}
9213 	}
9214 
9215 
9216 	/++
9217 		Loads a Windows font. You probably want to use [load] instead to be more generic.
9218 
9219 		History:
9220 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9221 	+/
9222 	version(Windows)
9223 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
9224 		unload();
9225 
9226 		WCharzBuffer buffer = WCharzBuffer(name);
9227 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
9228 
9229 		prepareFontInfo(hdc);
9230 
9231 		return !isNull();
9232 	}
9233 
9234 	version(Windows)
9235 	void prepareFontInfo(HDC hdc = null) {
9236 		if(font is null)
9237 			return;
9238 
9239 		TEXTMETRIC tm;
9240 		auto dc = hdc ? hdc : GetDC(null);
9241 		auto orig = SelectObject(dc, font);
9242 		GetTextMetrics(dc, &tm);
9243 		SelectObject(dc, orig);
9244 		if(hdc is null)
9245 			ReleaseDC(null, dc);
9246 
9247 		width_ = tm.tmAveCharWidth;
9248 		height_ = tm.tmHeight;
9249 		ascent_ = tm.tmAscent;
9250 		descent_ = tm.tmDescent;
9251 		// 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.
9252 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
9253 	}
9254 
9255 
9256 	/++
9257 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
9258 
9259 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
9260 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
9261 
9262 		On Windows, it forwards directly to [loadWin32].
9263 
9264 		Params:
9265 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
9266 			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.
9267 			weight = approximate boldness, results may vary.
9268 			italic = try to get a slanted version of the given font.
9269 
9270 		History:
9271 			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.
9272 	+/
9273 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9274 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
9275 		version(X11) {
9276 			version(with_xft) {
9277 				if(name.length > 5 && name[0 .. 5] == "core:") {
9278 					goto core;
9279 				}
9280 
9281 				if(loadXft(name, size, weight, italic))
9282 					return true;
9283 				// if xft fails, fallback to core to avoid breaking
9284 				// code that already depended on this.
9285 			}
9286 
9287 			core:
9288 
9289 			if(name.length > 5 && name[0 .. 5] == "core:") {
9290 				name = name[5 .. $];
9291 			}
9292 
9293 			return loadCoreX(name, size, weight, italic);
9294 		} else version(Windows) {
9295 			return loadWin32(name, size, weight, italic);
9296 		} else version(OSXCocoa) {
9297 			return loadCocoa(name, size, weight, italic);
9298 		} else static assert(0);
9299 	}
9300 
9301 	version(OSXCocoa)
9302 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
9303 		unload();
9304 
9305 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
9306 		font.retain();
9307 		prepareFontInfo();
9308 
9309 		return !isNull();
9310 	}
9311 
9312 	private struct LoadedInfo {
9313 		string name;
9314 		int size;
9315 		FontWeight weight;
9316 		bool italic;
9317 	}
9318 	private LoadedInfo loadedInfo;
9319 
9320 	// int size() { return loadedInfo.size; }
9321 
9322 	///
9323 	void unload() {
9324 		if(isNull())
9325 			return;
9326 
9327 		version(X11) {
9328 			auto display = XDisplayConnection.display;
9329 
9330 			if(display is null)
9331 				return;
9332 
9333 			version(with_xft) {
9334 				if(isXft) {
9335 					if(xftFont)
9336 						XftFontClose(display, xftFont);
9337 					isXft = false;
9338 					xftFont = null;
9339 					return;
9340 				}
9341 			}
9342 
9343 			if(font && font !is ScreenPainterImplementation.defaultfont)
9344 				XFreeFont(display, font);
9345 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
9346 				XFreeFontSet(display, fontset);
9347 
9348 			font = null;
9349 			fontset = null;
9350 		} else version(Windows) {
9351 			DeleteObject(font);
9352 			font = null;
9353 		} else version(OSXCocoa) {
9354 			font.release();
9355 			font = null;
9356 		} else static assert(0);
9357 	}
9358 
9359 	private bool isMonospace_;
9360 
9361 	/++
9362 		History:
9363 			Added January 16, 2021
9364 	+/
9365 	bool isMonospace() {
9366 		return isMonospace_;
9367 	}
9368 
9369 	/++
9370 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
9371 
9372 		History:
9373 			Added March 26, 2020
9374 			Documented January 16, 2021
9375 	+/
9376 	fnum averageWidth() {
9377 		version(X11) {
9378 			return stringWidth("x");
9379 		} version(OSXCocoa) {
9380 			return stringWidth("x");
9381 		} else version(Windows)
9382 			return width_;
9383 		else assert(0);
9384 	}
9385 
9386 	/++
9387 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
9388 
9389 		History:
9390 			Added January 16, 2021
9391 	+/
9392 	fnum stringWidth(scope const(char)[] s, SimpleWindow window = null) {
9393 	// FIXME: what about tab?
9394 		if(isNull)
9395 			return 0;
9396 
9397 		version(X11) {
9398 			version(with_xft)
9399 				if(isXft && xftFont !is null) {
9400 					//return xftFont.max_advance_width;
9401 					XGlyphInfo extents;
9402 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
9403 					// writeln(extents);
9404 					return extents.xOff;
9405 				}
9406 			if(font is null)
9407 				return 0;
9408 			else if(fontset) {
9409 				XRectangle rect;
9410 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
9411 
9412 				return rect.width;
9413 			} else {
9414 				return XTextWidth(font, s.ptr, cast(int) s.length);
9415 			}
9416 		} else version(Windows) {
9417 			WCharzBuffer buffer = WCharzBuffer(s);
9418 
9419 			return stringWidth(buffer.slice, window);
9420 		} else version(OSXCocoa) {
9421 			/+
9422 			int charCount = [string length];
9423 			CGGlyph glyphs[charCount];
9424 			CGRect rects[charCount];
9425 
9426 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
9427 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
9428 
9429 			int totalwidth = 0, maxheight = 0;
9430 			for (int i=0; i < charCount; i++)
9431 			{
9432 				totalwidth += rects[i].size.width;
9433 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
9434 			}
9435 
9436 			dim = CGSizeMake(totalwidth, maxheight);
9437 			+/
9438 			MacString str = MacString(s);
9439 			NSDictionary dict = NSDictionary.dictionaryWithObject(
9440 				font,
9441 				/*forKey:*/cast(void*) NSFontAttributeName
9442 			);
9443 			// scope(exit) dict.release();
9444 			NSSize size = str.borrow.sizeWithAttributes(dict);
9445 
9446 			// import std.stdio; writeln(s, " ", size);
9447 
9448 			return size.width; // cast(int) (size.width + 0.9 /* to round up */); // FIXME
9449 		}
9450 		else assert(0);
9451 	}
9452 
9453 	version(Windows)
9454 	/// ditto
9455 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
9456 		if(isNull)
9457 			return 0;
9458 		version(Windows) {
9459 			SIZE size;
9460 
9461 			prepareContext(window);
9462 			scope(exit) releaseContext();
9463 
9464 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
9465 
9466 			return size.cx;
9467 		} else {
9468 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
9469 			static assert(0, "not implemented yet");
9470 			//return stringWidth(s, window);
9471 		}
9472 	}
9473 
9474 	private {
9475 		int prepRefcount;
9476 
9477 		version(Windows) {
9478 			HDC dc;
9479 			HANDLE orig;
9480 			HWND hwnd;
9481 		}
9482 	}
9483 	/++
9484 		[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.
9485 
9486 		History:
9487 			Added January 23, 2021
9488 	+/
9489 	void prepareContext(SimpleWindow window = null) {
9490 		prepRefcount++;
9491 		if(prepRefcount == 1) {
9492 			version(Windows) {
9493 				hwnd = window is null ? null : window.impl.hwnd;
9494 				dc = GetDC(hwnd);
9495 				orig = SelectObject(dc, font);
9496 			}
9497 		}
9498 	}
9499 	/// ditto
9500 	void releaseContext() {
9501 		prepRefcount--;
9502 		if(prepRefcount == 0) {
9503 			version(Windows) {
9504 				SelectObject(dc, orig);
9505 				ReleaseDC(hwnd, dc);
9506 				hwnd = null;
9507 				dc = null;
9508 				orig = null;
9509 			}
9510 		}
9511 	}
9512 
9513 	/+
9514 		FIXME: I think I need advance and kerning pair
9515 
9516 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
9517 	+/
9518 
9519 	/++
9520 		Returns the height of the font.
9521 
9522 		History:
9523 			Added March 26, 2020
9524 			Documented January 16, 2021
9525 	+/
9526 	fnum height() {
9527 		version(X11) {
9528 			version(with_xft)
9529 				if(isXft && xftFont !is null) {
9530 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
9531 				}
9532 			if(font is null)
9533 				return 0;
9534 			return font.max_bounds.ascent + font.max_bounds.descent;
9535 		} else version(Windows) {
9536 			return height_;
9537 		} else version(OSXCocoa) {
9538 			if(font is null)
9539 				return 0;
9540 			// the descender likely negative so minus means we actually add
9541 			return cast(int) (font.ascender - font.descender + 0.9 /* to round up */);
9542 			// return cast(int) font.capHeight;
9543 		}
9544 		else assert(0);
9545 	}
9546 
9547 	private fnum ascent_;
9548 	private fnum descent_;
9549 
9550 	/++
9551 		Max ascent above the baseline.
9552 
9553 		History:
9554 			Added January 22, 2021
9555 	+/
9556 	fnum ascent() {
9557 		return ascent_;
9558 	}
9559 
9560 	/++
9561 		Max descent below the baseline.
9562 
9563 		History:
9564 			Added January 22, 2021
9565 	+/
9566 	fnum descent() {
9567 		return descent_;
9568 	}
9569 
9570 	/++
9571 		Loads the default font used by [ScreenPainter] if none others are loaded.
9572 
9573 		Returns:
9574 			This method mutates the `this` object, but then returns `this` for
9575 			easy chaining like:
9576 
9577 			---
9578 			auto font = foo.isNull ? foo : foo.loadDefault
9579 			---
9580 
9581 		History:
9582 			Added previously, but left unimplemented until January 24, 2021.
9583 	+/
9584 	OperatingSystemFont loadDefault() {
9585 		unload();
9586 
9587 		loadedInfo = LoadedInfo.init;
9588 
9589 		version(X11) {
9590 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9591 			// but meh since sdpy does its own thing, this should be ok too
9592 
9593 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9594 			this.font = ScreenPainterImplementation.defaultfont;
9595 			this.fontset = ScreenPainterImplementation.defaultfontset;
9596 
9597 			prepareFontInfo();
9598 			return this;
9599 		} else version(Windows) {
9600 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9601 			this.font = ScreenPainterImplementation.defaultGuiFont;
9602 
9603 			prepareFontInfo();
9604 			return this;
9605 		} else version(OSXCocoa) {
9606 			this.font = NSFont.systemFontOfSize(15);
9607 			font.retain();
9608 
9609 			prepareFontInfo();
9610 
9611 			// import std.stdio; writeln("Load default: ", this.height());
9612 			return this;
9613 		} else throw new NotYetImplementedException();
9614 	}
9615 
9616 	///
9617 	bool isNull() {
9618 		version(with_xft)
9619 			if(isXft)
9620 				return xftFont is null;
9621 		return font is null;
9622 	}
9623 
9624 	/* Metrics */
9625 	/+
9626 		GetABCWidth
9627 		GetKerningPairs
9628 
9629 		if I do it right, I can size it all here, and match
9630 		what happens when I draw the full string with the OS functions.
9631 
9632 		subclasses might do the same thing while getting the glyphs on images
9633 	struct GlyphInfo {
9634 		int glyph;
9635 
9636 		size_t stringIdxStart;
9637 		size_t stringIdxEnd;
9638 
9639 		Rectangle boundingBox;
9640 	}
9641 	GlyphInfo[] getCharBoxes() {
9642 		// XftTextExtentsUtf8
9643 		return null;
9644 
9645 	}
9646 	+/
9647 
9648 	~this() {
9649 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9650 		unload();
9651 	}
9652 }
9653 
9654 version(Windows)
9655 private string sliceCString(const(wchar)[] w) {
9656 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9657 }
9658 
9659 private inout(char)[] sliceCString(inout(char)* s) {
9660 	import core.stdc.string;
9661 	auto len = strlen(s);
9662 	return s[0 .. len];
9663 }
9664 
9665 version(OSXCocoa)
9666 	alias PaintingHandle = NSObject;
9667 else
9668 	alias PaintingHandle = NativeWindowHandle;
9669 
9670 /**
9671 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9672 	than constructing it directly. Then, it is reference counted so you can pass it
9673 	at around and when the last ref goes out of scope, the buffered drawing activities
9674 	are all carried out.
9675 
9676 
9677 	Most functions use the outlineColor instead of taking a color themselves.
9678 	ScreenPainter is reference counted and draws its buffer to the screen when its
9679 	final reference goes out of scope.
9680 */
9681 struct ScreenPainter {
9682 	CapableOfBeingDrawnUpon window;
9683 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9684 		this.window = window;
9685 		if(window.closed)
9686 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9687 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9688 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9689 		if(window.activeScreenPainter !is null) {
9690 			impl = window.activeScreenPainter;
9691 			if(impl.referenceCount == 0) {
9692 				impl.window = window;
9693 				impl.create(handle);
9694 			}
9695 			impl.manualInvalidations = manualInvalidations;
9696 			impl.referenceCount++;
9697 		//	writeln("refcount ++ ", impl.referenceCount);
9698 		} else {
9699 			impl = new ScreenPainterImplementation;
9700 			impl.window = window;
9701 			impl.create(handle);
9702 			impl.referenceCount = 1;
9703 			impl.manualInvalidations = manualInvalidations;
9704 			window.activeScreenPainter = impl;
9705 			// writeln("constructed");
9706 		}
9707 
9708 		copyActiveOriginals();
9709 	}
9710 
9711 	/++
9712 		EXPERIMENTAL. subject to change.
9713 
9714 		When you draw a cursor, you can draw this to notify your window of where it is,
9715 		for IME systems to use.
9716 	+/
9717 	void notifyCursorPosition(int x, int y, int width, int height) {
9718 		if(auto w = cast(SimpleWindow) window) {
9719 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9720 		}
9721 	}
9722 
9723 	/++
9724 		If you are using manual invalidations, this informs the
9725 		window system that a section needs to be redrawn.
9726 
9727 		If you didn't opt into manual invalidation, you don't
9728 		have to call this.
9729 
9730 		History:
9731 			Added December 30, 2021 (dub v10.5)
9732 	+/
9733 	void invalidateRect(Rectangle rect) {
9734 		if(impl is null) return;
9735 
9736 		// transform(rect)
9737 		rect.left += _originX;
9738 		rect.right += _originX;
9739 		rect.top += _originY;
9740 		rect.bottom += _originY;
9741 
9742 		impl.invalidateRect(rect);
9743 	}
9744 
9745 	private Pen originalPen;
9746 	private Color originalFillColor;
9747 	private arsd.color.Rectangle originalClipRectangle;
9748 	private OperatingSystemFont originalFont;
9749 	void copyActiveOriginals() {
9750 		if(impl is null) return;
9751 		originalPen = impl._activePen;
9752 		originalFillColor = impl._fillColor;
9753 		originalClipRectangle = impl._clipRectangle;
9754 		version(OSXCocoa) {} else
9755 		originalFont = impl._activeFont;
9756 	}
9757 
9758 	~this() {
9759 		if(impl is null) return;
9760 		impl.referenceCount--;
9761 		//writeln("refcount -- ", impl.referenceCount);
9762 		if(impl.referenceCount == 0) {
9763 			// writeln("destructed");
9764 			impl.dispose();
9765 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9766 			// writeln("paint finished");
9767 		} else {
9768 			// there is still an active reference, reset stuff so the
9769 			// next user doesn't get weirdness via the reference
9770 			this.rasterOp = RasterOp.normal;
9771 			pen = originalPen;
9772 			fillColor = originalFillColor;
9773 			if(originalFont)
9774 				setFont(originalFont);
9775 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9776 		}
9777 	}
9778 
9779 	this(this) {
9780 		if(impl is null) return;
9781 		impl.referenceCount++;
9782 		//writeln("refcount ++ ", impl.referenceCount);
9783 
9784 		copyActiveOriginals();
9785 	}
9786 
9787 	private int _originX;
9788 	private int _originY;
9789 	@property int originX() { return _originX; }
9790 	@property int originY() { return _originY; }
9791 	@property int originX(int a) {
9792 		_originX = a;
9793 		return _originX;
9794 	}
9795 	@property int originY(int a) {
9796 		_originY = a;
9797 		return _originY;
9798 	}
9799 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9800 	private void transform(ref Point p) {
9801 		if(impl is null) return;
9802 		p.x += _originX;
9803 		p.y += _originY;
9804 	}
9805 
9806 	// this needs to be checked BEFORE the originX/Y transformation
9807 	private bool isClipped(Point p) {
9808 		return !currentClipRectangle.contains(p);
9809 	}
9810 	private bool isClipped(Point p, int width, int height) {
9811 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9812 	}
9813 	private bool isClipped(Point p, Size s) {
9814 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9815 	}
9816 	private bool isClipped(Point p, Point p2) {
9817 		// need to ensure the end points are actually included inside, so the +1 does that
9818 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9819 	}
9820 
9821 
9822 	/++
9823 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9824 
9825 		Returns:
9826 			The old clip rectangle.
9827 
9828 		History:
9829 			Return value was `void` prior to May 10, 2021.
9830 
9831 	+/
9832 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9833 		if(impl is null) return currentClipRectangle;
9834 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9835 			return currentClipRectangle; // no need to do anything
9836 		auto old = currentClipRectangle;
9837 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9838 		transform(pt);
9839 
9840 		impl.setClipRectangle(pt.x, pt.y, width, height);
9841 
9842 		return old;
9843 	}
9844 
9845 	/// ditto
9846 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9847 		if(impl is null) return currentClipRectangle;
9848 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9849 	}
9850 
9851 	///
9852 	void setFont(OperatingSystemFont font) {
9853 		if(impl is null) return;
9854 		impl.setFont(font);
9855 	}
9856 
9857 	///
9858 	int fontHeight() {
9859 		if(impl is null) return 0;
9860 		return impl.fontHeight();
9861 	}
9862 
9863 	private Pen activePen;
9864 
9865 	///
9866 	@property void pen(Pen p) {
9867 		if(impl is null) return;
9868 		activePen = p;
9869 		impl.pen(p);
9870 	}
9871 
9872 	///
9873 	@scriptable
9874 	@property void outlineColor(Color c) {
9875 		if(impl is null) return;
9876 		if(activePen.color == c)
9877 			return;
9878 		activePen.color = c;
9879 		impl.pen(activePen);
9880 	}
9881 
9882 	///
9883 	@scriptable
9884 	@property void fillColor(Color c) {
9885 		if(impl is null) return;
9886 		impl.fillColor(c);
9887 	}
9888 
9889 	///
9890 	@property void rasterOp(RasterOp op) {
9891 		if(impl is null) return;
9892 		impl.rasterOp(op);
9893 	}
9894 
9895 
9896 	void updateDisplay() {
9897 		// FIXME this should do what the dtor does
9898 	}
9899 
9900 	/// 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)
9901 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9902 		if(impl is null) return;
9903 		if(isClipped(upperLeft, width, height)) return;
9904 		transform(upperLeft);
9905 		version(Windows) {
9906 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9907 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9908 			RECT clip = scroll;
9909 			RECT uncovered;
9910 			HRGN hrgn;
9911 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9912 				throw new WindowsApiException("ScrollDC", GetLastError());
9913 
9914 		} else version(X11) {
9915 			// FIXME: clip stuff outside this rectangle
9916 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9917 		} else version(OSXCocoa) {
9918 			throw new NotYetImplementedException();
9919 		} else static assert(0);
9920 	}
9921 
9922 	///
9923 	void clear(Color color = Color.white()) {
9924 		if(impl is null) return;
9925 		fillColor = color;
9926 		outlineColor = color;
9927 		drawRectangle(Point(0, 0), window.width, window.height);
9928 	}
9929 
9930 	/++
9931 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9932 
9933 		Params:
9934 			upperLeft = point on the window where the upper left corner of the image will be drawn
9935 			imageUpperLeft = point on the image to start the slice to draw
9936 			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.
9937 		History:
9938 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9939 	+/
9940 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9941 		if(impl is null) return;
9942 		if(isClipped(upperLeft, s.width, s.height)) return;
9943 		transform(upperLeft);
9944 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9945 	}
9946 
9947 	///
9948 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9949 		if(impl is null) return;
9950 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9951 		transform(upperLeft);
9952 		if(w == 0 || w > i.width)
9953 			w = i.width;
9954 		if(h == 0 || h > i.height)
9955 			h = i.height;
9956 		if(upperLeftOfImage.x < 0)
9957 			upperLeftOfImage.x = 0;
9958 		if(upperLeftOfImage.y < 0)
9959 			upperLeftOfImage.y = 0;
9960 
9961 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9962 	}
9963 
9964 	///
9965 	Size textSize(in char[] text) {
9966 		if(impl is null) return Size(0, 0);
9967 		return impl.textSize(text);
9968 	}
9969 
9970 	/++
9971 		Draws a string in the window with the set font (see [setFont] to change it).
9972 
9973 		Params:
9974 			upperLeft = the upper left point of the bounding box of the text
9975 			text = the string to draw
9976 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9977 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9978 	+/
9979 	@scriptable
9980 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9981 		if(impl is null) return;
9982 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9983 			if(isClipped(upperLeft, lowerRight)) return;
9984 			transform(lowerRight);
9985 		} else {
9986 			if(isClipped(upperLeft, textSize(text))) return;
9987 		}
9988 		transform(upperLeft);
9989 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9990 	}
9991 
9992 	/++
9993 		Draws text using a custom font.
9994 
9995 		This is still MAJOR work in progress.
9996 
9997 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9998 	+/
9999 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
10000 		if(impl is null) return;
10001 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
10002 		transform(upperLeft);
10003 		font.drawString(this, upperLeft, text);
10004 	}
10005 
10006 	version(Windows)
10007 	void drawText(Point upperLeft, scope const(wchar)[] text) {
10008 		if(impl is null) return;
10009 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
10010 		transform(upperLeft);
10011 
10012 		if(text.length && text[$-1] == '\n')
10013 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
10014 
10015 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
10016 	}
10017 
10018 	static struct TextDrawingContext {
10019 		Point boundingBoxUpperLeft;
10020 		Point boundingBoxLowerRight;
10021 
10022 		Point currentLocation;
10023 
10024 		Point lastDrewUpperLeft;
10025 		Point lastDrewLowerRight;
10026 
10027 		// how do i do right aligned rich text?
10028 		// i kinda want to do a pre-made drawing then right align
10029 		// draw the whole block.
10030 		//
10031 		// That's exactly the diff: inline vs block stuff.
10032 
10033 		// I need to get coordinates of an inline section out too,
10034 		// not just a bounding box, but a series of bounding boxes
10035 		// should be ok. Consider what's needed to detect a click
10036 		// on a link in the middle of a paragraph breaking a line.
10037 		//
10038 		// Generally, we should be able to get the rectangles of
10039 		// any portion we draw.
10040 		//
10041 		// It also needs to tell what text is left if it overflows
10042 		// out of the box, so we can do stuff like float images around
10043 		// it. It should not attempt to draw a letter that would be
10044 		// clipped.
10045 		//
10046 		// I might also turn off word wrap stuff.
10047 	}
10048 
10049 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
10050 		if(impl is null) return;
10051 		// FIXME
10052 	}
10053 
10054 	/// Drawing an individual pixel is slow. Avoid it if possible.
10055 	void drawPixel(Point where) {
10056 		if(impl is null) return;
10057 		if(isClipped(where)) return;
10058 		transform(where);
10059 		impl.drawPixel(where.x, where.y);
10060 	}
10061 
10062 
10063 	/// Draws a pen using the current pen / outlineColor
10064 	@scriptable
10065 	void drawLine(Point starting, Point ending) {
10066 		if(impl is null) return;
10067 		if(isClipped(starting, ending)) return;
10068 		transform(starting);
10069 		transform(ending);
10070 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
10071 	}
10072 
10073 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
10074 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
10075 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
10076 	@scriptable
10077 	void drawRectangle(Point upperLeft, int width, int height) {
10078 		if(impl is null) return;
10079 		if(isClipped(upperLeft, width, height)) return;
10080 		transform(upperLeft);
10081 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
10082 	}
10083 
10084 	/// ditto
10085 	void drawRectangle(Point upperLeft, Size size) {
10086 		if(impl is null) return;
10087 		if(isClipped(upperLeft, size.width, size.height)) return;
10088 		transform(upperLeft);
10089 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
10090 	}
10091 
10092 	/// ditto
10093 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
10094 		if(impl is null) return;
10095 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
10096 		transform(upperLeft);
10097 		transform(lowerRightInclusive);
10098 		impl.drawRectangle(upperLeft.x, upperLeft.y,
10099 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
10100 	}
10101 
10102 	// overload added on May 12, 2021
10103 	/// ditto
10104 	void drawRectangle(Rectangle rect) {
10105 		drawRectangle(rect.upperLeft, rect.size);
10106 	}
10107 
10108 	/// Arguments are the points of the bounding rectangle
10109 	void drawEllipse(Point upperLeft, Point lowerRight) {
10110 		if(impl is null) return;
10111 		if(isClipped(upperLeft, lowerRight)) return;
10112 		transform(upperLeft);
10113 		transform(lowerRight);
10114 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
10115 	}
10116 
10117 	/++
10118 		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.
10119 
10120 
10121 		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.
10122 
10123 		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.
10124 
10125 		Bugs:
10126 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
10127 
10128 			The arc outline on Linux sometimes goes over the target.
10129 
10130 			The fill on Windows sometimes stops short.
10131 
10132 		History:
10133 			This function was broken af, totally inconsistent on platforms until September 24, 2021.
10134 
10135 			The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024.
10136 	+/
10137 	void drawArc(Point upperLeft, int width, int height, int start, int length) {
10138 		if(impl is null) return;
10139 		// FIXME: not actually implemented
10140 		if(isClipped(upperLeft, width, height)) return;
10141 		transform(upperLeft);
10142 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length);
10143 	}
10144 
10145 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
10146 	void drawCircle(Point upperLeft, int diameter) {
10147 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
10148 	}
10149 
10150 	/++
10151 		Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush.
10152 
10153 
10154 		Bugs:
10155 			Not implemented on Mac; it will instead draw a non-rounded rectangle for now.
10156 
10157 		History:
10158 			Added August 3, 2024
10159 	+/
10160 	void drawRectangleRounded(Rectangle rect, int borderRadius) {
10161 		drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius);
10162 	}
10163 
10164 	/// ditto
10165 	void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) {
10166 		drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius);
10167 	}
10168 
10169 	/// ditto
10170 	void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
10171 		if(borderRadius <= 0) {
10172 			drawRectangle(upperLeft, lowerRight);
10173 			return;
10174 		}
10175 
10176 		transform(upperLeft);
10177 		transform(lowerRight);
10178 
10179 		impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius);
10180 	}
10181 
10182 	/// .
10183 	void drawPolygon(Point[] vertexes) {
10184 		if(impl is null) return;
10185 		assert(vertexes.length);
10186 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
10187 		foreach(ref vertex; vertexes) {
10188 			if(vertex.x < minX)
10189 				minX = vertex.x;
10190 			if(vertex.y < minY)
10191 				minY = vertex.y;
10192 			if(vertex.x > maxX)
10193 				maxX = vertex.x;
10194 			if(vertex.y > maxY)
10195 				maxY = vertex.y;
10196 			transform(vertex);
10197 		}
10198 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
10199 		impl.drawPolygon(vertexes);
10200 	}
10201 
10202 	/// ditto
10203 	void drawPolygon(Point[] vertexes...) {
10204 		if(impl is null) return;
10205 		drawPolygon(vertexes);
10206 	}
10207 
10208 
10209 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
10210 
10211 	//mixin NativeScreenPainterImplementation!() impl;
10212 
10213 
10214 	// HACK: if I mixin the impl directly, it won't let me override the copy
10215 	// constructor! The linker complains about there being multiple definitions.
10216 	// I'll make the best of it and reference count it though.
10217 	ScreenPainterImplementation* impl;
10218 }
10219 
10220 	// HACK: I need a pointer to the implementation so it's separate
10221 	struct ScreenPainterImplementation {
10222 		CapableOfBeingDrawnUpon window;
10223 		int referenceCount;
10224 		mixin NativeScreenPainterImplementation!();
10225 	}
10226 
10227 // FIXME: i haven't actually tested the sprite class on MS Windows
10228 
10229 /**
10230 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
10231 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
10232 
10233 
10234 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
10235 	though I'm not sure that's ideal and the implementation might change.
10236 
10237 	You create one by giving a window and an image. It optimizes for that window,
10238 	and copies the image into it to use as the initial picture. Creating a sprite
10239 	can be quite slow (especially over a network connection) so you should do it
10240 	as little as possible and just hold on to your sprite handles after making them.
10241 	simpledisplay does try to do its best though, using the XSHM extension if available,
10242 	but you should still write your code as if it will always be slow.
10243 
10244 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
10245 	a fast operation - much faster than drawing the Image itself every time.
10246 
10247 	`Sprite` represents a scarce resource which should be freed when you
10248 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
10249 	after it has been disposed. If you are unsure about this, don't take chances,
10250 	just let the garbage collector do it for you. But ideally, you can manage its
10251 	lifetime more efficiently.
10252 
10253 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
10254 	support alpha blending in its drawing at this time. That might change in the
10255 	future, but if you need alpha blending right now, use OpenGL instead. See
10256 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
10257 
10258 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
10259 	in by setting the enableAlpha = true in the constructor.
10260 */
10261 class Sprite : CapableOfBeingDrawnUpon {
10262 
10263 	///
10264 	ScreenPainter draw() {
10265 		return ScreenPainter(this, handle, false);
10266 	}
10267 
10268 	/++
10269 		Copies the sprite's current state into a [TrueColorImage].
10270 
10271 		Be warned: this can be a very slow operation
10272 
10273 		History:
10274 			Actually implemented on March 14, 2021
10275 	+/
10276 	TrueColorImage takeScreenshot() {
10277 		return trueColorImageFromNativeHandle(handle, width, height);
10278 	}
10279 
10280 	void delegate() paintingFinishedDg() { return null; }
10281 	bool closed() { return false; }
10282 	ScreenPainterImplementation* activeScreenPainter_;
10283 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
10284 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
10285 
10286 	version(Windows)
10287 		private ubyte* rawData;
10288 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
10289 	// ditto on the XPicture stuff
10290 
10291 	version(X11) {
10292 		private static XRenderPictFormat* RGB24;
10293 		private static XRenderPictFormat* ARGB32;
10294 
10295 		private Picture xrenderPicture;
10296 	}
10297 
10298 	version(X11)
10299 	private static void requireXRender() {
10300 		if(!XRenderLibrary.loadAttempted) {
10301 			XRenderLibrary.loadDynamicLibrary();
10302 		}
10303 
10304 		if(!XRenderLibrary.loadSuccessful)
10305 			throw new Exception("XRender library load failure");
10306 
10307 		auto display = XDisplayConnection.get;
10308 
10309 		// FIXME: if we migrate X displays, these need to be changed
10310 		if(RGB24 is null)
10311 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
10312 		if(ARGB32 is null)
10313 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
10314 	}
10315 
10316 	protected this() {}
10317 
10318 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
10319 		this._width = width;
10320 		this._height = height;
10321 		this.enableAlpha = enableAlpha;
10322 
10323 		version(X11) {
10324 			auto display = XDisplayConnection.get();
10325 
10326 			if(enableAlpha) {
10327 				requireXRender();
10328 			}
10329 
10330 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
10331 
10332 			if(enableAlpha) {
10333 				XRenderPictureAttributes attrs;
10334 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
10335 			}
10336 		} else version(Windows) {
10337 			version(CRuntime_DigitalMars) {
10338 				//if(enableAlpha)
10339 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
10340 			}
10341 
10342 			BITMAPINFO infoheader;
10343 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
10344 			infoheader.bmiHeader.biWidth = width;
10345 			infoheader.bmiHeader.biHeight = height;
10346 			infoheader.bmiHeader.biPlanes = 1;
10347 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
10348 			infoheader.bmiHeader.biCompression = BI_RGB;
10349 
10350 			// FIXME: this should prolly be a device dependent bitmap...
10351 			handle = CreateDIBSection(
10352 				null,
10353 				&infoheader,
10354 				DIB_RGB_COLORS,
10355 				cast(void**) &rawData,
10356 				null,
10357 				0);
10358 
10359 			if(handle is null)
10360 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
10361 		}
10362 	}
10363 
10364 	/// Makes a sprite based on the image with the initial contents from the Image
10365 	this(SimpleWindow win, Image i) {
10366 		this(win, i.width, i.height, i.enableAlpha);
10367 
10368 		version(X11) {
10369 			auto display = XDisplayConnection.get();
10370 			auto gc = XCreateGC(display, this.handle, 0, null);
10371 			scope(exit) XFreeGC(display, gc);
10372 			if(i.usingXshm)
10373 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
10374 			else
10375 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
10376 		} else version(Windows) {
10377 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
10378 			auto arrLength = itemsPerLine * height;
10379 			rawData[0..arrLength] = i.rawData[0..arrLength];
10380 		} else version(OSXCocoa) {
10381 			// FIXME: I have no idea if this is even any good
10382 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
10383 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
10384 				colorSpace,
10385 				kCGImageAlphaPremultipliedLast
10386 				|kCGBitmapByteOrder32Big);
10387 			CGColorSpaceRelease(colorSpace);
10388 			auto rawData = CGBitmapContextGetData(handle);
10389 
10390 			auto rdl = (width * height * 4);
10391 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
10392 		} else static assert(0);
10393 	}
10394 
10395 	/++
10396 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
10397 
10398 		Params:
10399 			where = point on the window where the upper left corner of the image will be drawn
10400 			imageUpperLeft = point on the image to start the slice to draw
10401 			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.
10402 		History:
10403 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
10404 	+/
10405 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
10406 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
10407 	}
10408 
10409 	/// Call this when you're ready to get rid of it
10410 	void dispose() {
10411 		version(X11) {
10412 			staticDispose(xrenderPicture, handle);
10413 			xrenderPicture = None;
10414 			handle = None;
10415 		} else version(Windows) {
10416 			staticDispose(handle);
10417 			handle = null;
10418 		} else version(OSXCocoa) {
10419 			staticDispose(handle);
10420 			handle = null;
10421 		} else static assert(0);
10422 
10423 	}
10424 
10425 	version(X11)
10426 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
10427 		if(xrenderPicture)
10428 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
10429 		if(handle)
10430 			XFreePixmap(XDisplayConnection.get(), handle);
10431 	}
10432 	else version(Windows)
10433 	static void staticDispose(HBITMAP handle) {
10434 		if(handle)
10435 			DeleteObject(handle);
10436 	}
10437 	else version(OSXCocoa)
10438 	static void staticDispose(CGContextRef context) {
10439 		if(context)
10440 			CGContextRelease(context);
10441 	}
10442 
10443 	~this() {
10444 		version(X11) { if(xrenderPicture || handle)
10445 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
10446 		} else version(Windows) { if(handle)
10447 			cleanupQueue.queue!staticDispose(handle);
10448 		} else version(OSXCocoa) { if(handle)
10449 			cleanupQueue.queue!staticDispose(handle);
10450 		} else static assert(0);
10451 	}
10452 
10453 	///
10454 	final @property int width() { return _width; }
10455 
10456 	///
10457 	final @property int height() { return _height; }
10458 
10459 	///
10460 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
10461 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
10462 	}
10463 
10464 	auto nativeHandle() {
10465 		return handle;
10466 	}
10467 
10468 	private:
10469 
10470 	int _width;
10471 	int _height;
10472 	bool enableAlpha;
10473 	version(X11)
10474 		Pixmap handle;
10475 	else version(Windows)
10476 		HBITMAP handle;
10477 	else version(OSXCocoa)
10478 		CGContextRef handle;
10479 	else version(Emscripten)
10480 		void* handle;
10481 	else static assert(0);
10482 }
10483 
10484 /++
10485 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
10486 
10487 	History:
10488 		Added November 20, 2021 (dub v10.4)
10489 +/
10490 version(OSXCocoa) {} else // NotYetImplementedException
10491 abstract class Gradient : Sprite {
10492 	protected this(int w, int h) {
10493 		version(X11) {
10494 			Sprite.requireXRender();
10495 
10496 			super();
10497 			enableAlpha = true;
10498 			_width = w;
10499 			_height = h;
10500 		} else version(Windows) {
10501 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
10502 		}
10503 	}
10504 
10505 	version(Windows)
10506 	final void forEachPixel(scope Color delegate(int x, int y) dg) @system {
10507 		auto ptr = rawData;
10508 		foreach(j; 0 .. _height)
10509 		foreach(i; 0 .. _width) {
10510 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
10511 			*rawData = (color.a * color.b) / 255; rawData++;
10512 			*rawData = (color.a * color.g) / 255; rawData++;
10513 			*rawData = (color.a * color.r) / 255; rawData++;
10514 			*rawData = color.a; rawData++;
10515 		}
10516 	}
10517 
10518 	version(X11)
10519 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
10520 		assert(stops.length > 0);
10521 		assert(stops.length <= 16, "I got lazy with buffers");
10522 
10523 		XFixed[16] stopsPositions = void;
10524 		XRenderColor[16] colors = void;
10525 
10526 		foreach(idx, stop; stops) {
10527 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
10528 			auto c = stop.c;
10529 			colors[idx] = XRenderColor(
10530 				cast(ushort)(c.r * ushort.max / 255),
10531 				cast(ushort)(c.g * ushort.max / 255),
10532 				cast(ushort)(c.b * ushort.max / 255),
10533 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
10534 			);
10535 		}
10536 
10537 		xrenderPicture = dg(stopsPositions, colors);
10538 	}
10539 
10540 	///
10541 	static struct Stop {
10542 		float percentage; /// between 0 and 1.0
10543 		Color c;
10544 	}
10545 }
10546 
10547 /++
10548 	Creates a linear gradient between p1 and p2.
10549 
10550 	X ONLY RIGHT NOW
10551 
10552 	History:
10553 		Added November 20, 2021 (dub v10.4)
10554 
10555 	Bugs:
10556 		Not yet implemented on Windows.
10557 +/
10558 version(OSXCocoa) {} else // NotYetImplementedException
10559 class LinearGradient : Gradient {
10560 	/++
10561 
10562 	+/
10563 	this(Point p1, Point p2, Stop[] stops...) {
10564 		super(p2.x, p2.y);
10565 
10566 		version(X11) {
10567 			XLinearGradient gradient;
10568 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
10569 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
10570 
10571 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10572 				return XRenderCreateLinearGradient(
10573 					XDisplayConnection.get,
10574 					&gradient,
10575 					stopsPositions.ptr,
10576 					colors.ptr,
10577 					cast(int) stops.length);
10578 			});
10579 		} else version(Windows) {
10580 			// FIXME
10581 			forEachPixel((int x, int y) {
10582 				import core.stdc.math;
10583 
10584 				//sqrtf(
10585 
10586 				return Color.transparent;
10587 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10588 			});
10589 		}
10590 	}
10591 }
10592 
10593 /++
10594 	A conical gradient goes from color to color around a circumference from a center point.
10595 
10596 	X ONLY RIGHT NOW
10597 
10598 	History:
10599 		Added November 20, 2021 (dub v10.4)
10600 
10601 	Bugs:
10602 		Not yet implemented on Windows.
10603 +/
10604 version(OSXCocoa) {} else // NotYetImplementedException
10605 class ConicalGradient : Gradient {
10606 	/++
10607 
10608 	+/
10609 	this(Point center, float angleInDegrees, Stop[] stops...) {
10610 		super(center.x * 2, center.y * 2);
10611 
10612 		version(X11) {
10613 			XConicalGradient gradient;
10614 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
10615 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
10616 
10617 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10618 				return XRenderCreateConicalGradient(
10619 					XDisplayConnection.get,
10620 					&gradient,
10621 					stopsPositions.ptr,
10622 					colors.ptr,
10623 					cast(int) stops.length);
10624 			});
10625 		} else version(Windows) {
10626 			// FIXME
10627 			forEachPixel((int x, int y) {
10628 				import core.stdc.math;
10629 
10630 				//sqrtf(
10631 
10632 				return Color.transparent;
10633 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10634 			});
10635 
10636 		}
10637 	}
10638 }
10639 
10640 /++
10641 	A radial gradient goes from color to color based on distance from the center.
10642 	It is like rings of color.
10643 
10644 	X ONLY RIGHT NOW
10645 
10646 
10647 	More specifically, you create two circles: an inner circle and an outer circle.
10648 	The gradient is only drawn in the area outside the inner circle but inside the outer
10649 	circle. The closest line between those two circles forms the line for the gradient
10650 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10651 
10652 	History:
10653 		Added November 20, 2021 (dub v10.4)
10654 
10655 	Bugs:
10656 		Not yet implemented on Windows.
10657 +/
10658 version(OSXCocoa) {} else // NotYetImplementedException
10659 class RadialGradient : Gradient {
10660 	/++
10661 
10662 	+/
10663 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10664 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10665 
10666 		version(X11) {
10667 			XRadialGradient gradient;
10668 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10669 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10670 
10671 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10672 				return XRenderCreateRadialGradient(
10673 					XDisplayConnection.get,
10674 					&gradient,
10675 					stopsPositions.ptr,
10676 					colors.ptr,
10677 					cast(int) stops.length);
10678 			});
10679 		} else version(Windows) {
10680 			// FIXME
10681 			forEachPixel((int x, int y) {
10682 				import core.stdc.math;
10683 
10684 				//sqrtf(
10685 
10686 				return Color.transparent;
10687 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10688 			});
10689 		}
10690 	}
10691 }
10692 
10693 
10694 
10695 /+
10696 	NOT IMPLEMENTED
10697 
10698 	A display-stored image optimized for relatively quick drawing, like
10699 	[Sprite], but this one supports alpha channel blending and does NOT
10700 	support direct drawing upon it with a [ScreenPainter].
10701 
10702 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10703 	plain [ScreenPainter]... sort of.
10704 
10705 	On X11, it requires the Xrender extension and library. This is available
10706 	almost everywhere though.
10707 
10708 	History:
10709 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10710 +/
10711 version(none)
10712 class AlphaSprite {
10713 	/++
10714 		Copies the given image into it.
10715 	+/
10716 	this(MemoryImage img) {
10717 
10718 		if(!XRenderLibrary.loadAttempted) {
10719 			XRenderLibrary.loadDynamicLibrary();
10720 
10721 			// FIXME: this needs to be reconstructed when the X server changes
10722 			repopulateX();
10723 		}
10724 		if(!XRenderLibrary.loadSuccessful)
10725 			throw new Exception("XRender library load failure");
10726 
10727 		// I probably need to put the alpha mask in a separate Picture
10728 		// ugh
10729 		// maybe the Sprite itself can have an alpha bitmask anyway
10730 
10731 
10732 		auto display = XDisplayConnection.get();
10733 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10734 
10735 
10736 		XRenderPictureAttributes attrs;
10737 
10738 		handle = XRenderCreatePicture(
10739 			XDisplayConnection.get,
10740 			pixmap,
10741 			RGBA,
10742 			0,
10743 			&attrs
10744 		);
10745 
10746 	}
10747 
10748 	// maybe i'll use the create gradient functions too with static factories..
10749 
10750 	void drawAt(ScreenPainter painter, Point where) {
10751 		//painter.drawPixmap(this, where);
10752 
10753 		XRenderPictureAttributes attrs;
10754 
10755 		auto pic = XRenderCreatePicture(
10756 			XDisplayConnection.get,
10757 			painter.impl.d,
10758 			RGB,
10759 			0,
10760 			&attrs
10761 		);
10762 
10763 		XRenderComposite(
10764 			XDisplayConnection.get,
10765 			3, // PictOpOver
10766 			handle,
10767 			None,
10768 			pic,
10769 			0, // src
10770 			0,
10771 			0, // mask
10772 			0,
10773 			10, // dest
10774 			10,
10775 			100, // width
10776 			100
10777 		);
10778 
10779 		/+
10780 		XRenderFreePicture(
10781 			XDisplayConnection.get,
10782 			pic
10783 		);
10784 
10785 		XRenderFreePicture(
10786 			XDisplayConnection.get,
10787 			fill
10788 		);
10789 		+/
10790 		// on Windows you can stretch but Xrender still can't :(
10791 	}
10792 
10793 	static XRenderPictFormat* RGB;
10794 	static XRenderPictFormat* RGBA;
10795 	static void repopulateX() {
10796 		auto display = XDisplayConnection.get;
10797 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10798 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10799 	}
10800 
10801 	XPixmap pixmap;
10802 	Picture handle;
10803 }
10804 
10805 ///
10806 interface CapableOfBeingDrawnUpon {
10807 	///
10808 	ScreenPainter draw();
10809 	///
10810 	int width();
10811 	///
10812 	int height();
10813 	protected ScreenPainterImplementation* activeScreenPainter();
10814 	protected void activeScreenPainter(ScreenPainterImplementation*);
10815 	bool closed();
10816 
10817 	void delegate() paintingFinishedDg();
10818 
10819 	/// Be warned: this can be a very slow operation
10820 	TrueColorImage takeScreenshot();
10821 }
10822 
10823 /// 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].
10824 void flushGui() {
10825 	version(X11) {
10826 		auto dpy = XDisplayConnection.get();
10827 		XLockDisplay(dpy);
10828 		scope(exit) XUnlockDisplay(dpy);
10829 		XFlush(dpy);
10830 	}
10831 }
10832 
10833 /++
10834 	Runs the given code in the GUI thread when its event loop
10835 	is available, blocking until it completes. This allows you
10836 	to create and manipulate windows from another thread without
10837 	invoking undefined behavior.
10838 
10839 	If this is the gui thread, it runs the code immediately.
10840 
10841 	If no gui thread exists yet, the current thread is assumed
10842 	to be it. Attempting to create windows or run the event loop
10843 	in any other thread will cause an assertion failure.
10844 
10845 
10846 	$(TIP
10847 		Did you know you can use UFCS on delegate literals?
10848 
10849 		() {
10850 			// code here
10851 		}.runInGuiThread;
10852 	)
10853 
10854 	Returns:
10855 		`true` if the function was called, `false` if it was not.
10856 		The function may not be called because the gui thread had
10857 		already terminated by the time you called this.
10858 
10859 	History:
10860 		Added April 10, 2020 (v7.2.0)
10861 
10862 		Return value added and implementation tweaked to avoid locking
10863 		at program termination on February 24, 2021 (v9.2.1).
10864 +/
10865 bool runInGuiThread(scope void delegate() dg) @trusted {
10866 	claimGuiThread();
10867 
10868 	if(thisIsGuiThread) {
10869 		dg();
10870 		return true;
10871 	}
10872 
10873 	if(guiThreadTerminating)
10874 		return false;
10875 
10876 	import core.sync.semaphore;
10877 	static Semaphore sc;
10878 	if(sc is null)
10879 		sc = new Semaphore();
10880 
10881 	static RunQueueMember* rqm;
10882 	if(rqm is null)
10883 		rqm = new RunQueueMember;
10884 	rqm.dg = cast(typeof(rqm.dg)) dg;
10885 	rqm.signal = sc;
10886 	rqm.thrown = null;
10887 
10888 	synchronized(runInGuiThreadLock) {
10889 		runInGuiThreadQueue ~= rqm;
10890 	}
10891 
10892 	if(!SimpleWindow.eventWakeUp())
10893 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10894 
10895 	rqm.signal.wait();
10896 	auto t = rqm.thrown;
10897 
10898 	if(t)
10899 		throw t;
10900 
10901 	return true;
10902 }
10903 
10904 // note it runs sync if this is the gui thread....
10905 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10906 	claimGuiThread();
10907 
10908 	try {
10909 
10910 		if(thisIsGuiThread) {
10911 			dg();
10912 			return;
10913 		}
10914 
10915 		if(guiThreadTerminating)
10916 			return;
10917 
10918 		RunQueueMember* rqm = new RunQueueMember;
10919 		rqm.dg = cast(typeof(rqm.dg)) dg;
10920 		rqm.signal = null;
10921 		rqm.thrown = null;
10922 
10923 		synchronized(runInGuiThreadLock) {
10924 			runInGuiThreadQueue ~= rqm;
10925 		}
10926 
10927 		if(!SimpleWindow.eventWakeUp())
10928 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10929 	} catch(Exception e) {
10930 		if(handleError)
10931 			handleError(e);
10932 	}
10933 }
10934 
10935 private void runPendingRunInGuiThreadDelegates() {
10936 	more:
10937 	RunQueueMember* next;
10938 	synchronized(runInGuiThreadLock) {
10939 		if(runInGuiThreadQueue.length) {
10940 			next = runInGuiThreadQueue[0];
10941 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10942 		} else {
10943 			next = null;
10944 		}
10945 	}
10946 
10947 	if(next) {
10948 		try {
10949 			next.dg();
10950 			next.thrown = null;
10951 		} catch(Throwable t) {
10952 			next.thrown = t;
10953 		}
10954 
10955 		if(next.signal)
10956 			next.signal.notify();
10957 
10958 		goto more;
10959 	}
10960 }
10961 
10962 private void claimGuiThread() nothrow {
10963 	import core.atomic;
10964 	if(cas(&guiThreadExists_, false, true))
10965 		thisIsGuiThread = true;
10966 }
10967 
10968 private struct RunQueueMember {
10969 	void delegate() dg;
10970 	import core.sync.semaphore;
10971 	Semaphore signal;
10972 	Throwable thrown;
10973 }
10974 
10975 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10976 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10977 private bool thisIsGuiThread = false;
10978 private shared bool guiThreadExists_ = false;
10979 private shared bool guiThreadTerminating = false;
10980 
10981 /++
10982 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10983 	event loop. All windows must be exclusively created and managed by a single thread.
10984 
10985 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10986 	when you call one of its constructors.
10987 
10988 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10989 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10990 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10991 
10992 	The reason this function is available is in case you want to message pass between a gui
10993 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10994 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10995 
10996 	History:
10997 		Added December 3, 2021 (dub v10.5)
10998 +/
10999 public bool guiThreadExists() {
11000 	return guiThreadExists_;
11001 }
11002 
11003 /++
11004 	Returns `true` if this thread is either running or set to be running the
11005 	simpledisplay.d gui core event loop because it owns windows.
11006 
11007 	It is important to keep gui-related functionality in the right thread, so you will
11008 	want to `runInGuiThread` when you call them (with some specific exceptions called
11009 	out in those specific functions' documentation). Notably, all windows must be
11010 	created and managed only from the gui thread.
11011 
11012 	Will return false if simpledisplay's other functions haven't been called
11013 	yet; check [guiThreadExists] in addition to this.
11014 
11015 	History:
11016 		Added December 3, 2021 (dub v10.5)
11017 +/
11018 public bool thisThreadRunningGui() {
11019 	return thisIsGuiThread;
11020 }
11021 
11022 /++
11023 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
11024 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
11025 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
11026 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
11027 	file instead if you are in one of those situations).
11028 
11029 	It does not support outputting very many types; just strings and ints are likely to actually work.
11030 
11031 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
11032 	is unspecified meaning I can change it at any time. The only point of this function is to help
11033 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
11034 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
11035 	in those contexts.
11036 
11037 	$(WARNING
11038 		I reserve the right to change this function at any time. You can use it if it helps you
11039 		but do not rely on it for anything permanent.
11040 	)
11041 
11042 	History:
11043 		Added December 3, 2021. Not formally supported under any stable tag.
11044 +/
11045 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
11046 	try {
11047 		version(Windows) {
11048 			import core.sys.windows.wincon;
11049 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
11050 				AllocConsole();
11051 			const(char)* fn = "CONOUT$";
11052 		} else version(Posix) {
11053 			const(char)* fn = "/dev/tty";
11054 		} else static assert(0, "Function not implemented for your system");
11055 
11056 		if(fileOverride.length)
11057 			fn = fileOverride.ptr;
11058 
11059 		import core.stdc.stdio;
11060 		auto fp = fopen(fn, "wt");
11061 		if(fp is null) return;
11062 		scope(exit) fclose(fp);
11063 
11064 		string str;
11065 		foreach(item; t) {
11066 			static if(is(typeof(item) : const(char)[]))
11067 				str ~= item;
11068 			else
11069 				str ~= toInternal!string(item);
11070 			str ~= " ";
11071 		}
11072 		str ~= "\n";
11073 
11074 		fwrite(str.ptr, 1, str.length, fp);
11075 		fflush(fp);
11076 	} catch(Exception e) {
11077 		// sorry no hope
11078 	}
11079 }
11080 
11081 private void guiThreadFinalize() {
11082 	assert(thisIsGuiThread);
11083 
11084 	guiThreadTerminating = true; // don't add any more from this point on
11085 	runPendingRunInGuiThreadDelegates();
11086 }
11087 
11088 /+
11089 interface IPromise {
11090 	void reportProgress(int current, int max, string message);
11091 
11092 	/+ // not formally in cuz of templates but still
11093 	IPromise Then();
11094 	IPromise Catch();
11095 	IPromise Finally();
11096 	+/
11097 }
11098 
11099 /+
11100 	auto promise = async({ ... });
11101 	promise.Then(whatever).
11102 		Then(whateverelse).
11103 		Catch((exception) { });
11104 
11105 
11106 	A promise is run inside a fiber and it looks something like:
11107 
11108 	try {
11109 		auto res = whatever();
11110 		auto res2 = whateverelse(res);
11111 	} catch(Exception e) {
11112 		{ }(e);
11113 	}
11114 
11115 	When a thing succeeds, it is passed as an arg to the next
11116 +/
11117 class Promise(T) : IPromise {
11118 	auto Then() { return null; }
11119 	auto Catch() { return null; }
11120 	auto Finally() { return null; }
11121 
11122 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
11123 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
11124 	T await();
11125 }
11126 
11127 interface Task {
11128 }
11129 
11130 interface Resolvable(T) : Task {
11131 	void run();
11132 
11133 	void resolve(T);
11134 
11135 	Resolvable!T then(void delegate(T)); // returns a new promise
11136 	Resolvable!T error(Throwable); // js catch
11137 	Resolvable!T completed(); // js finally
11138 
11139 }
11140 
11141 /++
11142 	Runs `work` in a helper thread and sends its return value back to the main gui
11143 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
11144 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
11145 	kill the program.
11146 
11147 	You can call reportProgress(position, max, message) to update your parent window
11148 	on your progress.
11149 
11150 	I should also use `shared` methods. FIXME
11151 
11152 	History:
11153 		Added March 6, 2021 (dub version 9.3).
11154 +/
11155 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
11156 	uponCompletion(work(null));
11157 }
11158 
11159 +/
11160 
11161 /// Used internal to dispatch events to various classes.
11162 interface CapableOfHandlingNativeEvent {
11163 	NativeEventHandler getNativeEventHandler();
11164 
11165 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
11166 
11167 	version(X11) {
11168 		// if this is impossible, you are allowed to just throw from it
11169 		// Note: if you call it from another object, set a flag cuz the manger will call you again
11170 		void recreateAfterDisconnect();
11171 		// discard any *connection specific* state, but keep enough that you
11172 		// can be recreated if possible. discardConnectionState() is always called immediately
11173 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
11174 		// you need initialization order
11175 		void discardConnectionState();
11176 	}
11177 }
11178 
11179 version(X11)
11180 /++
11181 	State of keys on mouse events, especially motion.
11182 
11183 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
11184 +/
11185 enum ModifierState : uint {
11186 	shift = 1, ///
11187 	capsLock = 2, ///
11188 	ctrl = 4, ///
11189 	alt = 8, /// Not always available on Windows
11190 	windows = 64, /// ditto
11191 	numLock = 16, ///
11192 
11193 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11194 	middleButtonDown = 512, /// ditto
11195 	rightButtonDown = 1024, /// ditto
11196 }
11197 else version(Emscripten)
11198 enum ModifierState : uint {
11199 	shift = 1, ///
11200 	capsLock = 2, ///
11201 	ctrl = 4, ///
11202 	alt = 8, /// Not always available on Windows
11203 	windows = 64, /// ditto
11204 	numLock = 16, ///
11205 
11206 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11207 	middleButtonDown = 512, /// ditto
11208 	rightButtonDown = 1024, /// ditto
11209 }
11210 else version(Windows)
11211 /// ditto
11212 enum ModifierState : uint {
11213 	shift = 4, ///
11214 	ctrl = 8, ///
11215 
11216 	// i'm not sure if the next two are available
11217 	alt = 256, /// not always available on Windows
11218 	windows = 512, /// ditto
11219 
11220 	capsLock = 1024, ///
11221 	numLock = 2048, ///
11222 
11223 	leftButtonDown = 1, /// not available on key events
11224 	middleButtonDown = 16, /// ditto
11225 	rightButtonDown = 2, /// ditto
11226 
11227 	backButtonDown = 0x20, /// not available on X
11228 	forwardButtonDown = 0x40, /// ditto
11229 }
11230 else version(OSXCocoa)
11231 // FIXME FIXME NotYetImplementedException
11232 enum ModifierState : uint {
11233 	shift = 1, ///
11234 	capsLock = 2, ///
11235 	ctrl = 4, ///
11236 	alt = 8, /// Not always available on Windows
11237 	windows = 64, /// ditto
11238 	numLock = 16, ///
11239 
11240 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11241 	middleButtonDown = 512, /// ditto
11242 	rightButtonDown = 1024, /// ditto
11243 }
11244 
11245 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
11246 enum MouseButton : int {
11247 	none = 0,
11248 	left = 1, ///
11249 	right = 2, ///
11250 	middle = 4, ///
11251 	wheelUp = 8, ///
11252 	wheelDown = 16, ///
11253 	backButton = 32, /// often found on the thumb and used for back in browsers
11254 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
11255 }
11256 
11257 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
11258 enum MouseButtonLinear : ubyte {
11259 	left = 1, ///
11260 	right, ///
11261 	middle, ///
11262 	wheelUp, ///
11263 	wheelDown, ///
11264 	backButton, /// often found on the thumb and used for back in browsers
11265 	forwardButton, /// often found on the thumb and used for forward in browsers
11266 }
11267 
11268 version(WebAssembly) {
11269 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11270 	enum Key {
11271 		Escape = 0xff1b, ///
11272 		F1 = 0xffbe, ///
11273 		F2 = 0xffbf, ///
11274 		F3 = 0xffc0, ///
11275 		F4 = 0xffc1, ///
11276 		F5 = 0xffc2, ///
11277 		F6 = 0xffc3, ///
11278 		F7 = 0xffc4, ///
11279 		F8 = 0xffc5, ///
11280 		F9 = 0xffc6, ///
11281 		F10 = 0xffc7, ///
11282 		F11 = 0xffc8, ///
11283 		F12 = 0xffc9, ///
11284 		PrintScreen = 0xff61, ///
11285 		ScrollLock = 0xff14, ///
11286 		Pause = 0xff13, ///
11287 		Grave = 0x60, /// The $(BACKTICK) ~ key
11288 		// number keys across the top of the keyboard
11289 		N1 = 0x31, /// Number key atop the keyboard
11290 		N2 = 0x32, ///
11291 		N3 = 0x33, ///
11292 		N4 = 0x34, ///
11293 		N5 = 0x35, ///
11294 		N6 = 0x36, ///
11295 		N7 = 0x37, ///
11296 		N8 = 0x38, ///
11297 		N9 = 0x39, ///
11298 		N0 = 0x30, ///
11299 		Dash = 0x2d, ///
11300 		Equals = 0x3d, ///
11301 		Backslash = 0x5c, /// The \ | key
11302 		Backspace = 0xff08, ///
11303 		Insert = 0xff63, ///
11304 		Home = 0xff50, ///
11305 		PageUp = 0xff55, ///
11306 		Delete = 0xffff, ///
11307 		End = 0xff57, ///
11308 		PageDown = 0xff56, ///
11309 		Up = 0xff52, ///
11310 		Down = 0xff54, ///
11311 		Left = 0xff51, ///
11312 		Right = 0xff53, ///
11313 
11314 		Tab = 0xff09, ///
11315 		Q = 0x71, ///
11316 		W = 0x77, ///
11317 		E = 0x65, ///
11318 		R = 0x72, ///
11319 		T = 0x74, ///
11320 		Y = 0x79, ///
11321 		U = 0x75, ///
11322 		I = 0x69, ///
11323 		O = 0x6f, ///
11324 		P = 0x70, ///
11325 		LeftBracket = 0x5b, /// the [ { key
11326 		RightBracket = 0x5d, /// the ] } key
11327 		CapsLock = 0xffe5, ///
11328 		A = 0x61, ///
11329 		S = 0x73, ///
11330 		D = 0x64, ///
11331 		F = 0x66, ///
11332 		G = 0x67, ///
11333 		H = 0x68, ///
11334 		J = 0x6a, ///
11335 		K = 0x6b, ///
11336 		L = 0x6c, ///
11337 		Semicolon = 0x3b, ///
11338 		Apostrophe = 0x27, ///
11339 		Enter = 0xff0d, ///
11340 		Shift = 0xffe1, ///
11341 		Z = 0x7a, ///
11342 		X = 0x78, ///
11343 		C = 0x63, ///
11344 		V = 0x76, ///
11345 		B = 0x62, ///
11346 		N = 0x6e, ///
11347 		M = 0x6d, ///
11348 		Comma = 0x2c, ///
11349 		Period = 0x2e, ///
11350 		Slash = 0x2f, /// the / ? key
11351 		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
11352 		Ctrl = 0xffe3, ///
11353 		Windows = 0xffeb, ///
11354 		Alt = 0xffe9, ///
11355 		Space = 0x20, ///
11356 		Alt_r = 0xffea, /// ditto of shift_r
11357 		Windows_r = 0xffec, ///
11358 		Menu = 0xff67, ///
11359 		Ctrl_r = 0xffe4, ///
11360 
11361 		NumLock = 0xff7f, ///
11362 		Divide = 0xffaf, /// The / key on the number pad
11363 		Multiply = 0xffaa, /// The * key on the number pad
11364 		Minus = 0xffad, /// The - key on the number pad
11365 		Plus = 0xffab, /// The + key on the number pad
11366 		PadEnter = 0xff8d, /// Numberpad enter key
11367 		Pad1 = 0xff9c, /// Numberpad keys
11368 		Pad2 = 0xff99, ///
11369 		Pad3 = 0xff9b, ///
11370 		Pad4 = 0xff96, ///
11371 		Pad5 = 0xff9d, ///
11372 		Pad6 = 0xff98, ///
11373 		Pad7 = 0xff95, ///
11374 		Pad8 = 0xff97, ///
11375 		Pad9 = 0xff9a, ///
11376 		Pad0 = 0xff9e, ///
11377 		PadDot = 0xff9f, ///
11378 	}
11379 } version(X11) {
11380 	// FIXME: match ASCII whenever we can. Most of it is already there,
11381 	// but there's a few exceptions and mismatches with Windows
11382 
11383 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11384 	enum Key {
11385 		Escape = 0xff1b, ///
11386 		F1 = 0xffbe, ///
11387 		F2 = 0xffbf, ///
11388 		F3 = 0xffc0, ///
11389 		F4 = 0xffc1, ///
11390 		F5 = 0xffc2, ///
11391 		F6 = 0xffc3, ///
11392 		F7 = 0xffc4, ///
11393 		F8 = 0xffc5, ///
11394 		F9 = 0xffc6, ///
11395 		F10 = 0xffc7, ///
11396 		F11 = 0xffc8, ///
11397 		F12 = 0xffc9, ///
11398 		PrintScreen = 0xff61, ///
11399 		ScrollLock = 0xff14, ///
11400 		Pause = 0xff13, ///
11401 		Grave = 0x60, /// The $(BACKTICK) ~ key
11402 		// number keys across the top of the keyboard
11403 		N1 = 0x31, /// Number key atop the keyboard
11404 		N2 = 0x32, ///
11405 		N3 = 0x33, ///
11406 		N4 = 0x34, ///
11407 		N5 = 0x35, ///
11408 		N6 = 0x36, ///
11409 		N7 = 0x37, ///
11410 		N8 = 0x38, ///
11411 		N9 = 0x39, ///
11412 		N0 = 0x30, ///
11413 		Dash = 0x2d, ///
11414 		Equals = 0x3d, ///
11415 		Backslash = 0x5c, /// The \ | key
11416 		Backspace = 0xff08, ///
11417 		Insert = 0xff63, ///
11418 		Home = 0xff50, ///
11419 		PageUp = 0xff55, ///
11420 		Delete = 0xffff, ///
11421 		End = 0xff57, ///
11422 		PageDown = 0xff56, ///
11423 		Up = 0xff52, ///
11424 		Down = 0xff54, ///
11425 		Left = 0xff51, ///
11426 		Right = 0xff53, ///
11427 
11428 		Tab = 0xff09, ///
11429 		Q = 0x71, ///
11430 		W = 0x77, ///
11431 		E = 0x65, ///
11432 		R = 0x72, ///
11433 		T = 0x74, ///
11434 		Y = 0x79, ///
11435 		U = 0x75, ///
11436 		I = 0x69, ///
11437 		O = 0x6f, ///
11438 		P = 0x70, ///
11439 		LeftBracket = 0x5b, /// the [ { key
11440 		RightBracket = 0x5d, /// the ] } key
11441 		CapsLock = 0xffe5, ///
11442 		A = 0x61, ///
11443 		S = 0x73, ///
11444 		D = 0x64, ///
11445 		F = 0x66, ///
11446 		G = 0x67, ///
11447 		H = 0x68, ///
11448 		J = 0x6a, ///
11449 		K = 0x6b, ///
11450 		L = 0x6c, ///
11451 		Semicolon = 0x3b, ///
11452 		Apostrophe = 0x27, ///
11453 		Enter = 0xff0d, ///
11454 		Shift = 0xffe1, ///
11455 		Z = 0x7a, ///
11456 		X = 0x78, ///
11457 		C = 0x63, ///
11458 		V = 0x76, ///
11459 		B = 0x62, ///
11460 		N = 0x6e, ///
11461 		M = 0x6d, ///
11462 		Comma = 0x2c, ///
11463 		Period = 0x2e, ///
11464 		Slash = 0x2f, /// the / ? key
11465 		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
11466 		Ctrl = 0xffe3, ///
11467 		Windows = 0xffeb, ///
11468 		Alt = 0xffe9, ///
11469 		Space = 0x20, ///
11470 		Alt_r = 0xffea, /// ditto of shift_r
11471 		Windows_r = 0xffec, ///
11472 		Menu = 0xff67, ///
11473 		Ctrl_r = 0xffe4, ///
11474 
11475 		NumLock = 0xff7f, ///
11476 		Divide = 0xffaf, /// The / key on the number pad
11477 		Multiply = 0xffaa, /// The * key on the number pad
11478 		Minus = 0xffad, /// The - key on the number pad
11479 		Plus = 0xffab, /// The + key on the number pad
11480 		PadEnter = 0xff8d, /// Numberpad enter key
11481 		Pad1 = 0xff9c, /// Numberpad keys
11482 		Pad2 = 0xff99, ///
11483 		Pad3 = 0xff9b, ///
11484 		Pad4 = 0xff96, ///
11485 		Pad5 = 0xff9d, ///
11486 		Pad6 = 0xff98, ///
11487 		Pad7 = 0xff95, ///
11488 		Pad8 = 0xff97, ///
11489 		Pad9 = 0xff9a, ///
11490 		Pad0 = 0xff9e, ///
11491 		PadDot = 0xff9f, ///
11492 	}
11493 } else version(Windows) {
11494 	// the character here is for en-us layouts and for illustration only
11495 	// if you actually want to get characters, wait for character events
11496 	// (the argument to your event handler is simply a dchar)
11497 	// those will be converted by the OS for the right locale.
11498 
11499 	enum Key {
11500 		Escape = 0x1b,
11501 		F1 = 0x70,
11502 		F2 = 0x71,
11503 		F3 = 0x72,
11504 		F4 = 0x73,
11505 		F5 = 0x74,
11506 		F6 = 0x75,
11507 		F7 = 0x76,
11508 		F8 = 0x77,
11509 		F9 = 0x78,
11510 		F10 = 0x79,
11511 		F11 = 0x7a,
11512 		F12 = 0x7b,
11513 		PrintScreen = 0x2c,
11514 		ScrollLock = 0x91,
11515 		Pause = 0x13,
11516 		Grave = 0xc0,
11517 		// number keys across the top of the keyboard
11518 		N1 = 0x31,
11519 		N2 = 0x32,
11520 		N3 = 0x33,
11521 		N4 = 0x34,
11522 		N5 = 0x35,
11523 		N6 = 0x36,
11524 		N7 = 0x37,
11525 		N8 = 0x38,
11526 		N9 = 0x39,
11527 		N0 = 0x30,
11528 		Dash = 0xbd,
11529 		Equals = 0xbb,
11530 		Backslash = 0xdc,
11531 		Backspace = 0x08,
11532 		Insert = 0x2d,
11533 		Home = 0x24,
11534 		PageUp = 0x21,
11535 		Delete = 0x2e,
11536 		End = 0x23,
11537 		PageDown = 0x22,
11538 		Up = 0x26,
11539 		Down = 0x28,
11540 		Left = 0x25,
11541 		Right = 0x27,
11542 
11543 		Tab = 0x09,
11544 		Q = 0x51,
11545 		W = 0x57,
11546 		E = 0x45,
11547 		R = 0x52,
11548 		T = 0x54,
11549 		Y = 0x59,
11550 		U = 0x55,
11551 		I = 0x49,
11552 		O = 0x4f,
11553 		P = 0x50,
11554 		LeftBracket = 0xdb,
11555 		RightBracket = 0xdd,
11556 		CapsLock = 0x14,
11557 		A = 0x41,
11558 		S = 0x53,
11559 		D = 0x44,
11560 		F = 0x46,
11561 		G = 0x47,
11562 		H = 0x48,
11563 		J = 0x4a,
11564 		K = 0x4b,
11565 		L = 0x4c,
11566 		Semicolon = 0xba,
11567 		Apostrophe = 0xde,
11568 		Enter = 0x0d,
11569 		Shift = 0x10,
11570 		Z = 0x5a,
11571 		X = 0x58,
11572 		C = 0x43,
11573 		V = 0x56,
11574 		B = 0x42,
11575 		N = 0x4e,
11576 		M = 0x4d,
11577 		Comma = 0xbc,
11578 		Period = 0xbe,
11579 		Slash = 0xbf,
11580 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11581 		Ctrl = 0x11,
11582 		Windows = 0x5b,
11583 		Alt = -5, // FIXME
11584 		Space = 0x20,
11585 		Alt_r = 0xffea, // ditto of shift_r
11586 		Windows_r = 0x5c, // ditto of shift_r
11587 		Menu = 0x5d,
11588 		Ctrl_r = 0xa3, // ditto of shift_r
11589 
11590 		NumLock = 0x90,
11591 		Divide = 0x6f,
11592 		Multiply = 0x6a,
11593 		Minus = 0x6d,
11594 		Plus = 0x6b,
11595 		PadEnter = -8, // FIXME
11596 		Pad1 = 0x61,
11597 		Pad2 = 0x62,
11598 		Pad3 = 0x63,
11599 		Pad4 = 0x64,
11600 		Pad5 = 0x65,
11601 		Pad6 = 0x66,
11602 		Pad7 = 0x67,
11603 		Pad8 = 0x68,
11604 		Pad9 = 0x69,
11605 		Pad0 = 0x60,
11606 		PadDot = 0x6e,
11607 	}
11608 
11609 	// I'm keeping this around for reference purposes
11610 	// ideally all these buttons will be listed for all platforms,
11611 	// but now now I'm just focusing on my US keyboard
11612 	version(none)
11613 	enum Key {
11614 		LBUTTON = 0x01,
11615 		RBUTTON = 0x02,
11616 		CANCEL = 0x03,
11617 		MBUTTON = 0x04,
11618 		//static if (_WIN32_WINNT > =  0x500) {
11619 		XBUTTON1 = 0x05,
11620 		XBUTTON2 = 0x06,
11621 		//}
11622 		BACK = 0x08,
11623 		TAB = 0x09,
11624 		CLEAR = 0x0C,
11625 		RETURN = 0x0D,
11626 		SHIFT = 0x10,
11627 		CONTROL = 0x11,
11628 		MENU = 0x12,
11629 		PAUSE = 0x13,
11630 		CAPITAL = 0x14,
11631 		KANA = 0x15,
11632 		HANGEUL = 0x15,
11633 		HANGUL = 0x15,
11634 		JUNJA = 0x17,
11635 		FINAL = 0x18,
11636 		HANJA = 0x19,
11637 		KANJI = 0x19,
11638 		ESCAPE = 0x1B,
11639 		CONVERT = 0x1C,
11640 		NONCONVERT = 0x1D,
11641 		ACCEPT = 0x1E,
11642 		MODECHANGE = 0x1F,
11643 		SPACE = 0x20,
11644 		PRIOR = 0x21,
11645 		NEXT = 0x22,
11646 		END = 0x23,
11647 		HOME = 0x24,
11648 		LEFT = 0x25,
11649 		UP = 0x26,
11650 		RIGHT = 0x27,
11651 		DOWN = 0x28,
11652 		SELECT = 0x29,
11653 		PRINT = 0x2A,
11654 		EXECUTE = 0x2B,
11655 		SNAPSHOT = 0x2C,
11656 		INSERT = 0x2D,
11657 		DELETE = 0x2E,
11658 		HELP = 0x2F,
11659 		LWIN = 0x5B,
11660 		RWIN = 0x5C,
11661 		APPS = 0x5D,
11662 		SLEEP = 0x5F,
11663 		NUMPAD0 = 0x60,
11664 		NUMPAD1 = 0x61,
11665 		NUMPAD2 = 0x62,
11666 		NUMPAD3 = 0x63,
11667 		NUMPAD4 = 0x64,
11668 		NUMPAD5 = 0x65,
11669 		NUMPAD6 = 0x66,
11670 		NUMPAD7 = 0x67,
11671 		NUMPAD8 = 0x68,
11672 		NUMPAD9 = 0x69,
11673 		MULTIPLY = 0x6A,
11674 		ADD = 0x6B,
11675 		SEPARATOR = 0x6C,
11676 		SUBTRACT = 0x6D,
11677 		DECIMAL = 0x6E,
11678 		DIVIDE = 0x6F,
11679 		F1 = 0x70,
11680 		F2 = 0x71,
11681 		F3 = 0x72,
11682 		F4 = 0x73,
11683 		F5 = 0x74,
11684 		F6 = 0x75,
11685 		F7 = 0x76,
11686 		F8 = 0x77,
11687 		F9 = 0x78,
11688 		F10 = 0x79,
11689 		F11 = 0x7A,
11690 		F12 = 0x7B,
11691 		F13 = 0x7C,
11692 		F14 = 0x7D,
11693 		F15 = 0x7E,
11694 		F16 = 0x7F,
11695 		F17 = 0x80,
11696 		F18 = 0x81,
11697 		F19 = 0x82,
11698 		F20 = 0x83,
11699 		F21 = 0x84,
11700 		F22 = 0x85,
11701 		F23 = 0x86,
11702 		F24 = 0x87,
11703 		NUMLOCK = 0x90,
11704 		SCROLL = 0x91,
11705 		LSHIFT = 0xA0,
11706 		RSHIFT = 0xA1,
11707 		LCONTROL = 0xA2,
11708 		RCONTROL = 0xA3,
11709 		LMENU = 0xA4,
11710 		RMENU = 0xA5,
11711 		//static if (_WIN32_WINNT > =  0x500) {
11712 		BROWSER_BACK = 0xA6,
11713 		BROWSER_FORWARD = 0xA7,
11714 		BROWSER_REFRESH = 0xA8,
11715 		BROWSER_STOP = 0xA9,
11716 		BROWSER_SEARCH = 0xAA,
11717 		BROWSER_FAVORITES = 0xAB,
11718 		BROWSER_HOME = 0xAC,
11719 		VOLUME_MUTE = 0xAD,
11720 		VOLUME_DOWN = 0xAE,
11721 		VOLUME_UP = 0xAF,
11722 		MEDIA_NEXT_TRACK = 0xB0,
11723 		MEDIA_PREV_TRACK = 0xB1,
11724 		MEDIA_STOP = 0xB2,
11725 		MEDIA_PLAY_PAUSE = 0xB3,
11726 		LAUNCH_MAIL = 0xB4,
11727 		LAUNCH_MEDIA_SELECT = 0xB5,
11728 		LAUNCH_APP1 = 0xB6,
11729 		LAUNCH_APP2 = 0xB7,
11730 		//}
11731 		OEM_1 = 0xBA,
11732 		//static if (_WIN32_WINNT > =  0x500) {
11733 		OEM_PLUS = 0xBB,
11734 		OEM_COMMA = 0xBC,
11735 		OEM_MINUS = 0xBD,
11736 		OEM_PERIOD = 0xBE,
11737 		//}
11738 		OEM_2 = 0xBF,
11739 		OEM_3 = 0xC0,
11740 		OEM_4 = 0xDB,
11741 		OEM_5 = 0xDC,
11742 		OEM_6 = 0xDD,
11743 		OEM_7 = 0xDE,
11744 		OEM_8 = 0xDF,
11745 		//static if (_WIN32_WINNT > =  0x500) {
11746 		OEM_102 = 0xE2,
11747 		//}
11748 		PROCESSKEY = 0xE5,
11749 		//static if (_WIN32_WINNT > =  0x500) {
11750 		PACKET = 0xE7,
11751 		//}
11752 		ATTN = 0xF6,
11753 		CRSEL = 0xF7,
11754 		EXSEL = 0xF8,
11755 		EREOF = 0xF9,
11756 		PLAY = 0xFA,
11757 		ZOOM = 0xFB,
11758 		NONAME = 0xFC,
11759 		PA1 = 0xFD,
11760 		OEM_CLEAR = 0xFE,
11761 	}
11762 
11763 } else version(OSXCocoa) {
11764 	enum Key {
11765 		Escape = 53,
11766 		F1 = 122,
11767 		F2 = 120,
11768 		F3 = 99,
11769 		F4 = 118,
11770 		F5 = 96,
11771 		F6 = 97,
11772 		F7 = 98,
11773 		F8 = 100,
11774 		F9 = 101,
11775 		F10 = 109,
11776 		F11 = 103,
11777 		F12 = 111,
11778 		PrintScreen = 105,
11779 		ScrollLock = 107,
11780 		Pause = 113,
11781 		Grave = 50,
11782 		// number keys across the top of the keyboard
11783 		N1 = 18,
11784 		N2 = 19,
11785 		N3 = 20,
11786 		N4 = 21,
11787 		N5 = 23,
11788 		N6 = 22,
11789 		N7 = 26,
11790 		N8 = 28,
11791 		N9 = 25,
11792 		N0 = 29,
11793 		Dash = 27,
11794 		Equals = 24,
11795 		Backslash = 42,
11796 		Backspace = 51,
11797 		Insert = 114,
11798 		Home = 115,
11799 		PageUp = 116,
11800 		Delete = 117,
11801 		End = 119,
11802 		PageDown = 121,
11803 		Up = 126,
11804 		Down = 125,
11805 		Left = 123,
11806 		Right = 124,
11807 
11808 		Tab = 48,
11809 		Q = 12,
11810 		W = 13,
11811 		E = 14,
11812 		R = 15,
11813 		T = 17,
11814 		Y = 16,
11815 		U = 32,
11816 		I = 34,
11817 		O = 31,
11818 		P = 35,
11819 		LeftBracket = 33,
11820 		RightBracket = 30,
11821 		CapsLock = 57,
11822 		A = 0,
11823 		S = 1,
11824 		D = 2,
11825 		F = 3,
11826 		G = 5,
11827 		H = 4,
11828 		J = 38,
11829 		K = 40,
11830 		L = 37,
11831 		Semicolon = 41,
11832 		Apostrophe = 39,
11833 		Enter = 36,
11834 		Shift = 56,
11835 		Z = 6,
11836 		X = 7,
11837 		C = 8,
11838 		V = 9,
11839 		B = 11,
11840 		N = 45,
11841 		M = 46,
11842 		Comma = 43,
11843 		Period = 47,
11844 		Slash = 44,
11845 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11846 		Ctrl = 59,
11847 		Windows = 55,
11848 		Alt = 58,
11849 		Space = 49,
11850 		Alt_r = -3, // ditto of shift_r
11851 		Windows_r = -2,
11852 		Menu = 110,
11853 		Ctrl_r = -1,
11854 
11855 		NumLock = 1,
11856 		Divide = 75,
11857 		Multiply = 67,
11858 		Minus = 78,
11859 		Plus = 69,
11860 		PadEnter = 76,
11861 		Pad1 = 83,
11862 		Pad2 = 84,
11863 		Pad3 = 85,
11864 		Pad4 = 86,
11865 		Pad5 = 87,
11866 		Pad6 = 88,
11867 		Pad7 = 89,
11868 		Pad8 = 91,
11869 		Pad9 = 92,
11870 		Pad0 = 82,
11871 		PadDot = 65,
11872 	}
11873 
11874 }
11875 
11876 char keyToLetterCharAssumingLotsOfThingsThatYouMightBetterNotAssume(Key key) {
11877 	version(OSXCocoa) {
11878 		return char.init; // FIXME
11879 	} else {
11880 		return cast(char)(key - Key.A + 'a');
11881 	}
11882 }
11883 
11884 /* Additional utilities */
11885 
11886 
11887 Color fromHsl(real h, real s, real l) {
11888 	return arsd.color.fromHsl([h,s,l]);
11889 }
11890 
11891 
11892 
11893 /* ********** What follows is the system-specific implementations *********/
11894 version(Windows) {
11895 
11896 
11897 	// helpers for making HICONs from MemoryImages
11898 	class WindowsIcon {
11899 		struct Win32Icon {
11900 			align(1):
11901 			uint biSize;
11902 			int biWidth;
11903 			int biHeight;
11904 			ushort biPlanes;
11905 			ushort biBitCount;
11906 			uint biCompression;
11907 			uint biSizeImage;
11908 			int biXPelsPerMeter;
11909 			int biYPelsPerMeter;
11910 			uint biClrUsed;
11911 			uint biClrImportant;
11912 			// RGBQUAD[colorCount] biColors;
11913 			/* Pixels:
11914 			Uint8 pixels[]
11915 			*/
11916 			/* Mask:
11917 			Uint8 mask[]
11918 			*/
11919 		}
11920 
11921 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11922 
11923 			assert(mi.width <= 256, "image too wide");
11924 			assert(mi.height <= 256, "image too tall");
11925 			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
11926 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11927 
11928 			int icon_plen = mi.width * mi.height * 4;
11929 			int icon_mlen = mi.width * mi.height / 8;
11930 
11931 			int colorCount = 0;
11932 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11933 
11934 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11935 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11936 
11937 			auto data = memory[Win32Icon.sizeof .. $];
11938 
11939 			width = mi.width;
11940 			height = mi.height;
11941 
11942 			auto trueColorImage = mi.getAsTrueColorImage();
11943 
11944 			icon_win32.biSize = 40;
11945 			icon_win32.biWidth = mi.width;
11946 			icon_win32.biHeight = mi.height*2;
11947 			icon_win32.biPlanes = 1;
11948 			icon_win32.biBitCount = 32;
11949 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11950 
11951 			int offset = 0;
11952 			int andOff = icon_plen * 8; // the and offset is in bits
11953 
11954 			// leaving the and mask as the default 0 so the rgba alpha blend
11955 			// does its thing instead
11956 			for(int y = height - 1; y >= 0; y--) {
11957 				int off2 = y * width * 4;
11958 				foreach(x; 0 .. width) {
11959 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11960 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11961 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11962 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11963 
11964 					offset += 4;
11965 					off2 += 4;
11966 				}
11967 			}
11968 
11969 			return memory;
11970 		}
11971 
11972 		this(MemoryImage mi) {
11973 			int icon_len, width, height;
11974 
11975 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11976 
11977 			/*
11978 			PNG* png = readPnpngData);
11979 			PNGHeader pngh = getHeader(png);
11980 			void* icon_win32;
11981 			if(pngh.depth == 4) {
11982 				auto i = new Win32Icon!(16);
11983 				i.fromPNG(png, pngh, icon_len, width, height);
11984 				icon_win32 = i;
11985 			}
11986 			else if(pngh.depth == 8) {
11987 				auto i = new Win32Icon!(256);
11988 				i.fromPNG(png, pngh, icon_len, width, height);
11989 				icon_win32 = i;
11990 			} else assert(0);
11991 			*/
11992 
11993 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11994 
11995 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11996 		}
11997 
11998 		~this() {
11999 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
12000 			DestroyIcon(hIcon);
12001 		}
12002 
12003 		HICON hIcon;
12004 	}
12005 
12006 
12007 
12008 
12009 
12010 
12011 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
12012 	alias HWND NativeWindowHandle;
12013 
12014 	extern(Windows)
12015 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
12016 		try {
12017 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
12018 				// it returns zero if the message is handled, so we won't do anything more there
12019 				// do I like that though?
12020 				int mustReturn;
12021 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
12022 				if(mustReturn)
12023 					return ret;
12024 			}
12025 
12026 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
12027 				if(window.getNativeEventHandler !is null) {
12028 					int mustReturn;
12029 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
12030 					if(mustReturn)
12031 						return ret;
12032 				}
12033 				if(auto w = cast(SimpleWindow) (*window))
12034 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
12035 				else
12036 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
12037 			} else {
12038 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
12039 			}
12040 		} catch (Exception e) {
12041 			try {
12042 				sdpy_abort(e);
12043 				return 0;
12044 			} catch(Exception e) { assert(0); }
12045 		}
12046 	}
12047 
12048 	void sdpy_abort(Throwable e) nothrow {
12049 		try
12050 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
12051 		catch(Exception e)
12052 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
12053 		ExitProcess(1);
12054 	}
12055 
12056 	mixin template NativeScreenPainterImplementation() {
12057 		HDC hdc;
12058 		HWND hwnd;
12059 		//HDC windowHdc;
12060 		HBITMAP oldBmp;
12061 
12062 		void create(PaintingHandle window) {
12063 			hwnd = window;
12064 
12065 			if(auto sw = cast(SimpleWindow) this.window) {
12066 				// drawing on a window, double buffer
12067 				auto windowHdc = GetDC(hwnd);
12068 
12069 				auto buffer = sw.impl.buffer;
12070 				if(buffer is null) {
12071 					hdc = windowHdc;
12072 					windowDc = true;
12073 				} else {
12074 					hdc = CreateCompatibleDC(windowHdc);
12075 
12076 					ReleaseDC(hwnd, windowHdc);
12077 
12078 					oldBmp = SelectObject(hdc, buffer);
12079 				}
12080 			} else {
12081 				// drawing on something else, draw directly
12082 				hdc = CreateCompatibleDC(null);
12083 				SelectObject(hdc, window);
12084 			}
12085 
12086 			// X doesn't draw a text background, so neither should we
12087 			SetBkMode(hdc, TRANSPARENT);
12088 
12089 			ensureDefaultFontLoaded();
12090 
12091 			if(defaultGuiFont) {
12092 				SelectObject(hdc, defaultGuiFont);
12093 				// DeleteObject(defaultGuiFont);
12094 			}
12095 		}
12096 
12097 		static HFONT defaultGuiFont;
12098 		static void ensureDefaultFontLoaded() {
12099 			static bool triedDefaultGuiFont = false;
12100 			if(!triedDefaultGuiFont) {
12101 				NONCLIENTMETRICS params;
12102 				params.cbSize = params.sizeof;
12103 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
12104 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
12105 				}
12106 				triedDefaultGuiFont = true;
12107 			}
12108 		}
12109 
12110 		private OperatingSystemFont _activeFont;
12111 
12112 		void setFont(OperatingSystemFont font) {
12113 			_activeFont = font;
12114 			if(font && font.font) {
12115 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
12116 					// error... how to handle tho?
12117 				} else {
12118 
12119 				}
12120 			}
12121 			else if(defaultGuiFont)
12122 				SelectObject(hdc, defaultGuiFont);
12123 		}
12124 
12125 		arsd.color.Rectangle _clipRectangle;
12126 
12127 		void setClipRectangle(int x, int y, int width, int height) {
12128 			auto old = _clipRectangle;
12129 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12130 			if(old == _clipRectangle)
12131 				return;
12132 
12133 			if(width == 0 || height == 0) {
12134 				SelectClipRgn(hdc, null);
12135 			} else {
12136 				auto region = CreateRectRgn(x, y, x + width, y + height);
12137 				SelectClipRgn(hdc, region);
12138 				DeleteObject(region);
12139 			}
12140 		}
12141 
12142 
12143 		// just because we can on Windows...
12144 		//void create(Image image);
12145 
12146 		void invalidateRect(Rectangle invalidRect) {
12147 			RECT rect;
12148 			rect.left = invalidRect.left;
12149 			rect.right = invalidRect.right;
12150 			rect.top = invalidRect.top;
12151 			rect.bottom = invalidRect.bottom;
12152 			InvalidateRect(hwnd, &rect, false);
12153 		}
12154 		bool manualInvalidations;
12155 
12156 		void dispose() {
12157 			// FIXME: this.window.width/height is probably wrong
12158 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
12159 			// ReleaseDC(hwnd, windowHdc);
12160 
12161 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
12162 			if(cast(SimpleWindow) this.window) {
12163 				if(!manualInvalidations)
12164 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
12165 			}
12166 
12167 			if(originalPen !is null)
12168 				SelectObject(hdc, originalPen);
12169 			if(currentPen !is null)
12170 				DeleteObject(currentPen);
12171 			if(originalBrush !is null)
12172 				SelectObject(hdc, originalBrush);
12173 			if(currentBrush !is null)
12174 				DeleteObject(currentBrush);
12175 
12176 			SelectObject(hdc, oldBmp);
12177 
12178 			if(windowDc)
12179 				ReleaseDC(hwnd, hdc);
12180 			else
12181 				DeleteDC(hdc);
12182 
12183 			if(window.paintingFinishedDg !is null)
12184 				window.paintingFinishedDg()();
12185 		}
12186 
12187 		bool windowDc;
12188 		HPEN originalPen;
12189 		HPEN currentPen;
12190 
12191 		Pen _activePen;
12192 
12193 		Color _outlineColor;
12194 
12195 		@property void pen(Pen p) {
12196 			_activePen = p;
12197 			_outlineColor = p.color;
12198 
12199 			HPEN pen;
12200 			if(p.color.a == 0) {
12201 				pen = GetStockObject(NULL_PEN);
12202 			} else {
12203 				int style = PS_SOLID;
12204 				final switch(p.style) {
12205 					case Pen.Style.Solid:
12206 						style = PS_SOLID;
12207 					break;
12208 					case Pen.Style.Dashed:
12209 						style = PS_DASH;
12210 					break;
12211 					case Pen.Style.Dotted:
12212 						style = PS_DOT;
12213 					break;
12214 				}
12215 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
12216 			}
12217 			auto orig = SelectObject(hdc, pen);
12218 			if(originalPen is null)
12219 				originalPen = orig;
12220 
12221 			if(currentPen !is null)
12222 				DeleteObject(currentPen);
12223 
12224 			currentPen = pen;
12225 
12226 			// the outline is like a foreground since it's done that way on X
12227 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
12228 
12229 		}
12230 
12231 		@property void rasterOp(RasterOp op) {
12232 			int mode;
12233 			final switch(op) {
12234 				case RasterOp.normal:
12235 					mode = R2_COPYPEN;
12236 				break;
12237 				case RasterOp.xor:
12238 					mode = R2_XORPEN;
12239 				break;
12240 			}
12241 			SetROP2(hdc, mode);
12242 		}
12243 
12244 		HBRUSH originalBrush;
12245 		HBRUSH currentBrush;
12246 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
12247 		@property void fillColor(Color c) {
12248 			if(c == _fillColor)
12249 				return;
12250 			_fillColor = c;
12251 			HBRUSH brush;
12252 			if(c.a == 0) {
12253 				brush = GetStockObject(HOLLOW_BRUSH);
12254 			} else {
12255 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12256 			}
12257 			auto orig = SelectObject(hdc, brush);
12258 			if(originalBrush is null)
12259 				originalBrush = orig;
12260 
12261 			if(currentBrush !is null)
12262 				DeleteObject(currentBrush);
12263 
12264 			currentBrush = brush;
12265 
12266 			// background color is NOT set because X doesn't draw text backgrounds
12267 			//   SetBkColor(hdc, RGB(255, 255, 255));
12268 		}
12269 
12270 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12271 			BITMAP bm;
12272 
12273 			HDC hdcMem = CreateCompatibleDC(hdc);
12274 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
12275 
12276 			GetObject(i.handle, bm.sizeof, &bm);
12277 
12278 			// or should I AlphaBlend!??!?!
12279 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
12280 
12281 			SelectObject(hdcMem, hbmOld);
12282 			DeleteDC(hdcMem);
12283 		}
12284 
12285 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12286 			BITMAP bm;
12287 
12288 			HDC hdcMem = CreateCompatibleDC(hdc);
12289 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
12290 
12291 			GetObject(s.handle, bm.sizeof, &bm);
12292 
12293 			version(CRuntime_DigitalMars) goto noalpha;
12294 
12295 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
12296 			if(s.enableAlpha) {
12297 				auto dw = w ? w : bm.bmWidth;
12298 				auto dh = h ? h : bm.bmHeight;
12299 				BLENDFUNCTION bf;
12300 				bf.BlendOp = AC_SRC_OVER;
12301 				bf.SourceConstantAlpha = 255;
12302 				bf.AlphaFormat = AC_SRC_ALPHA;
12303 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
12304 			} else {
12305 				noalpha:
12306 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
12307 			}
12308 
12309 			SelectObject(hdcMem, hbmOld);
12310 			DeleteDC(hdcMem);
12311 		}
12312 
12313 		Size textSize(scope const(char)[] text) {
12314 			bool dummyX;
12315 			if(text.length == 0) {
12316 				text = " ";
12317 				dummyX = true;
12318 			}
12319 			RECT rect;
12320 			WCharzBuffer buffer = WCharzBuffer(text);
12321 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
12322 			return Size(dummyX ? 0 : rect.right, rect.bottom);
12323 		}
12324 
12325 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
12326 			if(text.length && text[$-1] == '\n')
12327 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
12328 			if(text.length && text[$-1] == '\r')
12329 				text = text[0 .. $-1];
12330 
12331 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
12332 			if(x2 == 0 && y2 == 0) {
12333 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
12334 			} else {
12335 				RECT rect;
12336 				rect.left = x;
12337 				rect.top = y;
12338 				rect.right = x2;
12339 				rect.bottom = y2;
12340 
12341 				uint mode = DT_LEFT;
12342 				if(alignment & TextAlignment.Right)
12343 					mode = DT_RIGHT;
12344 				else if(alignment & TextAlignment.Center)
12345 					mode = DT_CENTER;
12346 
12347 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
12348 				if(alignment & TextAlignment.VerticalCenter)
12349 					mode |= DT_VCENTER | DT_SINGLELINE;
12350 
12351 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
12352 			}
12353 
12354 			/*
12355 			uint mode;
12356 
12357 			if(alignment & TextAlignment.Center)
12358 				mode = TA_CENTER;
12359 
12360 			SetTextAlign(hdc, mode);
12361 			*/
12362 		}
12363 
12364 		int fontHeight() {
12365 			TEXTMETRIC metric;
12366 			if(GetTextMetricsW(hdc, &metric)) {
12367 				return metric.tmHeight;
12368 			}
12369 
12370 			return 16; // idk just guessing here, maybe we should throw
12371 		}
12372 
12373 		void drawPixel(int x, int y) {
12374 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
12375 		}
12376 
12377 		// The basic shapes, outlined
12378 
12379 		void drawLine(int x1, int y1, int x2, int y2) {
12380 			MoveToEx(hdc, x1, y1, null);
12381 			LineTo(hdc, x2, y2);
12382 		}
12383 
12384 		void drawRectangle(int x, int y, int width, int height) {
12385 			// FIXME: with a wider pen this might not draw quite right. im not sure.
12386 			gdi.Rectangle(hdc, x, y, x + width, y + height);
12387 		}
12388 
12389 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
12390 			RoundRect(
12391 				hdc,
12392 				upperLeft.x, upperLeft.y,
12393 				lowerRight.x, lowerRight.y,
12394 				borderRadius, borderRadius
12395 			);
12396 		}
12397 
12398 		/// Arguments are the points of the bounding rectangle
12399 		void drawEllipse(int x1, int y1, int x2, int y2) {
12400 			Ellipse(hdc, x1, y1, x2, y2);
12401 		}
12402 
12403 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
12404 			//if(length > 360*64)
12405 				//length = 360*64;
12406 
12407 			if((start == 0 && length == 360*64)) {
12408 				drawEllipse(x1, y1, x1 + width, y1 + height);
12409 			} else {
12410 				import core.stdc.math;
12411 
12412 				bool clockwise = false;
12413 				if(length < 0) {
12414 					clockwise = true;
12415 					length = -length;
12416 				}
12417 
12418 				double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323;
12419 				double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323;
12420 
12421 				auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12422 				auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12423 				auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12424 				auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12425 
12426 				if(clockwise) {
12427 					auto t1 = c1;
12428 					auto t2 = c2;
12429 					c1 = c3;
12430 					c2 = c4;
12431 					c3 = t1;
12432 					c4 = t2;
12433 				}
12434 
12435 				//if(_activePen.color.a)
12436 					//Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12437 				//if(_fillColor.a)
12438 
12439 				Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12440 			}
12441 		}
12442 
12443 		void drawPolygon(Point[] vertexes) {
12444 			POINT[] points;
12445 			points.length = vertexes.length;
12446 
12447 			foreach(i, p; vertexes) {
12448 				points[i].x = p.x;
12449 				points[i].y = p.y;
12450 			}
12451 
12452 			Polygon(hdc, points.ptr, cast(int) points.length);
12453 		}
12454 	}
12455 
12456 
12457 	// Mix this into the SimpleWindow class
12458 	mixin template NativeSimpleWindowImplementation() {
12459 		int curHidden = 0; // counter
12460 		__gshared static bool[string] knownWinClasses;
12461 		static bool altPressed = false;
12462 
12463 		HANDLE oldCursor;
12464 
12465 		void hideCursor () {
12466 			if(curHidden == 0)
12467 				oldCursor = SetCursor(null);
12468 			++curHidden;
12469 		}
12470 
12471 		void showCursor () {
12472 			--curHidden;
12473 			if(curHidden == 0) {
12474 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
12475 			}
12476 		}
12477 
12478 
12479 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
12480 
12481 		void setMinSize (int minwidth, int minheight) {
12482 			minWidth = minwidth;
12483 			minHeight = minheight;
12484 		}
12485 		void setMaxSize (int maxwidth, int maxheight) {
12486 			maxWidth = maxwidth;
12487 			maxHeight = maxheight;
12488 		}
12489 
12490 		// FIXME i'm not sure that Windows has this functionality
12491 		// though it is nonessential anyway.
12492 		void setResizeGranularity (int granx, int grany) {}
12493 
12494 		ScreenPainter getPainter(bool manualInvalidations) {
12495 			return ScreenPainter(this, hwnd, manualInvalidations);
12496 		}
12497 
12498 		HBITMAP buffer;
12499 
12500 		void setTitle(string title) {
12501 			WCharzBuffer bfr = WCharzBuffer(title);
12502 			SetWindowTextW(hwnd, bfr.ptr);
12503 		}
12504 
12505 		string getTitle() {
12506 			auto len = GetWindowTextLengthW(hwnd);
12507 			if (!len)
12508 				return null;
12509 			wchar[256] tmpBuffer;
12510 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
12511 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12512 			auto str = buffer[0 .. len2];
12513 			return makeUtf8StringFromWindowsString(str);
12514 		}
12515 
12516 		void move(int x, int y) {
12517 			RECT rect;
12518 			GetWindowRect(hwnd, &rect);
12519 			// move it while maintaining the same size...
12520 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
12521 		}
12522 
12523 		void resize(int w, int h) {
12524 			RECT rect;
12525 			GetWindowRect(hwnd, &rect);
12526 
12527 			RECT client;
12528 			GetClientRect(hwnd, &client);
12529 
12530 			rect.right = rect.right - client.right + w;
12531 			rect.bottom = rect.bottom - client.bottom + h;
12532 
12533 			// same position, new size for the client rectangle
12534 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
12535 
12536 			updateOpenglViewportIfNeeded(w, h);
12537 		}
12538 
12539 		void moveResize (int x, int y, int w, int h) {
12540 			// what's given is the client rectangle, we need to adjust
12541 
12542 			RECT rect;
12543 			rect.left = x;
12544 			rect.top = y;
12545 			rect.right = w + x;
12546 			rect.bottom = h + y;
12547 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
12548 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
12549 
12550 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
12551 			updateOpenglViewportIfNeeded(w, h);
12552 			if (windowResized !is null) windowResized(w, h);
12553 		}
12554 
12555 		version(without_opengl) {} else {
12556 			HGLRC ghRC;
12557 			HDC ghDC;
12558 		}
12559 
12560 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
12561 			string cnamec;
12562 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
12563 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
12564 				cnamec = "DSimpleWindow";
12565 			} else {
12566 				cnamec = sdpyWindowClass;
12567 			}
12568 
12569 			WCharzBuffer cn = WCharzBuffer(cnamec);
12570 
12571 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
12572 
12573 			if(cnamec !in knownWinClasses) {
12574 				WNDCLASSEX wc;
12575 
12576 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
12577 				// to the object. Maybe.
12578 				wc.cbSize = wc.sizeof;
12579 				wc.cbClsExtra = 0;
12580 				wc.cbWndExtra = 0;
12581 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
12582 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
12583 				wc.hIcon = LoadIcon(hInstance, null);
12584 				wc.hInstance = hInstance;
12585 				wc.lpfnWndProc = &WndProc;
12586 				wc.lpszClassName = cn.ptr;
12587 				wc.hIconSm = null;
12588 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
12589 				if(!RegisterClassExW(&wc))
12590 					throw new WindowsApiException("RegisterClassExW", GetLastError());
12591 				knownWinClasses[cnamec] = true;
12592 			}
12593 
12594 			int style;
12595 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
12596 
12597 			// FIXME: windowType and customizationFlags
12598 			final switch(windowType) {
12599 				case WindowTypes.normal:
12600 					if(resizability == Resizability.fixedSize) {
12601 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
12602 					} else {
12603 						style = WS_OVERLAPPEDWINDOW;
12604 					}
12605 				break;
12606 				case WindowTypes.undecorated:
12607 					style = WS_POPUP | WS_SYSMENU;
12608 				break;
12609 				case WindowTypes.eventOnly:
12610 					_hidden = true;
12611 				break;
12612 				case WindowTypes.tooltip:
12613 				case WindowTypes.dnd:
12614 				case WindowTypes.comboBoxDropdown:
12615 				case WindowTypes.dropdownMenu:
12616 				case WindowTypes.popupMenu:
12617 				case WindowTypes.notification:
12618 					style = WS_POPUP;
12619 					flags |= WS_EX_NOACTIVATE;
12620 				break;
12621 				case WindowTypes.dialog:
12622 					style = WS_OVERLAPPEDWINDOW;
12623 				break;
12624 				case WindowTypes.nestedChild:
12625 					style = WS_CHILD;
12626 				break;
12627 				case WindowTypes.minimallyWrapped:
12628 					assert(0, "construct minimally wrapped through the other ctor overlad");
12629 			}
12630 
12631 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12632 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
12633 
12634 			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
12635 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
12636 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
12637 
12638 			if(!hwnd)
12639 				throw new WindowsApiException("CreateWindowEx", GetLastError());
12640 
12641 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12642 				setOpacity(255);
12643 
12644 			SimpleWindow.nativeMapping[hwnd] = this;
12645 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
12646 
12647 			if(windowType == WindowTypes.eventOnly)
12648 				return;
12649 
12650 			HDC hdc = GetDC(hwnd);
12651 
12652 			if(!hdc)
12653 				throw new WindowsApiException("GetDC", GetLastError());
12654 
12655 			version(without_opengl) {}
12656 			else {
12657 				if(opengl == OpenGlOptions.yes) {
12658 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
12659 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
12660 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
12661 					ghDC = hdc;
12662 					PIXELFORMATDESCRIPTOR pfd;
12663 
12664 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
12665 					pfd.nVersion = 1;
12666 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
12667 					pfd.dwLayerMask = PFD_MAIN_PLANE;
12668 					pfd.iPixelType = PFD_TYPE_RGBA;
12669 					pfd.cColorBits = 24;
12670 					pfd.cDepthBits = 24;
12671 					pfd.cAccumBits = 0;
12672 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
12673 
12674 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
12675 
12676 					if (pixelformat == 0)
12677 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
12678 
12679 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
12680 						throw new WindowsApiException("SetPixelFormat", GetLastError());
12681 
12682 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
12683 						// windoze is idiotic: we have to have OpenGL context to get function addresses
12684 						// so we will create fake context to get that stupid address
12685 						auto tmpcc = wglCreateContext(ghDC);
12686 						if (tmpcc !is null) {
12687 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
12688 							wglMakeCurrent(ghDC, tmpcc);
12689 							wglInitOtherFunctions();
12690 						}
12691 					}
12692 
12693 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
12694 						int[9] contextAttribs = [
12695 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
12696 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
12697 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
12698 							// for modern context, set "forward compatibility" flag too
12699 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
12700 							0/*None*/,
12701 						];
12702 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
12703 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
12704 							// activate fallback mode
12705 							// 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;
12706 							ghRC = wglCreateContext(ghDC);
12707 						}
12708 						if (ghRC is null)
12709 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
12710 					} else {
12711 						// try to do at least something
12712 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
12713 							sdpyOpenGLContextVersion = 0;
12714 							ghRC = wglCreateContext(ghDC);
12715 						}
12716 						if (ghRC is null)
12717 							throw new WindowsApiException("wglCreateContext", GetLastError());
12718 					}
12719 				}
12720 			}
12721 
12722 			if(opengl == OpenGlOptions.no) {
12723 				buffer = CreateCompatibleBitmap(hdc, width, height);
12724 
12725 				auto hdcBmp = CreateCompatibleDC(hdc);
12726 				// make sure it's filled with a blank slate
12727 				auto oldBmp = SelectObject(hdcBmp, buffer);
12728 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
12729 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
12730 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
12731 				SelectObject(hdcBmp, oldBmp);
12732 				SelectObject(hdcBmp, oldBrush);
12733 				SelectObject(hdcBmp, oldPen);
12734 				DeleteDC(hdcBmp);
12735 
12736 				bmpWidth = width;
12737 				bmpHeight = height;
12738 
12739 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
12740 			}
12741 
12742 			// We want the window's client area to match the image size
12743 			RECT rcClient, rcWindow;
12744 			POINT ptDiff;
12745 			GetClientRect(hwnd, &rcClient);
12746 			GetWindowRect(hwnd, &rcWindow);
12747 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
12748 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
12749 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
12750 
12751 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
12752 				ShowWindow(hwnd, SW_SHOWNORMAL);
12753 			} else {
12754 				_hidden = true;
12755 			}
12756 			this._visibleForTheFirstTimeCalled = false; // hack!
12757 		}
12758 
12759 
12760 		void dispose() {
12761 			if(buffer)
12762 				DeleteObject(buffer);
12763 		}
12764 
12765 		void closeWindow() {
12766 			if(ghRC) {
12767 				wglDeleteContext(ghRC);
12768 				ghRC = null;
12769 			}
12770 			DestroyWindow(hwnd);
12771 		}
12772 
12773 		bool setOpacity(ubyte alpha) {
12774 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
12775 		}
12776 
12777 		HANDLE currentCursor;
12778 
12779 		// returns zero if it recognized the event
12780 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
12781 			MouseEvent mouse;
12782 
12783 			void mouseEvent(bool isScreen, ulong mods) {
12784 				auto x = LOWORD(lParam);
12785 				auto y = HIWORD(lParam);
12786 				if(isScreen) {
12787 					POINT p;
12788 					p.x = x;
12789 					p.y = y;
12790 					ScreenToClient(hwnd, &p);
12791 					x = cast(ushort) p.x;
12792 					y = cast(ushort) p.y;
12793 				}
12794 
12795 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
12796 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
12797 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
12798 				}
12799 
12800 				mouse.x = x + offsetX;
12801 				mouse.y = y + offsetY;
12802 
12803 				wind.mdx(mouse);
12804 				mouse.modifierState = cast(int) mods;
12805 				mouse.window = wind;
12806 
12807 				if(wind.handleMouseEvent)
12808 					wind.handleMouseEvent(mouse);
12809 			}
12810 
12811 			switch(msg) {
12812 				case WM_GETMINMAXINFO:
12813 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
12814 
12815 					if(wind.minWidth > 0) {
12816 						RECT rect;
12817 						rect.left = 100;
12818 						rect.top = 100;
12819 						rect.right = wind.minWidth + 100;
12820 						rect.bottom = wind.minHeight + 100;
12821 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12822 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12823 
12824 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12825 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12826 					}
12827 
12828 					if(wind.maxWidth < int.max) {
12829 						RECT rect;
12830 						rect.left = 100;
12831 						rect.top = 100;
12832 						rect.right = wind.maxWidth + 100;
12833 						rect.bottom = wind.maxHeight + 100;
12834 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12835 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12836 
12837 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12838 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12839 					}
12840 				break;
12841 				case WM_CHAR:
12842 					wchar c = cast(wchar) wParam;
12843 					if(wind.handleCharEvent)
12844 						wind.handleCharEvent(cast(dchar) c);
12845 				break;
12846 				  case WM_SETFOCUS:
12847 				  case WM_KILLFOCUS:
12848 					wind._focused = (msg == WM_SETFOCUS);
12849 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12850 					if(wind.onFocusChange)
12851 						wind.onFocusChange(msg == WM_SETFOCUS);
12852 				  break;
12853 
12854 				case WM_SYSKEYDOWN:
12855 					goto case;
12856 				case WM_SYSKEYUP:
12857 					if(lParam & (1 << 29)) {
12858 						goto case;
12859 					} else {
12860 						// no window has keyboard focus
12861 						goto default;
12862 					}
12863 				case WM_KEYDOWN:
12864 				case WM_KEYUP:
12865 					KeyEvent ev;
12866 					ev.key = cast(Key) wParam;
12867 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12868 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12869 
12870 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12871 
12872 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12873 						ev.modifierState |= ModifierState.shift;
12874 					//k8: this doesn't work; thanks for nothing, windows
12875 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12876 						ev.modifierState |= ModifierState.alt;*/
12877 					// this never seems to actually be set
12878 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12879 
12880 					if (wParam == 0x12) {
12881 						altPressed = (msg == WM_SYSKEYDOWN);
12882 					}
12883 
12884 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12885 						altPressed = false;
12886 					}
12887 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12888 
12889 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12890 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12891 						ev.modifierState |= ModifierState.ctrl;
12892 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12893 						ev.modifierState |= ModifierState.windows;
12894 					if(GetKeyState(Key.NumLock))
12895 						ev.modifierState |= ModifierState.numLock;
12896 					if(GetKeyState(Key.CapsLock))
12897 						ev.modifierState |= ModifierState.capsLock;
12898 
12899 					/+
12900 					// we always want to send the character too, so let's convert it
12901 					ubyte[256] state;
12902 					wchar[16] buffer;
12903 					GetKeyboardState(state.ptr);
12904 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12905 
12906 					foreach(dchar d; buffer) {
12907 						ev.character = d;
12908 						break;
12909 					}
12910 					+/
12911 
12912 					ev.window = wind;
12913 					if(wind.handleKeyEvent)
12914 						wind.handleKeyEvent(ev);
12915 				break;
12916 				case 0x020a /*WM_MOUSEWHEEL*/:
12917 					// send click
12918 					mouse.type = cast(MouseEventType) 1;
12919 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12920 					mouseEvent(true, LOWORD(wParam));
12921 
12922 					// also send release
12923 					mouse.type = cast(MouseEventType) 2;
12924 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12925 					mouseEvent(true, LOWORD(wParam));
12926 				break;
12927 				case WM_MOUSEMOVE:
12928 					mouse.type = cast(MouseEventType) 0;
12929 					mouseEvent(false, wParam);
12930 				break;
12931 				case WM_LBUTTONDOWN:
12932 				case WM_LBUTTONDBLCLK:
12933 					mouse.type = cast(MouseEventType) 1;
12934 					mouse.button = MouseButton.left;
12935 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12936 					mouseEvent(false, wParam);
12937 				break;
12938 				case WM_LBUTTONUP:
12939 					mouse.type = cast(MouseEventType) 2;
12940 					mouse.button = MouseButton.left;
12941 					mouseEvent(false, wParam);
12942 				break;
12943 				case WM_RBUTTONDOWN:
12944 				case WM_RBUTTONDBLCLK:
12945 					mouse.type = cast(MouseEventType) 1;
12946 					mouse.button = MouseButton.right;
12947 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12948 					mouseEvent(false, wParam);
12949 				break;
12950 				case WM_RBUTTONUP:
12951 					mouse.type = cast(MouseEventType) 2;
12952 					mouse.button = MouseButton.right;
12953 					mouseEvent(false, wParam);
12954 				break;
12955 				case WM_MBUTTONDOWN:
12956 				case WM_MBUTTONDBLCLK:
12957 					mouse.type = cast(MouseEventType) 1;
12958 					mouse.button = MouseButton.middle;
12959 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12960 					mouseEvent(false, wParam);
12961 				break;
12962 				case WM_MBUTTONUP:
12963 					mouse.type = cast(MouseEventType) 2;
12964 					mouse.button = MouseButton.middle;
12965 					mouseEvent(false, wParam);
12966 				break;
12967 				case WM_XBUTTONDOWN:
12968 				case WM_XBUTTONDBLCLK:
12969 					mouse.type = cast(MouseEventType) 1;
12970 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12971 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12972 					mouseEvent(false, wParam);
12973 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12974 				case WM_XBUTTONUP:
12975 					mouse.type = cast(MouseEventType) 2;
12976 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12977 					mouseEvent(false, wParam);
12978 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12979 
12980 				default: return 1;
12981 			}
12982 			return 0;
12983 		}
12984 
12985 		HWND hwnd;
12986 		private int oldWidth;
12987 		private int oldHeight;
12988 		private bool inSizeMove;
12989 
12990 		/++
12991 			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.
12992 
12993 			History:
12994 				Added November 23, 2021
12995 
12996 				Not fully stable, may be moved out of the impl struct.
12997 
12998 				Default value changed to `true` on February 15, 2021
12999 		+/
13000 		bool doLiveResizing = true;
13001 
13002 		package int bmpWidth;
13003 		package int bmpHeight;
13004 
13005 		// the extern(Windows) wndproc should just forward to this
13006 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
13007 		try {
13008 			assert(hwnd is this.hwnd);
13009 
13010 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
13011 			switch(msg) {
13012 				case WM_MENUCHAR: // menu active but key not associated with a thing.
13013 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
13014 					// The main things we can do are select, execute, close, or ignore
13015 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
13016 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
13017 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
13018 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
13019 
13020 					// returns the value in the *high order word* of the return value
13021 					// hence the << 16
13022 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
13023 				case WM_SETCURSOR:
13024 					if(cast(HWND) wParam !is hwnd)
13025 						return 0; // further processing elsewhere
13026 
13027 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
13028 						SetCursor(this.curHidden > 0 ? null : currentCursor);
13029 						return 1;
13030 					} else {
13031 						return DefWindowProc(hwnd, msg, wParam, lParam);
13032 					}
13033 				//break;
13034 
13035 				case WM_CLOSE:
13036 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
13037 				break;
13038 				case WM_DESTROY:
13039 					if (this.visibilityChanged !is null && this._visible) this.visibilityChanged(false);
13040 
13041 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
13042 					SimpleWindow.nativeMapping.remove(hwnd);
13043 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
13044 
13045 					bool anyImportant = false;
13046 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
13047 						if(w.beingOpenKeepsAppOpen) {
13048 							anyImportant = true;
13049 							break;
13050 						}
13051 					if(!anyImportant) {
13052 						PostQuitMessage(0);
13053 					}
13054 				break;
13055 				case 0x02E0 /*WM_DPICHANGED*/:
13056 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
13057 
13058 					RECT* prcNewWindow = cast(RECT*)lParam;
13059 					// docs say this is the recommended position and we should honor it
13060 					SetWindowPos(hwnd,
13061 							null,
13062 							prcNewWindow.left,
13063 							prcNewWindow.top,
13064 							prcNewWindow.right - prcNewWindow.left,
13065 							prcNewWindow.bottom - prcNewWindow.top,
13066 							SWP_NOZORDER | SWP_NOACTIVATE);
13067 
13068 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
13069 					// im not sure it is completely correct
13070 					// but without it the tabs and such do look weird as things change.
13071 					if(SystemParametersInfoForDpi) {
13072 						LOGFONT lfText;
13073 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
13074 						HFONT hFontNew = CreateFontIndirect(&lfText);
13075 						if (hFontNew)
13076 						{
13077 							//DeleteObject(hFontOld);
13078 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
13079 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
13080 								return TRUE;
13081 							}
13082 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
13083 						}
13084 					}
13085 
13086 					if(this.onDpiChanged)
13087 						this.onDpiChanged();
13088 				break;
13089 				case WM_ENTERIDLE:
13090 					// when a menu is up, it stops normal event processing (modal message loop)
13091 					// but this at least gives us a chance to SOMETIMES catch up
13092 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
13093 					SimpleWindow.processAllCustomEvents;
13094 					SimpleWindow.processAllCustomEvents;
13095 					SleepEx(0, true);
13096 					break;
13097 				case WM_SIZE:
13098 					if(wParam == 1 /* SIZE_MINIMIZED */)
13099 						break;
13100 					_width = LOWORD(lParam);
13101 					_height = HIWORD(lParam);
13102 
13103 					// I want to avoid tearing in the windows (my code is inefficient
13104 					// so this is a hack around that) so while sizing, we don't trigger,
13105 					// but we do want to trigger on events like mazimize.
13106 					if(!inSizeMove || doLiveResizing)
13107 						goto size_changed;
13108 				break;
13109 				/+
13110 				case WM_SIZING:
13111 					writeln("size");
13112 				break;
13113 				+/
13114 				// I don't like the tearing I get when redrawing on WM_SIZE
13115 				// (I know there's other ways to fix that but I don't like that behavior anyway)
13116 				// so instead it is going to redraw only at the end of a size.
13117 				case 0x0231: /* WM_ENTERSIZEMOVE */
13118 					inSizeMove = true;
13119 				break;
13120 				case 0x0232: /* WM_EXITSIZEMOVE */
13121 					inSizeMove = false;
13122 
13123 					size_changed:
13124 
13125 					// nothing relevant changed, don't bother redrawing
13126 					if(oldWidth == _width && oldHeight == _height) {
13127 						if(msg == 0x0232)
13128 							goto finalize_resize;
13129 						break;
13130 					}
13131 
13132 					// note: OpenGL windows don't use a backing bmp, so no need to change them
13133 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
13134 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
13135 						// gotta get the double buffer bmp to match the window
13136 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
13137 
13138 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
13139 						if(resizability != Resizability.automaticallyScaleIfPossible)
13140 						if(_width > bmpWidth || _height > bmpHeight) {
13141 							auto hdc = GetDC(hwnd);
13142 							auto oldBuffer = buffer;
13143 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
13144 
13145 							auto hdcBmp = CreateCompatibleDC(hdc);
13146 							auto oldBmp = SelectObject(hdcBmp, buffer);
13147 
13148 							auto hdcOldBmp = CreateCompatibleDC(hdc);
13149 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
13150 
13151 							/+
13152 							RECT r;
13153 							r.left = 0;
13154 							r.top = 0;
13155 							r.right = width;
13156 							r.bottom = height;
13157 							auto c = Color.green;
13158 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
13159 							FillRect(hdcBmp, &r, brush);
13160 							DeleteObject(brush);
13161 							+/
13162 
13163 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
13164 
13165 							bmpWidth = _width;
13166 							bmpHeight = _height;
13167 
13168 							SelectObject(hdcOldBmp, oldOldBmp);
13169 							DeleteDC(hdcOldBmp);
13170 
13171 							SelectObject(hdcBmp, oldBmp);
13172 							DeleteDC(hdcBmp);
13173 
13174 							ReleaseDC(hwnd, hdc);
13175 
13176 							DeleteObject(oldBuffer);
13177 						}
13178 					}
13179 
13180 					updateOpenglViewportIfNeeded(_width, _height);
13181 
13182 					if(resizability != Resizability.automaticallyScaleIfPossible)
13183 					if(windowResized !is null)
13184 						windowResized(_width, _height);
13185 
13186 					/+
13187 					if(inSizeMove) {
13188 						// SimpleWindow.processAllCustomEvents();
13189 						// SimpleWindow.processAllCustomEvents();
13190 
13191 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13192 						//sdpyPrintDebugString("redraw b");
13193 					} else {
13194 					+/ {
13195 						finalize_resize:
13196 						// when it is all done, make sure everything is freshly drawn or there might be
13197 						// weird bugs left.
13198 						SimpleWindow.processAllCustomEvents();
13199 						SimpleWindow.processAllCustomEvents();
13200 
13201 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13202 						// sdpyPrintDebugString("redraw");
13203 					}
13204 
13205 					oldWidth = this._width;
13206 					oldHeight = this._height;
13207 				break;
13208 				case WM_ERASEBKGND:
13209 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
13210 					if (!this._visibleForTheFirstTimeCalled) {
13211 						this._visibleForTheFirstTimeCalled = true;
13212 						if (this.visibleForTheFirstTime !is null) {
13213 							this.visibleForTheFirstTime();
13214 						}
13215 					}
13216 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
13217 					version(without_opengl) {} else {
13218 						if (openglMode == OpenGlOptions.yes) return 1;
13219 					}
13220 					// call windows default handler, so it can paint standard controls
13221 					goto default;
13222 				case WM_CTLCOLORBTN:
13223 				case WM_CTLCOLORSTATIC:
13224 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
13225 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
13226 					GetSysColorBrush(COLOR_3DFACE);
13227 				//break;
13228 				case WM_SHOWWINDOW:
13229 					auto before = this._visible;
13230 					this._visible = (wParam != 0);
13231 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
13232 						this._visibleForTheFirstTimeCalled = true;
13233 						if (this.visibleForTheFirstTime !is null) {
13234 							this.visibleForTheFirstTime();
13235 						}
13236 					}
13237 					if (this.visibilityChanged !is null && this._visible != before) this.visibilityChanged(this._visible);
13238 					break;
13239 				case WM_PAINT: {
13240 					if (!this._visibleForTheFirstTimeCalled) {
13241 						this._visibleForTheFirstTimeCalled = true;
13242 						if (this.visibleForTheFirstTime !is null) {
13243 							this.visibleForTheFirstTime();
13244 						}
13245 					}
13246 
13247 					BITMAP bm;
13248 					PAINTSTRUCT ps;
13249 
13250 					HDC hdc = BeginPaint(hwnd, &ps);
13251 
13252 					if(openglMode == OpenGlOptions.no) {
13253 
13254 						HDC hdcMem = CreateCompatibleDC(hdc);
13255 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
13256 
13257 						GetObject(buffer, bm.sizeof, &bm);
13258 
13259 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
13260 						if(resizability == Resizability.automaticallyScaleIfPossible)
13261 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
13262 						else
13263 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
13264 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
13265 
13266 						SelectObject(hdcMem, hbmOld);
13267 						DeleteDC(hdcMem);
13268 						EndPaint(hwnd, &ps);
13269 					} else {
13270 						EndPaint(hwnd, &ps);
13271 						version(without_opengl) {} else
13272 							redrawOpenGlSceneSoon();
13273 					}
13274 				} break;
13275 				  default:
13276 					return DefWindowProc(hwnd, msg, wParam, lParam);
13277 			}
13278 			 return 0;
13279 
13280 		}
13281 		catch(Throwable t) {
13282 			sdpyPrintDebugString(t.toString);
13283 			return 0;
13284 		}
13285 		}
13286 	}
13287 
13288 	mixin template NativeImageImplementation() {
13289 		HBITMAP handle;
13290 		ubyte* rawData;
13291 
13292 	final:
13293 
13294 		Color getPixel(int x, int y) @system {
13295 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13296 			// remember, bmps are upside down
13297 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13298 
13299 			Color c;
13300 			if(enableAlpha)
13301 				c.a = rawData[offset + 3];
13302 			else
13303 				c.a = 255;
13304 			c.b = rawData[offset + 0];
13305 			c.g = rawData[offset + 1];
13306 			c.r = rawData[offset + 2];
13307 			c.unPremultiply();
13308 			return c;
13309 		}
13310 
13311 		void setPixel(int x, int y, Color c) @system {
13312 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13313 			// remember, bmps are upside down
13314 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13315 
13316 			if(enableAlpha)
13317 				c.premultiply();
13318 
13319 			rawData[offset + 0] = c.b;
13320 			rawData[offset + 1] = c.g;
13321 			rawData[offset + 2] = c.r;
13322 			if(enableAlpha)
13323 				rawData[offset + 3] = c.a;
13324 		}
13325 
13326 		void convertToRgbaBytes(ubyte[] where) @system {
13327 			assert(where.length == this.width * this.height * 4);
13328 
13329 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13330 			int idx = 0;
13331 			int offset = itemsPerLine * (height - 1);
13332 			// remember, bmps are upside down
13333 			for(int y = height - 1; y >= 0; y--) {
13334 				auto offsetStart = offset;
13335 				for(int x = 0; x < width; x++) {
13336 					where[idx + 0] = rawData[offset + 2]; // r
13337 					where[idx + 1] = rawData[offset + 1]; // g
13338 					where[idx + 2] = rawData[offset + 0]; // b
13339 					if(enableAlpha) {
13340 						where[idx + 3] = rawData[offset + 3]; // a
13341 						unPremultiplyRgba(where[idx .. idx + 4]);
13342 						offset++;
13343 					} else
13344 						where[idx + 3] = 255; // a
13345 					idx += 4;
13346 					offset += 3;
13347 				}
13348 
13349 				offset = offsetStart - itemsPerLine;
13350 			}
13351 		}
13352 
13353 		void setFromRgbaBytes(in ubyte[] what) @system {
13354 			assert(what.length == this.width * this.height * 4);
13355 
13356 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
13357 			int idx = 0;
13358 			int offset = itemsPerLine * (height - 1);
13359 			// remember, bmps are upside down
13360 			for(int y = height - 1; y >= 0; y--) {
13361 				auto offsetStart = offset;
13362 				for(int x = 0; x < width; x++) {
13363 					if(enableAlpha) {
13364 						auto a = what[idx + 3];
13365 
13366 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
13367 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
13368 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
13369 						rawData[offset + 3] = a; // a
13370 						//premultiplyBgra(rawData[offset .. offset + 4]);
13371 						offset++;
13372 					} else {
13373 						rawData[offset + 2] = what[idx + 0]; // r
13374 						rawData[offset + 1] = what[idx + 1]; // g
13375 						rawData[offset + 0] = what[idx + 2]; // b
13376 					}
13377 					idx += 4;
13378 					offset += 3;
13379 				}
13380 
13381 				offset = offsetStart - itemsPerLine;
13382 			}
13383 		}
13384 
13385 
13386 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
13387 			BITMAPINFO infoheader;
13388 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
13389 			infoheader.bmiHeader.biWidth = width;
13390 			infoheader.bmiHeader.biHeight = height;
13391 			infoheader.bmiHeader.biPlanes = 1;
13392 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
13393 			infoheader.bmiHeader.biCompression = BI_RGB;
13394 
13395 			handle = CreateDIBSection(
13396 				null,
13397 				&infoheader,
13398 				DIB_RGB_COLORS,
13399 				cast(void**) &rawData,
13400 				null,
13401 				0);
13402 			if(handle is null)
13403 				throw new WindowsApiException("create image failed", GetLastError());
13404 
13405 		}
13406 
13407 		void dispose() {
13408 			DeleteObject(handle);
13409 		}
13410 	}
13411 
13412 	enum KEY_ESCAPE = 27;
13413 }
13414 
13415 version(Emscripten) {
13416 	alias int delegate(void*) NativeEventHandler;
13417 	alias void* NativeWindowHandle;
13418 
13419 	mixin template NativeSimpleWindowImplementation() { }
13420 	mixin template NativeScreenPainterImplementation() { }
13421 	mixin template NativeImageImplementation() { }
13422 }
13423 
13424 version(X11) {
13425 	/// This is the default font used. You might change this before doing anything else with
13426 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
13427 	/// for cross-platform compatibility.
13428 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13429 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13430 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
13431 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
13432 
13433 	alias int delegate(XEvent) NativeEventHandler;
13434 	alias Window NativeWindowHandle;
13435 
13436 	enum KEY_ESCAPE = 9;
13437 
13438 	mixin template NativeScreenPainterImplementation() {
13439 		Display* display;
13440 		Drawable d;
13441 		Drawable destiny;
13442 
13443 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
13444 		GC gc;
13445 
13446 		__gshared bool fontAttempted;
13447 
13448 		__gshared XFontStruct* defaultfont;
13449 		__gshared XFontSet defaultfontset;
13450 
13451 		XFontStruct* font;
13452 		XFontSet fontset;
13453 
13454 		void create(PaintingHandle window) {
13455 			this.display = XDisplayConnection.get();
13456 
13457 			Drawable buffer = None;
13458 			if(auto sw = cast(SimpleWindow) this.window) {
13459 				buffer = sw.impl.buffer;
13460 				this.destiny = cast(Drawable) window;
13461 			} else {
13462 				buffer = cast(Drawable) window;
13463 				this.destiny = None;
13464 			}
13465 
13466 			this.d = cast(Drawable) buffer;
13467 
13468 			auto dgc = DefaultGC(display, DefaultScreen(display));
13469 
13470 			this.gc = XCreateGC(display, d, 0, null);
13471 
13472 			XCopyGC(display, dgc, 0xffffffff, this.gc);
13473 
13474 			ensureDefaultFontLoaded();
13475 
13476 			font = defaultfont;
13477 			fontset = defaultfontset;
13478 
13479 			if(font) {
13480 				XSetFont(display, gc, font.fid);
13481 			}
13482 		}
13483 
13484 		static void ensureDefaultFontLoaded() {
13485 			if(!fontAttempted) {
13486 				auto display = XDisplayConnection.get;
13487 				auto font = XLoadQueryFont(display, xfontstr.ptr);
13488 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
13489 				if(font is null) {
13490 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
13491 					font = XLoadQueryFont(display, xfontstr.ptr);
13492 				}
13493 
13494 				char** lol;
13495 				int lol2;
13496 				char* lol3;
13497 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
13498 
13499 				fontAttempted = true;
13500 
13501 				defaultfont = font;
13502 				defaultfontset = fontset;
13503 			}
13504 		}
13505 
13506 		arsd.color.Rectangle _clipRectangle;
13507 		void setClipRectangle(int x, int y, int width, int height) {
13508 			auto old = _clipRectangle;
13509 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
13510 			if(old == _clipRectangle)
13511 				return;
13512 
13513 			if(width == 0 || height == 0) {
13514 				XSetClipMask(display, gc, None);
13515 
13516 				if(xrenderPicturePainter) {
13517 
13518 					XRectangle[1] rects;
13519 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
13520 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13521 				}
13522 
13523 				version(with_xft) {
13524 					if(xftDraw is null)
13525 						return;
13526 					XftDrawSetClip(xftDraw, null);
13527 				}
13528 			} else {
13529 				XRectangle[1] rects;
13530 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
13531 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
13532 
13533 				if(xrenderPicturePainter)
13534 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13535 
13536 				version(with_xft) {
13537 					if(xftDraw is null)
13538 						return;
13539 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
13540 				}
13541 			}
13542 		}
13543 
13544 		version(with_xft) {
13545 			XftFont* xftFont;
13546 			XftDraw* xftDraw;
13547 
13548 			XftColor xftColor;
13549 
13550 			void updateXftColor() {
13551 				if(xftFont is null)
13552 					return;
13553 
13554 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
13555 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
13556 
13557 				XftColorAllocValue(
13558 					display,
13559 					DefaultVisual(display, DefaultScreen(display)),
13560 					DefaultColormap(display, 0),
13561 					&colorIn,
13562 					&xftColor
13563 				);
13564 			}
13565 		}
13566 
13567 		void enableXftDraw() {
13568 			if(xftDraw is null) {
13569 				xftDraw = XftDrawCreate(
13570 					display,
13571 					d,
13572 					DefaultVisual(display, DefaultScreen(display)),
13573 					DefaultColormap(display, 0)
13574 				);
13575 
13576 				updateXftColor();
13577 			}
13578 		}
13579 
13580 		private OperatingSystemFont _activeFont;
13581 		void setFont(OperatingSystemFont font) {
13582 			_activeFont = font;
13583 			version(with_xft) {
13584 				if(font && font.isXft && font.xftFont)
13585 					this.xftFont = font.xftFont;
13586 				else
13587 					this.xftFont = null;
13588 
13589 				if(this.xftFont) {
13590 					enableXftDraw();
13591 					return;
13592 				}
13593 			}
13594 
13595 			if(font && font.font) {
13596 				this.font = font.font;
13597 				this.fontset = font.fontset;
13598 				XSetFont(display, gc, font.font.fid);
13599 			} else {
13600 				this.font = defaultfont;
13601 				this.fontset = defaultfontset;
13602 			}
13603 
13604 		}
13605 
13606 		private Picture xrenderPicturePainter;
13607 
13608 		bool manualInvalidations;
13609 		void invalidateRect(Rectangle invalidRect) {
13610 			// FIXME if manualInvalidations
13611 		}
13612 
13613 		void dispose() {
13614 			this.rasterOp = RasterOp.normal;
13615 
13616 			if(xrenderPicturePainter) {
13617 				XRenderFreePicture(display, xrenderPicturePainter);
13618 				xrenderPicturePainter = None;
13619 			}
13620 
13621 			// FIXME: this.window.width/height is probably wrong
13622 
13623 			// src x,y     then dest x, y
13624 			if(destiny != None) {
13625 				// FIXME: if manual invalidations we can actually only copy some of the area.
13626 				// if(manualInvalidations)
13627 				XSetClipMask(display, gc, None);
13628 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
13629 			}
13630 
13631 			XFreeGC(display, gc);
13632 
13633 			version(with_xft)
13634 			if(xftDraw) {
13635 				XftDrawDestroy(xftDraw);
13636 				xftDraw = null;
13637 			}
13638 
13639 			/+
13640 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
13641 			if(font && font !is defaultfont) {
13642 				XFreeFont(display, font);
13643 				font = null;
13644 			}
13645 			if(fontset && fontset !is defaultfontset) {
13646 				XFreeFontSet(display, fontset);
13647 				fontset = null;
13648 			}
13649 			+/
13650 			XFlush(display);
13651 
13652 			if(window.paintingFinishedDg !is null)
13653 				window.paintingFinishedDg()();
13654 		}
13655 
13656 		bool backgroundIsNotTransparent = true;
13657 		bool foregroundIsNotTransparent = true;
13658 
13659 		bool _penInitialized = false;
13660 		Pen _activePen;
13661 
13662 		Color _outlineColor;
13663 		Color _fillColor;
13664 
13665 		@property void pen(Pen p) {
13666 			if(_penInitialized && p == _activePen) {
13667 				return;
13668 			}
13669 			_penInitialized = true;
13670 			_activePen = p;
13671 			_outlineColor = p.color;
13672 
13673 			int style;
13674 
13675 			byte dashLength;
13676 
13677 			final switch(p.style) {
13678 				case Pen.Style.Solid:
13679 					style = 0 /*LineSolid*/;
13680 				break;
13681 				case Pen.Style.Dashed:
13682 					style = 1 /*LineOnOffDash*/;
13683 					dashLength = 4;
13684 				break;
13685 				case Pen.Style.Dotted:
13686 					style = 1 /*LineOnOffDash*/;
13687 					dashLength = 1;
13688 				break;
13689 			}
13690 
13691 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
13692 			if(dashLength)
13693 				XSetDashes(display, gc, 0, &dashLength, 1);
13694 
13695 			if(p.color.a == 0) {
13696 				foregroundIsNotTransparent = false;
13697 				return;
13698 			}
13699 
13700 			foregroundIsNotTransparent = true;
13701 
13702 			XSetForeground(display, gc, colorToX(p.color, display));
13703 
13704 			version(with_xft)
13705 				updateXftColor();
13706 		}
13707 
13708 		RasterOp _currentRasterOp;
13709 		bool _currentRasterOpInitialized = false;
13710 		@property void rasterOp(RasterOp op) {
13711 			if(_currentRasterOpInitialized && _currentRasterOp == op)
13712 				return;
13713 			_currentRasterOp = op;
13714 			_currentRasterOpInitialized = true;
13715 			int mode;
13716 			final switch(op) {
13717 				case RasterOp.normal:
13718 					mode = GXcopy;
13719 				break;
13720 				case RasterOp.xor:
13721 					mode = GXxor;
13722 				break;
13723 			}
13724 			XSetFunction(display, gc, mode);
13725 		}
13726 
13727 
13728 		bool _fillColorInitialized = false;
13729 
13730 		@property void fillColor(Color c) {
13731 			if(_fillColorInitialized && _fillColor == c)
13732 				return; // already good, no need to waste time calling it
13733 			_fillColor = c;
13734 			_fillColorInitialized = true;
13735 			if(c.a == 0) {
13736 				backgroundIsNotTransparent = false;
13737 				return;
13738 			}
13739 
13740 			backgroundIsNotTransparent = true;
13741 
13742 			XSetBackground(display, gc, colorToX(c, display));
13743 
13744 		}
13745 
13746 		void swapColors() {
13747 			auto tmp = _fillColor;
13748 			fillColor = _outlineColor;
13749 			auto newPen = _activePen;
13750 			newPen.color = tmp;
13751 			pen(newPen);
13752 		}
13753 
13754 		uint colorToX(Color c, Display* display) {
13755 			auto visual = DefaultVisual(display, DefaultScreen(display));
13756 			import core.bitop;
13757 			uint color = 0;
13758 			{
13759 			auto startBit = bsf(visual.red_mask);
13760 			auto lastBit = bsr(visual.red_mask);
13761 			auto r = cast(uint) c.r;
13762 			r >>= 7 - (lastBit - startBit);
13763 			r <<= startBit;
13764 			color |= r;
13765 			}
13766 			{
13767 			auto startBit = bsf(visual.green_mask);
13768 			auto lastBit = bsr(visual.green_mask);
13769 			auto g = cast(uint) c.g;
13770 			g >>= 7 - (lastBit - startBit);
13771 			g <<= startBit;
13772 			color |= g;
13773 			}
13774 			{
13775 			auto startBit = bsf(visual.blue_mask);
13776 			auto lastBit = bsr(visual.blue_mask);
13777 			auto b = cast(uint) c.b;
13778 			b >>= 7 - (lastBit - startBit);
13779 			b <<= startBit;
13780 			color |= b;
13781 			}
13782 
13783 
13784 
13785 			return color;
13786 		}
13787 
13788 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
13789 			// source x, source y
13790 			if(ix >= i.width) return;
13791 			if(iy >= i.height) return;
13792 			if(ix + w > i.width) w = i.width - ix;
13793 			if(iy + h > i.height) h = i.height - iy;
13794 			if(i.usingXshm)
13795 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
13796 			else
13797 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
13798 		}
13799 
13800 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
13801 			if(s.enableAlpha) {
13802 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
13803 				if(this.xrenderPicturePainter == None) {
13804 					XRenderPictureAttributes attrs;
13805 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
13806 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
13807 
13808 					// need to initialize the clip
13809 					XRectangle[1] rects;
13810 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
13811 
13812 					if(_clipRectangle != Rectangle.init)
13813 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13814 				}
13815 
13816 				XRenderComposite(
13817 					display,
13818 					3, // PicOpOver
13819 					s.xrenderPicture,
13820 					None,
13821 					this.xrenderPicturePainter,
13822 					ix,
13823 					iy,
13824 					0,
13825 					0,
13826 					x,
13827 					y,
13828 					w ? w : s.width,
13829 					h ? h : s.height
13830 				);
13831 			} else {
13832 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
13833 			}
13834 		}
13835 
13836 		int fontHeight() {
13837 			version(with_xft)
13838 				if(xftFont !is null)
13839 					return xftFont.height;
13840 			if(font)
13841 				return font.max_bounds.ascent + font.max_bounds.descent;
13842 			return 12; // pretty common default...
13843 		}
13844 
13845 		int textWidth(in char[] line) {
13846 			version(with_xft)
13847 			if(xftFont) {
13848 				if(line.length == 0)
13849 					return 0;
13850 				XGlyphInfo extents;
13851 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
13852 				return extents.width;
13853 			}
13854 
13855 			if(fontset) {
13856 				if(line.length == 0)
13857 					return 0;
13858 				XRectangle rect;
13859 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13860 
13861 				return rect.width;
13862 			}
13863 
13864 			if(font)
13865 				// FIXME: unicode
13866 				return XTextWidth( font, line.ptr, cast(int) line.length);
13867 			else
13868 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13869 		}
13870 
13871 		Size textSize(in char[] text) {
13872 			auto maxWidth = 0;
13873 			auto lineHeight = fontHeight;
13874 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13875 			foreach(line; text.split('\n')) {
13876 				int textWidth = this.textWidth(line);
13877 				if(textWidth > maxWidth)
13878 					maxWidth = textWidth;
13879 				h += lineHeight + 4;
13880 			}
13881 			return Size(maxWidth, h);
13882 		}
13883 
13884 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13885 			const(char)[] text;
13886 			version(with_xft)
13887 			if(xftFont) {
13888 				text = originalText;
13889 				goto loaded;
13890 			}
13891 
13892 			if(fontset)
13893 				text = originalText;
13894 			else {
13895 				text.reserve(originalText.length);
13896 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13897 				// then strip the rest so there isn't garbage
13898 				foreach(dchar ch; originalText)
13899 					if(ch < 256)
13900 						text ~= cast(ubyte) ch;
13901 					else
13902 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13903 			}
13904 			loaded:
13905 			if(text.length == 0)
13906 				return;
13907 
13908 			// FIXME: should we clip it to the bounding box?
13909 			int textHeight = fontHeight;
13910 
13911 			auto lines = text.split('\n');
13912 
13913 			const lineHeight = textHeight;
13914 			textHeight *= lines.length;
13915 
13916 			int cy = y;
13917 
13918 			if(alignment & TextAlignment.VerticalBottom) {
13919 				if(y2 <= 0)
13920 					return;
13921 				auto h = y2 - y;
13922 				if(h > textHeight) {
13923 					cy += h - textHeight;
13924 					cy -= lineHeight / 2;
13925 				}
13926 			} else if(alignment & TextAlignment.VerticalCenter) {
13927 				if(y2 <= 0)
13928 					return;
13929 				auto h = y2 - y;
13930 				if(textHeight < h) {
13931 					cy += (h - textHeight) / 2;
13932 					//cy -= lineHeight / 4;
13933 				}
13934 			}
13935 
13936 			foreach(line; text.split('\n')) {
13937 				int textWidth = this.textWidth(line);
13938 
13939 				int px = x, py = cy;
13940 
13941 				if(alignment & TextAlignment.Center) {
13942 					if(x2 <= 0)
13943 						return;
13944 					auto w = x2 - x;
13945 					if(w > textWidth)
13946 						px += (w - textWidth) / 2;
13947 				} else if(alignment & TextAlignment.Right) {
13948 					if(x2 <= 0)
13949 						return;
13950 					auto pos = x2 - textWidth;
13951 					if(pos > x)
13952 						px = pos;
13953 				}
13954 
13955 				version(with_xft)
13956 				if(xftFont) {
13957 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13958 
13959 					goto carry_on;
13960 				}
13961 
13962 				if(fontset)
13963 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13964 				else
13965 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13966 				carry_on:
13967 				cy += lineHeight + 4;
13968 			}
13969 		}
13970 
13971 		void drawPixel(int x, int y) {
13972 			XDrawPoint(display, d, gc, x, y);
13973 		}
13974 
13975 		// The basic shapes, outlined
13976 
13977 		void drawLine(int x1, int y1, int x2, int y2) {
13978 			if(foregroundIsNotTransparent)
13979 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13980 		}
13981 
13982 		void drawRectangle(int x, int y, int width, int height) {
13983 			if(backgroundIsNotTransparent) {
13984 				swapColors();
13985 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13986 				swapColors();
13987 			}
13988 			// 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
13989 			if(foregroundIsNotTransparent)
13990 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13991 		}
13992 
13993 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
13994 			int[4] radii = borderRadius;
13995 			auto r = Rectangle(upperLeft, lowerRight);
13996 
13997 			if(backgroundIsNotTransparent) {
13998 				swapColors();
13999 				// FIXME these overlap and thus draw the pixels multiple times
14000 				XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius);
14001 				XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
14002 				swapColors();
14003 			}
14004 
14005 			drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top);
14006 			drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1);
14007 			drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2);
14008 			drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2);
14009 
14010 			//drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
14011 
14012 			drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64);
14013 			drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64);
14014 			drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64);
14015 			drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64);
14016 		}
14017 
14018 
14019 		/// Arguments are the points of the bounding rectangle
14020 		void drawEllipse(int x1, int y1, int x2, int y2) {
14021 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
14022 		}
14023 
14024 		// NOTE: start and finish are in units of degrees * 64
14025 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
14026 			if(backgroundIsNotTransparent) {
14027 				swapColors();
14028 				XFillArc(display, d, gc, x1, y1, width, height, start, length);
14029 				swapColors();
14030 			}
14031 			if(foregroundIsNotTransparent) {
14032 				XDrawArc(display, d, gc, x1, y1, width, height, start, length);
14033 
14034 				// Windows draws the straight lines on the edges too so FIXME sort of
14035 			}
14036 		}
14037 
14038 		void drawPolygon(Point[] vertexes) {
14039 			XPoint[16] pointsBuffer;
14040 			XPoint[] points;
14041 			if(vertexes.length <= pointsBuffer.length)
14042 				points = pointsBuffer[0 .. vertexes.length];
14043 			else
14044 				points.length = vertexes.length;
14045 
14046 			foreach(i, p; vertexes) {
14047 				points[i].x = cast(short) p.x;
14048 				points[i].y = cast(short) p.y;
14049 			}
14050 
14051 			if(backgroundIsNotTransparent) {
14052 				swapColors();
14053 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
14054 				swapColors();
14055 			}
14056 			if(foregroundIsNotTransparent) {
14057 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
14058 			}
14059 		}
14060 	}
14061 
14062 	/* XRender { */
14063 
14064 	struct XRenderColor {
14065 		ushort red;
14066 		ushort green;
14067 		ushort blue;
14068 		ushort alpha;
14069 	}
14070 
14071 	alias Picture = XID;
14072 	alias PictFormat = XID;
14073 
14074 	struct XGlyphInfo {
14075 		ushort width;
14076 		ushort height;
14077 		short x;
14078 		short y;
14079 		short xOff;
14080 		short yOff;
14081 	}
14082 
14083 struct XRenderDirectFormat {
14084     short   red;
14085     short   redMask;
14086     short   green;
14087     short   greenMask;
14088     short   blue;
14089     short   blueMask;
14090     short   alpha;
14091     short   alphaMask;
14092 }
14093 
14094 struct XRenderPictFormat {
14095     PictFormat		id;
14096     int			type;
14097     int			depth;
14098     XRenderDirectFormat	direct;
14099     Colormap		colormap;
14100 }
14101 
14102 enum PictFormatID	=   (1 << 0);
14103 enum PictFormatType	=   (1 << 1);
14104 enum PictFormatDepth	=   (1 << 2);
14105 enum PictFormatRed	=   (1 << 3);
14106 enum PictFormatRedMask  =(1 << 4);
14107 enum PictFormatGreen	=   (1 << 5);
14108 enum PictFormatGreenMask=(1 << 6);
14109 enum PictFormatBlue	=   (1 << 7);
14110 enum PictFormatBlueMask =(1 << 8);
14111 enum PictFormatAlpha	=   (1 << 9);
14112 enum PictFormatAlphaMask=(1 << 10);
14113 enum PictFormatColormap =(1 << 11);
14114 
14115 struct XRenderPictureAttributes {
14116 	int 		repeat;
14117 	Picture		alpha_map;
14118 	int			alpha_x_origin;
14119 	int			alpha_y_origin;
14120 	int			clip_x_origin;
14121 	int			clip_y_origin;
14122 	Pixmap		clip_mask;
14123 	Bool		graphics_exposures;
14124 	int			subwindow_mode;
14125 	int			poly_edge;
14126 	int			poly_mode;
14127 	Atom		dither;
14128 	Bool		component_alpha;
14129 }
14130 
14131 alias int XFixed;
14132 
14133 struct XPointFixed {
14134     XFixed  x, y;
14135 }
14136 
14137 struct XCircle {
14138     XFixed x;
14139     XFixed y;
14140     XFixed radius;
14141 }
14142 
14143 struct XTransform {
14144     XFixed[3][3]  matrix;
14145 }
14146 
14147 struct XFilters {
14148     int	    nfilter;
14149     char    **filter;
14150     int	    nalias;
14151     short   *alias_;
14152 }
14153 
14154 struct XIndexValue {
14155     c_ulong    pixel;
14156     ushort   red, green, blue, alpha;
14157 }
14158 
14159 struct XAnimCursor {
14160     Cursor	    cursor;
14161     c_ulong   delay;
14162 }
14163 
14164 struct XLinearGradient {
14165     XPointFixed p1;
14166     XPointFixed p2;
14167 }
14168 
14169 struct XRadialGradient {
14170     XCircle inner;
14171     XCircle outer;
14172 }
14173 
14174 struct XConicalGradient {
14175     XPointFixed center;
14176     XFixed angle; /* in degrees */
14177 }
14178 
14179 enum PictStandardARGB32  = 0;
14180 enum PictStandardRGB24   = 1;
14181 enum PictStandardA8	 =  2;
14182 enum PictStandardA4	 =  3;
14183 enum PictStandardA1	 =  4;
14184 enum PictStandardNUM	 =  5;
14185 
14186 interface XRender {
14187 extern(C) @nogc:
14188 
14189 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
14190 
14191 	Status XRenderQueryVersion (Display *dpy,
14192 			int     *major_versionp,
14193 			int     *minor_versionp);
14194 
14195 	Status XRenderQueryFormats (Display *dpy);
14196 
14197 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
14198 
14199 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
14200 
14201 	XRenderPictFormat *
14202 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
14203 
14204 	XRenderPictFormat *
14205 		XRenderFindFormat (Display			*dpy,
14206 				c_ulong		mask,
14207 				const XRenderPictFormat	*templ,
14208 				int				count);
14209 	XRenderPictFormat *
14210 		XRenderFindStandardFormat (Display		*dpy,
14211 				int			format);
14212 
14213 	XIndexValue *
14214 		XRenderQueryPictIndexValues(Display			*dpy,
14215 				const XRenderPictFormat	*format,
14216 				int				*num);
14217 
14218 	Picture XRenderCreatePicture(
14219 		Display *dpy,
14220 		Drawable drawable,
14221 		const XRenderPictFormat *format,
14222 		c_ulong valuemask,
14223 		const XRenderPictureAttributes *attributes);
14224 
14225 	void XRenderChangePicture (Display				*dpy,
14226 				Picture				picture,
14227 				c_ulong			valuemask,
14228 				const XRenderPictureAttributes  *attributes);
14229 
14230 	void
14231 		XRenderSetPictureClipRectangles (Display	    *dpy,
14232 				Picture	    picture,
14233 				int		    xOrigin,
14234 				int		    yOrigin,
14235 				const XRectangle *rects,
14236 				int		    n);
14237 
14238 	void
14239 		XRenderSetPictureClipRegion (Display	    *dpy,
14240 				Picture	    picture,
14241 				Region	    r);
14242 
14243 	void
14244 		XRenderSetPictureTransform (Display	    *dpy,
14245 				Picture	    picture,
14246 				XTransform	    *transform);
14247 
14248 	void
14249 		XRenderFreePicture (Display                   *dpy,
14250 				Picture                   picture);
14251 
14252 	void
14253 		XRenderComposite (Display   *dpy,
14254 				int	    op,
14255 				Picture   src,
14256 				Picture   mask,
14257 				Picture   dst,
14258 				int	    src_x,
14259 				int	    src_y,
14260 				int	    mask_x,
14261 				int	    mask_y,
14262 				int	    dst_x,
14263 				int	    dst_y,
14264 				uint	width,
14265 				uint	height);
14266 
14267 
14268 	Picture XRenderCreateSolidFill (Display *dpy,
14269 			const XRenderColor *color);
14270 
14271 	Picture XRenderCreateLinearGradient (Display *dpy,
14272 			const XLinearGradient *gradient,
14273 			const XFixed *stops,
14274 			const XRenderColor *colors,
14275 			int nstops);
14276 
14277 	Picture XRenderCreateRadialGradient (Display *dpy,
14278 			const XRadialGradient *gradient,
14279 			const XFixed *stops,
14280 			const XRenderColor *colors,
14281 			int nstops);
14282 
14283 	Picture XRenderCreateConicalGradient (Display *dpy,
14284 			const XConicalGradient *gradient,
14285 			const XFixed *stops,
14286 			const XRenderColor *colors,
14287 			int nstops);
14288 
14289 
14290 
14291 	Cursor
14292 		XRenderCreateCursor (Display	    *dpy,
14293 				Picture	    source,
14294 				uint   x,
14295 				uint   y);
14296 
14297 	XFilters *
14298 		XRenderQueryFilters (Display *dpy, Drawable drawable);
14299 
14300 	void
14301 		XRenderSetPictureFilter (Display    *dpy,
14302 				Picture    picture,
14303 				const char *filter,
14304 				XFixed	    *params,
14305 				int	    nparams);
14306 
14307 	Cursor
14308 		XRenderCreateAnimCursor (Display	*dpy,
14309 				int		ncursor,
14310 				XAnimCursor	*cursors);
14311 }
14312 
14313 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
14314 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
14315 
14316 	/* XRender } */
14317 
14318 	/* Xrandr { */
14319 
14320 struct XRRMonitorInfo {
14321     Atom name;
14322     Bool primary;
14323     Bool automatic;
14324     int noutput;
14325     int x;
14326     int y;
14327     int width;
14328     int height;
14329     int mwidth;
14330     int mheight;
14331     /*RROutput*/ void *outputs;
14332 }
14333 
14334 struct XRRScreenChangeNotifyEvent {
14335     int type;                   /* event base */
14336     c_ulong serial;       /* # of last request processed by server */
14337     Bool send_event;            /* true if this came from a SendEvent request */
14338     Display *display;           /* Display the event was read from */
14339     Window window;              /* window which selected for this event */
14340     Window root;                /* Root window for changed screen */
14341     Time timestamp;             /* when the screen change occurred */
14342     Time config_timestamp;      /* when the last configuration change */
14343     ushort/*SizeID*/ size_index;
14344     ushort/*SubpixelOrder*/ subpixel_order;
14345     ushort/*Rotation*/ rotation;
14346     int width;
14347     int height;
14348     int mwidth;
14349     int mheight;
14350 }
14351 
14352 enum RRScreenChangeNotify = 0;
14353 
14354 enum RRScreenChangeNotifyMask = 1;
14355 
14356 __gshared int xrrEventBase = -1;
14357 
14358 
14359 interface XRandr {
14360 extern(C) @nogc:
14361 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
14362 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
14363 
14364 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
14365 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
14366 
14367 	void XRRSelectInput(Display *dpy, Window window, int mask);
14368 }
14369 
14370 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
14371 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
14372 	/* Xrandr } */
14373 
14374 	/* Xft { */
14375 
14376 	// actually freetype
14377 	alias void FT_Face;
14378 
14379 	// actually fontconfig
14380 	private alias FcBool = int;
14381 	alias void FcCharSet;
14382 	alias void FcPattern;
14383 	alias void FcResult;
14384 	enum FcEndian { FcEndianBig, FcEndianLittle }
14385 	struct FcFontSet {
14386 		int nfont;
14387 		int sfont;
14388 		FcPattern** fonts;
14389 	}
14390 
14391 	// actually XRegion
14392 	struct BOX {
14393 		short x1, x2, y1, y2;
14394 	}
14395 	struct _XRegion {
14396 		c_long size;
14397 		c_long numRects;
14398 		BOX* rects;
14399 		BOX extents;
14400 	}
14401 
14402 	alias Region = _XRegion*;
14403 
14404 	// ok actually Xft
14405 
14406 	struct XftFontInfo;
14407 
14408 	struct XftFont {
14409 		int         ascent;
14410 		int         descent;
14411 		int         height;
14412 		int         max_advance_width;
14413 		FcCharSet*  charset;
14414 		FcPattern*  pattern;
14415 	}
14416 
14417 	struct XftDraw;
14418 
14419 	struct XftColor {
14420 		c_ulong pixel;
14421 		XRenderColor color;
14422 	}
14423 
14424 	struct XftCharSpec {
14425 		dchar           ucs4;
14426 		short           x;
14427 		short           y;
14428 	}
14429 
14430 	struct XftCharFontSpec {
14431 		XftFont         *font;
14432 		dchar           ucs4;
14433 		short           x;
14434 		short           y;
14435 	}
14436 
14437 	struct XftGlyphSpec {
14438 		uint            glyph;
14439 		short           x;
14440 		short           y;
14441 	}
14442 
14443 	struct XftGlyphFontSpec {
14444 		XftFont         *font;
14445 		uint            glyph;
14446 		short           x;
14447 		short           y;
14448 	}
14449 
14450 	interface Xft {
14451 	extern(C) @nogc pure:
14452 
14453 	Bool XftColorAllocName (Display  *dpy,
14454 				const Visual   *visual,
14455 				Colormap cmap,
14456 				const char     *name,
14457 				XftColor *result);
14458 
14459 	Bool XftColorAllocValue (Display         *dpy,
14460 				Visual          *visual,
14461 				Colormap        cmap,
14462 				const XRenderColor    *color,
14463 				XftColor        *result);
14464 
14465 	void XftColorFree (Display   *dpy,
14466 				Visual    *visual,
14467 				Colormap  cmap,
14468 				XftColor  *color);
14469 
14470 	Bool XftDefaultHasRender (Display *dpy);
14471 
14472 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
14473 
14474 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
14475 
14476 	XftDraw * XftDrawCreate (Display   *dpy,
14477 		       Drawable  drawable,
14478 		       Visual    *visual,
14479 		       Colormap  colormap);
14480 
14481 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
14482 			     Pixmap   bitmap);
14483 
14484 	XftDraw * XftDrawCreateAlpha (Display *dpy,
14485 			    Pixmap  pixmap,
14486 			    int     depth);
14487 
14488 	void XftDrawChange (XftDraw  *draw,
14489 		       Drawable drawable);
14490 
14491 	Display * XftDrawDisplay (XftDraw *draw);
14492 
14493 	Drawable XftDrawDrawable (XftDraw *draw);
14494 
14495 	Colormap XftDrawColormap (XftDraw *draw);
14496 
14497 	Visual * XftDrawVisual (XftDraw *draw);
14498 
14499 	void XftDrawDestroy (XftDraw *draw);
14500 
14501 	Picture XftDrawPicture (XftDraw *draw);
14502 
14503 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
14504 
14505 	void XftDrawGlyphs (XftDraw          *draw,
14506 				const XftColor *color,
14507 				XftFont          *pub,
14508 				int              x,
14509 				int              y,
14510 				const uint  *glyphs,
14511 				int              nglyphs);
14512 
14513 	void XftDrawString8 (XftDraw             *draw,
14514 				const XftColor    *color,
14515 				XftFont             *pub,
14516 				int                 x,
14517 				int                 y,
14518 				const char     *string,
14519 				int                 len);
14520 
14521 	void XftDrawString16 (XftDraw            *draw,
14522 				const XftColor   *color,
14523 				XftFont            *pub,
14524 				int                x,
14525 				int                y,
14526 				const wchar   *string,
14527 				int                len);
14528 
14529 	void XftDrawString32 (XftDraw            *draw,
14530 				const XftColor   *color,
14531 				XftFont            *pub,
14532 				int                x,
14533 				int                y,
14534 				const dchar   *string,
14535 				int                len);
14536 
14537 	void XftDrawStringUtf8 (XftDraw          *draw,
14538 				const XftColor *color,
14539 				XftFont          *pub,
14540 				int              x,
14541 				int              y,
14542 				const char  *string,
14543 				int              len);
14544 	void XftDrawStringUtf16 (XftDraw             *draw,
14545 				const XftColor    *color,
14546 				XftFont             *pub,
14547 				int                 x,
14548 				int                 y,
14549 				const char     *string,
14550 				FcEndian            endian,
14551 				int                 len);
14552 
14553 	void XftDrawCharSpec (XftDraw                *draw,
14554 				const XftColor       *color,
14555 				XftFont                *pub,
14556 				const XftCharSpec    *chars,
14557 				int                    len);
14558 
14559 	void XftDrawCharFontSpec (XftDraw                    *draw,
14560 				const XftColor           *color,
14561 				const XftCharFontSpec    *chars,
14562 				int                        len);
14563 
14564 	void XftDrawGlyphSpec (XftDraw               *draw,
14565 				const XftColor      *color,
14566 				XftFont               *pub,
14567 				const XftGlyphSpec  *glyphs,
14568 				int                   len);
14569 
14570 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
14571 				const XftColor          *color,
14572 				const XftGlyphFontSpec  *glyphs,
14573 				int                       len);
14574 
14575 	void XftDrawRect (XftDraw            *draw,
14576 				const XftColor   *color,
14577 				int                x,
14578 				int                y,
14579 				uint       width,
14580 				uint       height);
14581 
14582 	Bool XftDrawSetClip (XftDraw     *draw,
14583 				Region      r);
14584 
14585 
14586 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
14587 				int                   xOrigin,
14588 				int                   yOrigin,
14589 				const XRectangle    *rects,
14590 				int                   n);
14591 
14592 	void XftDrawSetSubwindowMode (XftDraw    *draw,
14593 				int        mode);
14594 
14595 	void XftGlyphExtents (Display            *dpy,
14596 				XftFont            *pub,
14597 				const uint    *glyphs,
14598 				int                nglyphs,
14599 				XGlyphInfo         *extents);
14600 
14601 	void XftTextExtents8 (Display            *dpy,
14602 				XftFont            *pub,
14603 				const char    *string,
14604 				int                len,
14605 				XGlyphInfo         *extents);
14606 
14607 	void XftTextExtents16 (Display           *dpy,
14608 				XftFont           *pub,
14609 				const wchar  *string,
14610 				int               len,
14611 				XGlyphInfo        *extents);
14612 
14613 	void XftTextExtents32 (Display           *dpy,
14614 				XftFont           *pub,
14615 				const dchar  *string,
14616 				int               len,
14617 				XGlyphInfo        *extents);
14618 
14619 	void XftTextExtentsUtf8 (Display         *dpy,
14620 				XftFont         *pub,
14621 				const char *string,
14622 				int             len,
14623 				XGlyphInfo      *extents);
14624 
14625 	void XftTextExtentsUtf16 (Display            *dpy,
14626 				XftFont            *pub,
14627 				const char    *string,
14628 				FcEndian           endian,
14629 				int                len,
14630 				XGlyphInfo         *extents);
14631 
14632 	FcPattern * XftFontMatch (Display           *dpy,
14633 				int               screen,
14634 				const FcPattern *pattern,
14635 				FcResult          *result);
14636 
14637 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
14638 
14639 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
14640 
14641 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
14642 
14643 	FT_Face XftLockFace (XftFont *pub);
14644 
14645 	void XftUnlockFace (XftFont *pub);
14646 
14647 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
14648 
14649 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
14650 
14651 	dchar XftFontInfoHash (const XftFontInfo *fi);
14652 
14653 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
14654 
14655 	XftFont * XftFontOpenInfo (Display        *dpy,
14656 				FcPattern      *pattern,
14657 				XftFontInfo    *fi);
14658 
14659 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
14660 
14661 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
14662 
14663 	void XftFontClose (Display *dpy, XftFont *pub);
14664 
14665 	FcBool XftInitFtLibrary();
14666 	void XftFontLoadGlyphs (Display          *dpy,
14667 				XftFont          *pub,
14668 				FcBool           need_bitmaps,
14669 				const uint  *glyphs,
14670 				int              nglyph);
14671 
14672 	void XftFontUnloadGlyphs (Display            *dpy,
14673 				XftFont            *pub,
14674 				const uint    *glyphs,
14675 				int                nglyph);
14676 
14677 	FcBool XftFontCheckGlyph (Display  *dpy,
14678 				XftFont  *pub,
14679 				FcBool   need_bitmaps,
14680 				uint  glyph,
14681 				uint  *missing,
14682 				int      *nmissing);
14683 
14684 	FcBool XftCharExists (Display      *dpy,
14685 				XftFont      *pub,
14686 				dchar    ucs4);
14687 
14688 	uint XftCharIndex (Display       *dpy,
14689 				XftFont       *pub,
14690 				dchar      ucs4);
14691 	FcBool XftInit (const char *config);
14692 
14693 	int XftGetVersion ();
14694 
14695 	FcFontSet * XftListFonts (Display   *dpy,
14696 				int       screen,
14697 				...);
14698 
14699 	FcPattern *XftNameParse (const char *name);
14700 
14701 	void XftGlyphRender (Display         *dpy,
14702 				int             op,
14703 				Picture         src,
14704 				XftFont         *pub,
14705 				Picture         dst,
14706 				int             srcx,
14707 				int             srcy,
14708 				int             x,
14709 				int             y,
14710 				const uint *glyphs,
14711 				int             nglyphs);
14712 
14713 	void XftGlyphSpecRender (Display                 *dpy,
14714 				int                     op,
14715 				Picture                 src,
14716 				XftFont                 *pub,
14717 				Picture                 dst,
14718 				int                     srcx,
14719 				int                     srcy,
14720 				const XftGlyphSpec    *glyphs,
14721 				int                     nglyphs);
14722 
14723 	void XftCharSpecRender (Display              *dpy,
14724 				int                  op,
14725 				Picture              src,
14726 				XftFont              *pub,
14727 				Picture              dst,
14728 				int                  srcx,
14729 				int                  srcy,
14730 				const XftCharSpec  *chars,
14731 				int                  len);
14732 	void XftGlyphFontSpecRender (Display                     *dpy,
14733 				int                         op,
14734 				Picture                     src,
14735 				Picture                     dst,
14736 				int                         srcx,
14737 				int                         srcy,
14738 				const XftGlyphFontSpec    *glyphs,
14739 				int                         nglyphs);
14740 
14741 	void XftCharFontSpecRender (Display                  *dpy,
14742 				int                      op,
14743 				Picture                  src,
14744 				Picture                  dst,
14745 				int                      srcx,
14746 				int                      srcy,
14747 				const XftCharFontSpec  *chars,
14748 				int                      len);
14749 
14750 	void XftTextRender8 (Display         *dpy,
14751 				int             op,
14752 				Picture         src,
14753 				XftFont         *pub,
14754 				Picture         dst,
14755 				int             srcx,
14756 				int             srcy,
14757 				int             x,
14758 				int             y,
14759 				const char *string,
14760 				int             len);
14761 	void XftTextRender16 (Display            *dpy,
14762 				int                op,
14763 				Picture            src,
14764 				XftFont            *pub,
14765 				Picture            dst,
14766 				int                srcx,
14767 				int                srcy,
14768 				int                x,
14769 				int                y,
14770 				const wchar   *string,
14771 				int                len);
14772 
14773 	void XftTextRender16BE (Display          *dpy,
14774 				int              op,
14775 				Picture          src,
14776 				XftFont          *pub,
14777 				Picture          dst,
14778 				int              srcx,
14779 				int              srcy,
14780 				int              x,
14781 				int              y,
14782 				const char  *string,
14783 				int              len);
14784 
14785 	void XftTextRender16LE (Display          *dpy,
14786 				int              op,
14787 				Picture          src,
14788 				XftFont          *pub,
14789 				Picture          dst,
14790 				int              srcx,
14791 				int              srcy,
14792 				int              x,
14793 				int              y,
14794 				const char  *string,
14795 				int              len);
14796 
14797 	void XftTextRender32 (Display            *dpy,
14798 				int                op,
14799 				Picture            src,
14800 				XftFont            *pub,
14801 				Picture            dst,
14802 				int                srcx,
14803 				int                srcy,
14804 				int                x,
14805 				int                y,
14806 				const dchar   *string,
14807 				int                len);
14808 
14809 	void XftTextRender32BE (Display          *dpy,
14810 				int              op,
14811 				Picture          src,
14812 				XftFont          *pub,
14813 				Picture          dst,
14814 				int              srcx,
14815 				int              srcy,
14816 				int              x,
14817 				int              y,
14818 				const char  *string,
14819 				int              len);
14820 
14821 	void XftTextRender32LE (Display          *dpy,
14822 				int              op,
14823 				Picture          src,
14824 				XftFont          *pub,
14825 				Picture          dst,
14826 				int              srcx,
14827 				int              srcy,
14828 				int              x,
14829 				int              y,
14830 				const char  *string,
14831 				int              len);
14832 
14833 	void XftTextRenderUtf8 (Display          *dpy,
14834 				int              op,
14835 				Picture          src,
14836 				XftFont          *pub,
14837 				Picture          dst,
14838 				int              srcx,
14839 				int              srcy,
14840 				int              x,
14841 				int              y,
14842 				const char  *string,
14843 				int              len);
14844 
14845 	void XftTextRenderUtf16 (Display         *dpy,
14846 				int             op,
14847 				Picture         src,
14848 				XftFont         *pub,
14849 				Picture         dst,
14850 				int             srcx,
14851 				int             srcy,
14852 				int             x,
14853 				int             y,
14854 				const char *string,
14855 				FcEndian        endian,
14856 				int             len);
14857 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
14858 
14859 	}
14860 
14861 	interface FontConfig {
14862 	extern(C) @nogc pure:
14863 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
14864 		void FcFontSetDestroy(FcFontSet*);
14865 		char* FcNameUnparse(const FcPattern *);
14866 	}
14867 
14868 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
14869 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
14870 
14871 
14872 	/* Xft } */
14873 
14874 	class XDisconnectException : Exception {
14875 		bool userRequested;
14876 		this(bool userRequested = true) {
14877 			this.userRequested = userRequested;
14878 			super("X disconnected");
14879 		}
14880 	}
14881 
14882 	/++
14883 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14884 
14885 		Please note that it returns
14886 	+/
14887 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14888 
14889 		static XErrorEvent[] errorBuffer;
14890 
14891 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14892 			errorBuffer ~= *evt;
14893 			return 0;
14894 		}
14895 
14896 		auto savedErrorHandler = XSetErrorHandler(&handler);
14897 
14898 		try {
14899 			dg();
14900 		} finally {
14901 			XSync(XDisplayConnection.get, 0/*False*/);
14902 			XSetErrorHandler(savedErrorHandler);
14903 		}
14904 
14905 		auto bfr = errorBuffer;
14906 		errorBuffer = null;
14907 
14908 		return bfr;
14909 	}
14910 
14911 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14912 	class XDisplayConnection {
14913 		private __gshared Display* display;
14914 		private __gshared XIM xim;
14915 		private __gshared char* displayName;
14916 
14917 		private __gshared int connectionSequence_;
14918 		private __gshared bool isLocal_;
14919 
14920 		/// use this for lazy caching when reconnection
14921 		static int connectionSequenceNumber() { return connectionSequence_; }
14922 
14923 		/++
14924 			Guesses if the connection appears to be local.
14925 
14926 			History:
14927 				Added June 3, 2021
14928 		+/
14929 		static @property bool isLocal() nothrow @trusted @nogc {
14930 			return isLocal_;
14931 		}
14932 
14933 		/// Attempts recreation of state, may require application assistance
14934 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14935 		/// then call this, and if successful, reenter the loop.
14936 		static void discardAndRecreate(string newDisplayString = null) {
14937 			if(insideXEventLoop)
14938 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14939 
14940 			// 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
14941 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14942 
14943 			foreach(handle; chnenhm) {
14944 				handle.discardConnectionState();
14945 			}
14946 
14947 			discardState();
14948 
14949 			if(newDisplayString !is null)
14950 				setDisplayName(newDisplayString);
14951 
14952 			auto display = get();
14953 
14954 			foreach(handle; chnenhm) {
14955 				handle.recreateAfterDisconnect();
14956 			}
14957 		}
14958 
14959 		private __gshared EventMask rootEventMask;
14960 
14961 		/++
14962 			Requests the specified input from the root window on the connection, in addition to any other request.
14963 
14964 
14965 			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.
14966 
14967 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14968 		+/
14969 		static void addRootInput(EventMask mask) {
14970 			auto old = rootEventMask;
14971 			rootEventMask |= mask;
14972 			get(); // to ensure display connected
14973 			if(display !is null && rootEventMask != old)
14974 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14975 		}
14976 
14977 		static void discardState() {
14978 			freeImages();
14979 
14980 			foreach(atomPtr; interredAtoms)
14981 				*atomPtr = 0;
14982 			interredAtoms = null;
14983 			interredAtoms.assumeSafeAppend();
14984 
14985 			ScreenPainterImplementation.fontAttempted = false;
14986 			ScreenPainterImplementation.defaultfont = null;
14987 			ScreenPainterImplementation.defaultfontset = null;
14988 
14989 			Image.impl.xshmQueryCompleted = false;
14990 			Image.impl._xshmAvailable = false;
14991 
14992 			SimpleWindow.nativeMapping = null;
14993 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14994 			// GlobalHotkeyManager
14995 
14996 			display = null;
14997 			xim = null;
14998 		}
14999 
15000 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
15001 		private static void createXIM () {
15002 			import core.stdc.locale : setlocale, LC_ALL;
15003 			import core.stdc.stdio : stderr, fprintf;
15004 			import core.stdc.stdlib : free;
15005 			import core.stdc.string : strdup;
15006 
15007 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
15008 
15009 			auto olocale = strdup(setlocale(LC_ALL, null));
15010 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
15011 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
15012 
15013 			//fprintf(stderr, "opening IM...\n");
15014 			foreach (string s; mtry) {
15015 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
15016 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
15017 			}
15018 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
15019 		}
15020 
15021 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
15022 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
15023 		static struct ImgList {
15024 			size_t img; // class; hide it from GC
15025 			ImgList* next;
15026 		}
15027 
15028 		static __gshared ImgList* imglist = null;
15029 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
15030 
15031 		static void registerImage (Image img) {
15032 			if (!imglistLocked && img !is null) {
15033 				import core.stdc.stdlib : malloc;
15034 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
15035 				assert(it !is null); // do proper checks
15036 				it.img = cast(size_t)cast(void*)img;
15037 				it.next = imglist;
15038 				imglist = it;
15039 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
15040 			}
15041 		}
15042 
15043 		static void unregisterImage (Image img) {
15044 			if (!imglistLocked && img !is null) {
15045 				import core.stdc.stdlib : free;
15046 				ImgList* prev = null;
15047 				ImgList* cur = imglist;
15048 				while (cur !is null) {
15049 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
15050 					prev = cur;
15051 					cur = cur.next;
15052 				}
15053 				if (cur !is null) {
15054 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
15055 					free(cur);
15056 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
15057 				} else {
15058 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
15059 				}
15060 			}
15061 		}
15062 
15063 		static void freeImages () { // needed for discardAndRecreate
15064 			imglistLocked = true;
15065 			scope(exit) imglistLocked = false;
15066 			ImgList* cur = imglist;
15067 			ImgList* next = null;
15068 			while (cur !is null) {
15069 				import core.stdc.stdlib : free;
15070 				next = cur.next;
15071 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
15072 				(cast(Image)cast(void*)cur.img).dispose();
15073 				free(cur);
15074 				cur = next;
15075 			}
15076 			imglist = null;
15077 		}
15078 
15079 		/// can be used to override normal handling of display name
15080 		/// from environment and/or command line
15081 		static setDisplayName(string newDisplayName) {
15082 			displayName = cast(char*) (newDisplayName ~ '\0');
15083 		}
15084 
15085 		/// resets to the default display string
15086 		static resetDisplayName() {
15087 			displayName = null;
15088 		}
15089 
15090 		///
15091 		static Display* get() {
15092 			if(display is null) {
15093 				if(!librariesSuccessfullyLoaded)
15094 					throw new Exception("Unable to load X11 client libraries");
15095 				display = XOpenDisplay(displayName);
15096 
15097 				isLocal_ = false;
15098 
15099 				connectionSequence_++;
15100 				if(display is null)
15101 					throw new Exception("Unable to open X display");
15102 
15103 				auto str = display.display_name;
15104 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
15105 				// and otherwise it probably isn't
15106 				if(str is null || (str[0] != ':' && str[0] != '/'))
15107 					isLocal_ = false;
15108 				else
15109 					isLocal_ = true;
15110 
15111 				XSetErrorHandler(&adrlogger);
15112 
15113 				debug(sdpy_x_errors) {
15114 					XSynchronize(display, true);
15115 
15116 					extern(C) int wtf() {
15117 						if(errorHappened) {
15118 							asm { int 3; }
15119 							errorHappened = false;
15120 						}
15121 						return 0;
15122 					}
15123 					XSetAfterFunction(display, &wtf);
15124 				}
15125 
15126 
15127 				XSetIOErrorHandler(&x11ioerrCB);
15128 				Bool sup;
15129 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
15130 				createXIM();
15131 				version(with_eventloop) {
15132 					import arsd.eventloop;
15133 					addFileEventListeners(display.fd, &eventListener, null, null);
15134 				}
15135 			}
15136 
15137 			return display;
15138 		}
15139 
15140 		extern(C)
15141 		static int x11ioerrCB(Display* dpy) {
15142 			throw new XDisconnectException(false);
15143 		}
15144 
15145 		version(with_eventloop) {
15146 			import arsd.eventloop;
15147 			static void eventListener(OsFileHandle fd) {
15148 				//this.mtLock();
15149 				//scope(exit) this.mtUnlock();
15150 				while(XPending(display))
15151 					doXNextEvent(display);
15152 			}
15153 		}
15154 
15155 		// close connection on program exit -- we need this to properly free all images
15156 		static ~this () {
15157 			// the gui thread must clean up after itself or else Xlib might deadlock
15158 			// using this flag on any thread destruction is the easiest way i know of
15159 			// (shared static this is run by the LAST thread to exit, which may not be
15160 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
15161 			if(thisIsGuiThread)
15162 				close();
15163 		}
15164 
15165 		///
15166 		static void close() {
15167 			if(display is null)
15168 				return;
15169 
15170 			version(with_eventloop) {
15171 				import arsd.eventloop;
15172 				removeFileEventListeners(display.fd);
15173 			}
15174 
15175 			// now remove all registered images to prevent shared memory leaks
15176 			freeImages();
15177 
15178 			// tbh I don't know why it is doing this but like if this happens to run
15179 			// from the other thread there's frequent hanging inside here.
15180 			if(thisIsGuiThread)
15181 				XCloseDisplay(display);
15182 			display = null;
15183 		}
15184 	}
15185 
15186 	mixin template NativeImageImplementation() {
15187 		XImage* handle;
15188 		ubyte* rawData;
15189 
15190 		XShmSegmentInfo shminfo;
15191 		bool premultiply = true;
15192 
15193 		__gshared bool xshmQueryCompleted;
15194 		__gshared bool _xshmAvailable;
15195 		public static @property bool xshmAvailable() {
15196 			if(!xshmQueryCompleted) {
15197 				int i1, i2, i3;
15198 				xshmQueryCompleted = true;
15199 
15200 				if(!XDisplayConnection.isLocal)
15201 					_xshmAvailable = false;
15202 				else
15203 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
15204 			}
15205 			return _xshmAvailable;
15206 		}
15207 
15208 		bool usingXshm;
15209 	final:
15210 
15211 		private __gshared bool xshmfailed;
15212 
15213 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
15214 			auto display = XDisplayConnection.get();
15215 			assert(display !is null);
15216 			auto screen = DefaultScreen(display);
15217 
15218 			// it will only use shared memory for somewhat largish images,
15219 			// since otherwise we risk wasting shared memory handles on a lot of little ones
15220 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
15221 
15222 
15223 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
15224 				// the actual use still fails. For example, if the program is in a container and permission denied
15225 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
15226 				//
15227 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
15228 
15229 
15230 				// synchronize so preexisting buffers are clear
15231 				XSync(display, false);
15232 				xshmfailed = false;
15233 
15234 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
15235 
15236 
15237 				usingXshm = true;
15238 				handle = XShmCreateImage(
15239 					display,
15240 					DefaultVisual(display, screen),
15241 					enableAlpha ? 32: 24,
15242 					ImageFormat.ZPixmap,
15243 					null,
15244 					&shminfo,
15245 					width, height);
15246 				if(handle is null)
15247 					goto abortXshm1;
15248 
15249 				if(handle.bytes_per_line != 4 * width)
15250 					goto abortXshm2;
15251 
15252 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
15253 				if(shminfo.shmid < 0)
15254 					goto abortXshm3;
15255 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
15256 				if(rawData == cast(ubyte*) -1)
15257 					goto abortXshm4;
15258 				shminfo.readOnly = 0;
15259 				XShmAttach(display, &shminfo);
15260 
15261 				// and now to the final error check to ensure it actually worked.
15262 				XSync(display, false);
15263 				if(xshmfailed)
15264 					goto abortXshm5;
15265 
15266 				XSetErrorHandler(oldErrorHandler);
15267 
15268 				XDisplayConnection.registerImage(this);
15269 				// if I don't flush here there's a chance the dtor will run before the
15270 				// ctor and lead to a bad value X error. While this hurts the efficiency
15271 				// it is local anyway so prolly better to keep it simple
15272 				XFlush(display);
15273 
15274 				return;
15275 
15276 				abortXshm5:
15277 					shmdt(shminfo.shmaddr);
15278 					rawData = null;
15279 
15280 				abortXshm4:
15281 					shmctl(shminfo.shmid, IPC_RMID, null);
15282 
15283 				abortXshm3:
15284 					// nothing needed, the shmget failed so there's nothing to free
15285 
15286 				abortXshm2:
15287 					XDestroyImage(handle);
15288 					handle = null;
15289 
15290 				abortXshm1:
15291 					XSetErrorHandler(oldErrorHandler);
15292 					usingXshm = false;
15293 					handle = null;
15294 
15295 					shminfo = typeof(shminfo).init;
15296 
15297 					_xshmAvailable = false; // don't try again in the future
15298 
15299 					// writeln("fallingback");
15300 
15301 					goto fallback;
15302 
15303 			} else {
15304 				fallback:
15305 
15306 				if (forcexshm) throw new Exception("can't create XShm Image");
15307 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
15308 				import core.stdc.stdlib : malloc;
15309 				rawData = cast(ubyte*) malloc(width * height * 4);
15310 
15311 				handle = XCreateImage(
15312 					display,
15313 					DefaultVisual(display, screen),
15314 					enableAlpha ? 32 : 24, // bpp
15315 					ImageFormat.ZPixmap,
15316 					0, // offset
15317 					rawData,
15318 					width, height,
15319 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
15320 			}
15321 		}
15322 
15323 		void dispose() {
15324 			// note: this calls free(rawData) for us
15325 			if(handle) {
15326 				if (usingXshm) {
15327 					XDisplayConnection.unregisterImage(this);
15328 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
15329 				}
15330 				XDestroyImage(handle);
15331 				if(usingXshm) {
15332 					shmdt(shminfo.shmaddr);
15333 					shmctl(shminfo.shmid, IPC_RMID, null);
15334 				}
15335 				handle = null;
15336 			}
15337 		}
15338 
15339 		Color getPixel(int x, int y) @system {
15340 			auto offset = (y * width + x) * 4;
15341 			Color c;
15342 			c.a = enableAlpha ? rawData[offset + 3] : 255;
15343 			c.b = rawData[offset + 0];
15344 			c.g = rawData[offset + 1];
15345 			c.r = rawData[offset + 2];
15346 			if(enableAlpha && premultiply)
15347 				c.unPremultiply;
15348 			return c;
15349 		}
15350 
15351 		void setPixel(int x, int y, Color c) @system {
15352 			if(enableAlpha && premultiply)
15353 				c.premultiply();
15354 			auto offset = (y * width + x) * 4;
15355 			rawData[offset + 0] = c.b;
15356 			rawData[offset + 1] = c.g;
15357 			rawData[offset + 2] = c.r;
15358 			if(enableAlpha)
15359 				rawData[offset + 3] = c.a;
15360 		}
15361 
15362 		void convertToRgbaBytes(ubyte[] where) @system {
15363 			assert(where.length == this.width * this.height * 4);
15364 
15365 			// if rawData had a length....
15366 			//assert(rawData.length == where.length);
15367 			for(int idx = 0; idx < where.length; idx += 4) {
15368 				where[idx + 0] = rawData[idx + 2]; // r
15369 				where[idx + 1] = rawData[idx + 1]; // g
15370 				where[idx + 2] = rawData[idx + 0]; // b
15371 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
15372 
15373 				if(enableAlpha && premultiply)
15374 					unPremultiplyRgba(where[idx .. idx + 4]);
15375 			}
15376 		}
15377 
15378 		void setFromRgbaBytes(in ubyte[] where) @system {
15379 			assert(where.length == this.width * this.height * 4);
15380 
15381 			// if rawData had a length....
15382 			//assert(rawData.length == where.length);
15383 			for(int idx = 0; idx < where.length; idx += 4) {
15384 				rawData[idx + 2] = where[idx + 0]; // r
15385 				rawData[idx + 1] = where[idx + 1]; // g
15386 				rawData[idx + 0] = where[idx + 2]; // b
15387 				if(enableAlpha) {
15388 					rawData[idx + 3] = where[idx + 3]; // a
15389 					if(premultiply)
15390 						premultiplyBgra(rawData[idx .. idx + 4]);
15391 				}
15392 			}
15393 		}
15394 
15395 	}
15396 
15397 	mixin template NativeSimpleWindowImplementation() {
15398 		GC gc;
15399 		Window window;
15400 		Display* display;
15401 
15402 		Pixmap buffer;
15403 		int bufferw, bufferh; // size of the buffer; can be bigger than window
15404 		XIC xic; // input context
15405 		int curHidden = 0; // counter
15406 		Cursor blankCurPtr = 0;
15407 		int cursorSequenceNumber = 0;
15408 		int warpEventCount = 0; // number of mouse movement events to eat
15409 
15410 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS
15411 		X11GetSelectionHandler[Atom] getSelectionHandlers;
15412 
15413 		version(without_opengl) {} else
15414 		GLXContext glc;
15415 
15416 		private void fixFixedSize(bool forced=false) (int width, int height) {
15417 			if (forced || this.resizability == Resizability.fixedSize) {
15418 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
15419 				XSizeHints sh;
15420 				static if (!forced) {
15421 					c_long spr;
15422 					XGetWMNormalHints(display, window, &sh, &spr);
15423 					sh.flags |= PMaxSize | PMinSize;
15424 				} else {
15425 					sh.flags = PMaxSize | PMinSize;
15426 				}
15427 				sh.min_width = width;
15428 				sh.min_height = height;
15429 				sh.max_width = width;
15430 				sh.max_height = height;
15431 				XSetWMNormalHints(display, window, &sh);
15432 				//XFlush(display);
15433 			}
15434 		}
15435 
15436 		ScreenPainter getPainter(bool manualInvalidations) {
15437 			return ScreenPainter(this, window, manualInvalidations);
15438 		}
15439 
15440 		void move(int x, int y) {
15441 			XMoveWindow(display, window, x, y);
15442 		}
15443 
15444 		void resize(int w, int h) {
15445 			if (w < 1) w = 1;
15446 			if (h < 1) h = 1;
15447 			XResizeWindow(display, window, w, h);
15448 
15449 			// calling this now to avoid waiting for the server to
15450 			// acknowledge the resize; draws without returning to the
15451 			// event loop will thus actually work. the server's event
15452 			// btw might overrule this and resize it again
15453 			recordX11Resize(display, this, w, h);
15454 
15455 			updateOpenglViewportIfNeeded(w, h);
15456 		}
15457 
15458 		void moveResize (int x, int y, int w, int h) {
15459 			if (w < 1) w = 1;
15460 			if (h < 1) h = 1;
15461 			XMoveResizeWindow(display, window, x, y, w, h);
15462 			updateOpenglViewportIfNeeded(w, h);
15463 		}
15464 
15465 		void hideCursor () {
15466 			if (curHidden++ == 0) {
15467 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
15468 					static const(char)[1] cmbmp = 0;
15469 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
15470 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
15471 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
15472 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
15473 					XFreePixmap(display, pm);
15474 				}
15475 				XDefineCursor(display, window, blankCurPtr);
15476 			}
15477 		}
15478 
15479 		void showCursor () {
15480 			if (--curHidden == 0) XUndefineCursor(display, window);
15481 		}
15482 
15483 		void warpMouse (int x, int y) {
15484 			// here i will send dummy "ignore next mouse motion" event,
15485 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
15486 			// and we don't need to report it to the user (as warping is
15487 			// used when the user needs movement deltas).
15488 			//XClientMessageEvent xclient;
15489 			XEvent e;
15490 			e.xclient.type = EventType.ClientMessage;
15491 			e.xclient.window = window;
15492 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15493 			e.xclient.format = 32;
15494 			e.xclient.data.l[0] = 0;
15495 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
15496 			//{ 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]); }
15497 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15498 			// now warp pointer...
15499 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
15500 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
15501 			// ...and flush
15502 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
15503 			XFlush(display);
15504 		}
15505 
15506 		void sendDummyEvent () {
15507 			// here i will send dummy event to ping event queue
15508 			XEvent e;
15509 			e.xclient.type = EventType.ClientMessage;
15510 			e.xclient.window = window;
15511 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15512 			e.xclient.format = 32;
15513 			e.xclient.data.l[0] = 0;
15514 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15515 			XFlush(display);
15516 		}
15517 
15518 		void setTitle(string title) {
15519 			if (title.ptr is null) title = "";
15520 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15521 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15522 			XTextProperty windowName;
15523 			windowName.value = title.ptr;
15524 			windowName.encoding = XA_UTF8; //XA_STRING;
15525 			windowName.format = 8;
15526 			windowName.nitems = cast(uint)title.length;
15527 			XSetWMName(display, window, &windowName);
15528 			char[1024] namebuf = 0;
15529 			auto maxlen = namebuf.length-1;
15530 			if (maxlen > title.length) maxlen = title.length;
15531 			namebuf[0..maxlen] = title[0..maxlen];
15532 			XStoreName(display, window, namebuf.ptr);
15533 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
15534 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
15535 		}
15536 
15537 		string[] getTitles() {
15538 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15539 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15540 			XTextProperty textProp;
15541 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
15542 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
15543 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
15544 				} else
15545 					return [];
15546 			} else
15547 				return null;
15548 		}
15549 
15550 		string getTitle() {
15551 			auto titles = getTitles();
15552 			return titles.length ? titles[0] : null;
15553 		}
15554 
15555 		void setMinSize (int minwidth, int minheight) {
15556 			import core.stdc.config : c_long;
15557 			if (minwidth < 1) minwidth = 1;
15558 			if (minheight < 1) minheight = 1;
15559 			XSizeHints sh;
15560 			c_long spr;
15561 			XGetWMNormalHints(display, window, &sh, &spr);
15562 			sh.min_width = minwidth;
15563 			sh.min_height = minheight;
15564 			sh.flags |= PMinSize;
15565 			XSetWMNormalHints(display, window, &sh);
15566 			flushGui();
15567 		}
15568 
15569 		void setMaxSize (int maxwidth, int maxheight) {
15570 			import core.stdc.config : c_long;
15571 			if (maxwidth < 1) maxwidth = 1;
15572 			if (maxheight < 1) maxheight = 1;
15573 			XSizeHints sh;
15574 			c_long spr;
15575 			XGetWMNormalHints(display, window, &sh, &spr);
15576 			sh.max_width = maxwidth;
15577 			sh.max_height = maxheight;
15578 			sh.flags |= PMaxSize;
15579 			XSetWMNormalHints(display, window, &sh);
15580 			flushGui();
15581 		}
15582 
15583 		void setResizeGranularity (int granx, int grany) {
15584 			import core.stdc.config : c_long;
15585 			if (granx < 1) granx = 1;
15586 			if (grany < 1) grany = 1;
15587 			XSizeHints sh;
15588 			c_long spr;
15589 			XGetWMNormalHints(display, window, &sh, &spr);
15590 			sh.width_inc = granx;
15591 			sh.height_inc = grany;
15592 			sh.flags |= PResizeInc;
15593 			XSetWMNormalHints(display, window, &sh);
15594 			flushGui();
15595 		}
15596 
15597 		void setOpacity (uint opacity) {
15598 			arch_ulong o = opacity;
15599 			if (opacity == uint.max)
15600 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
15601 			else
15602 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
15603 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
15604 		}
15605 
15606 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted {
15607 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
15608 			display = XDisplayConnection.get();
15609 			auto screen = DefaultScreen(display);
15610 
15611 			bool overrideRedirect = false;
15612 			if(
15613 				windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu ||
15614 				windowType == WindowTypes.tooltip ||
15615 				windowType == WindowTypes.notification ||
15616 				windowType == WindowTypes.dnd ||
15617 				windowType == WindowTypes.comboBoxDropdown ||
15618 				(customizationFlags & WindowFlags.overrideRedirect)
15619 			)// || windowType == WindowTypes.nestedChild)
15620 				overrideRedirect = true;
15621 
15622 			version(without_opengl) {}
15623 			else {
15624 				if(opengl == OpenGlOptions.yes) {
15625 					GLXFBConfig fbconf = null;
15626 					XVisualInfo* vi = null;
15627 					bool useLegacy = false;
15628 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
15629 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
15630 						int[23] visualAttribs = [
15631 							GLX_X_RENDERABLE , 1/*True*/,
15632 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
15633 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
15634 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
15635 							GLX_RED_SIZE     , 8,
15636 							GLX_GREEN_SIZE   , 8,
15637 							GLX_BLUE_SIZE    , 8,
15638 							GLX_ALPHA_SIZE   , 8,
15639 							GLX_DEPTH_SIZE   , 24,
15640 							GLX_STENCIL_SIZE , 8,
15641 							GLX_DOUBLEBUFFER , 1/*True*/,
15642 							0/*None*/,
15643 						];
15644 						int fbcount;
15645 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
15646 						if (fbcount == 0) {
15647 							useLegacy = true; // try to do at least something
15648 						} else {
15649 							// pick the FB config/visual with the most samples per pixel
15650 							int bestidx = -1, bestns = -1;
15651 							foreach (int fbi; 0..fbcount) {
15652 								int sb, samples;
15653 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
15654 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
15655 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
15656 							}
15657 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
15658 							fbconf = fbc[bestidx];
15659 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
15660 							XFree(fbc);
15661 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
15662 						}
15663 					}
15664 					if (vi is null || useLegacy) {
15665 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
15666 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
15667 						useLegacy = true;
15668 					}
15669 					if (vi is null) throw new Exception("no open gl visual found");
15670 
15671 					XSetWindowAttributes swa;
15672 					auto root = RootWindow(display, screen);
15673 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
15674 
15675 					swa.override_redirect = overrideRedirect;
15676 
15677 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15678 						0, 0, width, height,
15679 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
15680 
15681 					// now try to use `glXCreateContextAttribsARB()` if it's here
15682 					if (!useLegacy) {
15683 						// request fairly advanced context, even with stencil buffer!
15684 						int[9] contextAttribs = [
15685 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
15686 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
15687 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
15688 							// for modern context, set "forward compatibility" flag too
15689 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
15690 							0/*None*/,
15691 						];
15692 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
15693 						if (glc is null && sdpyOpenGLContextAllowFallback) {
15694 							sdpyOpenGLContextVersion = 0;
15695 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15696 						}
15697 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
15698 					} else {
15699 						// fallback to old GLX call
15700 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
15701 							sdpyOpenGLContextVersion = 0;
15702 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
15703 						}
15704 					}
15705 					// sync to ensure any errors generated are processed
15706 					XSync(display, 0/*False*/);
15707 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
15708 					if(glc is null)
15709 						throw new Exception("glc");
15710 				}
15711 			}
15712 
15713 			if(opengl == OpenGlOptions.no) {
15714 
15715 				XSetWindowAttributes swa;
15716 				swa.background_pixel = WhitePixel(display, screen);
15717 				swa.border_pixel = BlackPixel(display, screen);
15718 				swa.override_redirect = overrideRedirect;
15719 				auto root = RootWindow(display, screen);
15720 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
15721 
15722 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
15723 					0, 0, width, height,
15724 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
15725 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
15726 
15727 
15728 
15729 				/*
15730 				window = XCreateSimpleWindow(
15731 					display,
15732 					parent is null ? RootWindow(display, screen) : parent.impl.window,
15733 					0, 0, // x, y
15734 					width, height,
15735 					1, // border width
15736 					BlackPixel(display, screen), // border
15737 					WhitePixel(display, screen)); // background
15738 				*/
15739 
15740 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
15741 				bufferw = width;
15742 				bufferh = height;
15743 
15744 				gc = DefaultGC(display, screen);
15745 
15746 				// clear out the buffer to get us started...
15747 				XSetForeground(display, gc, WhitePixel(display, screen));
15748 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
15749 				XSetForeground(display, gc, BlackPixel(display, screen));
15750 			}
15751 
15752 			// input context
15753 			//TODO: create this only for top-level windows, and reuse that?
15754 			populateXic();
15755 
15756 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
15757 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
15758 			// window class
15759 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
15760 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
15761 				XClassHint klass;
15762 				XWMHints wh;
15763 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15764 					wh.input = true;
15765 					wh.flags |= InputHint;
15766 				}
15767 				XSizeHints size;
15768 				klass.res_name = sdpyWindowClassStr;
15769 				klass.res_class = sdpyWindowClassStr;
15770 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
15771 			}
15772 
15773 			setTitle(title);
15774 			SimpleWindow.nativeMapping[window] = this;
15775 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
15776 
15777 			// This gives our window a close button
15778 			if (windowType != WindowTypes.eventOnly) {
15779 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
15780 				int useAtoms;
15781 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
15782 					useAtoms = 2;
15783 				} else {
15784 					useAtoms = 1;
15785 				}
15786 				assert(useAtoms <= atoms.length);
15787 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
15788 			}
15789 
15790 			// FIXME: windowType and customizationFlags
15791 			Atom[8] wsatoms; // here, due to goto
15792 			int wmsacount = 0; // here, due to goto
15793 
15794 			try
15795 			final switch(windowType) {
15796 				case WindowTypes.normal:
15797 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15798 				break;
15799 				case WindowTypes.undecorated:
15800 					motifHideDecorations();
15801 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
15802 				break;
15803 				case WindowTypes.eventOnly:
15804 					_hidden = true;
15805 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
15806 					goto hiddenWindow;
15807 				//break;
15808 				case WindowTypes.nestedChild:
15809 					// handled in XCreateWindow calls
15810 				break;
15811 
15812 				case WindowTypes.dropdownMenu:
15813 					motifHideDecorations();
15814 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
15815 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15816 				break;
15817 				case WindowTypes.popupMenu:
15818 					motifHideDecorations();
15819 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
15820 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15821 				break;
15822 				case WindowTypes.notification:
15823 					motifHideDecorations();
15824 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
15825 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15826 				break;
15827 				case WindowTypes.minimallyWrapped:
15828 					assert(0, "don't create a minimallyWrapped thing explicitly!");
15829 
15830 				case WindowTypes.dialog:
15831 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display));
15832 				break;
15833 				case WindowTypes.comboBoxDropdown:
15834 					motifHideDecorations();
15835 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display));
15836 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15837 				break;
15838 				case WindowTypes.tooltip:
15839 					motifHideDecorations();
15840 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display));
15841 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15842 				break;
15843 				case WindowTypes.dnd:
15844 					motifHideDecorations();
15845 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display));
15846 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
15847 				break;
15848 				/+
15849 				case WindowTypes.menu:
15850 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15851 					motifHideDecorations();
15852 				break;
15853 				case WindowTypes.desktop:
15854 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
15855 				break;
15856 				case WindowTypes.dock:
15857 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
15858 				break;
15859 				case WindowTypes.toolbar:
15860 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
15861 				break;
15862 				case WindowTypes.menu:
15863 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
15864 				break;
15865 				case WindowTypes.utility:
15866 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
15867 				break;
15868 				case WindowTypes.splash:
15869 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
15870 				break;
15871 				case WindowTypes.notification:
15872 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
15873 				break;
15874 				+/
15875 			}
15876 			catch(Exception e) {
15877 				// XInternAtom failed, prolly a WM
15878 				// that doesn't support these things
15879 			}
15880 
15881 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
15882 			// the two following flags may be ignored by WM
15883 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
15884 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
15885 
15886 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
15887 
15888 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
15889 
15890 			// What would be ideal here is if they only were
15891 			// selected if there was actually an event handler
15892 			// for them...
15893 
15894 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15895 
15896 			hiddenWindow:
15897 
15898 			// set the pid property for lookup later by window managers
15899 			// a standard convenience
15900 			import core.sys.posix.unistd;
15901 			arch_ulong pid = getpid();
15902 
15903 			XChangeProperty(
15904 				display,
15905 				impl.window,
15906 				GetAtom!("_NET_WM_PID", true)(display),
15907 				XA_CARDINAL,
15908 				32 /* bits */,
15909 				0 /*PropModeReplace*/,
15910 				&pid,
15911 				1);
15912 
15913 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15914 				if(parent is null) assert(0);
15915 				// sdpyPrintDebugString("transient");
15916 				XChangeProperty(
15917 					display,
15918 					impl.window,
15919 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15920 					XA_WINDOW,
15921 					32 /* bits */,
15922 					0 /*PropModeReplace*/,
15923 					&parent.impl.window,
15924 					1);
15925 
15926 			}
15927 
15928 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15929 				XMapWindow(display, window);
15930 			} else {
15931 				_hidden = true;
15932 			}
15933 		}
15934 
15935 		void populateXic() {
15936 			if (XDisplayConnection.xim !is null) {
15937 				xic = XCreateIC(XDisplayConnection.xim,
15938 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15939 						/*XNClientWindow*/"clientWindow".ptr, window,
15940 						/*XNFocusWindow*/"focusWindow".ptr, window,
15941 						null);
15942 				if (xic is null) {
15943 					import core.stdc.stdio : stderr, fprintf;
15944 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15945 				}
15946 			}
15947 		}
15948 
15949 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15950 			auto mask = EventMask.ExposureMask |
15951 				EventMask.KeyPressMask |
15952 				EventMask.KeyReleaseMask |
15953 				EventMask.PropertyChangeMask |
15954 				EventMask.FocusChangeMask |
15955 				EventMask.StructureNotifyMask |
15956 				EventMask.SubstructureNotifyMask |
15957 				EventMask.VisibilityChangeMask
15958 				| EventMask.ButtonPressMask
15959 				| EventMask.ButtonReleaseMask
15960 			;
15961 
15962 			// xshm is our shortcut for local connections
15963 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15964 				mask |= EventMask.PointerMotionMask;
15965 			else
15966 				mask |= EventMask.ButtonMotionMask;
15967 
15968 			XSelectInput(display, window, mask);
15969 		}
15970 
15971 
15972 		void setNetWMWindowType(Atom type) {
15973 			Atom[2] atoms;
15974 
15975 			atoms[0] = type;
15976 			// generic fallback
15977 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15978 
15979 			XChangeProperty(
15980 				display,
15981 				impl.window,
15982 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15983 				XA_ATOM,
15984 				32 /* bits */,
15985 				0 /*PropModeReplace*/,
15986 				atoms.ptr,
15987 				cast(int) atoms.length);
15988 		}
15989 
15990 		void motifHideDecorations(bool hide = true) {
15991 			MwmHints hints;
15992 			hints.flags = MWM_HINTS_DECORATIONS;
15993 			hints.decorations = hide ? 0 : 1;
15994 
15995 			XChangeProperty(
15996 				display,
15997 				impl.window,
15998 				GetAtom!"_MOTIF_WM_HINTS"(display),
15999 				GetAtom!"_MOTIF_WM_HINTS"(display),
16000 				32 /* bits */,
16001 				0 /*PropModeReplace*/,
16002 				&hints,
16003 				hints.sizeof / 4);
16004 		}
16005 
16006 		/*k8: unused
16007 		void createOpenGlContext() {
16008 
16009 		}
16010 		*/
16011 
16012 		void closeWindow() {
16013 			// I can't close this or a child window closing will
16014 			// break events for everyone. So I'm just leaking it right
16015 			// now and that is probably perfectly fine...
16016 			version(none)
16017 			if (customEventFDRead != -1) {
16018 				import core.sys.posix.unistd : close;
16019 				auto same = customEventFDRead == customEventFDWrite;
16020 
16021 				close(customEventFDRead);
16022 				if(!same)
16023 					close(customEventFDWrite);
16024 				customEventFDRead = -1;
16025 				customEventFDWrite = -1;
16026 			}
16027 
16028 			version(without_opengl) {} else
16029 			if(glc !is null) {
16030 				glXDestroyContext(display, glc);
16031 				glc = null;
16032 			}
16033 
16034 			if(buffer)
16035 				XFreePixmap(display, buffer);
16036 			bufferw = bufferh = 0;
16037 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
16038 			XDestroyWindow(display, window);
16039 			XFlush(display);
16040 		}
16041 
16042 		void dispose() {
16043 		}
16044 
16045 		bool destroyed = false;
16046 	}
16047 
16048 	bool insideXEventLoop;
16049 }
16050 
16051 version(X11) {
16052 
16053 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
16054 
16055 	private class ResizeEvent {
16056 		int width, height;
16057 	}
16058 
16059 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
16060 		if(win.windowType == WindowTypes.minimallyWrapped)
16061 			return;
16062 
16063 		if(win.pendingResizeEvent is null) {
16064 			win.pendingResizeEvent = new ResizeEvent();
16065 			win.addEventListener((ResizeEvent re) {
16066 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
16067 			});
16068 		}
16069 		win.pendingResizeEvent.width = width;
16070 		win.pendingResizeEvent.height = height;
16071 		if(!win.eventQueued!ResizeEvent) {
16072 			win.postEvent(win.pendingResizeEvent);
16073 		}
16074 	}
16075 
16076 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
16077 		if(win.windowType == WindowTypes.minimallyWrapped)
16078 			return;
16079 		if(win.closed)
16080 			return;
16081 
16082 		if(width != win.width || height != win.height) {
16083 
16084 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
16085 			win._width = width;
16086 			win._height = height;
16087 
16088 			if(win.openglMode == OpenGlOptions.no) {
16089 				// FIXME: could this be more efficient?
16090 
16091 				if (win.bufferw < width || win.bufferh < height) {
16092 					//{ 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); }
16093 					// grow the internal buffer to match the window...
16094 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
16095 					{
16096 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
16097 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
16098 						scope(exit) XFreeGC(win.display, xgc);
16099 						XSetClipMask(win.display, xgc, None);
16100 						XSetForeground(win.display, xgc, 0);
16101 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
16102 					}
16103 					XCopyArea(display,
16104 						cast(Drawable) win.buffer,
16105 						cast(Drawable) newPixmap,
16106 						win.gc, 0, 0,
16107 						win.bufferw < width ? win.bufferw : win.width,
16108 						win.bufferh < height ? win.bufferh : win.height,
16109 						0, 0);
16110 
16111 					XFreePixmap(display, win.buffer);
16112 					win.buffer = newPixmap;
16113 					win.bufferw = width;
16114 					win.bufferh = height;
16115 				}
16116 
16117 				// clear unused parts of the buffer
16118 				if (win.bufferw > width || win.bufferh > height) {
16119 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
16120 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
16121 					scope(exit) XFreeGC(win.display, xgc);
16122 					XSetClipMask(win.display, xgc, None);
16123 					XSetForeground(win.display, xgc, 0);
16124 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
16125 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
16126 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
16127 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
16128 				}
16129 
16130 			}
16131 
16132 			win.updateOpenglViewportIfNeeded(width, height);
16133 
16134 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
16135 
16136 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
16137 			if(win.windowResized !is null) {
16138 				XUnlockDisplay(display);
16139 				scope(exit) XLockDisplay(display);
16140 				win.windowResized(width, height);
16141 			}
16142 		}
16143 	}
16144 
16145 
16146 	/// Platform-specific, you might use it when doing a custom event loop.
16147 	bool doXNextEvent(Display* display) {
16148 		bool done;
16149 		XEvent e;
16150 		XNextEvent(display, &e);
16151 		version(sddddd) {
16152 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16153 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
16154 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
16155 			}
16156 		}
16157 
16158 		// filter out compose events
16159 		if (XFilterEvent(&e, None)) {
16160 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
16161 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
16162 			return false;
16163 		}
16164 		// process keyboard mapping changes
16165 		if (e.type == EventType.KeymapNotify) {
16166 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
16167 			XRefreshKeyboardMapping(&e.xmapping);
16168 			return false;
16169 		}
16170 
16171 		version(with_eventloop)
16172 			import arsd.eventloop;
16173 
16174 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
16175 			// see windows impl's comments
16176 			XUnlockDisplay(display);
16177 			scope(exit) XLockDisplay(display);
16178 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
16179 			if(ret == 0)
16180 				return done;
16181 		}
16182 
16183 
16184 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16185 			if(win.getNativeEventHandler !is null) {
16186 				XUnlockDisplay(display);
16187 				scope(exit) XLockDisplay(display);
16188 				auto ret = win.getNativeEventHandler()(e);
16189 				if(ret == 0)
16190 					return done;
16191 			}
16192 		}
16193 
16194 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
16195 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
16196 				// we get this because of the RRScreenChangeNotifyMask
16197 
16198 				// this isn't actually an ideal way to do it since it wastes time
16199 				// but meh it is simple and it works.
16200 				win.actualDpiLoadAttempted = false;
16201 				SimpleWindow.xRandrInfoLoadAttemped = false;
16202 				win.updateActualDpi(); // trigger a reload
16203 			}
16204 		}
16205 
16206 		switch(e.type) {
16207 		  case EventType.SelectionClear:
16208 		  	// writeln("SelectionClear");
16209 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
16210 				// FIXME so it is supposed to finish any in progress transfers... but idk...
16211 				// writeln("SelectionClear");
16212 			}
16213 			SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
16214 			mightShortCircuitClipboard = false;
16215 		  break;
16216 		  case EventType.SelectionRequest:
16217 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
16218 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
16219 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
16220 				XUnlockDisplay(display);
16221 				scope(exit) XLockDisplay(display);
16222 				(*ssh).handleRequest(e);
16223 			}
16224 		  break;
16225 		  case EventType.PropertyNotify:
16226 			// import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
16227 
16228 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
16229 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
16230 					ssh.sendMoreIncr(&e.xproperty);
16231 			}
16232 
16233 
16234 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
16235 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16236 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
16237 					Atom target;
16238 					int format;
16239 					arch_ulong bytesafter, length;
16240 					void* value;
16241 
16242 					ubyte[] s;
16243 					Atom targetToKeep;
16244 
16245 					XGetWindowProperty(
16246 						e.xproperty.display,
16247 						e.xproperty.window,
16248 						e.xproperty.atom,
16249 						0,
16250 						100000 /* length */,
16251 						true, /* erase it to signal we got it and want more */
16252 						0 /*AnyPropertyType*/,
16253 						&target, &format, &length, &bytesafter, &value);
16254 
16255 					if(!targetToKeep)
16256 						targetToKeep = target;
16257 
16258 					auto id = (cast(ubyte*) value)[0 .. length];
16259 
16260 					handler.handleIncrData(targetToKeep, id);
16261 					if(length == 0) {
16262 						win.getSelectionHandlers.remove(e.xproperty.atom);
16263 					}
16264 
16265 					XFree(value);
16266 				}
16267 			}
16268 		  break;
16269 		  case EventType.SelectionNotify:
16270 		  	// import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom);
16271 			if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
16272 			if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16273 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
16274 					XUnlockDisplay(display);
16275 					scope(exit) XLockDisplay(display);
16276 					handler.handleData(None, null);
16277 					win.getSelectionHandlers.remove(e.xproperty.atom);
16278 				} else {
16279 					Atom target;
16280 					int format;
16281 					arch_ulong bytesafter, length;
16282 					void* value;
16283 					XGetWindowProperty(
16284 						e.xselection.display,
16285 						e.xselection.requestor,
16286 						e.xselection.property,
16287 						0,
16288 						100000 /* length */,
16289 						//false, /* don't erase it */
16290 						true, /* do erase it lol */
16291 						0 /*AnyPropertyType*/,
16292 						&target, &format, &length, &bytesafter, &value);
16293 
16294 					// FIXME: I don't have to copy it now since it is in char[] instead of string
16295 
16296 					{
16297 						XUnlockDisplay(display);
16298 						scope(exit) XLockDisplay(display);
16299 
16300 						if(target == XA_ATOM) {
16301 							// initial request, see what they are able to work with and request the best one
16302 							// we can handle, if available
16303 
16304 							Atom[] answer = (cast(Atom*) value)[0 .. length];
16305 							Atom best = handler.findBestFormat(answer);
16306 
16307 							/+
16308 							writeln("got ", answer);
16309 							foreach(a; answer)
16310 								writeln(XGetAtomName(display, a).stringz);
16311 							writeln("best ", best);
16312 							+/
16313 
16314 							if(best != None) {
16315 								// actually request the best format
16316 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
16317 							}
16318 						} else if(target == GetAtom!"INCR"(display)) {
16319 							// incremental
16320 
16321 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
16322 
16323 							// signal the sending program that we see
16324 							// the incr and are ready to receive more.
16325 							XDeleteProperty(
16326 								e.xselection.display,
16327 								e.xselection.requestor,
16328 								e.xselection.property);
16329 						} else {
16330 							// unsupported type... maybe, forward, then we done with it
16331 							if(target != None) {
16332 								handler.handleData(target, cast(ubyte[]) value[0 .. length]);
16333 								win.getSelectionHandlers.remove(e.xproperty.atom);
16334 							}
16335 						}
16336 					}
16337 					XFree(value);
16338 					/*
16339 					XDeleteProperty(
16340 						e.xselection.display,
16341 						e.xselection.requestor,
16342 						e.xselection.property);
16343 					*/
16344 				}
16345 			}
16346 			break;
16347 		  case EventType.ConfigureNotify:
16348 			auto event = e.xconfigure;
16349 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
16350 				if(win.windowType == WindowTypes.minimallyWrapped)
16351 					break;
16352 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
16353 
16354 				/+
16355 					The ICCCM says window managers must send a synthetic event when the window
16356 					is moved but NOT when it is resized. In the resize case, an event is sent
16357 					with position (0, 0) which can be wrong and break the dpi calculations.
16358 
16359 					So we only consider the synthetic events from the WM and otherwise
16360 					need to wait for some other event to get the position which... sucks.
16361 
16362 					I'd rather not have windows changing their layout on mouse motion after
16363 					switching monitors... might be forced to but for now just ignoring it.
16364 
16365 					Easiest way to switch monitors without sending a size position is by
16366 					maximize or fullscreen in a setup like mine, but on most setups those
16367 					work on the monitor it is already living on, so it should be ok most the
16368 					time.
16369 				+/
16370 				if(event.send_event) {
16371 					win.screenPositionKnown = true;
16372 					win.screenPositionX = event.x;
16373 					win.screenPositionY = event.y;
16374 					win.updateActualDpi();
16375 				}
16376 
16377 				win.updateIMEPopupLocation();
16378 				recordX11ResizeAsync(display, *win, event.width, event.height);
16379 			}
16380 		  break;
16381 		  case EventType.Expose:
16382 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
16383 				if(win.windowType == WindowTypes.minimallyWrapped)
16384 					break;
16385 				// if it is closing from a popup menu, it can get
16386 				// an Expose event right by the end and trigger a
16387 				// BadDrawable error ... we'll just check
16388 				// closed to handle that.
16389 				if((*win).closed) break;
16390 				if((*win).openglMode == OpenGlOptions.no) {
16391 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
16392 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
16393 					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);
16394 				} else {
16395 					// need to redraw the scene somehow
16396 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
16397 						XUnlockDisplay(display);
16398 						scope(exit) XLockDisplay(display);
16399 						version(without_opengl) {} else
16400 						win.redrawOpenGlSceneSoon();
16401 					}
16402 				}
16403 			}
16404 		  break;
16405 		  case EventType.FocusIn:
16406 		  case EventType.FocusOut:
16407 			mightShortCircuitClipboard = false; // if the focus has changed, good chance the clipboard cache is invalidated too, kinda hacky but always better to skip when unnecessary than use when we shouldn't have
16408 
16409 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16410 				/+
16411 
16412 				void info(string detail) {
16413 					string s;
16414 					// import std.conv;
16415 					// import std.datetime;
16416 					s ~= to!string(Clock.currTime);
16417 					s ~= " ";
16418 					s ~= e.type == EventType.FocusIn ? "in " : "out";
16419 					s ~= " ";
16420 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
16421 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
16422 					s ~= detail;
16423 					s ~= " ";
16424 
16425 					sdpyPrintDebugString(s);
16426 
16427 				}
16428 
16429 				switch(e.xfocus.detail) {
16430 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
16431 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
16432 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
16433 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
16434 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
16435 					case NotifyDetail.NotifyPointer: info("pointer"); break;
16436 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
16437 					case NotifyDetail.NotifyDetailNone: info("none"); break;
16438 					default:
16439 
16440 				}
16441 				+/
16442 
16443 
16444 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
16445 					break; // just ignore these they seem irrelevant
16446 
16447 				auto old = win._focused;
16448 				win._focused = e.type == EventType.FocusIn;
16449 
16450 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
16451 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
16452 					win._focused = true;
16453 
16454 				if(win.demandingAttention)
16455 					demandAttention(*win, false);
16456 
16457 				win.updateIMEFocused();
16458 
16459 				if(old != win._focused && win.onFocusChange) {
16460 					XUnlockDisplay(display);
16461 					scope(exit) XLockDisplay(display);
16462 					win.onFocusChange(win._focused);
16463 				}
16464 			}
16465 		  break;
16466 		  case EventType.VisibilityNotify:
16467 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16468 					auto before = (*win)._visible;
16469 					(*win)._visible = (e.xvisibility.state != VisibilityNotify.VisibilityFullyObscured);
16470 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
16471 						if (win.visibilityChanged !is null && before == true) {
16472 								XUnlockDisplay(display);
16473 								scope(exit) XLockDisplay(display);
16474 								win.visibilityChanged(false);
16475 							}
16476 					} else {
16477 						if (win.visibilityChanged !is null && before == false) {
16478 							XUnlockDisplay(display);
16479 							scope(exit) XLockDisplay(display);
16480 							win.visibilityChanged(true);
16481 						}
16482 					}
16483 				}
16484 				break;
16485 		  case EventType.ClientMessage:
16486 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
16487 					// "ignore next mouse motion" event, increment ignore counter for teh window
16488 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16489 						++(*win).warpEventCount;
16490 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
16491 					} else {
16492 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
16493 					}
16494 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
16495 					// user clicked the close button on the window manager
16496 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16497 						XUnlockDisplay(display);
16498 						scope(exit) XLockDisplay(display);
16499 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
16500 					}
16501 
16502 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
16503 					// writeln("HAPPENED");
16504 					// user clicked the close button on the window manager
16505 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16506 						XUnlockDisplay(display);
16507 						scope(exit) XLockDisplay(display);
16508 
16509 						auto setTo = *win;
16510 
16511 						if(win.setRequestedInputFocus !is null) {
16512 							auto s = win.setRequestedInputFocus();
16513 							if(s !is null) {
16514 								setTo = s;
16515 							}
16516 						}
16517 
16518 						assert(setTo !is null);
16519 
16520 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
16521 
16522 						// sdpyPrintDebugString("WM_TAKE_FOCUS ", setTo.impl.window);
16523 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
16524 					}
16525 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
16526 					foreach(nai; NotificationAreaIcon.activeIcons)
16527 						nai.newManager();
16528 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16529 
16530 					bool xDragWindow = true;
16531 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
16532 						//XDefineCursor(display, xDragWindow.impl.window,
16533 							//writeln("XdndStatus ", e.xclient.data.l);
16534 					}
16535 					if(auto dh = win.dropHandler) {
16536 
16537 						static Atom[3] xFormatsBuffer;
16538 						static Atom[] xFormats;
16539 
16540 						void resetXFormats() {
16541 							xFormatsBuffer[] = 0;
16542 							xFormats = xFormatsBuffer[];
16543 						}
16544 
16545 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
16546 							// on Windows it is supposed to return the effect you actually do FIXME
16547 
16548 							auto sourceWindow =  e.xclient.data.l[0];
16549 
16550 							xFormatsBuffer[0] = e.xclient.data.l[2];
16551 							xFormatsBuffer[1] = e.xclient.data.l[3];
16552 							xFormatsBuffer[2] = e.xclient.data.l[4];
16553 
16554 							if(e.xclient.data.l[1] & 1) {
16555 								// can just grab it all but like we don't necessarily need them...
16556 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
16557 							} else {
16558 								int len;
16559 								foreach(fmt; xFormatsBuffer)
16560 									if(fmt) len++;
16561 								xFormats = xFormatsBuffer[0 .. len];
16562 							}
16563 
16564 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
16565 
16566 							dh.dragEnter(&pkg);
16567 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
16568 
16569 							auto pack = e.xclient.data.l[2];
16570 
16571 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
16572 
16573 
16574 							XClientMessageEvent xclient;
16575 
16576 							xclient.type = EventType.ClientMessage;
16577 							xclient.window = e.xclient.data.l[0];
16578 							xclient.message_type = GetAtom!"XdndStatus"(display);
16579 							xclient.format = 32;
16580 							xclient.data.l[0] = win.impl.window;
16581 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
16582 							auto r = result.consistentWithin;
16583 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
16584 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
16585 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
16586 
16587 							XSendEvent(
16588 								display,
16589 								e.xclient.data.l[0],
16590 								false,
16591 								EventMask.NoEventMask,
16592 								cast(XEvent*) &xclient
16593 							);
16594 
16595 
16596 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
16597 							//writeln("XdndLeave");
16598 							// drop cancelled.
16599 							// data.l[0] is the source window
16600 							dh.dragLeave();
16601 
16602 							resetXFormats();
16603 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
16604 							// drop happening, should fetch data, then send finished
16605 							// writeln("XdndDrop");
16606 
16607 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
16608 
16609 							dh.drop(&pkg);
16610 
16611 							resetXFormats();
16612 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
16613 							// writeln("XdndFinished");
16614 
16615 							dh.finish();
16616 						}
16617 
16618 					}
16619 				}
16620 		  break;
16621 		  case EventType.MapNotify:
16622 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
16623 					auto before = (*win)._visible;
16624 					(*win)._visible = true;
16625 					if (!(*win)._visibleForTheFirstTimeCalled) {
16626 						(*win)._visibleForTheFirstTimeCalled = true;
16627 						if ((*win).visibleForTheFirstTime !is null) {
16628 							XUnlockDisplay(display);
16629 							scope(exit) XLockDisplay(display);
16630 							(*win).visibleForTheFirstTime();
16631 						}
16632 					}
16633 					if ((*win).visibilityChanged !is null && before == false) {
16634 						XUnlockDisplay(display);
16635 						scope(exit) XLockDisplay(display);
16636 						(*win).visibilityChanged(true);
16637 					}
16638 				}
16639 		  break;
16640 		  case EventType.UnmapNotify:
16641 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
16642 					auto before = (*win)._visible;
16643 					win._visible = false;
16644 					if (win.visibilityChanged !is null && before == true) {
16645 						XUnlockDisplay(display);
16646 						scope(exit) XLockDisplay(display);
16647 						win.visibilityChanged(false);
16648 					}
16649 			}
16650 		  break;
16651 		  case EventType.DestroyNotify:
16652 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
16653 				if(win.destroyed)
16654 					break; // might get a notification both for itself and from its parent
16655 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
16656 				win._closed = true; // just in case
16657 				win.destroyed = true;
16658 				if (win.xic !is null) {
16659 					XDestroyIC(win.xic);
16660 					win.xic = null; // just in case
16661 				}
16662 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
16663 				bool anyImportant = false;
16664 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
16665 					if(w.beingOpenKeepsAppOpen) {
16666 						anyImportant = true;
16667 						break;
16668 					}
16669 				if(!anyImportant) {
16670 					EventLoop.quitApplication();
16671 					done = true;
16672 				}
16673 			}
16674 			auto window = e.xdestroywindow.window;
16675 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
16676 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
16677 
16678 			version(with_eventloop) {
16679 				if(done) exit();
16680 			}
16681 		  break;
16682 
16683 		  case EventType.MotionNotify:
16684 			MouseEvent mouse;
16685 			auto event = e.xmotion;
16686 
16687 			mouse.type = MouseEventType.motion;
16688 			mouse.x = event.x;
16689 			mouse.y = event.y;
16690 			mouse.modifierState = event.state;
16691 
16692 			mouse.timestamp = event.time;
16693 
16694 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
16695 				mouse.window = *win;
16696 				if (win.warpEventCount > 0) {
16697 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
16698 					--(*win).warpEventCount;
16699 					(*win).mdx(mouse); // so deltas will be correctly updated
16700 				} else {
16701 					win.warpEventCount = 0; // just in case
16702 					(*win).mdx(mouse);
16703 					if((*win).handleMouseEvent) {
16704 						XUnlockDisplay(display);
16705 						scope(exit) XLockDisplay(display);
16706 						(*win).handleMouseEvent(mouse);
16707 					}
16708 				}
16709 			}
16710 
16711 		  	version(with_eventloop)
16712 				send(mouse);
16713 		  break;
16714 		  case EventType.ButtonPress:
16715 		  case EventType.ButtonRelease:
16716 			MouseEvent mouse;
16717 			auto event = e.xbutton;
16718 
16719 			mouse.timestamp = event.time;
16720 
16721 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
16722 			mouse.x = event.x;
16723 			mouse.y = event.y;
16724 
16725 			static Time lastMouseDownTime = 0;
16726 			static int lastMouseDownButton = -1;
16727 
16728 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
16729 			if(e.type == EventType.ButtonPress) {
16730 				lastMouseDownTime = event.time;
16731 				lastMouseDownButton = event.button;
16732 			}
16733 
16734 			switch(event.button) {
16735 				case 1: mouse.button = MouseButton.left; break; // left
16736 				case 2: mouse.button = MouseButton.middle; break; // middle
16737 				case 3: mouse.button = MouseButton.right; break; // right
16738 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
16739 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
16740 				case 6: break; // idk
16741 				case 7: break; // idk
16742 				case 8: mouse.button = MouseButton.backButton; break;
16743 				case 9: mouse.button = MouseButton.forwardButton; break;
16744 				default:
16745 			}
16746 
16747 			// FIXME: double check this
16748 			mouse.modifierState = event.state;
16749 
16750 			//mouse.modifierState = event.detail;
16751 
16752 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
16753 				mouse.window = *win;
16754 				(*win).mdx(mouse);
16755 				if((*win).handleMouseEvent) {
16756 					XUnlockDisplay(display);
16757 					scope(exit) XLockDisplay(display);
16758 					(*win).handleMouseEvent(mouse);
16759 				}
16760 			}
16761 			version(with_eventloop)
16762 				send(mouse);
16763 		  break;
16764 
16765 		  case EventType.KeyPress:
16766 		  case EventType.KeyRelease:
16767 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
16768 			KeyEvent ke;
16769 			ke.pressed = e.type == EventType.KeyPress;
16770 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
16771 
16772 			auto sym = XKeycodeToKeysym(
16773 				XDisplayConnection.get(),
16774 				e.xkey.keycode,
16775 				0);
16776 
16777 			ke.key = cast(Key) sym;//e.xkey.keycode;
16778 
16779 			ke.modifierState = e.xkey.state;
16780 
16781 			// writefln("%x", sym);
16782 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
16783 			int charbuflen = 0; // return value of XwcLookupString
16784 			if (ke.pressed) {
16785 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
16786 				if (win !is null && win.xic !is null) {
16787 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
16788 					Status status;
16789 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
16790 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
16791 				} else {
16792 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
16793 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
16794 					char[16] buffer;
16795 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
16796 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
16797 				}
16798 			}
16799 
16800 			// if there's no char, subst one
16801 			if (charbuflen == 0) {
16802 				switch (sym) {
16803 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
16804 					case 0xff8d: // keypad enter
16805 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
16806 					default : // ignore
16807 				}
16808 			}
16809 
16810 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
16811 				ke.window = *win;
16812 
16813 
16814 				if(win.inputProxy)
16815 					win = &win.inputProxy;
16816 
16817 				// char events are separate since they are on Windows too
16818 				// also, xcompose can generate long char sequences
16819 				// don't send char events if Meta and/or Hyper is pressed
16820 				// TODO: ctrl+char should only send control chars; not yet
16821 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
16822 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
16823 				}
16824 
16825 				dchar[32] charsComingBuffer;
16826 				int charsComingPosition;
16827 				dchar[] charsComing = charsComingBuffer[];
16828 
16829 				if (ke.pressed && charbuflen > 0) {
16830 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
16831 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
16832 						if(charsComingPosition >= charsComing.length)
16833 							charsComing.length = charsComingPosition + 8;
16834 
16835 						charsComing[charsComingPosition++] = ch;
16836 					}
16837 
16838 					charsComing = charsComing[0 .. charsComingPosition];
16839 				} else {
16840 					charsComing = null;
16841 				}
16842 
16843 				ke.charsPossible = charsComing;
16844 
16845 				if (win.handleKeyEvent) {
16846 					XUnlockDisplay(display);
16847 					scope(exit) XLockDisplay(display);
16848 					win.handleKeyEvent(ke);
16849 				}
16850 
16851 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
16852 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
16853 					XUnlockDisplay(display);
16854 					scope(exit) XLockDisplay(display);
16855 					foreach(ch; charsComing)
16856 						win.handleCharEvent(ch);
16857 				}
16858 			}
16859 
16860 			version(with_eventloop)
16861 				send(ke);
16862 		  break;
16863 		  default:
16864 		}
16865 
16866 		return done;
16867 	}
16868 }
16869 
16870 /* *************************************** */
16871 /*      Done with simpledisplay stuff      */
16872 /* *************************************** */
16873 
16874 // Necessary C library bindings follow
16875 version(Windows) {} else
16876 version(Emscripten) {} else
16877 version(X11) {
16878 
16879 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
16880 
16881 // X11 bindings needed here
16882 /*
16883 	A little of this is from the bindings project on
16884 	D Source and some of it is copy/paste from the C
16885 	header.
16886 
16887 	The DSource listing consistently used D's long
16888 	where C used long. That's wrong - C long is 32 bit, so
16889 	it should be int in D. I changed that here.
16890 
16891 	Note:
16892 	This isn't complete, just took what I needed for myself.
16893 */
16894 
16895 import core.stdc.stddef : wchar_t;
16896 
16897 interface XLib {
16898 extern(C) nothrow @nogc {
16899 	char* XResourceManagerString(Display*);
16900 	void XrmInitialize();
16901 	XrmDatabase XrmGetStringDatabase(char* data);
16902 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
16903 
16904 	Cursor XCreateFontCursor(Display*, uint shape);
16905 	int XDefineCursor(Display* display, Window w, Cursor cursor);
16906 	int XUndefineCursor(Display* display, Window w);
16907 
16908 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
16909 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
16910 	int XFreeCursor(Display* display, Cursor cursor);
16911 
16912 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16913 
16914 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16915 
16916 	XVaNestedList XVaCreateNestedList(int unused, ...);
16917 
16918 	char *XKeysymToString(KeySym keysym);
16919 	KeySym XKeycodeToKeysym(
16920 		Display*		/* display */,
16921 		KeyCode		/* keycode */,
16922 		int			/* index */
16923 	);
16924 
16925 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16926 
16927 	int XFree(void*);
16928 	int XDeleteProperty(Display *display, Window w, Atom property);
16929 
16930 	// int XSetCommand(Display*, Window, const char**, int);
16931 
16932 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16933 
16934 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16935 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16936 		*actual_type_return, int *actual_format_return, arch_ulong
16937 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16938 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16939 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16940 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16941 
16942 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16943 
16944 	Window XGetSelectionOwner(Display *display, Atom selection);
16945 
16946 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16947 
16948 	char** XListFonts(Display*, const char*, int, int*);
16949 	void XFreeFontNames(char**);
16950 
16951 	Display* XOpenDisplay(const char*);
16952 	int XCloseDisplay(Display*);
16953 
16954 	int function() XSynchronize(Display*, bool);
16955 	int function() XSetAfterFunction(Display*, int function() proc);
16956 
16957 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16958 
16959 	Bool XSupportsLocale();
16960 	char* XSetLocaleModifiers(const(char)* modifier_list);
16961 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16962 	Status XCloseOM(XOM om);
16963 
16964 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16965 	Status XCloseIM(XIM im);
16966 
16967 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16968 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16969 	Display* XDisplayOfIM(XIM im);
16970 	char* XLocaleOfIM(XIM im);
16971 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16972 	void XDestroyIC(XIC ic);
16973 	void XSetICFocus(XIC ic);
16974 	void XUnsetICFocus(XIC ic);
16975 	//wchar_t* XwcResetIC(XIC ic);
16976 	char* XmbResetIC(XIC ic);
16977 	char* Xutf8ResetIC(XIC ic);
16978 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16979 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16980 	XIM XIMOfIC(XIC ic);
16981 
16982 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16983 
16984 
16985 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16986 	int XFreeFont(Display *display, XFontStruct *font_struct);
16987 	int XSetFont(Display* display, GC gc, Font font);
16988 	int XTextWidth(XFontStruct*, scope const char*, int);
16989 
16990 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16991 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16992 
16993 	Window XCreateSimpleWindow(
16994 		Display*	/* display */,
16995 		Window		/* parent */,
16996 		int			/* x */,
16997 		int			/* y */,
16998 		uint		/* width */,
16999 		uint		/* height */,
17000 		uint		/* border_width */,
17001 		uint		/* border */,
17002 		uint		/* background */
17003 	);
17004 	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);
17005 
17006 	int XReparentWindow(Display*, Window, Window, int, int);
17007 	int XClearWindow(Display*, Window);
17008 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
17009 	int XMoveWindow(Display*, Window, int, int);
17010 	int XResizeWindow(Display *display, Window w, uint width, uint height);
17011 
17012 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
17013 
17014 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
17015 
17016 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
17017 
17018 	XImage *XCreateImage(
17019 		Display*		/* display */,
17020 		Visual*		/* visual */,
17021 		uint	/* depth */,
17022 		int			/* format */,
17023 		int			/* offset */,
17024 		ubyte*		/* data */,
17025 		uint	/* width */,
17026 		uint	/* height */,
17027 		int			/* bitmap_pad */,
17028 		int			/* bytes_per_line */
17029 	);
17030 
17031 	Status XInitImage (XImage* image);
17032 
17033 	Atom XInternAtom(
17034 		Display*		/* display */,
17035 		const char*	/* atom_name */,
17036 		Bool		/* only_if_exists */
17037 	);
17038 
17039 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
17040 	char* XGetAtomName(Display*, Atom);
17041 	Status XGetAtomNames(Display*, Atom*, int count, char**);
17042 
17043 	int XPutImage(
17044 		Display*	/* display */,
17045 		Drawable	/* d */,
17046 		GC			/* gc */,
17047 		XImage*	/* image */,
17048 		int			/* src_x */,
17049 		int			/* src_y */,
17050 		int			/* dest_x */,
17051 		int			/* dest_y */,
17052 		uint		/* width */,
17053 		uint		/* height */
17054 	);
17055 
17056 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
17057 
17058 
17059 	int XDestroyWindow(
17060 		Display*	/* display */,
17061 		Window		/* w */
17062 	);
17063 
17064 	int XDestroyImage(XImage*);
17065 
17066 	int XSelectInput(
17067 		Display*	/* display */,
17068 		Window		/* w */,
17069 		EventMask	/* event_mask */
17070 	);
17071 
17072 	int XMapWindow(
17073 		Display*	/* display */,
17074 		Window		/* w */
17075 	);
17076 
17077 	Status XIconifyWindow(Display*, Window, int);
17078 	int XMapRaised(Display*, Window);
17079 	int XMapSubwindows(Display*, Window);
17080 
17081 	int XNextEvent(
17082 		Display*	/* display */,
17083 		XEvent*		/* event_return */
17084 	);
17085 
17086 	int XMaskEvent(Display*, arch_long, XEvent*);
17087 
17088 	Bool XFilterEvent(XEvent *event, Window window);
17089 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
17090 
17091 	Status XSetWMProtocols(
17092 		Display*	/* display */,
17093 		Window		/* w */,
17094 		Atom*		/* protocols */,
17095 		int			/* count */
17096 	);
17097 
17098 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
17099 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
17100 
17101 
17102 	Status XInitThreads();
17103 	void XLockDisplay (Display* display);
17104 	void XUnlockDisplay (Display* display);
17105 
17106 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
17107 
17108 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
17109 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
17110 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
17111 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
17112 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
17113 
17114 
17115 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
17116 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
17117 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
17118 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
17119 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
17120 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
17121 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
17122 	int XDrawPoint(Display*, Drawable, GC, int, int);
17123 	int XSetForeground(Display*, GC, uint);
17124 	int XSetBackground(Display*, GC, uint);
17125 
17126 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
17127 	void XFreeFontSet(Display*, XFontSet);
17128 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
17129 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
17130 
17131 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
17132 
17133 
17134 //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);
17135 
17136 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
17137 	int XSetFunction(Display*, GC, int);
17138 
17139 	GC XCreateGC(Display*, Drawable, uint, void*);
17140 	int XCopyGC(Display*, GC, uint, GC);
17141 	int XFreeGC(Display*, GC);
17142 
17143 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
17144 	bool XCheckMaskEvent(Display*, int, XEvent*);
17145 
17146 	int XPending(Display*);
17147 	int XEventsQueued(Display* display, int mode);
17148 
17149 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
17150 	int XFreePixmap(Display*, Pixmap);
17151 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
17152 	int XFlush(Display*);
17153 	int XBell(Display*, int);
17154 	int XSync(Display*, bool);
17155 
17156 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
17157 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
17158 
17159 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
17160 	int XUngrabKeyboard(Display*, Time);
17161 
17162 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
17163 
17164 	KeySym XStringToKeysym(const char *string);
17165 
17166 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
17167 
17168 	Window XDefaultRootWindow(Display*);
17169 
17170 	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);
17171 
17172 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
17173 
17174 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
17175 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
17176 
17177 	Status XAllocColor(Display*, Colormap, XColor*);
17178 
17179 	int XWithdrawWindow(Display*, Window, int);
17180 	int XUnmapWindow(Display*, Window);
17181 	int XLowerWindow(Display*, Window);
17182 	int XRaiseWindow(Display*, Window);
17183 
17184 	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);
17185 	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);
17186 
17187 	int XGetInputFocus(Display*, Window*, int*);
17188 	int XSetInputFocus(Display*, Window, int, Time);
17189 
17190 	XErrorHandler XSetErrorHandler(XErrorHandler);
17191 
17192 	int XGetErrorText(Display*, int, char*, int);
17193 
17194 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
17195 
17196 
17197 	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);
17198 	int XUngrabPointer(Display *display, Time time);
17199 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
17200 
17201 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
17202 
17203 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
17204 	int XSetClipMask(Display*, GC, Pixmap);
17205 	int XSetClipOrigin(Display*, GC, int, int);
17206 
17207 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
17208 
17209 	void XSetWMName(Display*, Window, XTextProperty*);
17210 	Status XGetWMName(Display*, Window, XTextProperty*);
17211 	int XStoreName(Display* display, Window w, const(char)* window_name);
17212 
17213 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
17214 
17215 }
17216 }
17217 
17218 interface Xext {
17219 extern(C) nothrow @nogc {
17220 	Status XShmAttach(Display*, XShmSegmentInfo*);
17221 	Status XShmDetach(Display*, XShmSegmentInfo*);
17222 	Status XShmPutImage(
17223 		Display*            /* dpy */,
17224 		Drawable            /* d */,
17225 		GC                  /* gc */,
17226 		XImage*             /* image */,
17227 		int                 /* src_x */,
17228 		int                 /* src_y */,
17229 		int                 /* dst_x */,
17230 		int                 /* dst_y */,
17231 		uint        /* src_width */,
17232 		uint        /* src_height */,
17233 		Bool                /* send_event */
17234 	);
17235 
17236 	Status XShmQueryExtension(Display*);
17237 
17238 	XImage *XShmCreateImage(
17239 		Display*            /* dpy */,
17240 		Visual*             /* visual */,
17241 		uint        /* depth */,
17242 		int                 /* format */,
17243 		char*               /* data */,
17244 		XShmSegmentInfo*    /* shminfo */,
17245 		uint        /* width */,
17246 		uint        /* height */
17247 	);
17248 
17249 	Pixmap XShmCreatePixmap(
17250 		Display*            /* dpy */,
17251 		Drawable            /* d */,
17252 		char*               /* data */,
17253 		XShmSegmentInfo*    /* shminfo */,
17254 		uint        /* width */,
17255 		uint        /* height */,
17256 		uint        /* depth */
17257 	);
17258 
17259 }
17260 }
17261 
17262 	// this requires -lXpm
17263 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
17264 
17265 
17266 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
17267 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
17268 shared static this() {
17269 	xlib.loadDynamicLibrary();
17270 	xext.loadDynamicLibrary();
17271 }
17272 
17273 
17274 extern(C) nothrow @nogc {
17275 
17276 alias XrmDatabase = void*;
17277 struct XrmValue {
17278 	uint size;
17279 	void* addr;
17280 }
17281 
17282 struct XVisualInfo {
17283 	Visual* visual;
17284 	VisualID visualid;
17285 	int screen;
17286 	uint depth;
17287 	int c_class;
17288 	c_ulong red_mask;
17289 	c_ulong green_mask;
17290 	c_ulong blue_mask;
17291 	int colormap_size;
17292 	int bits_per_rgb;
17293 }
17294 
17295 enum VisualNoMask=	0x0;
17296 enum VisualIDMask=	0x1;
17297 enum VisualScreenMask=0x2;
17298 enum VisualDepthMask=	0x4;
17299 enum VisualClassMask=	0x8;
17300 enum VisualRedMaskMask=0x10;
17301 enum VisualGreenMaskMask=0x20;
17302 enum VisualBlueMaskMask=0x40;
17303 enum VisualColormapSizeMask=0x80;
17304 enum VisualBitsPerRGBMask=0x100;
17305 enum VisualAllMask=	0x1FF;
17306 
17307 enum AnyKey = 0;
17308 enum AnyModifier = 1 << 15;
17309 
17310 // XIM and other crap
17311 struct _XOM {}
17312 struct _XIM {}
17313 struct _XIC {}
17314 alias XOM = _XOM*;
17315 alias XIM = _XIM*;
17316 alias XIC = _XIC*;
17317 
17318 alias XVaNestedList = void*;
17319 
17320 alias XIMStyle = arch_ulong;
17321 enum : arch_ulong {
17322 	XIMPreeditArea      = 0x0001,
17323 	XIMPreeditCallbacks = 0x0002,
17324 	XIMPreeditPosition  = 0x0004,
17325 	XIMPreeditNothing   = 0x0008,
17326 	XIMPreeditNone      = 0x0010,
17327 	XIMStatusArea       = 0x0100,
17328 	XIMStatusCallbacks  = 0x0200,
17329 	XIMStatusNothing    = 0x0400,
17330 	XIMStatusNone       = 0x0800,
17331 }
17332 
17333 
17334 /* X Shared Memory Extension functions */
17335 	//pragma(lib, "Xshm");
17336 	alias arch_ulong ShmSeg;
17337 	struct XShmSegmentInfo {
17338 		ShmSeg shmseg;
17339 		int shmid;
17340 		ubyte* shmaddr;
17341 		Bool readOnly;
17342 	}
17343 
17344 	// and the necessary OS functions
17345 	int shmget(int, size_t, int);
17346 	void* shmat(int, scope const void*, int);
17347 	int shmdt(scope const void*);
17348 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
17349 
17350 	enum IPC_PRIVATE = 0;
17351 	enum IPC_CREAT = 512;
17352 	enum IPC_RMID = 0;
17353 
17354 /* MIT-SHM end */
17355 
17356 
17357 enum MappingType:int {
17358 	MappingModifier		=0,
17359 	MappingKeyboard		=1,
17360 	MappingPointer		=2
17361 }
17362 
17363 /* ImageFormat -- PutImage, GetImage */
17364 enum ImageFormat:int {
17365 	XYBitmap	=0,	/* depth 1, XYFormat */
17366 	XYPixmap	=1,	/* depth == drawable depth */
17367 	ZPixmap	=2	/* depth == drawable depth */
17368 }
17369 
17370 enum ModifierName:int {
17371 	ShiftMapIndex	=0,
17372 	LockMapIndex	=1,
17373 	ControlMapIndex	=2,
17374 	Mod1MapIndex	=3,
17375 	Mod2MapIndex	=4,
17376 	Mod3MapIndex	=5,
17377 	Mod4MapIndex	=6,
17378 	Mod5MapIndex	=7
17379 }
17380 
17381 enum ButtonMask:int {
17382 	Button1Mask	=1<<8,
17383 	Button2Mask	=1<<9,
17384 	Button3Mask	=1<<10,
17385 	Button4Mask	=1<<11,
17386 	Button5Mask	=1<<12,
17387 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17388 }
17389 
17390 enum KeyOrButtonMask:uint {
17391 	ShiftMask	=1<<0,
17392 	LockMask	=1<<1,
17393 	ControlMask	=1<<2,
17394 	Mod1Mask	=1<<3,
17395 	Mod2Mask	=1<<4,
17396 	Mod3Mask	=1<<5,
17397 	Mod4Mask	=1<<6,
17398 	Mod5Mask	=1<<7,
17399 	Button1Mask	=1<<8,
17400 	Button2Mask	=1<<9,
17401 	Button3Mask	=1<<10,
17402 	Button4Mask	=1<<11,
17403 	Button5Mask	=1<<12,
17404 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
17405 }
17406 
17407 enum ButtonName:int {
17408 	Button1	=1,
17409 	Button2	=2,
17410 	Button3	=3,
17411 	Button4	=4,
17412 	Button5	=5
17413 }
17414 
17415 /* Notify modes */
17416 enum NotifyModes:int
17417 {
17418 	NotifyNormal		=0,
17419 	NotifyGrab			=1,
17420 	NotifyUngrab		=2,
17421 	NotifyWhileGrabbed	=3
17422 }
17423 enum NotifyHint = 1;	/* for MotionNotify events */
17424 
17425 /* Notify detail */
17426 enum NotifyDetail:int
17427 {
17428 	NotifyAncestor			=0,
17429 	NotifyVirtual			=1,
17430 	NotifyInferior			=2,
17431 	NotifyNonlinear			=3,
17432 	NotifyNonlinearVirtual	=4,
17433 	NotifyPointer			=5,
17434 	NotifyPointerRoot		=6,
17435 	NotifyDetailNone		=7
17436 }
17437 
17438 /* Visibility notify */
17439 
17440 enum VisibilityNotify:int
17441 {
17442 VisibilityUnobscured		=0,
17443 VisibilityPartiallyObscured	=1,
17444 VisibilityFullyObscured		=2
17445 }
17446 
17447 
17448 enum WindowStackingMethod:int
17449 {
17450 	Above		=0,
17451 	Below		=1,
17452 	TopIf		=2,
17453 	BottomIf	=3,
17454 	Opposite	=4
17455 }
17456 
17457 /* Circulation request */
17458 enum CirculationRequest:int
17459 {
17460 	PlaceOnTop		=0,
17461 	PlaceOnBottom	=1
17462 }
17463 
17464 enum PropertyNotification:int
17465 {
17466 	PropertyNewValue	=0,
17467 	PropertyDelete		=1
17468 }
17469 
17470 enum ColorMapNotification:int
17471 {
17472 	ColormapUninstalled	=0,
17473 	ColormapInstalled		=1
17474 }
17475 
17476 
17477 	struct _XPrivate {}
17478 	struct _XrmHashBucketRec {}
17479 
17480 	alias void* XPointer;
17481 	alias void* XExtData;
17482 
17483 	version( X86_64 ) {
17484 		alias ulong XID;
17485 		alias ulong arch_ulong;
17486 		alias long arch_long;
17487 	} else version (AArch64) {
17488 		alias ulong XID;
17489 		alias ulong arch_ulong;
17490 		alias long arch_long;
17491 	} else {
17492 		alias uint XID;
17493 		alias uint arch_ulong;
17494 		alias int arch_long;
17495 	}
17496 
17497 	alias XID Window;
17498 	alias XID Drawable;
17499 	alias XID Pixmap;
17500 
17501 	alias arch_ulong Atom;
17502 	alias int Bool;
17503 	alias Display XDisplay;
17504 
17505 	alias int ByteOrder;
17506 	alias arch_ulong Time;
17507 	alias void ScreenFormat;
17508 
17509 	struct XImage {
17510 		int width, height;			/* size of image */
17511 		int xoffset;				/* number of pixels offset in X direction */
17512 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
17513 		void *data;					/* pointer to image data */
17514 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
17515 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
17516 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
17517 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
17518 		int depth;					/* depth of image */
17519 		int bytes_per_line;			/* accelarator to next line */
17520 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
17521 		arch_ulong red_mask;	/* bits in z arrangment */
17522 		arch_ulong green_mask;
17523 		arch_ulong blue_mask;
17524 		XPointer obdata;			/* hook for the object routines to hang on */
17525 		static struct F {				/* image manipulation routines */
17526 			XImage* function(
17527 				XDisplay* 			/* display */,
17528 				Visual*				/* visual */,
17529 				uint				/* depth */,
17530 				int					/* format */,
17531 				int					/* offset */,
17532 				ubyte*				/* data */,
17533 				uint				/* width */,
17534 				uint				/* height */,
17535 				int					/* bitmap_pad */,
17536 				int					/* bytes_per_line */) create_image;
17537 			int function(XImage *) destroy_image;
17538 			arch_ulong function(XImage *, int, int) get_pixel;
17539 			int function(XImage *, int, int, arch_ulong) put_pixel;
17540 			XImage* function(XImage *, int, int, uint, uint) sub_image;
17541 			int function(XImage *, arch_long) add_pixel;
17542 		}
17543 		F f;
17544 	}
17545 	version(X86_64) static assert(XImage.sizeof == 136);
17546 	else version(X86) static assert(XImage.sizeof == 88);
17547 
17548 struct XCharStruct {
17549 	short       lbearing;       /* origin to left edge of raster */
17550 	short       rbearing;       /* origin to right edge of raster */
17551 	short       width;          /* advance to next char's origin */
17552 	short       ascent;         /* baseline to top edge of raster */
17553 	short       descent;        /* baseline to bottom edge of raster */
17554 	ushort attributes;  /* per char flags (not predefined) */
17555 }
17556 
17557 /*
17558  * To allow arbitrary information with fonts, there are additional properties
17559  * returned.
17560  */
17561 struct XFontProp {
17562 	Atom name;
17563 	arch_ulong card32;
17564 }
17565 
17566 alias Atom Font;
17567 
17568 struct XFontStruct {
17569 	XExtData *ext_data;           /* Hook for extension to hang data */
17570 	Font fid;                     /* Font ID for this font */
17571 	uint direction;           /* Direction the font is painted */
17572 	uint min_char_or_byte2;   /* First character */
17573 	uint max_char_or_byte2;   /* Last character */
17574 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
17575 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
17576 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
17577 	uint default_char;        /* Char to print for undefined character */
17578 	int n_properties;             /* How many properties there are */
17579 	XFontProp *properties;        /* Pointer to array of additional properties*/
17580 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
17581 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
17582 	XCharStruct *per_char;        /* first_char to last_char information */
17583 	int ascent;                   /* Max extent above baseline for spacing */
17584 	int descent;                  /* Max descent below baseline for spacing */
17585 }
17586 
17587 
17588 /*
17589  * Definitions of specific events.
17590  */
17591 struct XKeyEvent
17592 {
17593 	int type;			/* of event */
17594 	arch_ulong serial;		/* # of last request processed by server */
17595 	Bool send_event;	/* true if this came from a SendEvent request */
17596 	Display *display;	/* Display the event was read from */
17597 	Window window;	        /* "event" window it is reported relative to */
17598 	Window root;	        /* root window that the event occurred on */
17599 	Window subwindow;	/* child window */
17600 	Time time;		/* milliseconds */
17601 	int x, y;		/* pointer x, y coordinates in event window */
17602 	int x_root, y_root;	/* coordinates relative to root */
17603 	KeyOrButtonMask state;	/* key or button mask */
17604 	uint keycode;	/* detail */
17605 	Bool same_screen;	/* same screen flag */
17606 }
17607 version(X86_64) static assert(XKeyEvent.sizeof == 96);
17608 alias XKeyEvent XKeyPressedEvent;
17609 alias XKeyEvent XKeyReleasedEvent;
17610 
17611 struct XButtonEvent
17612 {
17613 	int type;		/* of event */
17614 	arch_ulong serial;	/* # of last request processed by server */
17615 	Bool send_event;	/* true if this came from a SendEvent request */
17616 	Display *display;	/* Display the event was read from */
17617 	Window window;	        /* "event" window it is reported relative to */
17618 	Window root;	        /* root window that the event occurred on */
17619 	Window subwindow;	/* child window */
17620 	Time time;		/* milliseconds */
17621 	int x, y;		/* pointer x, y coordinates in event window */
17622 	int x_root, y_root;	/* coordinates relative to root */
17623 	KeyOrButtonMask state;	/* key or button mask */
17624 	uint button;	/* detail */
17625 	Bool same_screen;	/* same screen flag */
17626 }
17627 alias XButtonEvent XButtonPressedEvent;
17628 alias XButtonEvent XButtonReleasedEvent;
17629 
17630 struct XMotionEvent{
17631 	int type;		/* of event */
17632 	arch_ulong serial;	/* # of last request processed by server */
17633 	Bool send_event;	/* true if this came from a SendEvent request */
17634 	Display *display;	/* Display the event was read from */
17635 	Window window;	        /* "event" window reported relative to */
17636 	Window root;	        /* root window that the event occurred on */
17637 	Window subwindow;	/* child window */
17638 	Time time;		/* milliseconds */
17639 	int x, y;		/* pointer x, y coordinates in event window */
17640 	int x_root, y_root;	/* coordinates relative to root */
17641 	KeyOrButtonMask state;	/* key or button mask */
17642 	byte is_hint;		/* detail */
17643 	Bool same_screen;	/* same screen flag */
17644 }
17645 alias XMotionEvent XPointerMovedEvent;
17646 
17647 struct XCrossingEvent{
17648 	int type;		/* of event */
17649 	arch_ulong serial;	/* # of last request processed by server */
17650 	Bool send_event;	/* true if this came from a SendEvent request */
17651 	Display *display;	/* Display the event was read from */
17652 	Window window;	        /* "event" window reported relative to */
17653 	Window root;	        /* root window that the event occurred on */
17654 	Window subwindow;	/* child window */
17655 	Time time;		/* milliseconds */
17656 	int x, y;		/* pointer x, y coordinates in event window */
17657 	int x_root, y_root;	/* coordinates relative to root */
17658 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
17659 	NotifyDetail detail;
17660 	/*
17661 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17662 	 * NotifyNonlinear,NotifyNonlinearVirtual
17663 	 */
17664 	Bool same_screen;	/* same screen flag */
17665 	Bool focus;		/* Boolean focus */
17666 	KeyOrButtonMask state;	/* key or button mask */
17667 }
17668 alias XCrossingEvent XEnterWindowEvent;
17669 alias XCrossingEvent XLeaveWindowEvent;
17670 
17671 struct XFocusChangeEvent{
17672 	int type;		/* FocusIn or FocusOut */
17673 	arch_ulong serial;	/* # of last request processed by server */
17674 	Bool send_event;	/* true if this came from a SendEvent request */
17675 	Display *display;	/* Display the event was read from */
17676 	Window window;		/* window of event */
17677 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
17678 				   NotifyGrab, NotifyUngrab */
17679 	NotifyDetail detail;
17680 	/*
17681 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
17682 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
17683 	 * NotifyPointerRoot, NotifyDetailNone
17684 	 */
17685 }
17686 alias XFocusChangeEvent XFocusInEvent;
17687 alias XFocusChangeEvent XFocusOutEvent;
17688 
17689 enum CWBackPixmap              = (1L<<0);
17690 enum CWBackPixel               = (1L<<1);
17691 enum CWBorderPixmap            = (1L<<2);
17692 enum CWBorderPixel             = (1L<<3);
17693 enum CWBitGravity              = (1L<<4);
17694 enum CWWinGravity              = (1L<<5);
17695 enum CWBackingStore            = (1L<<6);
17696 enum CWBackingPlanes           = (1L<<7);
17697 enum CWBackingPixel            = (1L<<8);
17698 enum CWOverrideRedirect        = (1L<<9);
17699 enum CWSaveUnder               = (1L<<10);
17700 enum CWEventMask               = (1L<<11);
17701 enum CWDontPropagate           = (1L<<12);
17702 enum CWColormap                = (1L<<13);
17703 enum CWCursor                  = (1L<<14);
17704 
17705 struct XWindowAttributes {
17706 	int x, y;			/* location of window */
17707 	int width, height;		/* width and height of window */
17708 	int border_width;		/* border width of window */
17709 	int depth;			/* depth of window */
17710 	Visual *visual;			/* the associated visual structure */
17711 	Window root;			/* root of screen containing window */
17712 	int class_;			/* InputOutput, InputOnly*/
17713 	int bit_gravity;		/* one of the bit gravity values */
17714 	int win_gravity;		/* one of the window gravity values */
17715 	int backing_store;		/* NotUseful, WhenMapped, Always */
17716 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
17717 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
17718 	Bool save_under;		/* boolean, should bits under be saved? */
17719 	Colormap colormap;		/* color map to be associated with window */
17720 	Bool map_installed;		/* boolean, is color map currently installed*/
17721 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
17722 	arch_long all_event_masks;		/* set of events all people have interest in*/
17723 	arch_long your_event_mask;		/* my event mask */
17724 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
17725 	Bool override_redirect;		/* boolean value for override-redirect */
17726 	Screen *screen;			/* back pointer to correct screen */
17727 }
17728 
17729 enum IsUnmapped = 0;
17730 enum IsUnviewable = 1;
17731 enum IsViewable = 2;
17732 
17733 struct XSetWindowAttributes {
17734 	Pixmap background_pixmap;/* background, None, or ParentRelative */
17735 	arch_ulong background_pixel;/* background pixel */
17736 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
17737 	arch_ulong border_pixel;/* border pixel value */
17738 	int bit_gravity;         /* one of bit gravity values */
17739 	int win_gravity;         /* one of the window gravity values */
17740 	int backing_store;       /* NotUseful, WhenMapped, Always */
17741 	arch_ulong backing_planes;/* planes to be preserved if possible */
17742 	arch_ulong backing_pixel;/* value to use in restoring planes */
17743 	Bool save_under;         /* should bits under be saved? (popups) */
17744 	arch_long event_mask;         /* set of events that should be saved */
17745 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
17746 	Bool override_redirect;  /* boolean value for override_redirect */
17747 	Colormap colormap;       /* color map to be associated with window */
17748 	Cursor cursor;           /* cursor to be displayed (or None) */
17749 }
17750 
17751 
17752 alias int Status;
17753 
17754 
17755 enum EventMask:int
17756 {
17757 	NoEventMask				=0,
17758 	KeyPressMask			=1<<0,
17759 	KeyReleaseMask			=1<<1,
17760 	ButtonPressMask			=1<<2,
17761 	ButtonReleaseMask		=1<<3,
17762 	EnterWindowMask			=1<<4,
17763 	LeaveWindowMask			=1<<5,
17764 	PointerMotionMask		=1<<6,
17765 	PointerMotionHintMask	=1<<7,
17766 	Button1MotionMask		=1<<8,
17767 	Button2MotionMask		=1<<9,
17768 	Button3MotionMask		=1<<10,
17769 	Button4MotionMask		=1<<11,
17770 	Button5MotionMask		=1<<12,
17771 	ButtonMotionMask		=1<<13,
17772 	KeymapStateMask		=1<<14,
17773 	ExposureMask			=1<<15,
17774 	VisibilityChangeMask	=1<<16,
17775 	StructureNotifyMask		=1<<17,
17776 	ResizeRedirectMask		=1<<18,
17777 	SubstructureNotifyMask	=1<<19,
17778 	SubstructureRedirectMask=1<<20,
17779 	FocusChangeMask			=1<<21,
17780 	PropertyChangeMask		=1<<22,
17781 	ColormapChangeMask		=1<<23,
17782 	OwnerGrabButtonMask		=1<<24
17783 }
17784 
17785 struct MwmHints {
17786 	c_ulong flags;
17787 	c_ulong functions;
17788 	c_ulong decorations;
17789 	c_long input_mode;
17790 	c_ulong status;
17791 }
17792 
17793 enum {
17794 	MWM_HINTS_FUNCTIONS = (1L << 0),
17795 	MWM_HINTS_DECORATIONS =  (1L << 1),
17796 
17797 	MWM_FUNC_ALL = (1L << 0),
17798 	MWM_FUNC_RESIZE = (1L << 1),
17799 	MWM_FUNC_MOVE = (1L << 2),
17800 	MWM_FUNC_MINIMIZE = (1L << 3),
17801 	MWM_FUNC_MAXIMIZE = (1L << 4),
17802 	MWM_FUNC_CLOSE = (1L << 5),
17803 
17804 	MWM_DECOR_ALL = (1L << 0),
17805 	MWM_DECOR_BORDER = (1L << 1),
17806 	MWM_DECOR_RESIZEH = (1L << 2),
17807 	MWM_DECOR_TITLE = (1L << 3),
17808 	MWM_DECOR_MENU = (1L << 4),
17809 	MWM_DECOR_MINIMIZE = (1L << 5),
17810 	MWM_DECOR_MAXIMIZE = (1L << 6),
17811 }
17812 
17813 import core.stdc.config : c_long, c_ulong;
17814 
17815 	/* Size hints mask bits */
17816 
17817 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
17818 	enum   USSize      = (1L << 1)          /* user specified width, height */;
17819 	enum   PPosition   = (1L << 2)          /* program specified position */;
17820 	enum   PSize       = (1L << 3)          /* program specified size */;
17821 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
17822 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
17823 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
17824 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
17825 	enum   PBaseSize   = (1L << 8);
17826 	enum   PWinGravity = (1L << 9);
17827 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
17828 	struct XSizeHints {
17829 		arch_long flags;         /* marks which fields in this structure are defined */
17830 		int x, y;           /* Obsolete */
17831 		int width, height;  /* Obsolete */
17832 		int min_width, min_height;
17833 		int max_width, max_height;
17834 		int width_inc, height_inc;
17835 		struct Aspect {
17836 			int x;       /* numerator */
17837 			int y;       /* denominator */
17838 		}
17839 
17840 		Aspect min_aspect;
17841 		Aspect max_aspect;
17842 		int base_width, base_height;
17843 		int win_gravity;
17844 		/* this structure may be extended in the future */
17845 	}
17846 
17847 
17848 
17849 enum EventType:int
17850 {
17851 	KeyPress			=2,
17852 	KeyRelease			=3,
17853 	ButtonPress			=4,
17854 	ButtonRelease		=5,
17855 	MotionNotify		=6,
17856 	EnterNotify			=7,
17857 	LeaveNotify			=8,
17858 	FocusIn				=9,
17859 	FocusOut			=10,
17860 	KeymapNotify		=11,
17861 	Expose				=12,
17862 	GraphicsExpose		=13,
17863 	NoExpose			=14,
17864 	VisibilityNotify	=15,
17865 	CreateNotify		=16,
17866 	DestroyNotify		=17,
17867 	UnmapNotify		=18,
17868 	MapNotify			=19,
17869 	MapRequest			=20,
17870 	ReparentNotify		=21,
17871 	ConfigureNotify		=22,
17872 	ConfigureRequest	=23,
17873 	GravityNotify		=24,
17874 	ResizeRequest		=25,
17875 	CirculateNotify		=26,
17876 	CirculateRequest	=27,
17877 	PropertyNotify		=28,
17878 	SelectionClear		=29,
17879 	SelectionRequest	=30,
17880 	SelectionNotify		=31,
17881 	ColormapNotify		=32,
17882 	ClientMessage		=33,
17883 	MappingNotify		=34,
17884 	LASTEvent			=35	/* must be bigger than any event # */
17885 }
17886 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
17887 struct XKeymapEvent
17888 {
17889 	int type;
17890 	arch_ulong serial;	/* # of last request processed by server */
17891 	Bool send_event;	/* true if this came from a SendEvent request */
17892 	Display *display;	/* Display the event was read from */
17893 	Window window;
17894 	byte[32] key_vector;
17895 }
17896 
17897 struct XExposeEvent
17898 {
17899 	int type;
17900 	arch_ulong serial;	/* # of last request processed by server */
17901 	Bool send_event;	/* true if this came from a SendEvent request */
17902 	Display *display;	/* Display the event was read from */
17903 	Window window;
17904 	int x, y;
17905 	int width, height;
17906 	int count;		/* if non-zero, at least this many more */
17907 }
17908 
17909 struct XGraphicsExposeEvent{
17910 	int type;
17911 	arch_ulong serial;	/* # of last request processed by server */
17912 	Bool send_event;	/* true if this came from a SendEvent request */
17913 	Display *display;	/* Display the event was read from */
17914 	Drawable drawable;
17915 	int x, y;
17916 	int width, height;
17917 	int count;		/* if non-zero, at least this many more */
17918 	int major_code;		/* core is CopyArea or CopyPlane */
17919 	int minor_code;		/* not defined in the core */
17920 }
17921 
17922 struct XNoExposeEvent{
17923 	int type;
17924 	arch_ulong serial;	/* # of last request processed by server */
17925 	Bool send_event;	/* true if this came from a SendEvent request */
17926 	Display *display;	/* Display the event was read from */
17927 	Drawable drawable;
17928 	int major_code;		/* core is CopyArea or CopyPlane */
17929 	int minor_code;		/* not defined in the core */
17930 }
17931 
17932 struct XVisibilityEvent{
17933 	int type;
17934 	arch_ulong serial;	/* # of last request processed by server */
17935 	Bool send_event;	/* true if this came from a SendEvent request */
17936 	Display *display;	/* Display the event was read from */
17937 	Window window;
17938 	VisibilityNotify state;		/* Visibility state */
17939 }
17940 
17941 struct XCreateWindowEvent{
17942 	int type;
17943 	arch_ulong serial;	/* # of last request processed by server */
17944 	Bool send_event;	/* true if this came from a SendEvent request */
17945 	Display *display;	/* Display the event was read from */
17946 	Window parent;		/* parent of the window */
17947 	Window window;		/* window id of window created */
17948 	int x, y;		/* window location */
17949 	int width, height;	/* size of window */
17950 	int border_width;	/* border width */
17951 	Bool override_redirect;	/* creation should be overridden */
17952 }
17953 
17954 struct XDestroyWindowEvent
17955 {
17956 	int type;
17957 	arch_ulong serial;		/* # of last request processed by server */
17958 	Bool send_event;	/* true if this came from a SendEvent request */
17959 	Display *display;	/* Display the event was read from */
17960 	Window event;
17961 	Window window;
17962 }
17963 
17964 struct XUnmapEvent
17965 {
17966 	int type;
17967 	arch_ulong serial;		/* # of last request processed by server */
17968 	Bool send_event;	/* true if this came from a SendEvent request */
17969 	Display *display;	/* Display the event was read from */
17970 	Window event;
17971 	Window window;
17972 	Bool from_configure;
17973 }
17974 
17975 struct XMapEvent
17976 {
17977 	int type;
17978 	arch_ulong serial;		/* # of last request processed by server */
17979 	Bool send_event;	/* true if this came from a SendEvent request */
17980 	Display *display;	/* Display the event was read from */
17981 	Window event;
17982 	Window window;
17983 	Bool override_redirect;	/* Boolean, is override set... */
17984 }
17985 
17986 struct XMapRequestEvent
17987 {
17988 	int type;
17989 	arch_ulong serial;	/* # of last request processed by server */
17990 	Bool send_event;	/* true if this came from a SendEvent request */
17991 	Display *display;	/* Display the event was read from */
17992 	Window parent;
17993 	Window window;
17994 }
17995 
17996 struct XReparentEvent
17997 {
17998 	int type;
17999 	arch_ulong serial;	/* # of last request processed by server */
18000 	Bool send_event;	/* true if this came from a SendEvent request */
18001 	Display *display;	/* Display the event was read from */
18002 	Window event;
18003 	Window window;
18004 	Window parent;
18005 	int x, y;
18006 	Bool override_redirect;
18007 }
18008 
18009 struct XConfigureEvent
18010 {
18011 	int type;
18012 	arch_ulong serial;	/* # of last request processed by server */
18013 	Bool send_event;	/* true if this came from a SendEvent request */
18014 	Display *display;	/* Display the event was read from */
18015 	Window event;
18016 	Window window;
18017 	int x, y;
18018 	int width, height;
18019 	int border_width;
18020 	Window above;
18021 	Bool override_redirect;
18022 }
18023 
18024 struct XGravityEvent
18025 {
18026 	int type;
18027 	arch_ulong serial;	/* # of last request processed by server */
18028 	Bool send_event;	/* true if this came from a SendEvent request */
18029 	Display *display;	/* Display the event was read from */
18030 	Window event;
18031 	Window window;
18032 	int x, y;
18033 }
18034 
18035 struct XResizeRequestEvent
18036 {
18037 	int type;
18038 	arch_ulong serial;	/* # of last request processed by server */
18039 	Bool send_event;	/* true if this came from a SendEvent request */
18040 	Display *display;	/* Display the event was read from */
18041 	Window window;
18042 	int width, height;
18043 }
18044 
18045 struct  XConfigureRequestEvent
18046 {
18047 	int type;
18048 	arch_ulong serial;	/* # of last request processed by server */
18049 	Bool send_event;	/* true if this came from a SendEvent request */
18050 	Display *display;	/* Display the event was read from */
18051 	Window parent;
18052 	Window window;
18053 	int x, y;
18054 	int width, height;
18055 	int border_width;
18056 	Window above;
18057 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
18058 	arch_ulong value_mask;
18059 }
18060 
18061 struct XCirculateEvent
18062 {
18063 	int type;
18064 	arch_ulong serial;	/* # of last request processed by server */
18065 	Bool send_event;	/* true if this came from a SendEvent request */
18066 	Display *display;	/* Display the event was read from */
18067 	Window event;
18068 	Window window;
18069 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
18070 }
18071 
18072 struct XCirculateRequestEvent
18073 {
18074 	int type;
18075 	arch_ulong serial;	/* # of last request processed by server */
18076 	Bool send_event;	/* true if this came from a SendEvent request */
18077 	Display *display;	/* Display the event was read from */
18078 	Window parent;
18079 	Window window;
18080 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
18081 }
18082 
18083 struct XPropertyEvent
18084 {
18085 	int type;
18086 	arch_ulong serial;	/* # of last request processed by server */
18087 	Bool send_event;	/* true if this came from a SendEvent request */
18088 	Display *display;	/* Display the event was read from */
18089 	Window window;
18090 	Atom atom;
18091 	Time time;
18092 	PropertyNotification state;		/* NewValue, Deleted */
18093 }
18094 
18095 struct XSelectionClearEvent
18096 {
18097 	int type;
18098 	arch_ulong serial;	/* # of last request processed by server */
18099 	Bool send_event;	/* true if this came from a SendEvent request */
18100 	Display *display;	/* Display the event was read from */
18101 	Window window;
18102 	Atom selection;
18103 	Time time;
18104 }
18105 
18106 struct XSelectionRequestEvent
18107 {
18108 	int type;
18109 	arch_ulong serial;	/* # of last request processed by server */
18110 	Bool send_event;	/* true if this came from a SendEvent request */
18111 	Display *display;	/* Display the event was read from */
18112 	Window owner;
18113 	Window requestor;
18114 	Atom selection;
18115 	Atom target;
18116 	Atom property;
18117 	Time time;
18118 }
18119 
18120 struct XSelectionEvent
18121 {
18122 	int type;
18123 	arch_ulong serial;	/* # of last request processed by server */
18124 	Bool send_event;	/* true if this came from a SendEvent request */
18125 	Display *display;	/* Display the event was read from */
18126 	Window requestor;
18127 	Atom selection;
18128 	Atom target;
18129 	Atom property;		/* ATOM or None */
18130 	Time time;
18131 }
18132 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
18133 
18134 struct XColormapEvent
18135 {
18136 	int type;
18137 	arch_ulong serial;	/* # of last request processed by server */
18138 	Bool send_event;	/* true if this came from a SendEvent request */
18139 	Display *display;	/* Display the event was read from */
18140 	Window window;
18141 	Colormap colormap;	/* COLORMAP or None */
18142 	Bool new_;		/* C++ */
18143 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
18144 }
18145 version(X86_64) static assert(XColormapEvent.sizeof == 56);
18146 
18147 struct XClientMessageEvent
18148 {
18149 	int type;
18150 	arch_ulong serial;	/* # of last request processed by server */
18151 	Bool send_event;	/* true if this came from a SendEvent request */
18152 	Display *display;	/* Display the event was read from */
18153 	Window window;
18154 	Atom message_type;
18155 	int format;
18156 	union Data{
18157 		byte[20] b;
18158 		short[10] s;
18159 		arch_ulong[5] l;
18160 	}
18161 	Data data;
18162 
18163 }
18164 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
18165 
18166 struct XMappingEvent
18167 {
18168 	int type;
18169 	arch_ulong serial;	/* # of last request processed by server */
18170 	Bool send_event;	/* true if this came from a SendEvent request */
18171 	Display *display;	/* Display the event was read from */
18172 	Window window;		/* unused */
18173 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
18174 				   MappingPointer */
18175 	int first_keycode;	/* first keycode */
18176 	int count;		/* defines range of change w. first_keycode*/
18177 }
18178 
18179 struct XErrorEvent
18180 {
18181 	int type;
18182 	Display *display;	/* Display the event was read from */
18183 	XID resourceid;		/* resource id */
18184 	arch_ulong serial;	/* serial number of failed request */
18185 	ubyte error_code;	/* error code of failed request */
18186 	ubyte request_code;	/* Major op-code of failed request */
18187 	ubyte minor_code;	/* Minor op-code of failed request */
18188 }
18189 
18190 struct XAnyEvent
18191 {
18192 	int type;
18193 	arch_ulong serial;	/* # of last request processed by server */
18194 	Bool send_event;	/* true if this came from a SendEvent request */
18195 	Display *display;/* Display the event was read from */
18196 	Window window;	/* window on which event was requested in event mask */
18197 }
18198 
18199 union XEvent{
18200 	int type;		/* must not be changed; first element */
18201 	XAnyEvent xany;
18202 	XKeyEvent xkey;
18203 	XButtonEvent xbutton;
18204 	XMotionEvent xmotion;
18205 	XCrossingEvent xcrossing;
18206 	XFocusChangeEvent xfocus;
18207 	XExposeEvent xexpose;
18208 	XGraphicsExposeEvent xgraphicsexpose;
18209 	XNoExposeEvent xnoexpose;
18210 	XVisibilityEvent xvisibility;
18211 	XCreateWindowEvent xcreatewindow;
18212 	XDestroyWindowEvent xdestroywindow;
18213 	XUnmapEvent xunmap;
18214 	XMapEvent xmap;
18215 	XMapRequestEvent xmaprequest;
18216 	XReparentEvent xreparent;
18217 	XConfigureEvent xconfigure;
18218 	XGravityEvent xgravity;
18219 	XResizeRequestEvent xresizerequest;
18220 	XConfigureRequestEvent xconfigurerequest;
18221 	XCirculateEvent xcirculate;
18222 	XCirculateRequestEvent xcirculaterequest;
18223 	XPropertyEvent xproperty;
18224 	XSelectionClearEvent xselectionclear;
18225 	XSelectionRequestEvent xselectionrequest;
18226 	XSelectionEvent xselection;
18227 	XColormapEvent xcolormap;
18228 	XClientMessageEvent xclient;
18229 	XMappingEvent xmapping;
18230 	XErrorEvent xerror;
18231 	XKeymapEvent xkeymap;
18232 	arch_ulong[24] pad;
18233 }
18234 
18235 
18236 	struct Display {
18237 		XExtData *ext_data;	/* hook for extension to hang data */
18238 		_XPrivate *private1;
18239 		int fd;			/* Network socket. */
18240 		int private2;
18241 		int proto_major_version;/* major version of server's X protocol */
18242 		int proto_minor_version;/* minor version of servers X protocol */
18243 		char *vendor;		/* vendor of the server hardware */
18244 	    	XID private3;
18245 		XID private4;
18246 		XID private5;
18247 		int private6;
18248 		XID function(Display*)resource_alloc;/* allocator function */
18249 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
18250 		int bitmap_unit;	/* padding and data requirements */
18251 		int bitmap_pad;		/* padding requirements on bitmaps */
18252 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
18253 		int nformats;		/* number of pixmap formats in list */
18254 		ScreenFormat *pixmap_format;	/* pixmap format list */
18255 		int private8;
18256 		int release;		/* release of the server */
18257 		_XPrivate *private9;
18258 		_XPrivate *private10;
18259 		int qlen;		/* Length of input event queue */
18260 		arch_ulong last_request_read; /* seq number of last event read */
18261 		arch_ulong request;	/* sequence number of last request. */
18262 		XPointer private11;
18263 		XPointer private12;
18264 		XPointer private13;
18265 		XPointer private14;
18266 		uint max_request_size; /* maximum number 32 bit words in request*/
18267 		_XrmHashBucketRec *db;
18268 		int function  (Display*)private15;
18269 		char *display_name;	/* "host:display" string used on this connect*/
18270 		int default_screen;	/* default screen for operations */
18271 		int nscreens;		/* number of screens on this server*/
18272 		Screen *screens;	/* pointer to list of screens */
18273 		arch_ulong motion_buffer;	/* size of motion buffer */
18274 		arch_ulong private16;
18275 		int min_keycode;	/* minimum defined keycode */
18276 		int max_keycode;	/* maximum defined keycode */
18277 		XPointer private17;
18278 		XPointer private18;
18279 		int private19;
18280 		byte *xdefaults;	/* contents of defaults from server */
18281 		/* there is more to this structure, but it is private to Xlib */
18282 	}
18283 
18284 	// I got these numbers from a C program as a sanity test
18285 	version(X86_64) {
18286 		static assert(Display.sizeof == 296);
18287 		static assert(XPointer.sizeof == 8);
18288 		static assert(XErrorEvent.sizeof == 40);
18289 		static assert(XAnyEvent.sizeof == 40);
18290 		static assert(XMappingEvent.sizeof == 56);
18291 		static assert(XEvent.sizeof == 192);
18292     	} else version (AArch64) {
18293 		// omit check for aarch64
18294 	} else {
18295 		static assert(Display.sizeof == 176);
18296 		static assert(XPointer.sizeof == 4);
18297 		static assert(XEvent.sizeof == 96);
18298 	}
18299 
18300 struct Depth
18301 {
18302 	int depth;		/* this depth (Z) of the depth */
18303 	int nvisuals;		/* number of Visual types at this depth */
18304 	Visual *visuals;	/* list of visuals possible at this depth */
18305 }
18306 
18307 alias void* GC;
18308 alias c_ulong VisualID;
18309 alias XID Colormap;
18310 alias XID Cursor;
18311 alias XID KeySym;
18312 alias uint KeyCode;
18313 enum None = 0;
18314 }
18315 
18316 version(without_opengl) {}
18317 else {
18318 extern(C) nothrow @nogc {
18319 
18320 
18321 static if(!SdpyIsUsingIVGLBinds) {
18322 enum GLX_USE_GL=            1;       /* support GLX rendering */
18323 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
18324 enum GLX_LEVEL=             3;       /* level in plane stacking */
18325 enum GLX_RGBA=              4;       /* true if RGBA mode */
18326 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
18327 enum GLX_STEREO=            6;       /* stereo buffering supported */
18328 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
18329 enum GLX_RED_SIZE=          8;       /* number of red component bits */
18330 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
18331 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
18332 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
18333 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
18334 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
18335 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
18336 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
18337 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
18338 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
18339 
18340 
18341 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
18342 
18343 
18344 
18345 enum GL_TRUE = 1;
18346 enum GL_FALSE = 0;
18347 }
18348 
18349 alias XID GLXContextID;
18350 alias XID GLXPixmap;
18351 alias XID GLXDrawable;
18352 alias XID GLXPbuffer;
18353 alias XID GLXWindow;
18354 alias XID GLXFBConfigID;
18355 alias void* GLXContext;
18356 
18357 }
18358 }
18359 
18360 enum AllocNone = 0;
18361 
18362 extern(C) {
18363 	/* WARNING, this type not in Xlib spec */
18364 	extern(C) alias XIOErrorHandler = int function (Display* display);
18365 }
18366 
18367 extern(C) nothrow
18368 alias XErrorHandler = int function(Display*, XErrorEvent*);
18369 
18370 extern(C) nothrow @nogc {
18371 struct Screen{
18372 	XExtData *ext_data;		/* hook for extension to hang data */
18373 	Display *display;		/* back pointer to display structure */
18374 	Window root;			/* Root window id. */
18375 	int width, height;		/* width and height of screen */
18376 	int mwidth, mheight;	/* width and height of  in millimeters */
18377 	int ndepths;			/* number of depths possible */
18378 	Depth *depths;			/* list of allowable depths on the screen */
18379 	int root_depth;			/* bits per pixel */
18380 	Visual *root_visual;	/* root visual */
18381 	GC default_gc;			/* GC for the root root visual */
18382 	Colormap cmap;			/* default color map */
18383 	uint white_pixel;
18384 	uint black_pixel;		/* White and Black pixel values */
18385 	int max_maps, min_maps;	/* max and min color maps */
18386 	int backing_store;		/* Never, WhenMapped, Always */
18387 	bool save_unders;
18388 	int root_input_mask;	/* initial root input mask */
18389 }
18390 
18391 struct Visual
18392 {
18393 	XExtData *ext_data;	/* hook for extension to hang data */
18394 	VisualID visualid;	/* visual id of this visual */
18395 	int class_;			/* class of screen (monochrome, etc.) */
18396 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
18397 	int bits_per_rgb;	/* log base 2 of distinct color values */
18398 	int map_entries;	/* color map entries */
18399 }
18400 
18401 	alias Display* _XPrivDisplay;
18402 
18403 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system {
18404 		assert(dpy !is null);
18405 		return &dpy.screens[scr];
18406 	}
18407 
18408 	extern(D) Window RootWindow(Display *dpy,int scr) {
18409 		return ScreenOfDisplay(dpy,scr).root;
18410 	}
18411 
18412 	struct XWMHints {
18413 		arch_long flags;
18414 		Bool input;
18415 		int initial_state;
18416 		Pixmap icon_pixmap;
18417 		Window icon_window;
18418 		int icon_x, icon_y;
18419 		Pixmap icon_mask;
18420 		XID window_group;
18421 	}
18422 
18423 	struct XClassHint {
18424 		char* res_name;
18425 		char* res_class;
18426 	}
18427 
18428 	extern(D) int DefaultScreen(Display *dpy) {
18429 		return dpy.default_screen;
18430 	}
18431 
18432 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
18433 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
18434 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
18435 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
18436 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
18437 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
18438 
18439 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
18440 
18441 	enum int AnyPropertyType = 0;
18442 	enum int Success = 0;
18443 
18444 	enum int RevertToNone = None;
18445 	enum int PointerRoot = 1;
18446 	enum Time CurrentTime = 0;
18447 	enum int RevertToPointerRoot = PointerRoot;
18448 	enum int RevertToParent = 2;
18449 
18450 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
18451 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
18452 	}
18453 
18454 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
18455 		return ScreenOfDisplay(dpy,scr).root_visual;
18456 	}
18457 
18458 	extern(D) GC DefaultGC(Display *dpy,int scr) {
18459 		return ScreenOfDisplay(dpy,scr).default_gc;
18460 	}
18461 
18462 	extern(D) uint BlackPixel(Display *dpy,int scr) {
18463 		return ScreenOfDisplay(dpy,scr).black_pixel;
18464 	}
18465 
18466 	extern(D) uint WhitePixel(Display *dpy,int scr) {
18467 		return ScreenOfDisplay(dpy,scr).white_pixel;
18468 	}
18469 
18470 	alias void* XFontSet; // i think
18471 	struct XmbTextItem {
18472 		char* chars;
18473 		int nchars;
18474 		int delta;
18475 		XFontSet font_set;
18476 	}
18477 
18478 	struct XTextItem {
18479 		char* chars;
18480 		int nchars;
18481 		int delta;
18482 		Font font;
18483 	}
18484 
18485 	enum {
18486 		GXclear        = 0x0, /* 0 */
18487 		GXand          = 0x1, /* src AND dst */
18488 		GXandReverse   = 0x2, /* src AND NOT dst */
18489 		GXcopy         = 0x3, /* src */
18490 		GXandInverted  = 0x4, /* NOT src AND dst */
18491 		GXnoop         = 0x5, /* dst */
18492 		GXxor          = 0x6, /* src XOR dst */
18493 		GXor           = 0x7, /* src OR dst */
18494 		GXnor          = 0x8, /* NOT src AND NOT dst */
18495 		GXequiv        = 0x9, /* NOT src XOR dst */
18496 		GXinvert       = 0xa, /* NOT dst */
18497 		GXorReverse    = 0xb, /* src OR NOT dst */
18498 		GXcopyInverted = 0xc, /* NOT src */
18499 		GXorInverted   = 0xd, /* NOT src OR dst */
18500 		GXnand         = 0xe, /* NOT src OR NOT dst */
18501 		GXset          = 0xf, /* 1 */
18502 	}
18503 	enum QueueMode : int {
18504 		QueuedAlready,
18505 		QueuedAfterReading,
18506 		QueuedAfterFlush
18507 	}
18508 
18509 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
18510 
18511 	struct XPoint {
18512 		short x;
18513 		short y;
18514 	}
18515 
18516 	enum CoordMode:int {
18517 		CoordModeOrigin = 0,
18518 		CoordModePrevious = 1
18519 	}
18520 
18521 	enum PolygonShape:int {
18522 		Complex = 0,
18523 		Nonconvex = 1,
18524 		Convex = 2
18525 	}
18526 
18527 	struct XTextProperty {
18528 		const(char)* value;		/* same as Property routines */
18529 		Atom encoding;			/* prop type */
18530 		int format;				/* prop data format: 8, 16, or 32 */
18531 		arch_ulong nitems;		/* number of data items in value */
18532 	}
18533 
18534 	version( X86_64 ) {
18535 		static assert(XTextProperty.sizeof == 32);
18536 	}
18537 
18538 
18539 	struct XGCValues {
18540 		int function_;           /* logical operation */
18541 		arch_ulong plane_mask;/* plane mask */
18542 		arch_ulong foreground;/* foreground pixel */
18543 		arch_ulong background;/* background pixel */
18544 		int line_width;         /* line width */
18545 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
18546 		int cap_style;          /* CapNotLast, CapButt,
18547 					   CapRound, CapProjecting */
18548 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
18549 		int fill_style;         /* FillSolid, FillTiled,
18550 					   FillStippled, FillOpaeueStippled */
18551 		int fill_rule;          /* EvenOddRule, WindingRule */
18552 		int arc_mode;           /* ArcChord, ArcPieSlice */
18553 		Pixmap tile;            /* tile pixmap for tiling operations */
18554 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
18555 		int ts_x_origin;        /* offset for tile or stipple operations */
18556 		int ts_y_origin;
18557 		Font font;              /* default text font for text operations */
18558 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
18559 		Bool graphics_exposures;/* boolean, should exposures be generated */
18560 		int clip_x_origin;      /* origin for clipping */
18561 		int clip_y_origin;
18562 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
18563 		int dash_offset;        /* patterned/dashed line information */
18564 		char dashes;
18565 	}
18566 
18567 	struct XColor {
18568 		arch_ulong pixel;
18569 		ushort red, green, blue;
18570 		byte flags;
18571 		byte pad;
18572 	}
18573 
18574 	struct XRectangle {
18575 		short x;
18576 		short y;
18577 		ushort width;
18578 		ushort height;
18579 	}
18580 
18581 	enum ClipByChildren = 0;
18582 	enum IncludeInferiors = 1;
18583 
18584 	enum Atom XA_PRIMARY = 1;
18585 	enum Atom XA_SECONDARY = 2;
18586 	enum Atom XA_STRING = 31;
18587 	enum Atom XA_CARDINAL = 6;
18588 	enum Atom XA_WM_NAME = 39;
18589 	enum Atom XA_ATOM = 4;
18590 	enum Atom XA_WINDOW = 33;
18591 	enum Atom XA_WM_HINTS = 35;
18592 	enum int PropModeAppend = 2;
18593 	enum int PropModeReplace = 0;
18594 	enum int PropModePrepend = 1;
18595 
18596 	enum int CopyFromParent = 0;
18597 	enum int InputOutput = 1;
18598 
18599 	// XWMHints
18600 	enum InputHint = 1 << 0;
18601 	enum StateHint = 1 << 1;
18602 	enum IconPixmapHint = (1L << 2);
18603 	enum IconWindowHint = (1L << 3);
18604 	enum IconPositionHint = (1L << 4);
18605 	enum IconMaskHint = (1L << 5);
18606 	enum WindowGroupHint = (1L << 6);
18607 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
18608 	enum XUrgencyHint = (1L << 8);
18609 
18610 	// GC Components
18611 	enum GCFunction           =   (1L<<0);
18612 	enum GCPlaneMask         =    (1L<<1);
18613 	enum GCForeground       =     (1L<<2);
18614 	enum GCBackground      =      (1L<<3);
18615 	enum GCLineWidth      =       (1L<<4);
18616 	enum GCLineStyle     =        (1L<<5);
18617 	enum GCCapStyle     =         (1L<<6);
18618 	enum GCJoinStyle   =          (1L<<7);
18619 	enum GCFillStyle  =           (1L<<8);
18620 	enum GCFillRule  =            (1L<<9);
18621 	enum GCTile     =             (1L<<10);
18622 	enum GCStipple           =    (1L<<11);
18623 	enum GCTileStipXOrigin  =     (1L<<12);
18624 	enum GCTileStipYOrigin =      (1L<<13);
18625 	enum GCFont               =   (1L<<14);
18626 	enum GCSubwindowMode     =    (1L<<15);
18627 	enum GCGraphicsExposures=     (1L<<16);
18628 	enum GCClipXOrigin     =      (1L<<17);
18629 	enum GCClipYOrigin    =       (1L<<18);
18630 	enum GCClipMask      =        (1L<<19);
18631 	enum GCDashOffset   =         (1L<<20);
18632 	enum GCDashList    =          (1L<<21);
18633 	enum GCArcMode    =           (1L<<22);
18634 	enum GCLastBit   =            22;
18635 
18636 
18637 	enum int WithdrawnState = 0;
18638 	enum int NormalState = 1;
18639 	enum int IconicState = 3;
18640 
18641 }
18642 } else version (OSXCocoa) {
18643 
18644 /+
18645 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
18646 +/
18647 
18648 	private __gshared AppDelegate globalAppDelegate;
18649 
18650 	extern(Objective-C)
18651 	class AppDelegate : NSObject, NSApplicationDelegate {
18652 		override static AppDelegate alloc() @selector("alloc");
18653 
18654 
18655 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
18656 			SimpleWindow.processAllCustomEvents();
18657 		}
18658 
18659 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
18660 			immutable style = NSWindowStyleMask.resizable |
18661 				NSWindowStyleMask.closable |
18662 				NSWindowStyleMask.miniaturizable |
18663 				NSWindowStyleMask.titled;
18664 
18665 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
18666 
18667 			{
18668 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
18669 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
18670 				mainMenu.setSubmenu(menu, item);
18671 
18672 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
18673 				newItem.target = NSApp;
18674 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
18675 				newItem2.target = NSApp;
18676 			}
18677 
18678 			{
18679 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
18680 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
18681 				mainMenu.setSubmenu(menu, item);
18682 
18683 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
18684 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
18685 			}
18686 
18687 
18688 			NSApp.menu = mainMenu;
18689 
18690 
18691 			// auto controller = ViewController.alloc.init;
18692 
18693 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
18694 
18695 			/+
18696 			this.window = window;
18697 			this.controller = controller;
18698 			+/
18699 		}
18700 
18701 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
18702 			NSApplication.shared_.activateIgnoringOtherApps(false);
18703 
18704 			sdpyPrintDebugString("before");
18705 			NSApp.stop(cast(void*) NSApp); // stop NSApp.run and let arsd.core event loop take over...
18706 			sdpyPrintDebugString("after");
18707 		}
18708 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
18709 			return true;
18710 		}
18711 	}
18712 
18713 	extern(Objective-C)
18714 	class SDWindowDelegate : NSObject, NSWindowDelegate {
18715 		override static SDWindowDelegate alloc() @selector("alloc");
18716 		override SDWindowDelegate init() @selector("init");
18717 
18718 		SimpleWindow simpleWindow;
18719 
18720 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
18721 			auto window = cast(void*) notification.object;
18722 
18723 			// FIXME: do i need to release it?
18724 			if(auto swp = window in SimpleWindow.nativeMapping) {
18725 				auto sw = *swp;
18726 
18727 				sw._closed = true;
18728 
18729 				if (sw.visibilityChanged !is null && sw._visible) sw.visibilityChanged(false);
18730 
18731 				if (sw.onDestroyed !is null) try { sw.onDestroyed(); } catch (Exception e) {} // sorry
18732 				SimpleWindow.nativeMapping.remove(window);
18733 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(cast(NSWindow) window);
18734 
18735 				bool anyImportant = false;
18736 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
18737 					if(w.beingOpenKeepsAppOpen) {
18738 						anyImportant = true;
18739 						break;
18740 					}
18741 				if(!anyImportant) {
18742 					EventLoop.quitApplication();
18743 				}
18744 
18745 			}
18746 		}
18747 
18748 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
18749 			if(simpleWindow.windowResized) {
18750 				// FIXME: automaticallyScaleIfPossible behaviors
18751 
18752 				simpleWindow._width = cast(int) frameSize.width;
18753 				simpleWindow._height = cast(int) frameSize.height;
18754 
18755 				simpleWindow.view.setFrameSize(frameSize);
18756 
18757 				/+
18758 				auto size = simpleWindow.view.frame.size;
18759 				writeln(cast(int) size.width, "x", cast(int) size.height);
18760 				+/
18761 
18762 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
18763 
18764 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
18765 
18766 				// simpleWindow.view.setNeedsDisplay(true);
18767 			}
18768 
18769 			return frameSize;
18770 		}
18771 
18772 		/+
18773 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
18774 			if(simpleWindow.windowResized) {
18775 				auto window = simpleWindow.window;
18776 				auto rect = window.contentRectForFrameRect(window.frame);
18777 				import std.stdio; writeln(window.frame.size);
18778 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
18779 			}
18780 		}
18781 		+/
18782 	}
18783 
18784 	extern(Objective-C)
18785 	class SDGraphicsView : NSView {
18786 		SimpleWindow simpleWindow;
18787 
18788 		override static SDGraphicsView alloc() @selector("alloc");
18789 		override SDGraphicsView init() @selector("init");/* {
18790 			super.init();
18791 			return this;
18792 		}*/
18793 
18794 		override void drawRect(NSRect rect) @selector("drawRect:") {
18795 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
18796 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
18797 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
18798 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
18799 			CGImageRelease(cgImage);
18800 		}
18801 
18802 		extern(D)
18803 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
18804 			MouseEvent me;
18805 			me.type = type;
18806 
18807 			auto pos = event.locationInWindow;
18808 
18809 			me.x = cast(int) pos.x;
18810 
18811 			// FIXME: 1-based things here might need fixup
18812 			me.y = cast(int) (simpleWindow.height - pos.y);
18813 
18814 			me.dx = 0; // FIXME
18815 			me.dy = 0; // FIXME
18816 
18817 			me.button = button;
18818 			me.modifierState = cast(uint) event.modifierFlags;
18819 			me.window = simpleWindow;
18820 
18821 			me.doubleClick = false;
18822 
18823 			if(simpleWindow && simpleWindow.handleMouseEvent)
18824 				simpleWindow.handleMouseEvent(me);
18825 		}
18826 
18827 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
18828 			// writeln(event.pressedMouseButtons);
18829 
18830 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18831 		}
18832 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
18833 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
18834 		}
18835 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
18836 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
18837 		}
18838 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
18839 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
18840 		}
18841 		/+
18842 			// FIXME
18843 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
18844 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18845 		}
18846 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
18847 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
18848 		}
18849 		+/
18850 
18851 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
18852 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
18853 		}
18854 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
18855 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
18856 		}
18857 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
18858 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
18859 		}
18860 
18861 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
18862 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
18863 		}
18864 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
18865 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
18866 		}
18867 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
18868 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
18869 		}
18870 
18871 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
18872 			// import std.stdio; writeln(event.deltaY);
18873 		}
18874 
18875 		override void keyDown(NSEvent event) @selector("keyDown:") {
18876 			// the event may have multiple characters, and we send them all at once.
18877 			if (simpleWindow.handleCharEvent) {
18878 				auto chars = DeifiedNSString(event.characters);
18879 				foreach (dchar dc; chars.str)
18880 					simpleWindow.handleCharEvent(dc);
18881 			}
18882 
18883 			keyHelper(event, true);
18884 		}
18885 
18886 		override void keyUp(NSEvent event) @selector("keyUp:") {
18887 			keyHelper(event, false);
18888 		}
18889 
18890 		extern(D)
18891 		private void keyHelper(NSEvent event, bool pressed) {
18892 			if(simpleWindow.handleKeyEvent) {
18893 				KeyEvent ev;
18894 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
18895 				ev.pressed = pressed;
18896 				ev.hardwareCode = cast(ubyte) event.keyCode;
18897 				ev.modifierState = cast(uint) event.modifierFlags;
18898 				ev.window = simpleWindow;
18899 
18900 				simpleWindow.handleKeyEvent(ev);
18901 			}
18902 		}
18903 
18904 		override bool isFlipped() @selector("isFlipped") {
18905 			return true;
18906 		}
18907 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
18908 			return true;
18909 		}
18910 
18911 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
18912 			if(simpleWindow && simpleWindow.handlePulse)
18913 				simpleWindow.handlePulse();
18914 			/+
18915 			setNeedsDisplay = true;
18916 			+/
18917 		}
18918 	}
18919 
18920 private:
18921 	alias const(void)* CFStringRef;
18922 	alias const(void)* CFAllocatorRef;
18923 	alias const(void)* CFTypeRef;
18924 	alias const(void)* CGColorSpaceRef;
18925 	alias const(void)* CGImageRef;
18926 	alias ulong CGBitmapInfo;
18927 	alias NSGraphicsContext CGContextRef; // actually CGContextRef should be a subclass...
18928 
18929 	alias NSPoint CGPoint;
18930 	alias NSSize CGSize;
18931 	alias NSRect CGRect;
18932 
18933 	struct CGAffineTransform {
18934 		double a, b, c, d, tx, ty;
18935 	}
18936 
18937 	enum NSApplicationActivationPolicyRegular = 0;
18938 	enum NSBackingStoreBuffered = 2;
18939 	enum kCFStringEncodingUTF8 = 0x08000100;
18940 
18941 	enum : size_t {
18942 		NSBorderlessWindowMask = 0,
18943 		NSTitledWindowMask = 1 << 0,
18944 		NSClosableWindowMask = 1 << 1,
18945 		NSMiniaturizableWindowMask = 1 << 2,
18946 		NSResizableWindowMask = 1 << 3,
18947 		NSTexturedBackgroundWindowMask = 1 << 8
18948 	}
18949 
18950 	enum : ulong {
18951 		kCGImageAlphaNone,
18952 		kCGImageAlphaPremultipliedLast,
18953 		kCGImageAlphaPremultipliedFirst,
18954 		kCGImageAlphaLast,
18955 		kCGImageAlphaFirst,
18956 		kCGImageAlphaNoneSkipLast,
18957 		kCGImageAlphaNoneSkipFirst
18958 	}
18959 	enum : ulong {
18960 		kCGBitmapAlphaInfoMask = 0x1F,
18961 		kCGBitmapFloatComponents = (1 << 8),
18962 		kCGBitmapByteOrderMask = 0x7000,
18963 		kCGBitmapByteOrderDefault = (0 << 12),
18964 		kCGBitmapByteOrder16Little = (1 << 12),
18965 		kCGBitmapByteOrder32Little = (2 << 12),
18966 		kCGBitmapByteOrder16Big = (3 << 12),
18967 		kCGBitmapByteOrder32Big = (4 << 12)
18968 	}
18969 	enum CGPathDrawingMode {
18970 		kCGPathFill,
18971 		kCGPathEOFill,
18972 		kCGPathStroke,
18973 		kCGPathFillStroke,
18974 		kCGPathEOFillStroke
18975 	}
18976 	enum objc_AssociationPolicy : size_t {
18977 		OBJC_ASSOCIATION_ASSIGN = 0,
18978 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18979 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18980 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18981 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18982 	}
18983 
18984 	extern(C) {
18985 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18986 		void CGContextRelease(CGContextRef c);
18987 		ubyte* CGBitmapContextGetData(CGContextRef c);
18988 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18989 		size_t CGBitmapContextGetWidth(CGContextRef c);
18990 		size_t CGBitmapContextGetHeight(CGContextRef c);
18991 
18992 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18993 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18994 
18995 		alias void* CGFontRef;
18996 		alias CTFontRef = NSFont;
18997 		CGFontRef CTFontCopyGraphicsFont(CTFontRef font, void  /*CTFontDescriptorRef*/ * attributes);
18998 
18999 
19000 		void CGContextSetFont(CGContextRef c, CGFontRef font);
19001 		void CGContextSetFontSize(CGContextRef c, CGFloat size);
19002 
19003 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
19004 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
19005 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
19006 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
19007 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
19008 		void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
19009 
19010 		void CGContextBeginPath(CGContextRef c);
19011 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
19012 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
19013 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
19014 		void CGContextAddRect(CGContextRef c, CGRect rect);
19015 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
19016 		void CGContextSaveGState(CGContextRef c);
19017 		void CGContextRestoreGState(CGContextRef c);
19018 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
19019 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
19020 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
19021 
19022 		void CGImageRelease(CGImageRef image);
19023 	}
19024 } else static assert(0, "Unsupported operating system");
19025 
19026 
19027 version(OSXCocoa) {
19028 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
19029 	//
19030 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
19031 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
19032 	//
19033 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
19034 	// Probably won't even fully compile right now
19035 
19036 	private enum double PI = 3.14159265358979323;
19037 
19038 	alias NSWindow NativeWindowHandle;
19039 	alias void delegate(NSid) NativeEventHandler;
19040 
19041 	enum KEY_ESCAPE = 27;
19042 
19043 	mixin template NativeImageImplementation() {
19044 		CGContextRef context;
19045 		ubyte* rawData;
19046 
19047 		final:
19048 
19049 		void convertToRgbaBytes(ubyte[] where) @system {
19050 			assert(where.length == this.width * this.height * 4);
19051 
19052 			// if rawData had a length....
19053 			//assert(rawData.length == where.length);
19054 			for(long idx = 0; idx < where.length; idx += 4) {
19055 				auto alpha = rawData[idx + 3];
19056 				if(alpha == 255) {
19057 					where[idx + 0] = rawData[idx + 0]; // r
19058 					where[idx + 1] = rawData[idx + 1]; // g
19059 					where[idx + 2] = rawData[idx + 2]; // b
19060 					where[idx + 3] = rawData[idx + 3]; // a
19061 				} else {
19062 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
19063 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
19064 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
19065 					where[idx + 3] = rawData[idx + 3]; // a
19066 
19067 				}
19068 			}
19069 		}
19070 
19071 		void setFromRgbaBytes(in ubyte[] where) @system {
19072 			// FIXME: this is probably wrong
19073 			assert(where.length == this.width * this.height * 4);
19074 
19075 			// if rawData had a length....
19076 			//assert(rawData.length == where.length);
19077 			for(long idx = 0; idx < where.length; idx += 4) {
19078 				auto alpha = where[idx + 3];
19079 				if(alpha == 255) {
19080 					rawData[idx + 0] = where[idx + 0]; // r
19081 					rawData[idx + 1] = where[idx + 1]; // g
19082 					rawData[idx + 2] = where[idx + 2]; // b
19083 					rawData[idx + 3] = where[idx + 3]; // a
19084 				} else if(alpha == 0) {
19085 					rawData[idx + 0] = 0;
19086 					rawData[idx + 1] = 0;
19087 					rawData[idx + 2] = 0;
19088 					rawData[idx + 3] = 0;
19089 				} else {
19090 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
19091 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
19092 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
19093 					rawData[idx + 3] = where[idx + 3]; // a
19094 				}
19095 			}
19096 		}
19097 
19098 
19099 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
19100 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
19101 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
19102 			CGColorSpaceRelease(colorSpace);
19103 			rawData = CGBitmapContextGetData(context);
19104 		}
19105 		void dispose() {
19106 			CGContextRelease(context);
19107 		}
19108 
19109 		void setPixel(int x, int y, Color c) @system {
19110 			auto offset = (y * width + x) * 4;
19111 			if (c.a == 255) {
19112 				rawData[offset + 0] = c.r;
19113 				rawData[offset + 1] = c.g;
19114 				rawData[offset + 2] = c.b;
19115 				rawData[offset + 3] = c.a;
19116 			} else {
19117 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
19118 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
19119 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
19120 				rawData[offset + 3] = c.a;
19121 			}
19122 		}
19123 	}
19124 
19125 	mixin template NativeScreenPainterImplementation() {
19126 		CGContextRef context;
19127 		ubyte[4] _outlineComponents;
19128 		NSView view;
19129 
19130 		Pen _activePen;
19131 		Color _fillColor;
19132 		Rectangle _clipRectangle;
19133 		OperatingSystemFont _font;
19134 
19135 		OperatingSystemFont getFont() {
19136 			if(_font is null) {
19137 				static OperatingSystemFont _defaultFont;
19138 				if(_defaultFont is null) {
19139 					_defaultFont = new OperatingSystemFont();
19140 					_defaultFont.loadDefault();
19141 				}
19142 				_font = _defaultFont;
19143 			}
19144 
19145 			return _font;
19146 		}
19147 
19148 		void create(PaintingHandle window) {
19149 			// this.destiny = window;
19150 			if(auto sw = cast(SimpleWindow) this.window) {
19151 				context = sw.drawingContext;
19152 				view = sw.view;
19153 			} else {
19154 				throw new NotYetImplementedException();
19155 			}
19156 		}
19157 
19158 		void dispose() {
19159 			view.setNeedsDisplay(true);
19160 		}
19161 
19162 		bool manualInvalidations;
19163 		void invalidateRect(Rectangle invalidRect) { }
19164 
19165 		// NotYetImplementedException
19166 		void rasterOp(RasterOp op) {
19167 		}
19168 		void setClipRectangle(int, int, int, int) {
19169 		}
19170 		Size textSize(in char[] txt) {
19171 			auto font = getFont();
19172 			return Size(castFnumToCnum(font.stringWidth(txt)), castFnumToCnum(font.height()));
19173 		}
19174 
19175 		void setFont(OperatingSystemFont font) {
19176 			_font = font;
19177 			// font.font.setInContext(context);
19178 			if(font) {
19179 				// FIXME: should i free this thing?
19180 				/+
19181 				auto f = CTFontCopyGraphicsFont(font.font, null);
19182 				if(font.font is null)
19183 					sdpyPrintDebugString("input is null");
19184 				if(f is null)
19185 					sdpyPrintDebugString("f is null");
19186 				CGContextSetFont(context, f);
19187 				+/
19188 				// CGContextSetFontSize(context, font.size);
19189 
19190 				// FIXME kinda hacky
19191 				CGContextSelectFont(context, (font.loadedInfo.name ~ "\0").ptr, font.loadedInfo.size, 1);
19192 			} else {} // FIMXE
19193 		}
19194 		int fontHeight() {
19195 			auto font = getFont();
19196 			return castFnumToCnum(font.height);
19197 		}
19198 
19199 		// end
19200 
19201 		void pen(Pen pen) {
19202 			_activePen = pen;
19203 			auto color = pen.color; // FIXME
19204 			double alphaComponent = color.a/255.0f;
19205 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
19206 
19207 			double[2] patternBuffer;
19208 			double[] pattern;
19209 			final switch(pen.style) {
19210 				case Pen.Style.Solid:
19211 					pattern = null;
19212 				break;
19213 				case Pen.Style.Dashed:
19214 					patternBuffer[0] = 4;
19215 					patternBuffer[1] = 1;
19216 					pattern = patternBuffer[];
19217 				break;
19218 				case Pen.Style.Dotted:
19219 					patternBuffer[0] = 1;
19220 					patternBuffer[1] = 1;
19221 					pattern = patternBuffer[];
19222 				break;
19223 			}
19224 
19225 			CGContextSetLineDash(context, 0, pattern.ptr, pattern.length);
19226 
19227 			if (color.a != 255) {
19228 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
19229 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
19230 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
19231 				_outlineComponents[3] = color.a;
19232 			} else {
19233 				_outlineComponents[0] = color.r;
19234 				_outlineComponents[1] = color.g;
19235 				_outlineComponents[2] = color.b;
19236 				_outlineComponents[3] = color.a;
19237 			}
19238 		}
19239 
19240 		@property void fillColor(Color color) {
19241 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
19242 		}
19243 
19244 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
19245 		// NotYetImplementedException for upper left/width/height
19246 			auto cgImage = CGBitmapContextCreateImage(image.context);
19247 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
19248 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
19249 			CGImageRelease(cgImage);
19250 		}
19251 
19252 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
19253 		// FIXME: is this efficient?
19254 			auto cgImage = CGBitmapContextCreateImage(s.handle);
19255 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
19256 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
19257 			CGImageRelease(cgImage);
19258 		}
19259 
19260 
19261 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
19262 		// FIXME: alignment
19263 			if (_outlineComponents[3] != 0) {
19264 				CGContextSaveGState(context);
19265 				auto invAlpha = 1.0f/_outlineComponents[3];
19266 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
19267 												  _outlineComponents[1]*invAlpha,
19268 												  _outlineComponents[2]*invAlpha,
19269 												  _outlineComponents[3]/255.0f);
19270 
19271 
19272 
19273 				// FIXME: should we clip it to the bounding box?
19274 				int textHeight = fontHeight;
19275 
19276 				auto lines = text.split('\n');
19277 
19278 				const lineHeight = textHeight;
19279 				textHeight *= lines.length;
19280 
19281 				int cy = y;
19282 
19283 				if(alignment & TextAlignment.VerticalBottom) {
19284 					if(y2 <= 0)
19285 						return;
19286 					auto h = y2 - y;
19287 					if(h > textHeight) {
19288 						cy += h - textHeight;
19289 						cy -= lineHeight / 2;
19290 					}
19291 				} else if(alignment & TextAlignment.VerticalCenter) {
19292 					if(y2 <= 0)
19293 						return;
19294 					auto h = y2 - y;
19295 					if(textHeight < h) {
19296 						cy += (h - textHeight) / 2;
19297 						//cy -= lineHeight / 4;
19298 					}
19299 				}
19300 
19301 				foreach(line; text.split('\n')) {
19302 					int textWidth = this.textSize(line).width;
19303 
19304 					int px = x, py = cy;
19305 
19306 					if(alignment & TextAlignment.Center) {
19307 						if(x2 <= 0)
19308 							return;
19309 						auto w = x2 - x;
19310 						if(w > textWidth)
19311 							px += (w - textWidth) / 2;
19312 					} else if(alignment & TextAlignment.Right) {
19313 						if(x2 <= 0)
19314 							return;
19315 						auto pos = x2 - textWidth;
19316 						if(pos > x)
19317 							px = pos;
19318 					}
19319 
19320 					CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length);
19321 
19322 					carry_on:
19323 					cy += lineHeight + 4;
19324 				}
19325 
19326 // auto cfstr = cast(NSid)createCFString(text);
19327 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
19328 // NSPoint(x, y), null);
19329 // CFRelease(cfstr);
19330 				CGContextRestoreGState(context);
19331 			}
19332 		}
19333 
19334 		void drawPixel(int x, int y) {
19335 			auto rawData = CGBitmapContextGetData(context);
19336 			auto width = CGBitmapContextGetWidth(context);
19337 			auto height = CGBitmapContextGetHeight(context);
19338 			auto offset = ((height - y - 1) * width + x) * 4;
19339 			rawData[offset .. offset+4] = _outlineComponents;
19340 		}
19341 
19342 		void drawLine(int x1, int y1, int x2, int y2) {
19343 			CGPoint[2] linePoints;
19344 			linePoints[0] = CGPoint(x1, y1);
19345 			linePoints[1] = CGPoint(x2, y2);
19346 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
19347 		}
19348 
19349 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
19350 			drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded
19351 		}
19352 
19353 		void drawRectangle(int x, int y, int width, int height) {
19354 			CGContextBeginPath(context);
19355 			// trying to align with actual pixels...
19356 			auto rect = CGRect(CGPoint(x + 0.5, y + 0.5), CGSize(width - 1, height - 1));
19357 			CGContextAddRect(context, rect);
19358 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19359 		}
19360 
19361 		void drawEllipse(int x1, int y1, int x2, int y2) {
19362 			CGContextBeginPath(context);
19363 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
19364 			CGContextAddEllipseInRect(context, rect);
19365 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19366 		}
19367 
19368 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
19369 			// @@@BUG@@@ Does not support elliptic arc (width != height).
19370 			CGContextBeginPath(context);
19371 			int clockwise = 0;
19372 			if(length < 0) {
19373 				clockwise = 1;
19374 				length = -length;
19375 			}
19376 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
19377 							start*PI/(180*64), (start+length)*PI/(180*64), clockwise);
19378 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19379 		}
19380 
19381 		void drawPolygon(Point[] intPoints) {
19382 			CGContextBeginPath(context);
19383 			CGPoint[16] pointsBuffer;
19384 			CGPoint[] points;
19385 			if(intPoints.length <= pointsBuffer.length)
19386 				points = pointsBuffer[0 .. intPoints.length];
19387 			else
19388 				points = new CGPoint[](intPoints.length);
19389 
19390 			foreach(idx, pt; intPoints)
19391 				points[idx] = CGPoint(pt.x, pt.y);
19392 
19393 			CGContextAddLines(context, points.ptr, points.length);
19394 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
19395 		}
19396 	}
19397 
19398 	private bool appInitialized = false;
19399 	void initializeApp() {
19400 		if(appInitialized)
19401 			return;
19402 		synchronized {
19403 			if(appInitialized)
19404 				return;
19405 
19406 			auto app = NSApp(); // ensure the is initialized
19407 
19408 			auto dg = AppDelegate.alloc;
19409 			globalAppDelegate = dg;
19410 			NSApp.delegate_ = dg;
19411 
19412 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
19413 
19414 			appInitialized = true;
19415 		}
19416 	}
19417 
19418 	mixin template NativeSimpleWindowImplementation() {
19419 		void setTitle(string title) {
19420 			window.title = MacString(title).borrow;
19421 		}
19422 
19423 		void moveResize (int x, int y, int w, int h) {
19424 			//auto f = window.frame;
19425 			// FIXME: finish
19426 			sdpyPrintDebugString("moveResize not implemented");
19427 		}
19428 
19429 		void resize(int w, int h) {
19430 			// FIXME: finish
19431 			sdpyPrintDebugString("resize not implemented");
19432 		}
19433 
19434 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
19435 			initializeApp();
19436 
19437 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
19438 
19439 			auto window = NSWindow.alloc.initWithContentRect(
19440 				contentRect,
19441 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
19442 				NSBackingStoreType.buffered,
19443 				true
19444 			);
19445 
19446 			SimpleWindow.nativeMapping[cast(void*) window] = this;
19447 
19448 			window.title = MacString(title).borrow;
19449 
19450 			auto dg = SDWindowDelegate.alloc.init;
19451 			dg.simpleWindow = this;
19452 			window.delegate_ = dg;
19453 
19454 			auto view = SDGraphicsView.alloc.init;
19455 			assert(view !is null);
19456 			window.contentView = view;
19457 			this.view = view;
19458 			view.simpleWindow = this;
19459 
19460 			window.center();
19461 
19462 			window.makeKeyAndOrderFront(null);
19463 
19464 			// no need to make a bitmap on mac since everything is double buffered already
19465 
19466 			// create area to draw on.
19467 			createNewDrawingContext(width, height);
19468 
19469 			window.setBackgroundColor(NSColor.whiteColor);
19470 
19471 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
19472 				// show it
19473 				view.setNeedsDisplay(true);
19474 			} else {
19475 
19476 				view.setNeedsDisplay(true);
19477 				// hide it
19478 				//window.setIsVisible = false;
19479 			}
19480 		}
19481 
19482 		void createNewDrawingContext(int width, int height) {
19483 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
19484 			if(this.drawingContext)
19485 				CGContextRelease(this.drawingContext);
19486 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
19487 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
19488 			CGColorSpaceRelease(colorSpace);
19489 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
19490 			auto matrix = CGContextGetTextMatrix(drawingContext);
19491 			matrix.c = -matrix.c;
19492 			matrix.d = -matrix.d;
19493 			CGContextSetTextMatrix(drawingContext, matrix);
19494 
19495 		}
19496 
19497 		void dispose() {
19498 			closeWindow();
19499 			// window.release(); // closing the window does this automatically i think
19500 		}
19501 		void closeWindow() {
19502 			window.close();
19503 		}
19504 
19505 		ScreenPainter getPainter(bool manualInvalidations) {
19506 			return ScreenPainter(this, this.window, manualInvalidations);
19507 		}
19508 
19509 		NSWindow window;
19510 		NSView view;
19511 		CGContextRef drawingContext;
19512 	}
19513 }
19514 
19515 version(without_opengl) {} else
19516 extern(System) nothrow @nogc {
19517 	//enum uint GL_VERSION = 0x1F02;
19518 	//const(char)* glGetString (/*GLenum*/uint);
19519 	version(X11) {
19520 	static if (!SdpyIsUsingIVGLBinds) {
19521 
19522 		enum GLX_X_RENDERABLE = 0x8012;
19523 		enum GLX_DRAWABLE_TYPE = 0x8010;
19524 		enum GLX_RENDER_TYPE = 0x8011;
19525 		enum GLX_X_VISUAL_TYPE = 0x22;
19526 		enum GLX_TRUE_COLOR = 0x8002;
19527 		enum GLX_WINDOW_BIT = 0x00000001;
19528 		enum GLX_RGBA_BIT = 0x00000001;
19529 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
19530 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
19531 		enum GLX_SAMPLES = 0x186a1;
19532 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19533 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19534 	}
19535 
19536 		// GLX_EXT_swap_control
19537 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
19538 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
19539 
19540 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
19541 		extern(System) {
19542 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
19543 		}
19544 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
19545 
19546 		// this made public so we don't have to get it again and again
19547 		public bool glXCreateContextAttribsARB_present () @system {
19548 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
19549 				// get it
19550 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
19551 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
19552 			}
19553 			return (glXCreateContextAttribsARBFn !is null);
19554 		}
19555 
19556 		// this made public so we don't have to get it again and again
19557 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system {
19558 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
19559 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
19560 		}
19561 
19562 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
19563 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
19564 
19565 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
19566 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
19567 			if (_glx_swapInterval_fn is null) {
19568 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
19569 				if (_glx_swapInterval_fn is null) {
19570 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
19571 					return;
19572 				}
19573 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
19574 			}
19575 
19576 			if(glXSwapIntervalMESA is null) {
19577 				// it seems to require both to actually take effect on many computers
19578 				// idk why
19579 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
19580 				if(glXSwapIntervalMESA is null)
19581 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
19582 			}
19583 
19584 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
19585 				glXSwapIntervalMESA(wait ? 1 : 0);
19586 
19587 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
19588 		}
19589 	} else version(Windows) {
19590 	static if (!SdpyIsUsingIVGLBinds) {
19591 	enum GL_TRUE = 1;
19592 	enum GL_FALSE = 0;
19593 
19594 	public void* glbindGetProcAddress (const(char)* name) {
19595 		void* res = wglGetProcAddress(name);
19596 		if (res is null) {
19597 			/+
19598 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
19599 			import core.sys.windows.windef, core.sys.windows.winbase;
19600 			__gshared HINSTANCE dll = null;
19601 			if (dll is null) {
19602 				dll = LoadLibraryA("opengl32.dll");
19603 				if (dll is null) return null; // <32, but idc
19604 			}
19605 			res = GetProcAddress(dll, name);
19606 			+/
19607 			res = GetProcAddress(gl.libHandle, name);
19608 		}
19609 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
19610 		return res;
19611 	}
19612 	}
19613 
19614 
19615  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
19616 	void wglSetVSync(bool wait) {
19617 		if(wglSwapIntervalEXT is null) {
19618 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
19619 			if(wglSwapIntervalEXT is null)
19620 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
19621 		}
19622 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
19623 			return;
19624 
19625 		wglSwapIntervalEXT(wait ? 1 : 0);
19626 	}
19627 
19628 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
19629 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
19630 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
19631 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
19632 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
19633 
19634 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
19635 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
19636 
19637 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
19638 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
19639 
19640 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
19641 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
19642 
19643 		void wglInitOtherFunctions () {
19644 			if (wglCreateContextAttribsARB is null) {
19645 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
19646 			}
19647 		}
19648 	}
19649 
19650 	static if (!SdpyIsUsingIVGLBinds) {
19651 
19652 	interface GL {
19653 		extern(System) @nogc nothrow:
19654 
19655 		void glGetIntegerv(int, void*);
19656 		void glMatrixMode(int);
19657 		void glPushMatrix();
19658 		void glLoadIdentity();
19659 		void glOrtho(double, double, double, double, double, double);
19660 		void glFrustum(double, double, double, double, double, double);
19661 
19662 		void glPopMatrix();
19663 		void glEnable(int);
19664 		void glDisable(int);
19665 		void glClear(int);
19666 		void glBegin(int);
19667 		void glVertex2f(float, float);
19668 		void glVertex3f(float, float, float);
19669 		void glEnd();
19670 		void glColor3b(byte, byte, byte);
19671 		void glColor3ub(ubyte, ubyte, ubyte);
19672 		void glColor4b(byte, byte, byte, byte);
19673 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
19674 		void glColor3i(int, int, int);
19675 		void glColor3ui(uint, uint, uint);
19676 		void glColor4i(int, int, int, int);
19677 		void glColor4ui(uint, uint, uint, uint);
19678 		void glColor3f(float, float, float);
19679 		void glColor4f(float, float, float, float);
19680 		void glTranslatef(float, float, float);
19681 		void glScalef(float, float, float);
19682 		version(X11) {
19683 			void glSecondaryColor3b(byte, byte, byte);
19684 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
19685 			void glSecondaryColor3i(int, int, int);
19686 			void glSecondaryColor3ui(uint, uint, uint);
19687 			void glSecondaryColor3f(float, float, float);
19688 		}
19689 
19690 		void glDrawElements(int, int, int, void*);
19691 
19692 		void glRotatef(float, float, float, float);
19693 
19694 		uint glGetError();
19695 
19696 		void glDeleteTextures(int, uint*);
19697 
19698 
19699 		void glRasterPos2i(int, int);
19700 		void glDrawPixels(int, int, uint, uint, void*);
19701 		void glClearColor(float, float, float, float);
19702 
19703 
19704 		void glPixelStorei(uint, int);
19705 
19706 		void glGenTextures(uint, uint*);
19707 		void glBindTexture(int, int);
19708 		void glTexParameteri(uint, uint, int);
19709 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19710 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
19711 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
19712 			/*GLsizei*/int width, /*GLsizei*/int height,
19713 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19714 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
19715 
19716 		void glLineWidth(int);
19717 
19718 
19719 		void glTexCoord2f(float, float);
19720 		void glVertex2i(int, int);
19721 		void glBlendFunc (int, int);
19722 		void glDepthFunc (int);
19723 		void glViewport(int, int, int, int);
19724 
19725 		void glClearDepth(double);
19726 
19727 		void glReadBuffer(uint);
19728 		void glReadPixels(int, int, int, int, int, int, void*);
19729 
19730 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
19731 
19732 		void glFlush();
19733 		void glFinish();
19734 
19735 		version(Windows) {
19736 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
19737 			HGLRC wglCreateContext(HDC);
19738 			HGLRC wglCreateLayerContext(HDC, int);
19739 			BOOL wglDeleteContext(HGLRC);
19740 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
19741 			HGLRC wglGetCurrentContext();
19742 			HDC wglGetCurrentDC();
19743 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
19744 			PROC wglGetProcAddress(LPCSTR);
19745 			BOOL wglMakeCurrent(HDC, HGLRC);
19746 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
19747 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
19748 			BOOL wglShareLists(HGLRC, HGLRC);
19749 			BOOL wglSwapLayerBuffers(HDC, UINT);
19750 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
19751 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
19752 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19753 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
19754 		}
19755 
19756 	}
19757 
19758 	interface GL3 {
19759 		extern(System) @nogc nothrow:
19760 
19761 		void glGenVertexArrays(GLsizei, GLuint*);
19762 		void glBindVertexArray(GLuint);
19763 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
19764 		void glGenerateMipmap(GLenum);
19765 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
19766 		void glStencilMask(GLuint);
19767 		void glStencilFunc(GLenum, GLint, GLuint);
19768 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19769 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
19770 		GLuint glCreateProgram();
19771 		GLuint glCreateShader(GLenum);
19772 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
19773 		void glCompileShader(GLuint);
19774 		void glGetShaderiv(GLuint, GLenum, GLint*);
19775 		void glAttachShader(GLuint, GLuint);
19776 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
19777 		void glLinkProgram(GLuint);
19778 		void glGetProgramiv(GLuint, GLenum, GLint*);
19779 		void glDeleteProgram(GLuint);
19780 		void glDeleteShader(GLuint);
19781 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
19782 		void glGenBuffers(GLsizei, GLuint*);
19783 
19784 		void glUniform1f(GLint location, GLfloat v0);
19785 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
19786 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
19787 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
19788 		void glUniform1i(GLint location, GLint v0);
19789 		void glUniform2i(GLint location, GLint v0, GLint v1);
19790 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
19791 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
19792 		void glUniform1ui(GLint location, GLuint v0);
19793 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
19794 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
19795 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
19796 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
19797 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
19798 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
19799 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
19800 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
19801 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
19802 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
19803 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
19804 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
19805 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
19806 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
19807 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
19808 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19809 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19810 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19811 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19812 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19813 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19814 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19815 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19816 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
19817 
19818 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
19819 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
19820 		void glDrawArrays(GLenum, GLint, GLsizei);
19821 		void glStencilOp(GLenum, GLenum, GLenum);
19822 		void glUseProgram(GLuint);
19823 		void glCullFace(GLenum);
19824 		void glFrontFace(GLenum);
19825 		void glActiveTexture(GLenum);
19826 		void glBindBuffer(GLenum, GLuint);
19827 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
19828 		void glEnableVertexAttribArray(GLuint);
19829 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
19830 		void glUniform1i(GLint, GLint);
19831 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
19832 		void glDisableVertexAttribArray(GLuint);
19833 		void glDeleteBuffers(GLsizei, const(GLuint)*);
19834 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
19835 		void glLogicOp (GLenum opcode);
19836 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
19837 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
19838 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
19839 		GLenum glCheckFramebufferStatus (GLenum target);
19840 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
19841 	}
19842 
19843 	interface GL4 {
19844 		extern(System) @nogc nothrow:
19845 
19846 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
19847 			/*GLsizei*/int width, /*GLsizei*/int height,
19848 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
19849 	}
19850 
19851 	interface GLU {
19852 		extern(System) @nogc nothrow:
19853 
19854 		void gluLookAt(double, double, double, double, double, double, double, double, double);
19855 		void gluPerspective(double, double, double, double);
19856 
19857 		char* gluErrorString(uint);
19858 	}
19859 
19860 
19861 	enum GL_RED = 0x1903;
19862 	enum GL_ALPHA = 0x1906;
19863 
19864 	enum uint GL_FRONT = 0x0404;
19865 
19866 	enum uint GL_BLEND = 0x0be2;
19867 	enum uint GL_LEQUAL = 0x0203;
19868 
19869 
19870 	enum uint GL_RGB = 0x1907;
19871 	enum uint GL_BGRA = 0x80e1;
19872 	enum uint GL_RGBA = 0x1908;
19873 	enum uint GL_RGBA8 = 0x8058;
19874 	enum uint GL_TEXTURE_2D =   0x0DE1;
19875 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
19876 	enum uint GL_NEAREST = 0x2600;
19877 	enum uint GL_LINEAR = 0x2601;
19878 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
19879 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
19880 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
19881 	enum uint GL_REPEAT = 0x2901;
19882 	enum uint GL_CLAMP = 0x2900;
19883 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
19884 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
19885 	enum uint GL_DECAL = 0x2101;
19886 	enum uint GL_MODULATE = 0x2100;
19887 	enum uint GL_TEXTURE_ENV = 0x2300;
19888 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
19889 	enum uint GL_REPLACE = 0x1E01;
19890 	enum uint GL_LIGHTING = 0x0B50;
19891 	enum uint GL_DITHER = 0x0BD0;
19892 
19893 	enum uint GL_NO_ERROR = 0;
19894 
19895 
19896 
19897 	enum int GL_VIEWPORT = 0x0BA2;
19898 	enum int GL_MODELVIEW = 0x1700;
19899 	enum int GL_TEXTURE = 0x1702;
19900 	enum int GL_PROJECTION = 0x1701;
19901 	enum int GL_DEPTH_TEST = 0x0B71;
19902 
19903 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
19904 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
19905 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
19906 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
19907 
19908 	enum int GL_POINTS = 0x0000;
19909 	enum int GL_LINES =  0x0001;
19910 	enum int GL_LINE_LOOP = 0x0002;
19911 	enum int GL_LINE_STRIP = 0x0003;
19912 	enum int GL_TRIANGLES = 0x0004;
19913 	enum int GL_TRIANGLE_STRIP = 5;
19914 	enum int GL_TRIANGLE_FAN = 6;
19915 	enum int GL_QUADS = 7;
19916 	enum int GL_QUAD_STRIP = 8;
19917 	enum int GL_POLYGON = 9;
19918 
19919 	alias GLvoid = void;
19920 	alias GLboolean = ubyte;
19921 	alias GLint = int;
19922 	alias GLuint = uint;
19923 	alias GLenum = uint;
19924 	alias GLchar = char;
19925 	alias GLsizei = int;
19926 	alias GLfloat = float;
19927 	alias GLintptr = size_t;
19928 	alias GLsizeiptr = ptrdiff_t;
19929 
19930 
19931 	enum uint GL_INVALID_ENUM = 0x0500;
19932 
19933 	enum uint GL_ZERO = 0;
19934 	enum uint GL_ONE = 1;
19935 
19936 	enum uint GL_BYTE = 0x1400;
19937 	enum uint GL_UNSIGNED_BYTE = 0x1401;
19938 	enum uint GL_SHORT = 0x1402;
19939 	enum uint GL_UNSIGNED_SHORT = 0x1403;
19940 	enum uint GL_INT = 0x1404;
19941 	enum uint GL_UNSIGNED_INT = 0x1405;
19942 	enum uint GL_FLOAT = 0x1406;
19943 	enum uint GL_2_BYTES = 0x1407;
19944 	enum uint GL_3_BYTES = 0x1408;
19945 	enum uint GL_4_BYTES = 0x1409;
19946 	enum uint GL_DOUBLE = 0x140A;
19947 
19948 	enum uint GL_STREAM_DRAW = 0x88E0;
19949 
19950 	enum uint GL_CCW = 0x0901;
19951 
19952 	enum uint GL_STENCIL_TEST = 0x0B90;
19953 	enum uint GL_SCISSOR_TEST = 0x0C11;
19954 
19955 	enum uint GL_EQUAL = 0x0202;
19956 	enum uint GL_NOTEQUAL = 0x0205;
19957 
19958 	enum uint GL_ALWAYS = 0x0207;
19959 	enum uint GL_KEEP = 0x1E00;
19960 
19961 	enum uint GL_INCR = 0x1E02;
19962 
19963 	enum uint GL_INCR_WRAP = 0x8507;
19964 	enum uint GL_DECR_WRAP = 0x8508;
19965 
19966 	enum uint GL_CULL_FACE = 0x0B44;
19967 	enum uint GL_BACK = 0x0405;
19968 
19969 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
19970 	enum uint GL_VERTEX_SHADER = 0x8B31;
19971 
19972 	enum uint GL_COMPILE_STATUS = 0x8B81;
19973 	enum uint GL_LINK_STATUS = 0x8B82;
19974 
19975 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
19976 
19977 	enum uint GL_STATIC_DRAW = 0x88E4;
19978 
19979 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
19980 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
19981 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
19982 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
19983 
19984 	enum uint GL_GENERATE_MIPMAP = 0x8191;
19985 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
19986 
19987 	enum uint GL_TEXTURE0 = 0x84C0U;
19988 	enum uint GL_TEXTURE1 = 0x84C1U;
19989 
19990 	enum uint GL_ARRAY_BUFFER = 0x8892;
19991 
19992 	enum uint GL_SRC_COLOR = 0x0300;
19993 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
19994 	enum uint GL_SRC_ALPHA = 0x0302;
19995 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
19996 	enum uint GL_DST_ALPHA = 0x0304;
19997 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
19998 	enum uint GL_DST_COLOR = 0x0306;
19999 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
20000 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
20001 
20002 	enum uint GL_INVERT = 0x150AU;
20003 
20004 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
20005 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
20006 
20007 	enum uint GL_FRAMEBUFFER = 0x8D40U;
20008 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
20009 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
20010 
20011 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
20012 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
20013 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
20014 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
20015 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
20016 
20017 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
20018 	enum uint GL_CLEAR = 0x1500U;
20019 	enum uint GL_COPY = 0x1503U;
20020 	enum uint GL_XOR = 0x1506U;
20021 
20022 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
20023 
20024 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
20025 
20026 	}
20027 }
20028 
20029 /++
20030 	History:
20031 		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.
20032 +/
20033 __gshared bool gluSuccessfullyLoaded = true;
20034 
20035 version(without_opengl) {} else {
20036 static if(!SdpyIsUsingIVGLBinds) {
20037 	version(Windows) {
20038 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
20039 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
20040 	} else {
20041 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
20042 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
20043 	}
20044 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
20045 
20046 
20047 	shared static this() {
20048 		gl.loadDynamicLibrary();
20049 
20050 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
20051 		// unless those functions are actually used
20052 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
20053 		glu.loadDynamicLibrary();
20054 	}
20055 }
20056 }
20057 
20058 /++
20059 	Convenience method for converting D arrays to opengl buffer data
20060 
20061 	I would LOVE to overload it with the original glBufferData, but D won't
20062 	let me since glBufferData is a function pointer :(
20063 
20064 	Added: August 25, 2020 (version 8.5)
20065 +/
20066 version(without_opengl) {} else
20067 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
20068 	glBufferData(target, data.length, data.ptr, usage);
20069 }
20070 
20071 /++
20072 	History:
20073 		Added September 1, 2024
20074 +/
20075 version(without_opengl) {} else
20076 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) {
20077 	glBufferSubData(target, offset, data.length, data.ptr);
20078 }
20079 
20080 /++
20081 	Convenience class for using opengl shaders.
20082 
20083 	Ensure that you've loaded opengl 3+ and set your active
20084 	context before trying to use this.
20085 
20086 	Added: August 25, 2020 (version 8.5)
20087 +/
20088 version(without_opengl) {} else
20089 final class OpenGlShader {
20090 	private int shaderProgram_;
20091 	private @property void shaderProgram(int a) {
20092 		shaderProgram_ = a;
20093 	}
20094 	/// Get the program ID for use in OpenGL functions.
20095 	public @property int shaderProgram() {
20096 		return shaderProgram_;
20097 	}
20098 
20099 	/++
20100 
20101 	+/
20102 	static struct Source {
20103 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
20104 		string code; ///
20105 	}
20106 
20107 	/++
20108 		Helper method to just compile some shader code and check for errors
20109 		while you do glCreateShader, etc. on the outside yourself.
20110 
20111 		This just does `glShaderSource` and `glCompileShader` for the given code.
20112 
20113 		If you the OpenGlShader class constructor, you never need to call this yourself.
20114 	+/
20115 	static void compile(int sid, Source code) {
20116 		const(char)*[1] buffer;
20117 		int[1] lengthBuffer;
20118 
20119 		buffer[0] = code.code.ptr;
20120 		lengthBuffer[0] = cast(int) code.code.length;
20121 
20122 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
20123 		glCompileShader(sid);
20124 
20125 		int success;
20126 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
20127 		if(!success) {
20128 			char[512] info;
20129 			int len;
20130 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
20131 
20132 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
20133 		}
20134 	}
20135 
20136 	/++
20137 		Calls `glLinkProgram` and throws if error a occurs.
20138 
20139 		If you the OpenGlShader class constructor, you never need to call this yourself.
20140 	+/
20141 	static void link(int shaderProgram) {
20142 		glLinkProgram(shaderProgram);
20143 		int success;
20144 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
20145 		if(!success) {
20146 			char[512] info;
20147 			int len;
20148 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
20149 
20150 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
20151 		}
20152 	}
20153 
20154 	/++
20155 		Constructs the shader object by calling `glCreateProgram`, then
20156 		compiling each given [Source], and finally, linking them together.
20157 
20158 		Throws: on compile or link failure.
20159 	+/
20160 	this(Source[] codes...) {
20161 		shaderProgram = glCreateProgram();
20162 
20163 		int[16] shadersBufferStack;
20164 
20165 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
20166 			shadersBufferStack[0 .. codes.length] :
20167 			new int[](codes.length);
20168 
20169 		foreach(idx, code; codes) {
20170 			shadersBuffer[idx] = glCreateShader(code.type);
20171 
20172 			compile(shadersBuffer[idx], code);
20173 
20174 			glAttachShader(shaderProgram, shadersBuffer[idx]);
20175 		}
20176 
20177 		link(shaderProgram);
20178 
20179 		foreach(s; shadersBuffer)
20180 			glDeleteShader(s);
20181 	}
20182 
20183 	/// Calls `glUseProgram(this.shaderProgram)`
20184 	void use() {
20185 		glUseProgram(this.shaderProgram);
20186 	}
20187 
20188 	/// Deletes the program.
20189 	void delete_() {
20190 		glDeleteProgram(shaderProgram);
20191 		shaderProgram = 0;
20192 	}
20193 
20194 	/++
20195 		[OpenGlShader.uniforms].name gives you one of these.
20196 
20197 		You can get the id out of it or just assign
20198 	+/
20199 	static struct Uniform {
20200 		/// the id passed to glUniform*
20201 		int id;
20202 
20203 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
20204 		void opAssign(float x, float y, float z, float w) {
20205 			if(id != -1)
20206 			glUniform4f(id, x, y, z, w);
20207 		}
20208 
20209 		void opAssign(float x) {
20210 			if(id != -1)
20211 			glUniform1f(id, x);
20212 		}
20213 
20214 		void opAssign(float x, float y) {
20215 			if(id != -1)
20216 			glUniform2f(id, x, y);
20217 		}
20218 
20219 		void opAssign(T)(T t) {
20220 			t.glUniform(id);
20221 		}
20222 	}
20223 
20224 	static struct UniformsHelper {
20225 		OpenGlShader _shader;
20226 
20227 		@property Uniform opDispatch(string name)() {
20228 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
20229 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
20230 			//if(i == -1)
20231 				//throw new Exception("Could not find uniform " ~ name);
20232 			return Uniform(i);
20233 		}
20234 
20235 		@property void opDispatch(string name, T)(T t) {
20236 			Uniform f = this.opDispatch!name;
20237 			t.glUniform(f);
20238 		}
20239 	}
20240 
20241 	/++
20242 		Gives access to the uniforms through dot access.
20243 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
20244 	+/
20245 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
20246 }
20247 
20248 version(without_opengl) {} else {
20249 /++
20250 	A static container of experimental types and value constructors for opengl 3+ shaders.
20251 
20252 
20253 	You can declare variables like:
20254 
20255 	```
20256 	OGL.vec3f something;
20257 	```
20258 
20259 	But generally it would be used with [OpenGlShader]'s uniform helpers like
20260 
20261 	```
20262 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
20263 	```
20264 
20265 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
20266 
20267 
20268 	History:
20269 		Added December 7, 2021. Not yet stable.
20270 +/
20271 final class OGL {
20272 	static:
20273 
20274 	private template typeFromSpecifier(string specifier) {
20275 		static if(specifier == "f")
20276 			alias typeFromSpecifier = GLfloat;
20277 		else static if(specifier == "i")
20278 			alias typeFromSpecifier = GLint;
20279 		else static if(specifier == "ui")
20280 			alias typeFromSpecifier = GLuint;
20281 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
20282 	}
20283 
20284 	private template CommonType(T...) {
20285 		static if(T.length == 1)
20286 			alias CommonType = T[0];
20287 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
20288 			alias CommonType = CommonType!(C, T[2 .. $]);
20289 	}
20290 
20291 	private template typesToSpecifier(T...) {
20292 		static if(is(CommonType!T == float))
20293 			enum typesToSpecifier = "f";
20294 		else static if(is(CommonType!T == int))
20295 			enum typesToSpecifier = "i";
20296 		else static if(is(CommonType!T == uint))
20297 			enum typesToSpecifier = "ui";
20298 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
20299 	}
20300 
20301 	private template genNames(size_t dim, size_t dim2 = 0) {
20302 		string helper() {
20303 			string s;
20304 			if(dim2) {
20305 				static if(__VERSION__ < 2102)
20306 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug
20307 				else
20308 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;";
20309 			} else {
20310 				if(dim > 0) s ~= "type x = 0;";
20311 				if(dim > 1) s ~= "type y = 0;";
20312 				if(dim > 2) s ~= "type z = 0;";
20313 				if(dim > 3) s ~= "type w = 0;";
20314 			}
20315 
20316 			s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }";
20317 			if(dim2)
20318 				s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }";
20319 
20320 			return s;
20321 		}
20322 
20323 		enum genNames = helper();
20324 	}
20325 
20326 	// there's vec, arrays of vec, mat, and arrays of mat
20327 	template opDispatch(string name)
20328 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
20329 	{
20330 		static if(name[4] == 'x') {
20331 			enum dimX = cast(int) (name[3] - '0');
20332 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
20333 
20334 			enum dimY = cast(int) (name[5] - '0');
20335 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
20336 
20337 			enum isArray = name[$ - 1] == 'v';
20338 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
20339 			alias type = typeFromSpecifier!typeSpecifier;
20340 		} else {
20341 			enum dim = cast(int) (name[3] - '0');
20342 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
20343 			enum isArray = name[$ - 1] == 'v';
20344 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
20345 			alias type = typeFromSpecifier!typeSpecifier;
20346 		}
20347 
20348 		align(1)
20349 		struct opDispatch {
20350 			align(1):
20351 			static if(name[4] == 'x')
20352 				mixin(genNames!(dimX, dimY));
20353 			else
20354 				mixin(genNames!dim);
20355 
20356 			private void glUniform(OpenGlShader.Uniform assignTo) {
20357 				glUniform(assignTo.id);
20358 			}
20359 			private void glUniform(int assignTo) {
20360 				static if(name[4] == 'x') {
20361 					static if(name[3] == name[5]) {
20362 						// import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY);
20363 						mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]);
20364 					} else {
20365 						mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr);
20366 					}
20367 				} else
20368 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
20369 			}
20370 		}
20371 	}
20372 
20373 	auto vec(T...)(T members) {
20374 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
20375 	}
20376 }
20377 
20378 void checkGlError() {
20379 	auto error = glGetError();
20380 	int[] errors;
20381 	string[] errorStrings;
20382 	while(error != GL_NO_ERROR) {
20383 		errors ~= error;
20384 		switch(error) {
20385 			case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break;
20386 			case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break;
20387 			case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break;
20388 			case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break;
20389 			case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break;
20390 			case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break;
20391 			default: errorStrings ~= "idk";
20392 		}
20393 		error = glGetError();
20394 	}
20395 	if(errors.length)
20396 		throw ArsdException!"glGetError"(errors, errorStrings);
20397 }
20398 
20399 /++
20400 	A matrix for simple uses that easily integrates with [OpenGlShader].
20401 
20402 	Might not be useful to you since it only as some simple functions and
20403 	probably isn't that fast.
20404 
20405 	Note it uses an inline static array for its storage, so copying it
20406 	may be expensive.
20407 +/
20408 struct BasicMatrix(int columns, int rows, T = float) {
20409 	static import core.stdc.math;
20410 	static if(is(T == float)) {
20411 		alias cos = core.stdc.math.cosf;
20412 		alias sin = core.stdc.math.sinf;
20413 	} else {
20414 		alias cos = core.stdc.math.cos;
20415 		alias sin = core.stdc.math.sin;
20416 	}
20417 
20418 	T[columns * rows] data = 0.0;
20419 
20420 	/++
20421 
20422 	+/
20423 	this(T[columns * rows] data) {
20424 		this.data = data;
20425 	}
20426 
20427 	/++
20428 		Basic operations that operate *in place*.
20429 	+/
20430 	static if(columns == 4 && rows == 4)
20431 	void translate(T x, T y, T z) {
20432 		BasicMatrix m = [
20433 			1, 0, 0, x,
20434 			0, 1, 0, y,
20435 			0, 0, 1, z,
20436 			0, 0, 0, 1
20437 		];
20438 
20439 		this *= m;
20440 	}
20441 
20442 	/// ditto
20443 	static if(columns == 4 && rows == 4)
20444 	void scale(T x, T y, T z) {
20445 		BasicMatrix m = [
20446 			x, 0, 0, 0,
20447 			0, y, 0, 0,
20448 			0, 0, z, 0,
20449 			0, 0, 0, 1
20450 		];
20451 
20452 		this *= m;
20453 	}
20454 
20455 	/// ditto
20456 	static if(columns == 4 && rows == 4)
20457 	void rotateX(T theta) {
20458 		BasicMatrix m = [
20459 			1,          0,           0, 0,
20460 			0, cos(theta), -sin(theta), 0,
20461 			0, sin(theta),  cos(theta), 0,
20462 			0,          0,           0, 1
20463 		];
20464 
20465 		this *= m;
20466 	}
20467 
20468 	/// ditto
20469 	static if(columns == 4 && rows == 4)
20470 	void rotateY(T theta) {
20471 		BasicMatrix m = [
20472 			 cos(theta), 0,  sin(theta), 0,
20473 			          0, 1,           0, 0,
20474 			-sin(theta), 0,  cos(theta), 0,
20475 			          0, 0,           0, 1
20476 		];
20477 
20478 		this *= m;
20479 	}
20480 
20481 	/// ditto
20482 	static if(columns == 4 && rows == 4)
20483 	void rotateZ(T theta) {
20484 		BasicMatrix m = [
20485 			cos(theta), -sin(theta), 0, 0,
20486 			sin(theta),  cos(theta), 0, 0,
20487 			         0,           0, 1, 0,
20488 				 0,           0, 0, 1
20489 		];
20490 
20491 		this *= m;
20492 	}
20493 
20494 	/++
20495 
20496 	+/
20497 	static if(columns == rows)
20498 	static BasicMatrix identity() {
20499 		BasicMatrix m;
20500 		foreach(i; 0 .. columns)
20501 			m.data[0 + i + i * columns] = 1.0;
20502 		return m;
20503 	}
20504 
20505 	static if(columns == rows)
20506 	void loadIdentity() {
20507 		this = identity();
20508 	}
20509 
20510 	static if(columns == 4 && rows == 4)
20511 	static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) {
20512 		return BasicMatrix([
20513 			2/(r-l),       0,        0, -(r+l)/(r-l),
20514 			      0, 2/(t-b),        0, -(t+b)/(t-b),
20515 			      0,       0, -2/(f-n), -(f+n)/(f-n),
20516 			      0,       0,        0,            1
20517 		]);
20518 	}
20519 
20520 	static if(columns == 4 && rows == 4)
20521 	void loadOrtho(T l, T r, T b, T t, T n, T f) {
20522 		this = ortho(l, r, b, t, n, f);
20523 	}
20524 
20525 	void opOpAssign(string op : "+")(const BasicMatrix rhs) {
20526 		this.data[] += rhs.data;
20527 	}
20528 	void opOpAssign(string op : "-")(const BasicMatrix rhs) {
20529 		this.data[] -= rhs.data;
20530 	}
20531 	void opOpAssign(string op : "*")(const T rhs) {
20532 		this.data[] *= rhs;
20533 	}
20534 	void opOpAssign(string op : "/")(const T rhs) {
20535 		this.data[] /= rhs;
20536 	}
20537 	void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) {
20538 		static assert(columns == rhsRows);
20539 		auto multiplySize = columns;
20540 
20541 		auto tmp = this.data; // copy cuz it is a value type
20542 
20543 		int idx = 0;
20544 		foreach(r; 0 .. rows)
20545 		foreach(c; 0 .. columns) {
20546 			T sum = 0.0;
20547 
20548 			foreach(i; 0 .. multiplySize)
20549 				sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c];
20550 
20551 			tmp[idx++] = sum;
20552 		}
20553 
20554 		this.data = tmp;
20555 	}
20556 }
20557 
20558 unittest {
20559 	auto m = BasicMatrix!(2, 2)([
20560 		1, 2,
20561 		3, 4
20562 	]);
20563 
20564 	auto m2 = BasicMatrix!(2, 2)([
20565 		5, 6,
20566 		7, 8
20567 	]);
20568 
20569 	//import std.conv;
20570 	m *= m2;
20571 	assert(m.data == [
20572 		19, 22,
20573 		43, 50
20574 	]);//, to!string(m.data));
20575 }
20576 
20577 
20578 
20579 class GlObjectBase {
20580 	protected uint _vao;
20581 	protected uint _elementsCount;
20582 
20583 	protected uint element_buffer;
20584 
20585 	void gen() {
20586 		glGenVertexArrays(1, &_vao);
20587 	}
20588 
20589 	void bind() {
20590 		glBindVertexArray(_vao);
20591 	}
20592 
20593 	void dispose() {
20594 		glDeleteVertexArrays(1, &_vao);
20595 	}
20596 
20597 	void draw() {
20598 		bind();
20599 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20600 		glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null);
20601 	}
20602 }
20603 
20604 /++
20605 
20606 +/
20607 class GlObject(T) : GlObjectBase {
20608 	protected uint VBO;
20609 
20610 	this(T[] arr, uint[] indices) {
20611 		gen();
20612 		bind();
20613 
20614 		glGenBuffers(1, &VBO);
20615 		glGenBuffers(1, &element_buffer);
20616 
20617 		glBindBuffer(GL_ARRAY_BUFFER, VBO);
20618 		glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW);
20619 
20620 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
20621 		glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
20622 		_elementsCount = cast(int) indices.length;
20623 
20624 		foreach(int idx, memberName; __traits(allMembers, T)) {
20625 			static if(memberName != "__ctor") {
20626 			static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) {
20627 				glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof);
20628 				glEnableVertexAttribArray(idx);
20629 			} else static assert(0); }
20630 		}
20631 	}
20632 
20633 	static string generateShaderDefinitions() {
20634 		string code;
20635 
20636 		foreach(idx, memberName; __traits(allMembers, T)) {
20637 			// never use stringof ladies and gents it has a LU thing at the end of it
20638 			static if(memberName != "__ctor")
20639 			code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n";
20640 		}
20641 
20642 		return code;
20643 	}
20644 }
20645 
20646 private string typeToGl(T)() {
20647 	static if(is(T == float[4]))
20648 		return "vec4";
20649 	else static if(is(T == float[3]))
20650 		return "vec3";
20651 	else static if(is(T == float[2]))
20652 		return "vec2";
20653 	else static assert(0, T.stringof);
20654 }
20655 
20656 
20657 }
20658 
20659 version(Emscripten) {
20660 
20661 } else version(linux) {
20662 	version(with_eventloop) {} else {
20663 		private int epollFd = -1;
20664 		void prepareEventLoop() {
20665 			if(epollFd != -1)
20666 				return; // already initialized, no need to do it again
20667 			import ep = core.sys.linux.epoll;
20668 
20669 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
20670 			if(epollFd == -1)
20671 				throw new Exception("epoll create failure");
20672 		}
20673 	}
20674 } else version(Posix) {
20675 	void prepareEventLoop() {}
20676 }
20677 
20678 version(X11) {
20679 	import core.stdc.locale : LC_ALL; // rdmd fix
20680 	__gshared bool sdx_isUTF8Locale;
20681 
20682 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
20683 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
20684 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
20685 	// anal magic is here. I (Ketmar) hope you like it.
20686 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
20687 	// always return correct unicode symbols. The detection is here 'cause user can change locale
20688 	// later.
20689 
20690 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
20691 	shared static this () @system {
20692 		if(!librariesSuccessfullyLoaded)
20693 			return;
20694 
20695 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
20696 
20697 		// this doesn't hurt; it may add some locking, but the speed is still
20698 		// allows doing 60 FPS videogames; also, ignore the result, as most
20699 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
20700 		// never seen this failing).
20701 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
20702 
20703 		setlocale(LC_ALL, "");
20704 		// check if out locale is UTF-8
20705 		auto lct = setlocale(LC_CTYPE, null);
20706 		if (lct is null) {
20707 			sdx_isUTF8Locale = false;
20708 		} else {
20709 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
20710 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
20711 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
20712 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
20713 				{
20714 					sdx_isUTF8Locale = true;
20715 					break;
20716 				}
20717 			}
20718 		}
20719 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
20720 	}
20721 }
20722 
20723 /++
20724 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20725 
20726 	History:
20727 		Added February 19, 2021
20728 +/
20729 /// Group: drag_and_drop
20730 interface DropHandler {
20731 	/++
20732 		Called when the drag enters the handler's area.
20733 	+/
20734 	DragAndDropAction dragEnter(DropPackage*);
20735 	/++
20736 		Called when the drag leaves the handler's area or is
20737 		cancelled. You should free your resources when this is called.
20738 	+/
20739 	void dragLeave();
20740 	/++
20741 		Called continually as the drag moves over the handler's area.
20742 
20743 		Returns: feedback to the dragger
20744 	+/
20745 	DropParameters dragOver(Point pt);
20746 	/++
20747 		The user dropped the data and you should process it now. You can
20748 		access the data through the given [DropPackage].
20749 	+/
20750 	void drop(scope DropPackage*);
20751 	/++
20752 		Called when the drop is complete. You should free whatever temporary
20753 		resources you were using. It is often reasonable to simply forward
20754 		this call to [dragLeave].
20755 	+/
20756 	void finish();
20757 
20758 	/++
20759 		Parameters returned by [DropHandler.drop].
20760 	+/
20761 	static struct DropParameters {
20762 		/++
20763 			Acceptable action over this area.
20764 		+/
20765 		DragAndDropAction action;
20766 		/++
20767 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20768 
20769 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20770 		+/
20771 		Rectangle consistentWithin;
20772 	}
20773 }
20774 
20775 /++
20776 	History:
20777 		Added February 19, 2021
20778 +/
20779 /// Group: drag_and_drop
20780 enum DragAndDropAction {
20781 	none = 0,
20782 	copy,
20783 	move,
20784 	link,
20785 	ask,
20786 	custom
20787 }
20788 
20789 /++
20790 	An opaque structure representing dropped data. It contains
20791 	private, platform-specific data that your `drop` function
20792 	should simply forward.
20793 
20794 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20795 
20796 	History:
20797 		Added February 19, 2021
20798 +/
20799 /// Group: drag_and_drop
20800 struct DropPackage {
20801 	/++
20802 		Lists the available formats as magic numbers. You should compare these
20803 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20804 		understand the passed data.
20805 	+/
20806 	DraggableData.FormatId[] availableFormats() {
20807 		version(X11) {
20808 			return xFormats;
20809 		} else version(Windows) {
20810 			if(pDataObj is null)
20811 				return null;
20812 
20813 			typeof(return) ret;
20814 
20815 			IEnumFORMATETC ef;
20816 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20817 				FORMATETC fmt;
20818 				ULONG fetched;
20819 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20820 					if(fetched == 0)
20821 						break;
20822 
20823 					if(fmt.lindex != -1)
20824 						continue;
20825 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20826 						continue;
20827 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20828 						continue;
20829 
20830 					ret ~= fmt.cfFormat;
20831 				}
20832 			}
20833 
20834 			return ret;
20835 		} else throw new NotYetImplementedException();
20836 	}
20837 
20838 	/++
20839 		Gets data from the drop and optionally accepts it.
20840 
20841 		Returns:
20842 			void because the data is fed asynchronously through the `dg` parameter.
20843 
20844 		Params:
20845 			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.
20846 
20847 			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.
20848 
20849 			Calling `getData` again after accepting a drop is not permitted.
20850 
20851 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20852 
20853 			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.
20854 
20855 		Throws:
20856 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20857 
20858 		History:
20859 			Included in first release of [DropPackage].
20860 	+/
20861 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20862 		version(X11) {
20863 
20864 			auto display = XDisplayConnection.get();
20865 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20866 			auto best = format;
20867 
20868 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20869 
20870 				XDisplay* display;
20871 				Atom selectionAtom;
20872 				DraggableData.FormatId best;
20873 				DraggableData.FormatId format;
20874 				void delegate(scope ubyte[] data) dg;
20875 				DragAndDropAction acceptedAction;
20876 				Window sourceWindow;
20877 				SimpleWindow win;
20878 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20879 					this.display = display;
20880 					this.win = win;
20881 					this.sourceWindow = sourceWindow;
20882 					this.format = format;
20883 					this.selectionAtom = selectionAtom;
20884 					this.best = best;
20885 					this.dg = dg;
20886 					this.acceptedAction = acceptedAction;
20887 				}
20888 
20889 
20890 				mixin X11GetSelectionHandler_Basics;
20891 
20892 				void handleData(Atom target, in ubyte[] data) {
20893 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20894 
20895 					dg(cast(ubyte[]) data);
20896 
20897 					if(acceptedAction != DragAndDropAction.none) {
20898 						auto display = XDisplayConnection.get;
20899 
20900 						XClientMessageEvent xclient;
20901 
20902 						xclient.type = EventType.ClientMessage;
20903 						xclient.window = sourceWindow;
20904 						xclient.message_type = GetAtom!"XdndFinished"(display);
20905 						xclient.format = 32;
20906 						xclient.data.l[0] = win.impl.window;
20907 						xclient.data.l[1] = 1; // drop successful
20908 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20909 
20910 						XSendEvent(
20911 							display,
20912 							sourceWindow,
20913 							false,
20914 							EventMask.NoEventMask,
20915 							cast(XEvent*) &xclient
20916 						);
20917 
20918 						XFlush(display);
20919 					}
20920 				}
20921 
20922 				Atom findBestFormat(Atom[] answer) {
20923 					Atom best = None;
20924 					foreach(option; answer) {
20925 						if(option == format) {
20926 							best = option;
20927 							break;
20928 						}
20929 						/*
20930 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20931 							best = option;
20932 							break;
20933 						} else if(option == XA_STRING) {
20934 							best = option;
20935 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20936 							best = option;
20937 						}
20938 						*/
20939 					}
20940 					return best;
20941 				}
20942 			}
20943 
20944 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20945 
20946 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20947 
20948 		} else version(Windows) {
20949 
20950 			// clean up like DragLeave
20951 			// pass effect back up
20952 
20953 			FORMATETC t;
20954 			assert(format >= 0 && format <= ushort.max);
20955 			t.cfFormat = cast(ushort) format;
20956 			t.lindex = -1;
20957 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20958 			t.tymed = TYMED.TYMED_HGLOBAL;
20959 
20960 			STGMEDIUM m;
20961 
20962 			if(pDataObj.GetData(&t, &m) != S_OK) {
20963 				// fail
20964 			} else {
20965 				// succeed, take the data and clean up
20966 
20967 				// FIXME: ensure it is legit HGLOBAL
20968 				auto handle = m.hGlobal;
20969 
20970 				if(handle) {
20971 					auto sz = GlobalSize(handle);
20972 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20973 						scope(exit) GlobalUnlock(handle);
20974 						scope(exit) GlobalFree(handle);
20975 
20976 						auto data = ptr[0 .. sz];
20977 
20978 						dg(data);
20979 					}
20980 				}
20981 			}
20982 		}
20983 	}
20984 
20985 	private:
20986 
20987 	version(X11) {
20988 		SimpleWindow win;
20989 		Window sourceWindow;
20990 		Time dataTimestamp;
20991 
20992 		Atom[] xFormats;
20993 	}
20994 	version(Windows) {
20995 		IDataObject pDataObj;
20996 	}
20997 }
20998 
20999 /++
21000 	A generic helper base class for making a drop handler with a preference list of custom types.
21001 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
21002 	droppers too.
21003 
21004 	It assumes the whole window it used, but you can subclass to change that.
21005 
21006 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21007 
21008 	History:
21009 		Added February 19, 2021
21010 +/
21011 /// Group: drag_and_drop
21012 class GenericDropHandlerBase : DropHandler {
21013 	// no fancy state here so no need to do anything here
21014 	void finish() { }
21015 	void dragLeave() { }
21016 
21017 	private DragAndDropAction acceptedAction;
21018 	private DraggableData.FormatId acceptedFormat;
21019 	private void delegate(scope ubyte[]) acceptedHandler;
21020 
21021 	struct FormatHandler {
21022 		DraggableData.FormatId format;
21023 		void delegate(scope ubyte[]) handler;
21024 	}
21025 
21026 	protected abstract FormatHandler[] formatHandlers();
21027 
21028 	DragAndDropAction dragEnter(DropPackage* pkg) {
21029 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
21030 		foreach(fmt; formatHandlers())
21031 		foreach(f; pkg.availableFormats())
21032 			if(f == fmt.format) {
21033 				acceptedFormat = f;
21034 				acceptedHandler = fmt.handler;
21035 				return acceptedAction = DragAndDropAction.copy;
21036 			}
21037 		return acceptedAction = DragAndDropAction.none;
21038 	}
21039 	DropParameters dragOver(Point pt) {
21040 		return DropParameters(acceptedAction);
21041 	}
21042 
21043 	void drop(scope DropPackage* dropPackage) {
21044 		if(!acceptedFormat || acceptedHandler is null) {
21045 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
21046 			return; // prolly shouldn't happen anyway...
21047 		}
21048 
21049 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
21050 	}
21051 }
21052 
21053 /++
21054 	A simple handler for making your window accept drops of plain text.
21055 
21056 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21057 
21058 	History:
21059 		Added February 22, 2021
21060 +/
21061 /// Group: drag_and_drop
21062 class TextDropHandler : GenericDropHandlerBase {
21063 	private void delegate(in char[] text) dg;
21064 
21065 	/++
21066 
21067 	+/
21068 	this(void delegate(in char[] text) dg) {
21069 		this.dg = dg;
21070 	}
21071 
21072 	protected override FormatHandler[] formatHandlers() {
21073 		version(X11)
21074 			return [
21075 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21076 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21077 			];
21078 		else version(Windows)
21079 			return [
21080 				FormatHandler(CF_UNICODETEXT, &translator),
21081 			];
21082 		else throw new NotYetImplementedException();
21083 	}
21084 
21085 	private void translator(scope ubyte[] data) {
21086 		version(X11)
21087 			dg(cast(char[]) data);
21088 		else version(Windows)
21089 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21090 	}
21091 }
21092 
21093 /++
21094 	A simple handler for making your window accept drops of files, issued to you as file names.
21095 
21096 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21097 
21098 	History:
21099 		Added February 22, 2021
21100 +/
21101 /// Group: drag_and_drop
21102 
21103 class FilesDropHandler : GenericDropHandlerBase {
21104 	private void delegate(in char[][]) dg;
21105 
21106 	/++
21107 
21108 	+/
21109 	this(void delegate(in char[][] fileNames) dg) {
21110 		this.dg = dg;
21111 	}
21112 
21113 	protected override FormatHandler[] formatHandlers() {
21114 		version(X11)
21115 			return [
21116 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
21117 			];
21118 		else version(Windows)
21119 			return [
21120 				FormatHandler(CF_HDROP, &translator),
21121 			];
21122 		else throw new NotYetImplementedException();
21123 	}
21124 
21125 	private void translator(scope ubyte[] data) @system {
21126 		version(X11) {
21127 			char[] listString = cast(char[]) data;
21128 			char[][16] buffer;
21129 			int count;
21130 			char[][] result = buffer[];
21131 
21132 			void commit(char[] s) {
21133 				if(count == result.length)
21134 					result.length += 16;
21135 				if(s.length > 7 && s[0 ..7] == "file://")
21136 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
21137 				result[count++] = s;
21138 			}
21139 
21140 			size_t last;
21141 			foreach(idx, char c; listString) {
21142 				if(c == '\n') {
21143 					commit(listString[last .. idx - 1]); // a \r
21144 					last = idx + 1; // a \n
21145 				}
21146 			}
21147 
21148 			if(last < listString.length) {
21149 				commit(listString[last .. $]);
21150 			}
21151 
21152 			// FIXME: they are uris now, should I translate it to local file names?
21153 			// of course the host name is supposed to be there cuz of X rokking...
21154 
21155 			dg(result[0 .. count]);
21156 		} else version(Windows) {
21157 
21158 			static struct DROPFILES {
21159 				DWORD pFiles;
21160 				POINT pt;
21161 				BOOL  fNC;
21162 				BOOL  fWide;
21163 			}
21164 
21165 
21166 			const(char)[][16] buffer;
21167 			int count;
21168 			const(char)[][] result = buffer[];
21169 			size_t last;
21170 
21171 			void commitA(in char[] stuff) {
21172 				if(count == result.length)
21173 					result.length += 16;
21174 				result[count++] = stuff;
21175 			}
21176 
21177 			void commitW(in wchar[] stuff) {
21178 				commitA(makeUtf8StringFromWindowsString(stuff));
21179 			}
21180 
21181 			void magic(T)(T chars) {
21182 				size_t idx;
21183 				while(chars[idx]) {
21184 					last = idx;
21185 					while(chars[idx]) {
21186 						idx++;
21187 					}
21188 					static if(is(T == char*))
21189 						commitA(chars[last .. idx]);
21190 					else
21191 						commitW(chars[last .. idx]);
21192 					idx++;
21193 				}
21194 			}
21195 
21196 			auto df = cast(DROPFILES*) data.ptr;
21197 			if(df.fWide) {
21198 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
21199 				magic(chars);
21200 			} else {
21201 				char* chars = cast(char*) (data.ptr + df.pFiles);
21202 				magic(chars);
21203 			}
21204 			dg(result[0 .. count]);
21205 		}
21206 		else throw new NotYetImplementedException();
21207 	}
21208 }
21209 
21210 /++
21211 	Interface to describe data being dragged. See also [draggable] helper function.
21212 
21213 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21214 
21215 	History:
21216 		Added February 19, 2021
21217 +/
21218 interface DraggableData {
21219 	version(X11)
21220 		alias FormatId = Atom;
21221 	else
21222 		alias FormatId = uint;
21223 	/++
21224 		Gets the platform-specific FormatId associated with the given named format.
21225 
21226 		This may be a MIME type, but may also be other various strings defined by the
21227 		programs you want to interoperate with.
21228 
21229 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
21230 		and convert it to some particular type for you.
21231 	+/
21232 	static FormatId getFormatId(string name)() {
21233 		version(X11)
21234 			return GetAtom!name(XDisplayConnection.get);
21235 		else version(Windows) {
21236 			static UINT cache;
21237 			if(!cache)
21238 				cache = RegisterClipboardFormatA(name);
21239 			return cache;
21240 		} else
21241 			throw new NotYetImplementedException();
21242 	}
21243 
21244 	/++
21245 		Looks up a string to represent the name for the given format, if there is one.
21246 
21247 		You should avoid using this function because it is slow. It is provided more for
21248 		debugging than for primary use.
21249 	+/
21250 	static string getFormatName(FormatId format) {
21251 		version(X11) {
21252 			if(format == 0)
21253 				return "None";
21254 			else
21255 				return getAtomName(format, XDisplayConnection.get);
21256 		} else version(Windows) {
21257 			switch(format) {
21258 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
21259 				case CF_DIBV5: return "CF_DIBV5";
21260 				case CF_RIFF: return "CF_RIFF";
21261 				case CF_WAVE: return "CF_WAVE";
21262 				case CF_HDROP: return "CF_HDROP";
21263 				default:
21264 					char[1024] name;
21265 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
21266 					return name[0 .. count].idup;
21267 			}
21268 		} else throw new NotYetImplementedException();
21269 	}
21270 
21271 	FormatId[] availableFormats();
21272 	// Return the slice of data you filled, empty slice if done.
21273 	// this is to support the incremental thing
21274 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21275 
21276 	size_t dataLength(FormatId format);
21277 }
21278 
21279 /++
21280 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21281 
21282 	History:
21283 		Added February 19, 2021
21284 +/
21285 DraggableData draggable(string s) {
21286 	version(X11)
21287 	return new class X11SetSelectionHandler_Text, DraggableData {
21288 		this() {
21289 			super(s);
21290 		}
21291 
21292 		override FormatId[] availableFormats() {
21293 			return X11SetSelectionHandler_Text.availableFormats();
21294 		}
21295 
21296 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21297 			return X11SetSelectionHandler_Text.getData(format, data);
21298 		}
21299 
21300 		size_t dataLength(FormatId format) {
21301 			return s.length;
21302 		}
21303 	};
21304 	else version(Windows)
21305 	return new class DraggableData {
21306 		FormatId[] availableFormats() {
21307 			return [CF_UNICODETEXT];
21308 		}
21309 
21310 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21311 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21312 		}
21313 
21314 		size_t dataLength(FormatId format) {
21315 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21316 		}
21317 	};
21318 	else
21319 	throw new NotYetImplementedException();
21320 }
21321 
21322 /++
21323 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21324 
21325 	History:
21326 		Added February 19, 2021
21327 +/
21328 /// Group: drag_and_drop
21329 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21330 in {
21331 	assert(window !is null);
21332 	assert(handler !is null);
21333 }
21334 do
21335 {
21336 	version(X11) {
21337 		auto sh = cast(X11SetSelectionHandler) handler;
21338 		if(sh is null) {
21339 			// gotta make my own adapter.
21340 			sh = new class X11SetSelectionHandler {
21341 				mixin X11SetSelectionHandler_Basics;
21342 
21343 				Atom[] availableFormats() { return handler.availableFormats(); }
21344 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21345 					return handler.getData(format, data);
21346 				}
21347 
21348 				// since the drop selection is only ever used once it isn't important
21349 				// to reset it.
21350 				void done() {}
21351 			};
21352 		}
21353 		return doDragDropX11(window, sh, action);
21354 	} else version(Windows) {
21355 		return doDragDropWindows(window, handler, action);
21356 	} else throw new NotYetImplementedException();
21357 }
21358 
21359 version(Windows)
21360 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21361 	IDataObject obj = new class IDataObject {
21362 		ULONG refCount;
21363 		ULONG AddRef() {
21364 			return ++refCount;
21365 		}
21366 		ULONG Release() {
21367 			return --refCount;
21368 		}
21369 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21370 			if (IID_IUnknown == *riid) {
21371 				*ppv = cast(void*) cast(IUnknown) this;
21372 			}
21373 			else if (IID_IDataObject == *riid) {
21374 				*ppv = cast(void*) cast(IDataObject) this;
21375 			}
21376 			else {
21377 				*ppv = null;
21378 				return E_NOINTERFACE;
21379 			}
21380 
21381 			AddRef();
21382 			return NOERROR;
21383 		}
21384 
21385 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21386 			//  writeln("Advise");
21387 			return E_NOTIMPL;
21388 		}
21389 		HRESULT DUnadvise(DWORD dwConnection) {
21390 			return E_NOTIMPL;
21391 		}
21392 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21393 			//  writeln("EnumDAdvise");
21394 			return OLE_E_ADVISENOTSUPPORTED;
21395 		}
21396 		// tell what formats it supports
21397 
21398 		FORMATETC[] types;
21399 		this() {
21400 			FORMATETC t;
21401 			foreach(ty; handler.availableFormats()) {
21402 				assert(ty <= ushort.max && ty >= 0);
21403 				t.cfFormat = cast(ushort) ty;
21404 				t.lindex = -1;
21405 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21406 				t.tymed = TYMED.TYMED_HGLOBAL;
21407 			}
21408 			types ~= t;
21409 		}
21410 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21411 			if(dwDirection == DATADIR.DATADIR_GET) {
21412 				*ppenumFormatEtc = new class IEnumFORMATETC {
21413 					ULONG refCount;
21414 					ULONG AddRef() {
21415 						return ++refCount;
21416 					}
21417 					ULONG Release() {
21418 						return --refCount;
21419 					}
21420 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21421 						if (IID_IUnknown == *riid) {
21422 							*ppv = cast(void*) cast(IUnknown) this;
21423 						}
21424 						else if (IID_IEnumFORMATETC == *riid) {
21425 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21426 						}
21427 						else {
21428 							*ppv = null;
21429 							return E_NOINTERFACE;
21430 						}
21431 
21432 						AddRef();
21433 						return NOERROR;
21434 					}
21435 
21436 
21437 					int pos;
21438 					this() {
21439 						pos = 0;
21440 					}
21441 
21442 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21443 						// writeln("clone");
21444 						return E_NOTIMPL; // FIXME
21445 					}
21446 
21447 					// Caller is responsible for freeing memory
21448 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21449 						// fetched may be null if celt is one
21450 						if(celt != 1)
21451 							return E_NOTIMPL; // FIXME
21452 
21453 						if(celt + pos > types.length)
21454 							return S_FALSE;
21455 
21456 						*rgelt = types[pos++];
21457 
21458 						if(pceltFetched !is null)
21459 							*pceltFetched = 1;
21460 
21461 						// writeln("ok celt ", celt);
21462 						return S_OK;
21463 					}
21464 
21465 					HRESULT Reset() {
21466 						pos = 0;
21467 						return S_OK;
21468 					}
21469 
21470 					HRESULT Skip(ULONG celt) {
21471 						if(celt + pos <= types.length) {
21472 							pos += celt;
21473 							return S_OK;
21474 						}
21475 						return S_FALSE;
21476 					}
21477 				};
21478 
21479 				return S_OK;
21480 			} else
21481 				return E_NOTIMPL;
21482 		}
21483 		// given a format, return the format you'd prefer to use cuz it is identical
21484 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21485 			// FIXME: prolly could be better but meh
21486 			// writeln("gcf: ", *pformatectIn);
21487 			*pformatetcOut = *pformatectIn;
21488 			return S_OK;
21489 		}
21490 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21491 			foreach(ty; types) {
21492 				if(ty == *pformatetcIn) {
21493 					auto format = ty.cfFormat;
21494 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
21495 					STGMEDIUM medium;
21496 					medium.tymed = TYMED.TYMED_HGLOBAL;
21497 
21498 					auto sz = handler.dataLength(format);
21499 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21500 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21501 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21502 						auto slice = data[0 .. sz];
21503 						scope(exit)
21504 							GlobalUnlock(handle);
21505 
21506 						handler.getData(format, cast(ubyte[]) slice[]);
21507 					}
21508 
21509 
21510 					medium.hGlobal = handle; // FIXME
21511 					*pmedium = medium;
21512 					return S_OK;
21513 				}
21514 			}
21515 			return DV_E_FORMATETC;
21516 		}
21517 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21518 			// writeln("GDH: ", *pformatetcIn);
21519 			return E_NOTIMPL; // FIXME
21520 		}
21521 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21522 			auto search = *pformatetc;
21523 			search.tymed &= TYMED.TYMED_HGLOBAL;
21524 			foreach(ty; types)
21525 				if(ty == search) {
21526 					// writeln("QueryGetData ", search, " ", types[0]);
21527 					return S_OK;
21528 				}
21529 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21530 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
21531 			}
21532 			return S_FALSE;
21533 		}
21534 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21535 			//  writeln("SetData: ");
21536 			return E_NOTIMPL;
21537 		}
21538 	};
21539 
21540 
21541 	IDropSource src = new class IDropSource {
21542 		ULONG refCount;
21543 		ULONG AddRef() {
21544 			return ++refCount;
21545 		}
21546 		ULONG Release() {
21547 			return --refCount;
21548 		}
21549 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21550 			if (IID_IUnknown == *riid) {
21551 				*ppv = cast(void*) cast(IUnknown) this;
21552 			}
21553 			else if (IID_IDropSource == *riid) {
21554 				*ppv = cast(void*) cast(IDropSource) this;
21555 			}
21556 			else {
21557 				*ppv = null;
21558 				return E_NOINTERFACE;
21559 			}
21560 
21561 			AddRef();
21562 			return NOERROR;
21563 		}
21564 
21565 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21566 			if(fEscapePressed)
21567 				return DRAGDROP_S_CANCEL;
21568 			if(!(grfKeyState & MK_LBUTTON))
21569 				return DRAGDROP_S_DROP;
21570 			return S_OK;
21571 		}
21572 
21573 		int GiveFeedback(uint dwEffect) {
21574 			return DRAGDROP_S_USEDEFAULTCURSORS;
21575 		}
21576 	};
21577 
21578 	DWORD effect;
21579 
21580 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21581 
21582 	DROPEFFECT de = win32DragAndDropAction(action);
21583 
21584 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21585 	// but still prolly a FIXME
21586 
21587 	auto ret = DoDragDrop(obj, src, de, &effect);
21588 	/+
21589 	if(ret == DRAGDROP_S_DROP)
21590 		writeln("drop ", effect);
21591 	else if(ret == DRAGDROP_S_CANCEL)
21592 		writeln("cancel");
21593 	else if(ret == S_OK)
21594 		writeln("ok");
21595 	else writeln(ret);
21596 	+/
21597 
21598 	return ret;
21599 }
21600 
21601 version(Windows)
21602 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21603 	DROPEFFECT de;
21604 
21605 	with(DragAndDropAction)
21606 	with(DROPEFFECT)
21607 	final switch(action) {
21608 		case none: de = DROPEFFECT_NONE; break;
21609 		case copy: de = DROPEFFECT_COPY; break;
21610 		case move: de = DROPEFFECT_MOVE; break;
21611 		case link: de = DROPEFFECT_LINK; break;
21612 		case ask: throw new Exception("ask not implemented yet");
21613 		case custom: throw new Exception("custom not implemented yet");
21614 	}
21615 
21616 	return de;
21617 }
21618 
21619 
21620 /++
21621 	History:
21622 		Added February 19, 2021
21623 +/
21624 /// Group: drag_and_drop
21625 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21626 	version(X11) {
21627 		auto display = XDisplayConnection.get;
21628 
21629 		Atom atom = 5; // right???
21630 
21631 		XChangeProperty(
21632 			display,
21633 			window.impl.window,
21634 			GetAtom!"XdndAware"(display),
21635 			XA_ATOM,
21636 			32 /* bits */,
21637 			PropModeReplace,
21638 			&atom,
21639 			1);
21640 
21641 		window.dropHandler = handler;
21642 	} else version(Windows) {
21643 
21644 		initDnd();
21645 
21646 		auto dropTarget = new class (handler) IDropTarget {
21647 			DropHandler handler;
21648 			this(DropHandler handler) {
21649 				this.handler = handler;
21650 			}
21651 			ULONG refCount;
21652 			ULONG AddRef() {
21653 				return ++refCount;
21654 			}
21655 			ULONG Release() {
21656 				return --refCount;
21657 			}
21658 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21659 				if (IID_IUnknown == *riid) {
21660 					*ppv = cast(void*) cast(IUnknown) this;
21661 				}
21662 				else if (IID_IDropTarget == *riid) {
21663 					*ppv = cast(void*) cast(IDropTarget) this;
21664 				}
21665 				else {
21666 					*ppv = null;
21667 					return E_NOINTERFACE;
21668 				}
21669 
21670 				AddRef();
21671 				return NOERROR;
21672 			}
21673 
21674 
21675 			// ///////////////////
21676 
21677 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21678 				DropPackage dropPackage = DropPackage(pDataObj);
21679 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21680 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21681 			}
21682 
21683 			HRESULT DragLeave() {
21684 				handler.dragLeave();
21685 				// release the IDataObject if needed
21686 				return S_OK;
21687 			}
21688 
21689 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21690 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21691 
21692 				*pdwEffect = win32DragAndDropAction(res.action);
21693 				// same as DragEnter basically
21694 				return S_OK;
21695 			}
21696 
21697 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21698 				DropPackage pkg = DropPackage(pDataObj);
21699 				handler.drop(&pkg);
21700 
21701 				return S_OK;
21702 			}
21703 		};
21704 		// Windows can hold on to the handler and try to call it
21705 		// during which time the GC can't see it. so important to
21706 		// manually manage this. At some point i'll FIXME and make
21707 		// all my com instances manually managed since they supposed
21708 		// to respect the refcount.
21709 		import core.memory;
21710 		GC.addRoot(cast(void*) dropTarget);
21711 
21712 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21713 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21714 
21715 		window.dropHandler = handler;
21716 	} else throw new NotYetImplementedException();
21717 }
21718 
21719 
21720 
21721 static if(UsingSimpledisplayX11) {
21722 
21723 enum _NET_WM_STATE_ADD = 1;
21724 enum _NET_WM_STATE_REMOVE = 0;
21725 enum _NET_WM_STATE_TOGGLE = 2;
21726 
21727 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21728 void demandAttention(SimpleWindow window, bool needs = true) {
21729 	demandAttention(window.impl.window, needs);
21730 }
21731 
21732 /// ditto
21733 void demandAttention(Window window, bool needs = true) {
21734 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21735 }
21736 
21737 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21738 	auto display = XDisplayConnection.get();
21739 	if(atom == None)
21740 		return; // non-failure error
21741 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21742 
21743 	XClientMessageEvent xclient;
21744 
21745 	xclient.type = EventType.ClientMessage;
21746 	xclient.window = window;
21747 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21748 	xclient.format = 32;
21749 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21750 	xclient.data.l[1] = atom;
21751 	xclient.data.l[2] = atom2;
21752 	xclient.data.l[3] = 1;
21753 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21754 
21755 	XSendEvent(
21756 		display,
21757 		RootWindow(display, DefaultScreen(display)),
21758 		false,
21759 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21760 		cast(XEvent*) &xclient
21761 	);
21762 
21763 	/+
21764 	XChangeProperty(
21765 		display,
21766 		window.impl.window,
21767 		GetAtom!"_NET_WM_STATE"(display),
21768 		XA_ATOM,
21769 		32 /* bits */,
21770 		PropModeAppend,
21771 		&atom,
21772 		1);
21773 	+/
21774 }
21775 
21776 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21777 	Atom actionAtom;
21778 	with(DragAndDropAction)
21779 	final switch(action) {
21780 		case none: actionAtom = None; break;
21781 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21782 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21783 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21784 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21785 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21786 	}
21787 
21788 	return actionAtom;
21789 }
21790 
21791 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21792 	// FIXME: I need to show user feedback somehow.
21793 	auto display = XDisplayConnection.get;
21794 
21795 	auto actionAtom = dndActionAtom(display, action);
21796 	assert(actionAtom, "Don't use action none to accept a drop");
21797 
21798 	setX11Selection!"XdndSelection"(window, handler, null);
21799 
21800 	auto oldKeyHandler = window.handleKeyEvent;
21801 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21802 
21803 	auto oldCharHandler = window.handleCharEvent;
21804 	scope(exit) window.handleCharEvent = oldCharHandler;
21805 
21806 	auto oldMouseHandler = window.handleMouseEvent;
21807 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21808 
21809 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21810 
21811 	import core.sys.posix.sys.time;
21812 	timeval tv;
21813 	gettimeofday(&tv, null);
21814 
21815 	Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 );
21816 
21817 	Time lastMouseTimestamp;
21818 
21819 	bool dnding = true;
21820 	Window lastIn = None;
21821 
21822 	void leave() {
21823 		if(lastIn == None)
21824 			return;
21825 
21826 		XEvent ev;
21827 		ev.xclient.type = EventType.ClientMessage;
21828 		ev.xclient.window = lastIn;
21829 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21830 		ev.xclient.format = 32;
21831 		ev.xclient.data.l[0] = window.impl.window;
21832 
21833 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21834 		XFlush(display);
21835 
21836 		lastIn = None;
21837 	}
21838 
21839 	void enter(Window w) {
21840 		assert(lastIn == None);
21841 
21842 		lastIn = w;
21843 
21844 		XEvent ev;
21845 		ev.xclient.type = EventType.ClientMessage;
21846 		ev.xclient.window = lastIn;
21847 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21848 		ev.xclient.format = 32;
21849 		ev.xclient.data.l[0] = window.impl.window;
21850 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21851 
21852 		auto types = handler.availableFormats();
21853 		assert(types.length > 0);
21854 
21855 		ev.xclient.data.l[2] = types[0];
21856 		if(types.length > 1)
21857 			ev.xclient.data.l[3] = types[1];
21858 		if(types.length > 2)
21859 			ev.xclient.data.l[4] = types[2];
21860 
21861 		// FIXME: other types?!?!? and make sure we skip TARGETS
21862 
21863 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21864 		XFlush(display);
21865 	}
21866 
21867 	void position(int rootX, int rootY) {
21868 		assert(lastIn != None);
21869 
21870 		XEvent ev;
21871 		ev.xclient.type = EventType.ClientMessage;
21872 		ev.xclient.window = lastIn;
21873 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21874 		ev.xclient.format = 32;
21875 		ev.xclient.data.l[0] = window.impl.window;
21876 		ev.xclient.data.l[1] = 0; // reserved
21877 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21878 		ev.xclient.data.l[3] = dataTimestamp;
21879 		ev.xclient.data.l[4] = actionAtom;
21880 
21881 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21882 		XFlush(display);
21883 
21884 	}
21885 
21886 	void drop() {
21887 		XEvent ev;
21888 		ev.xclient.type = EventType.ClientMessage;
21889 		ev.xclient.window = lastIn;
21890 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21891 		ev.xclient.format = 32;
21892 		ev.xclient.data.l[0] = window.impl.window;
21893 		ev.xclient.data.l[1] = 0; // reserved
21894 		ev.xclient.data.l[2] = dataTimestamp;
21895 
21896 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21897 		XFlush(display);
21898 
21899 		lastIn = None;
21900 		dnding = false;
21901 	}
21902 
21903 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21904 	// but idk if i should...
21905 
21906 	window.setEventHandlers(
21907 		delegate(KeyEvent ev) {
21908 			if(ev.pressed == true && ev.key == Key.Escape) {
21909 				// cancel
21910 				dnding = false;
21911 			}
21912 		},
21913 		delegate(MouseEvent ev) {
21914 			if(ev.timestamp < lastMouseTimestamp)
21915 				return;
21916 
21917 			lastMouseTimestamp = ev.timestamp;
21918 
21919 			if(ev.type == MouseEventType.motion) {
21920 				auto display = XDisplayConnection.get;
21921 				auto root = RootWindow(display, DefaultScreen(display));
21922 
21923 				Window topWindow;
21924 				int rootX, rootY;
21925 
21926 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21927 
21928 				if(topWindow == None)
21929 					return;
21930 
21931 				top:
21932 				if(auto result = topWindow in eligibility) {
21933 					auto dropWindow = *result;
21934 					if(dropWindow == None) {
21935 						leave();
21936 						return;
21937 					}
21938 
21939 					if(dropWindow != lastIn) {
21940 						leave();
21941 						enter(dropWindow);
21942 						position(rootX, rootY);
21943 					} else {
21944 						position(rootX, rootY);
21945 					}
21946 				} else {
21947 					// determine eligibility
21948 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21949 					if(data.length == 1) {
21950 						// in case there is no WM or it isn't reparenting
21951 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21952 					} else {
21953 
21954 						Window tryScanChildren(Window search, int maxRecurse) {
21955 							// could be reparenting window manager, so gotta check the next few children too
21956 							Window child;
21957 							int x;
21958 							int y;
21959 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21960 
21961 							if(child == None)
21962 								return None;
21963 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21964 							if(data.length == 1) {
21965 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21966 							} else {
21967 								if(maxRecurse)
21968 									return tryScanChildren(child, maxRecurse - 1);
21969 								else
21970 									return None;
21971 							}
21972 
21973 						}
21974 
21975 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21976 						auto topResult = tryScanChildren(topWindow, 3);
21977 						// it is easy to have a false negative due to the mouse going over a WM
21978 						// child window like the close button if separate from the frame... so I
21979 						// can't really cache negatives, :(
21980 						if(topResult != None) {
21981 							eligibility[topWindow] = topResult;
21982 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21983 						}
21984 					}
21985 
21986 				}
21987 
21988 			} else if(ev.type == MouseEventType.buttonReleased) {
21989 				drop();
21990 				dnding = false;
21991 			}
21992 		}
21993 	);
21994 
21995 	window.grabInput();
21996 	scope(exit)
21997 		window.releaseInputGrab();
21998 
21999 
22000 	EventLoop.get.run(() => dnding);
22001 
22002 	return 0;
22003 }
22004 
22005 /// X-specific
22006 TrueColorImage getWindowNetWmIcon(Window window) {
22007 	try {
22008 		auto display = XDisplayConnection.get;
22009 
22010 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
22011 
22012 		if (data.length > arch_ulong.sizeof * 2) {
22013 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
22014 			// these are an array of rgba images that we have to convert into pixmaps ourself
22015 
22016 			int width = cast(int) meta[0];
22017 			int height = cast(int) meta[1];
22018 
22019 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
22020 
22021 			static if(arch_ulong.sizeof == 4) {
22022 				bytes = bytes[0 .. width * height * 4];
22023 				alias imageData = bytes;
22024 			} else static if(arch_ulong.sizeof == 8) {
22025 				bytes = bytes[0 .. width * height * 8];
22026 				auto imageData = new ubyte[](4 * width * height);
22027 			} else static assert(0);
22028 
22029 
22030 
22031 			// this returns ARGB. Remember it is little-endian so
22032 			//                                         we have BGRA
22033 			// our thing uses RGBA, which in little endian, is ABGR
22034 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
22035 				auto r = bytes[idx + 2];
22036 				auto g = bytes[idx + 1];
22037 				auto b = bytes[idx + 0];
22038 				auto a = bytes[idx + 3];
22039 
22040 				imageData[idx2 + 0] = r;
22041 				imageData[idx2 + 1] = g;
22042 				imageData[idx2 + 2] = b;
22043 				imageData[idx2 + 3] = a;
22044 			}
22045 
22046 			return new TrueColorImage(width, height, imageData);
22047 		}
22048 
22049 		return null;
22050 	} catch(Exception e) {
22051 		return null;
22052 	}
22053 }
22054 
22055 } /* UsingSimpledisplayX11 */
22056 
22057 
22058 void loadBinNameToWindowClassName () {
22059 	import core.stdc.stdlib : realloc;
22060 	version(linux) {
22061 		// args[0] MAY be empty, so we'll just use this
22062 		import core.sys.posix.unistd : readlink;
22063 		char[1024] ebuf = void; // 1KB should be enough for everyone!
22064 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
22065 		if (len < 1) return;
22066 	} else /*version(Windows)*/ {
22067 		import core.runtime : Runtime;
22068 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
22069 		auto ebuf = Runtime.args[0];
22070 		auto len = ebuf.length;
22071 	}
22072 	auto pos = len;
22073 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22074 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22075 	if (sdpyWindowClassStr is null) return; // oops
22076 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22077 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22078 }
22079 
22080 /++
22081 	An interface representing a font that is drawn with custom facilities.
22082 
22083 	You might want [OperatingSystemFont] instead, which represents
22084 	a font loaded and drawn by functions native to the operating system.
22085 
22086 	WARNING: I might still change this.
22087 +/
22088 interface DrawableFont : MeasurableFont {
22089 	/++
22090 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22091 
22092 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22093 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22094 		fill color, but that's up to the implementation.
22095 	+/
22096 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22097 
22098 	/++
22099 		Requests that the given string is added to the image cache. You should only do this rarely, but
22100 		if you have a string that you know will be used over and over again, adding it to a cache can
22101 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22102 		to implement this as a do-nothing method).
22103 	+/
22104 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22105 }
22106 
22107 /++
22108 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
22109 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
22110 
22111 	You should also consider [OperatingSystemFont], which loads and draws a font with
22112 	facilities native to the user's operating system. You might also consider
22113 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
22114 	of game, as they have their own ways to draw text too.
22115 
22116 	Be warned: this can be slow, especially on remote connections to the X server, since
22117 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
22118 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
22119 	experiment in your specific case.
22120 
22121 	Please note that the return type of [DrawableFont] also includes an implementation of
22122 	[MeasurableFont].
22123 +/
22124 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
22125 	import arsd.ttf;
22126 	static class ArsdTtfFont : DrawableFont {
22127 		TtfFont font;
22128 		int size;
22129 		this(in ubyte[] data, int size) {
22130 			font = TtfFont(data);
22131 			this.size = size;
22132 
22133 
22134 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
22135 			int ascent_, descent_, line_gap;
22136 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
22137 
22138 			int advance, lsb;
22139 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
22140 			xWidth = cast(int) (advance * scale);
22141 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
22142 			MWidth = cast(int) (advance * scale);
22143 		}
22144 
22145 		private int ascent_;
22146 		private int descent_;
22147 		private int xWidth;
22148 		private int MWidth;
22149 
22150 		bool isMonospace() {
22151 			return xWidth == MWidth;
22152 		}
22153 		int averageWidth() {
22154 			return xWidth;
22155 		}
22156 		int height() {
22157 			return size;
22158 		}
22159 		int ascent() {
22160 			return ascent_;
22161 		}
22162 		int descent() {
22163 			return descent_;
22164 		}
22165 
22166 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
22167 			int width, height;
22168 			font.getStringSize(s, size, width, height);
22169 			return width;
22170 		}
22171 
22172 
22173 
22174 		Sprite[string] cache;
22175 
22176 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
22177 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
22178 			cache[text] = sprite;
22179 		}
22180 
22181 		Image stringToImage(Color fg, Color bg, in char[] text) {
22182 			int width, height;
22183 			auto data = font.renderString(text, size, width, height);
22184 			auto image = new TrueColorImage(width, height);
22185 			int pos = 0;
22186 			foreach(y; 0 .. height)
22187 			foreach(x; 0 .. width) {
22188 				fg.a = data[0];
22189 				bg.a = 255;
22190 				auto color = alphaBlend(fg, bg);
22191 				image.imageData.bytes[pos++] = color.r;
22192 				image.imageData.bytes[pos++] = color.g;
22193 				image.imageData.bytes[pos++] = color.b;
22194 				image.imageData.bytes[pos++] = data[0];
22195 				data = data[1 .. $];
22196 			}
22197 			assert(data.length == 0);
22198 
22199 			return Image.fromMemoryImage(image);
22200 		}
22201 
22202 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
22203 			Sprite sprite = (text in cache) ? *(text in cache) : null;
22204 
22205 			auto fg = painter.impl._outlineColor;
22206 			auto bg = painter.impl._fillColor;
22207 
22208 			if(sprite !is null) {
22209 				auto w = cast(SimpleWindow) painter.window;
22210 				assert(w !is null);
22211 
22212 				sprite.drawAt(painter, upperLeft);
22213 			} else {
22214 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
22215 			}
22216 		}
22217 	}
22218 
22219 	return new ArsdTtfFont(data, size);
22220 }
22221 
22222 class NotYetImplementedException : Exception {
22223 	this(string file = __FILE__, size_t line = __LINE__) {
22224 		super("Not yet implemented", file, line);
22225 	}
22226 }
22227 
22228 ///
22229 __gshared bool librariesSuccessfullyLoaded = true;
22230 ///
22231 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
22232 
22233 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
22234 	// mixin(staticForeachReplacement!Iface);
22235 	static foreach(name; __traits(derivedMembers, Iface))
22236 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22237 
22238 	void loadDynamicLibrary() @nogc {
22239 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22240 	}
22241 
22242 	void loadDynamicLibraryForReal() {
22243 		foreach(name; __traits(derivedMembers, Iface)) {
22244 			mixin("alias tmp = " ~ name ~ ";");
22245 			tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
22246 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
22247 		}
22248 	}
22249 }
22250 
22251 /+
22252 private const(char)[] staticForeachReplacement(Iface)() pure {
22253 /*
22254 	// just this for gdc 9....
22255 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
22256 
22257 	static foreach(name; __traits(derivedMembers, Iface))
22258 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22259 */
22260 
22261 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
22262 	size_t pos;
22263 
22264 	void append(in char[] what) {
22265 		if(pos + what.length > code.length)
22266 			code.length = (code.length * 3) / 2;
22267 		code[pos .. pos + what.length] = what[];
22268 		pos += what.length;
22269 	}
22270 
22271 	foreach(name; __traits(derivedMembers, Iface)) {
22272 		append(`__gshared typeof(&__traits(getMember, Iface, "`);
22273 		append(name);
22274 		append(`")) `);
22275 		append(name);
22276 		append(";");
22277 	}
22278 
22279 	return code[0 .. pos];
22280 }
22281 +/
22282 
22283 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22284 	//mixin(staticForeachReplacement!Iface);
22285 	static foreach(name; __traits(derivedMembers, Iface))
22286 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22287 
22288 	private __gshared void* libHandle;
22289 	private __gshared bool attempted;
22290 
22291 	void loadDynamicLibrary() @nogc {
22292 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22293 	}
22294 
22295 	bool loadAttempted() {
22296 		return attempted;
22297 	}
22298 	bool loadSuccessful() {
22299 		return libHandle !is null;
22300 	}
22301 
22302 	void loadDynamicLibraryForReal() {
22303 		attempted = true;
22304 		version(Posix) {
22305 			import core.sys.posix.dlfcn;
22306 			version(OSX) {
22307 				version(X11)
22308 					libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22309 				else
22310 					libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22311 			} else {
22312 				version(apitrace) {
22313 					if(library == "GL" || library == "GLX") {
22314 						libHandle = dlopen("glxtrace.so", RTLD_NOW);
22315 						if(libHandle is null) {
22316 							assert(false, "Failed to load `glxtrace.so`.");
22317 						}
22318 					}
22319 					else {
22320 						libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22321 					}
22322 				}
22323 				else {
22324 					libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22325 				}
22326 				if(libHandle is null) {
22327 					libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22328 				}
22329 			}
22330 
22331 			static void* loadsym(void* l, const char* name) {
22332 				import core.stdc.stdlib;
22333 				if(l is null)
22334 					return &abort;
22335 				return dlsym(l, name);
22336 			}
22337 		} else version(Windows) {
22338 			import core.sys.windows.winbase;
22339 			libHandle = LoadLibrary(library ~ ".dll");
22340 			static void* loadsym(void* l, const char* name) {
22341 				import core.stdc.stdlib;
22342 				if(l is null)
22343 					return &abort;
22344 				return GetProcAddress(l, name);
22345 			}
22346 		}
22347 		if(libHandle is null) {
22348 			success = false;
22349 			//throw new Exception("load failure of library " ~ library);
22350 		}
22351 		foreach(name; __traits(derivedMembers, Iface)) {
22352 			mixin("alias tmp = " ~ name ~ ";");
22353 			tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22354 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22355 		}
22356 	}
22357 
22358 	void unloadDynamicLibrary() {
22359 		version(Posix) {
22360 			import core.sys.posix.dlfcn;
22361 			dlclose(libHandle);
22362 		} else version(Windows) {
22363 			import core.sys.windows.winbase;
22364 			FreeLibrary(libHandle);
22365 		}
22366 		foreach(name; __traits(derivedMembers, Iface))
22367 			mixin(name ~ " = null;");
22368 	}
22369 }
22370 
22371 // version(X11)
22372 /++
22373 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22374 
22375 	$(WARNING
22376 		This function is exempted from stability guarantees.
22377 	)
22378 +/
22379 float customScalingFactorForMonitor(int monitorNumber) @system {
22380 	import core.stdc.stdlib;
22381 	auto val = getenv("ARSD_SCALING_FACTOR");
22382 
22383 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
22384 	if(val is null)
22385 		return 1.0;
22386 
22387 	char[16] buffer = 0;
22388 	int pos;
22389 
22390 	const(char)* at = val;
22391 
22392 	foreach(item; 0 .. monitorNumber + 1) {
22393 		if(*at == 0)
22394 			break; // reuse the last number when we at the end of the string
22395 		pos = 0;
22396 		while(pos + 1 < buffer.length && *at && *at != ';') {
22397 			buffer[pos++] = *at;
22398 			at++;
22399 		}
22400 		if(*at)
22401 			at++; // skip the semicolon
22402 		buffer[pos] = 0;
22403 	}
22404 
22405 	//sdpyPrintDebugString(buffer[0 .. pos]);
22406 
22407 	import core.stdc.math;
22408 	auto f = atof(buffer.ptr);
22409 
22410 	if(f <= 0.0 || isnan(f) || isinf(f))
22411 		return 1.0;
22412 
22413 	return f;
22414 }
22415 
22416 void guiAbortProcess(string msg) {
22417 	import core.stdc.stdlib;
22418 	version(Windows) {
22419 		WCharzBuffer t = WCharzBuffer(msg);
22420 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22421 	} else {
22422 		import core.stdc.stdio;
22423 		fwrite(msg.ptr, 1, msg.length, stderr);
22424 		msg = "\n";
22425 		fwrite(msg.ptr, 1, msg.length, stderr);
22426 		fflush(stderr);
22427 	}
22428 
22429 	abort();
22430 }
22431 
22432 private int minInternal(int a, int b) {
22433 	return (a < b) ? a : b;
22434 }
22435 
22436 private alias scriptable = arsd_jsvar_compatible;