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 		module opengl3test;
371 		import arsd.simpledisplay;
372 
373 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
374 
375 		void main() {
376 			// First thing we do, before creating the window, is declare what version we want.
377 			setOpenGLContextVersion(3, 3);
378 			// turning off legacy compat is required to use version 3.3 and newer
379 			openGLContextCompatible = false;
380 
381 			uint VAO;
382 			OpenGlShader shader;
383 
384 			// then we can create the window.
385 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
386 
387 			// additional setup needs to be done when it is visible, simpledisplay offers a property
388 			// for exactly that:
389 			window.visibleForTheFirstTime = delegate() {
390 				// now with the window loaded, we can start loading the modern opengl functions.
391 
392 				// you MUST set the context first.
393 				window.setAsCurrentOpenGlContext;
394 				// then load the remainder of the library
395 				gl3.loadDynamicLibrary();
396 
397 				// now you can create the shaders, etc.
398 				shader = new OpenGlShader(
399 					OpenGlShader.Source(GL_VERTEX_SHADER, `
400 						#version 330 core
401 						layout (location = 0) in vec3 aPos;
402 						void main() {
403 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
404 						}
405 					`),
406 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
407 						#version 330 core
408 						out vec4 FragColor;
409 						uniform vec4 mycolor;
410 						void main() {
411 							FragColor = mycolor;
412 						}
413 					`),
414 				);
415 
416 				// and do whatever other setup you want.
417 
418 				float[] vertices = [
419 					0.5f,  0.5f, 0.0f,  // top right
420 					0.5f, -0.5f, 0.0f,  // bottom right
421 					-0.5f, -0.5f, 0.0f,  // bottom left
422 					-0.5f,  0.5f, 0.0f   // top left
423 				];
424 				uint[] indices = [  // note that we start from 0!
425 					0, 1, 3,  // first Triangle
426 					1, 2, 3   // second Triangle
427 				];
428 				uint VBO, EBO;
429 				glGenVertexArrays(1, &VAO);
430 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
431 				glBindVertexArray(VAO);
432 
433 				glGenBuffers(1, &VBO);
434 				glGenBuffers(1, &EBO);
435 
436 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
437 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
438 
439 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
440 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
441 
442 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
443 				glEnableVertexAttribArray(0);
444 
445 				// the library will set the initial viewport and trigger our first draw,
446 				// so these next two lines are NOT needed. they are just here as comments
447 				// to show what would happen next.
448 
449 				// glViewport(0, 0, window.width, window.height);
450 				// window.redrawOpenGlSceneNow();
451 			};
452 
453 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
454 			// it is our render method.
455 			window.redrawOpenGlScene = delegate() {
456 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
457 				glClear(GL_COLOR_BUFFER_BIT);
458 
459 				glUseProgram(shader.shaderProgram);
460 
461 				// the shader helper class has methods to set uniforms too
462 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
463 
464 				glBindVertexArray(VAO);
465 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
466 			};
467 
468 			window.eventLoop(0);
469 		}
470 		---
471 
472 	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.
473 
474 	$(H3 $(ID vulkan) Vulkan)
475 
476 	See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings:
477 
478 	https://github.com/adamdruppe/VulkanizeDSdpy
479 
480 	https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo
481 
482 	$(H3 $(ID topic-images) Displaying images)
483 		You can also load PNG images using [arsd.png].
484 
485 		---
486 		// dmd example.d simpledisplay.d color.d png.d
487 		import arsd.simpledisplay;
488 		import arsd.png;
489 
490 		void main() {
491 			auto image = Image.fromMemoryImage(readPng("image.png"));
492 			displayImage(image);
493 		}
494 		---
495 
496 		Compile with `dmd example.d simpledisplay.d png.d`.
497 
498 		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.
499 
500 	$(H3 $(ID topic-sprites) Sprites)
501 		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.
502 
503 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
504 
505 	$(H3 $(ID topic-clipboard) Clipboard)
506 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
507 
508 		It also has helpers for handling X-specific events.
509 
510 	$(H3 $(ID topic-dnd) Drag and Drop)
511 		See [enableDragAndDrop] and [draggable].
512 
513 	$(H3 $(ID topic-timers) Timers)
514 		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].
515 
516 		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.
517 
518 		---
519 			import arsd.simpledisplay;
520 
521 			void main() {
522 				auto window = new SimpleWindow(400, 400);
523 				// every 100 ms, it will draw a random line
524 				// on the window.
525 				window.eventLoop(100, {
526 					auto painter = window.draw();
527 
528 					import std.random;
529 					// random color
530 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
531 					// random line
532 					painter.drawLine(
533 						Point(uniform(0, window.width), uniform(0, window.height)),
534 						Point(uniform(0, window.width), uniform(0, window.height)));
535 
536 				});
537 			}
538 		---
539 
540 		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.
541 
542 		The pulse timer and instances of the [Timer] class may be combined at will.
543 
544 		---
545 			import arsd.simpledisplay;
546 
547 			void main() {
548 				auto window = new SimpleWindow(400, 400);
549 				auto timer = new Timer(1000, delegate {
550 					auto painter = window.draw();
551 					painter.clear();
552 				});
553 
554 				window.eventLoop(0);
555 			}
556 		---
557 
558 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
559 
560 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
561 		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.
562 
563 		See also: `xwindows.d` from my github.
564 
565 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
566 		`handleNativeEvent` and `handleNativeGlobalEvent`.
567 
568 	$(H3 $(ID topic-integration) Integration with other libraries)
569 		Integration with a third-party event loop is possible.
570 
571 		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.
572 
573 	$(H3 $(ID topic-guis) GUI widgets)
574 		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!
575 
576 		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.
577 
578 		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.)
579 
580 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
581 
582 	$(H2 Platform-specific tips and tricks)
583 
584 	X_tips:
585 
586 	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.
587 
588 	Windows_tips:
589 
590 	You can add icons or manifest files to your exe using a resource file.
591 
592 	To create a Windows .ico file, use the gimp or something. I'll write a helper
593 	program later.
594 
595 	Create `yourapp.rc`:
596 
597 	```rc
598 		1 ICON filename.ico
599 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
600 	```
601 
602 	And `yourapp.exe.manifest`:
603 
604 	```xml
605 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
606 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
607 		<assemblyIdentity
608 		    version="1.0.0.0"
609 		    processorArchitecture="*"
610 		    name="CompanyName.ProductName.YourApplication"
611 		    type="win32"
612 		/>
613 		<description>Your application description here.</description>
614 		<dependency>
615 		    <dependentAssembly>
616 			<assemblyIdentity
617 			    type="win32"
618 			    name="Microsoft.Windows.Common-Controls"
619 			    version="6.0.0.0"
620 			    processorArchitecture="*"
621 			    publicKeyToken="6595b64144ccf1df"
622 			    language="*"
623 			/>
624 		    </dependentAssembly>
625 		</dependency>
626 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
627 			<windowsSettings>
628 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
629 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
630 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
631 				<!-- to render crisply in DPI-unaware contexts -->
632 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
633 			</windowsSettings>
634 		</application>
635 		</assembly>
636 	```
637 
638 	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`.
639 
640 	Doing this lets you opt into various new things since Windows XP.
641 
642 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
643 
644 	$(H2 Tips)
645 
646 	$(H3 Name conflicts)
647 
648 	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:
649 
650 	---
651 	static import sdpy = arsd.simpledisplay;
652 	import arsd.simpledisplay : SimpleWindow;
653 
654 	void main() {
655 		auto window = new SimpleWindow();
656 		sdpy.EventLoop.get.run();
657 	}
658 	---
659 
660 	$(H2 $(ID developer-notes) Developer notes)
661 
662 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
663 	implementation though.
664 
665 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
666 	suck. If I was rewriting it, I wouldn't do it that way again.
667 
668 	This file must not have any more required dependencies. If you need bindings, add
669 	them right to this file. Once it gets into druntime and is there for a while, remove
670 	bindings from here to avoid conflicts (or put them in an appropriate version block
671 	so it continues to just work on old dmd), but wait a couple releases before making the
672 	transition so this module remains usable with older versions of dmd.
673 
674 	You may have optional dependencies if needed by putting them in version blocks or
675 	template functions. You may also extend the module with other modules with UFCS without
676 	actually editing this - that is nice to do if you can.
677 
678 	Try to make functions work the same way across operating systems. I typically make
679 	it thinly wrap Windows, then emulate that on Linux.
680 
681 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
682 	Phobos! So try to avoid it.
683 
684 	See more comments throughout the source.
685 
686 	I realize this file is fairly large, but over half that is just bindings at the bottom
687 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
688 	to understand. I suggest you jump around the source by looking for a particular
689 	declaration you're interested in, like `class SimpleWindow` using your editor's search
690 	function, then look at one piece at a time.
691 
692 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
693 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
694 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
695 
696 	I live in the eastern United States, so I will most likely not be around at night in
697 	that US east timezone.
698 
699 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
700 
701 	Building documentation: use my adrdox generator, `dub run adrdox`.
702 
703 	Examples:
704 
705 	$(DIV $(ID Event-example))
706 	$(H3 $(ID event-example) Event example)
707 	This program creates a window and draws events inside them as they
708 	happen, scrolling the text in the window as needed. Run this program
709 	and experiment to get a feel for where basic input events take place
710 	in the library.
711 
712 	---
713 	// dmd example.d simpledisplay.d color.d
714 	import arsd.simpledisplay;
715 	import std.conv;
716 
717 	void main() {
718 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
719 
720 		int y = 0;
721 
722 		void addLine(string text) {
723 			auto painter = window.draw();
724 
725 			if(y + painter.fontHeight >= window.height) {
726 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
727 				y -= painter.fontHeight;
728 			}
729 
730 			painter.outlineColor = Color.red;
731 			painter.fillColor = Color.black;
732 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
733 
734 			painter.outlineColor = Color.white;
735 
736 			painter.drawText(Point(10, y), text);
737 
738 			y += painter.fontHeight;
739 		}
740 
741 		window.eventLoop(1000,
742 		  () {
743 			addLine("Timer went off!");
744 		  },
745 		  (KeyEvent event) {
746 			addLine(to!string(event));
747 		  },
748 		  (MouseEvent event) {
749 			addLine(to!string(event));
750 		  },
751 		  (dchar ch) {
752 			addLine(to!string(ch));
753 		  }
754 		);
755 	}
756 	---
757 
758 	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.
759 
760 	$(COMMENT
761 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
762 
763 	---
764 
765 	---
766 	)
767 
768 	History:
769 		Initial release in April 2011.
770 
771 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
772 
773 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
774 +/
775 module arsd.simpledisplay;
776 
777 import arsd.core;
778 
779 // FIXME: tetris demo
780 // FIXME: space invaders demo
781 // FIXME: asteroids demo
782 
783 /++ $(ID Pong-example)
784 	$(H3 Pong)
785 
786 	This program creates a little Pong-like game. Player one is controlled
787 	with the keyboard.  Player two is controlled with the mouse. It demos
788 	the pulse timer, event handling, and some basic drawing.
789 +/
790 version(demos)
791 unittest {
792 	// dmd example.d simpledisplay.d color.d
793 	import arsd.simpledisplay;
794 
795 	enum paddleMovementSpeed = 8;
796 	enum paddleHeight = 48;
797 
798 	void main() {
799 		auto window = new SimpleWindow(600, 400, "Pong game!");
800 
801 		int playerOnePosition, playerTwoPosition;
802 		int playerOneMovement, playerTwoMovement;
803 		int playerOneScore, playerTwoScore;
804 
805 		int ballX, ballY;
806 		int ballDx, ballDy;
807 
808 		void serve() {
809 			import std.random;
810 
811 			ballX = window.width / 2;
812 			ballY = window.height / 2;
813 			ballDx = uniform(-4, 4) * 3;
814 			ballDy = uniform(-4, 4) * 3;
815 			if(ballDx == 0)
816 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
817 		}
818 
819 		serve();
820 
821 		window.eventLoop(50, // set a 50 ms timer pulls
822 			// This runs once per timer pulse
823 			delegate () {
824 				auto painter = window.draw();
825 
826 				painter.clear();
827 
828 				// Update everyone's motion
829 				playerOnePosition += playerOneMovement;
830 				playerTwoPosition += playerTwoMovement;
831 
832 				ballX += ballDx;
833 				ballY += ballDy;
834 
835 				// Bounce off the top and bottom edges of the window
836 				if(ballY + 7 >= window.height)
837 					ballDy = -ballDy;
838 				if(ballY - 8 <= 0)
839 					ballDy = -ballDy;
840 
841 				// Bounce off the paddle, if it is in position
842 				if(ballX - 8 <= 16) {
843 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
844 						ballDx = -ballDx + 1; // add some speed to keep it interesting
845 						ballDy += playerOneMovement; // and y movement based on your controls too
846 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
847 					} else {
848 						// Missed it
849 						playerTwoScore ++;
850 						serve();
851 					}
852 				}
853 
854 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
855 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
856 						ballDx = -ballDx - 1;
857 						ballDy += playerTwoMovement;
858 						ballX = window.width - 24;
859 					} else {
860 						// Missed it
861 						playerOneScore ++;
862 						serve();
863 					}
864 				}
865 
866 				// Draw the paddles
867 				painter.outlineColor = Color.black;
868 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
869 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
870 
871 				// Draw the ball
872 				painter.fillColor = Color.red;
873 				painter.outlineColor = Color.yellow;
874 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
875 
876 				// Draw the score
877 				painter.outlineColor = Color.blue;
878 				import std.conv;
879 				painter.drawText(Point(64, 4), to!string(playerOneScore));
880 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
881 
882 			},
883 			delegate (KeyEvent event) {
884 				// Player 1's controls are the arrow keys on the keyboard
885 				if(event.key == Key.Down)
886 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
887 				if(event.key == Key.Up)
888 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
889 
890 			},
891 			delegate (MouseEvent event) {
892 				// Player 2's controls are mouse movement while the left button is held down
893 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
894 					if(event.dy > 0)
895 						playerTwoMovement = paddleMovementSpeed;
896 					else if(event.dy < 0)
897 						playerTwoMovement = -paddleMovementSpeed;
898 				} else {
899 					playerTwoMovement = 0;
900 				}
901 			}
902 		);
903 	}
904 }
905 
906 /++ $(H3 $(ID example-minesweeper) Minesweeper)
907 
908 	This minesweeper demo shows how we can implement another classic
909 	game with simpledisplay and shows some mouse input and basic output
910 	code.
911 +/
912 version(demos)
913 unittest {
914 	import arsd.simpledisplay;
915 
916 	enum GameSquare {
917 		mine = 0,
918 		clear,
919 		m1, m2, m3, m4, m5, m6, m7, m8
920 	}
921 
922 	enum UserSquare {
923 		unknown,
924 		revealed,
925 		flagged,
926 		questioned
927 	}
928 
929 	enum GameState {
930 		inProgress,
931 		lose,
932 		win
933 	}
934 
935 	GameSquare[] board;
936 	UserSquare[] userState;
937 	GameState gameState;
938 	int boardWidth;
939 	int boardHeight;
940 
941 	bool isMine(int x, int y) {
942 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
943 			return false;
944 		return board[y * boardWidth + x] == GameSquare.mine;
945 	}
946 
947 	GameState reveal(int x, int y) {
948 		if(board[y * boardWidth + x] == GameSquare.clear) {
949 			floodFill(userState, boardWidth, boardHeight,
950 				UserSquare.unknown, UserSquare.revealed,
951 				x, y,
952 				(x, y) {
953 					if(board[y * boardWidth + x] == GameSquare.clear)
954 						return true;
955 					else {
956 						userState[y * boardWidth + x] = UserSquare.revealed;
957 						return false;
958 					}
959 				});
960 		} else {
961 			userState[y * boardWidth + x] = UserSquare.revealed;
962 			if(isMine(x, y))
963 				return GameState.lose;
964 		}
965 
966 		foreach(state; userState) {
967 			if(state == UserSquare.unknown || state == UserSquare.questioned)
968 				return GameState.inProgress;
969 		}
970 
971 		return GameState.win;
972 	}
973 
974 	void initializeBoard(int width, int height, int numberOfMines) {
975 		boardWidth = width;
976 		boardHeight = height;
977 		board.length = width * height;
978 
979 		userState.length = width * height;
980 		userState[] = UserSquare.unknown;
981 
982 		import std.algorithm, std.random, std.range;
983 
984 		board[] = GameSquare.clear;
985 
986 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
987 			board[minePosition] = GameSquare.mine;
988 
989 		int x;
990 		int y;
991 		foreach(idx, ref square; board) {
992 			if(square == GameSquare.clear) {
993 				int danger = 0;
994 				danger += isMine(x-1, y-1)?1:0;
995 				danger += isMine(x-1, y)?1:0;
996 				danger += isMine(x-1, y+1)?1:0;
997 				danger += isMine(x, y-1)?1:0;
998 				danger += isMine(x, y+1)?1:0;
999 				danger += isMine(x+1, y-1)?1:0;
1000 				danger += isMine(x+1, y)?1:0;
1001 				danger += isMine(x+1, y+1)?1:0;
1002 
1003 				square = cast(GameSquare) (danger + 1);
1004 			}
1005 
1006 			x++;
1007 			if(x == width) {
1008 				x = 0;
1009 				y++;
1010 			}
1011 		}
1012 	}
1013 
1014 	void redraw(SimpleWindow window) {
1015 		import std.conv;
1016 
1017 		auto painter = window.draw();
1018 
1019 		painter.clear();
1020 
1021 		final switch(gameState) with(GameState) {
1022 			case inProgress:
1023 				break;
1024 			case win:
1025 				painter.fillColor = Color.green;
1026 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1027 				return;
1028 			case lose:
1029 				painter.fillColor = Color.red;
1030 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1031 				return;
1032 		}
1033 
1034 		int x = 0;
1035 		int y = 0;
1036 
1037 		foreach(idx, square; board) {
1038 			auto state = userState[idx];
1039 
1040 			final switch(state) with(UserSquare) {
1041 				case unknown:
1042 					painter.outlineColor = Color.black;
1043 					painter.fillColor = Color(128,128,128);
1044 
1045 					painter.drawRectangle(
1046 						Point(x * 20, y * 20),
1047 						20, 20
1048 					);
1049 				break;
1050 				case revealed:
1051 					if(square == GameSquare.clear) {
1052 						painter.outlineColor = Color.white;
1053 						painter.fillColor = Color.white;
1054 
1055 						painter.drawRectangle(
1056 							Point(x * 20, y * 20),
1057 							20, 20
1058 						);
1059 					} else {
1060 						painter.outlineColor = Color.black;
1061 						painter.fillColor = Color.white;
1062 
1063 						painter.drawText(
1064 							Point(x * 20, y * 20),
1065 							to!string(square)[1..2],
1066 							Point(x * 20 + 20, y * 20 + 20),
1067 							TextAlignment.Center | TextAlignment.VerticalCenter);
1068 					}
1069 				break;
1070 				case flagged:
1071 					painter.outlineColor = Color.black;
1072 					painter.fillColor = Color.red;
1073 					painter.drawRectangle(
1074 						Point(x * 20, y * 20),
1075 						20, 20
1076 					);
1077 				break;
1078 				case questioned:
1079 					painter.outlineColor = Color.black;
1080 					painter.fillColor = Color.yellow;
1081 					painter.drawRectangle(
1082 						Point(x * 20, y * 20),
1083 						20, 20
1084 					);
1085 				break;
1086 			}
1087 
1088 			x++;
1089 			if(x == boardWidth) {
1090 				x = 0;
1091 				y++;
1092 			}
1093 		}
1094 
1095 	}
1096 
1097 	void main() {
1098 		auto window = new SimpleWindow(200, 200);
1099 
1100 		initializeBoard(10, 10, 10);
1101 
1102 		redraw(window);
1103 		window.eventLoop(0,
1104 			delegate (MouseEvent me) {
1105 				if(me.type != MouseEventType.buttonPressed)
1106 					return;
1107 				auto x = me.x / 20;
1108 				auto y = me.y / 20;
1109 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1110 					if(me.button == MouseButton.left) {
1111 						gameState = reveal(x, y);
1112 					} else {
1113 						userState[y*boardWidth+x] = UserSquare.flagged;
1114 					}
1115 					redraw(window);
1116 				}
1117 			}
1118 		);
1119 	}
1120 }
1121 
1122 import arsd.core;
1123 
1124 // FIXME: tetris demo
1125 // FIXME: space invaders demo
1126 // FIXME: asteroids demo
1127 
1128 version(OSX) version(DigitalMars) version=OSXCocoa;
1129 
1130 
1131 version(OSXCocoa) {
1132 	version=without_opengl;
1133 	version=allow_unimplemented_features;
1134 	// version=OSXCocoa;
1135 	// pragma(linkerDirective, "-framework Cocoa");
1136 }
1137 
1138 version(without_opengl) {
1139 	enum SdpyIsUsingIVGLBinds = false;
1140 } else /*version(Posix)*/ {
1141 	static if (__traits(compiles, (){import iv.glbinds;})) {
1142 		enum SdpyIsUsingIVGLBinds = true;
1143 		public import iv.glbinds;
1144 		//pragma(msg, "SDPY: using iv.glbinds");
1145 	} else {
1146 		enum SdpyIsUsingIVGLBinds = false;
1147 	}
1148 //} else {
1149 //	enum SdpyIsUsingIVGLBinds = false;
1150 }
1151 
1152 
1153 version(Windows) {
1154 	//import core.sys.windows.windows;
1155 	import core.sys.windows.winnls;
1156 	import core.sys.windows.windef;
1157 	import core.sys.windows.basetyps;
1158 	import core.sys.windows.winbase;
1159 	import core.sys.windows.winuser;
1160 	import core.sys.windows.shellapi;
1161 	import core.sys.windows.wingdi;
1162 	static import gdi = core.sys.windows.wingdi; // so i
1163 
1164 	pragma(lib, "gdi32");
1165 	pragma(lib, "user32");
1166 
1167 	// for AlphaBlend... a breaking change....
1168 	version(CRuntime_DigitalMars) { } else
1169 		pragma(lib, "msimg32");
1170 } else version (linux) {
1171 	//k8: this is hack for rdmd. sorry.
1172 	static import core.sys.linux.epoll;
1173 	static import core.sys.linux.timerfd;
1174 }
1175 
1176 
1177 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1178 
1179 // http://wiki.dlang.org/Simpledisplay.d
1180 
1181 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1182 
1183 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1184 // but can i control the scroll lock led
1185 
1186 
1187 // Note: if you are using Image on X, you might want to do:
1188 /*
1189 	static if(UsingSimpledisplayX11) {
1190 		if(!Image.impl.xshmAvailable) {
1191 			// the images will use the slower XPutImage, you might
1192 			// want to consider an alternative method to get better speed
1193 		}
1194 	}
1195 
1196 	If the shared memory extension is available though, simpledisplay uses it
1197 	for a significant speed boost whenever you draw large Images.
1198 */
1199 
1200 // 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.
1201 
1202 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1203 
1204 /*
1205 	Biggest FIXME:
1206 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1207 
1208 		clean up opengl contexts when their windows close
1209 
1210 		fix resizing the bitmaps/pixmaps
1211 */
1212 
1213 // BTW on Windows:
1214 // -L/SUBSYSTEM:WINDOWS:5.0
1215 // to dmd will make a nice windows binary w/o a console if you want that.
1216 
1217 /*
1218 	Stuff to add:
1219 
1220 	use multibyte functions everywhere we can
1221 
1222 	OpenGL windows
1223 	more event stuff
1224 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1225 
1226 
1227 	resizeEvent
1228 		and make the windows non-resizable by default,
1229 		or perhaps stretched (if I can find something in X like StretchBlt)
1230 
1231 	take a screenshot function!
1232 
1233 	Pens and brushes?
1234 	Maybe a global event loop?
1235 
1236 	Mouse deltas
1237 	Key items
1238 */
1239 
1240 /*
1241 From MSDN:
1242 
1243 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1244 
1245 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.
1246 
1247 */
1248 
1249 version(linux) {
1250 	version = X11;
1251 	version(without_libnotify) {
1252 		// we cool
1253 	}
1254 	else
1255 		version = libnotify;
1256 }
1257 
1258 version(libnotify) {
1259 	pragma(lib, "dl");
1260 	import core.sys.posix.dlfcn;
1261 
1262 	void delegate()[int] libnotify_action_delegates;
1263 	int libnotify_action_delegates_count;
1264 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1265 		auto idx = cast(int) user_data;
1266 		if(auto dgptr = idx in libnotify_action_delegates) {
1267 			(*dgptr)();
1268 			libnotify_action_delegates.remove(idx);
1269 		}
1270 	}
1271 
1272 	struct C_DynamicLibrary {
1273 		void* handle;
1274 		this(string name) {
1275 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1276 			if(handle is null)
1277 				throw new Exception("dlopen");
1278 		}
1279 
1280 		void close() {
1281 			dlclose(handle);
1282 		}
1283 
1284 		~this() {
1285 			// close
1286 		}
1287 
1288 		// FIXME: this looks up by name every time....
1289 		template call(string func, Ret, Args...) {
1290 			extern(C) Ret function(Args) fptr;
1291 			typeof(fptr) call() {
1292 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1293 				return fptr;
1294 			}
1295 		}
1296 	}
1297 
1298 	C_DynamicLibrary* libnotify;
1299 }
1300 
1301 version(OSX) {
1302 	version(OSXCocoa) {}
1303 	else { version = X11; }
1304 }
1305 	//version = OSXCocoa; // this was written by KennyTM
1306 version(FreeBSD)
1307 	version = X11;
1308 version(Solaris)
1309 	version = X11;
1310 
1311 version(X11) {
1312 	version(without_xft) {}
1313 	else version=with_xft;
1314 }
1315 
1316 void featureNotImplemented()() {
1317 	version(allow_unimplemented_features)
1318 		throw new NotYetImplementedException();
1319 	else
1320 		static assert(0);
1321 }
1322 
1323 // these are so the static asserts don't trigger unless you want to
1324 // add support to it for an OS
1325 version(Windows)
1326 	version = with_timer;
1327 version(linux)
1328 	version = with_timer;
1329 version(OSXCocoa)
1330 	version = with_timer;
1331 
1332 version(with_timer)
1333 	enum bool SimpledisplayTimerAvailable = true;
1334 else
1335 	enum bool SimpledisplayTimerAvailable = false;
1336 
1337 /// 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.
1338 version(Windows)
1339 	enum bool UsingSimpledisplayWindows = true;
1340 else
1341 	enum bool UsingSimpledisplayWindows = false;
1342 
1343 /// 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.
1344 version(X11)
1345 	enum bool UsingSimpledisplayX11 = true;
1346 else
1347 	enum bool UsingSimpledisplayX11 = false;
1348 
1349 /// 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.
1350 version(OSXCocoa)
1351 	enum bool UsingSimpledisplayCocoa = true;
1352 else
1353 	enum bool UsingSimpledisplayCocoa = false;
1354 
1355 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1356 version(Windows)
1357 	enum multipleWindowsSupported = true;
1358 else version(X11)
1359 	enum multipleWindowsSupported = true;
1360 else version(OSXCocoa)
1361 	enum multipleWindowsSupported = true;
1362 else
1363 	static assert(0);
1364 
1365 version(without_opengl)
1366 	enum bool OpenGlEnabled = false;
1367 else
1368 	enum bool OpenGlEnabled = true;
1369 
1370 /++
1371 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1372 	If you mix this in above your `main` function, you no longer need to use the linker
1373 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1374 
1375 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1376 
1377 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1378 	stderr writeln. It will fail and throw an exception.
1379 
1380 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1381 
1382 	History:
1383 		Added November 24, 2021 (dub v10.4)
1384 +/
1385 mixin template EnableWindowsSubsystem() {
1386 	version(Windows)
1387 	version(CRuntime_Microsoft) {
1388 		pragma(linkerDirective, "/subsystem:windows");
1389 		version(LDC)
1390 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1391 		else
1392 			pragma(linkerDirective, "/entry:mainCRTStartup");
1393 	}
1394 }
1395 
1396 
1397 /++
1398 	After selecting a type from [WindowTypes], you may further customize
1399 	its behavior by setting one or more of these flags.
1400 
1401 
1402 	The different window types have different meanings of `normal`. If the
1403 	window type already is a good match for what you want to do, you should
1404 	just use [WindowFlags.normal], the default, which will do the right thing
1405 	for your users.
1406 
1407 	The window flags will not always be honored by the operating system
1408 	and window managers; they are hints, not commands.
1409 +/
1410 enum WindowFlags : int {
1411 	normal = 0, ///
1412 	skipTaskbar = 1, ///
1413 	alwaysOnTop = 2, ///
1414 	alwaysOnBottom = 4, ///
1415 	cannotBeActivated = 8, ///
1416 	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.
1417 	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.
1418 	/++
1419 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1420 		it is still a top-level window. This should NOT be set separately for most window types.
1421 
1422 		A transient window will not keep the application open if its main window closes.
1423 
1424 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1425 
1426 
1427 		From the ICCM:
1428 
1429 		$(BLOCKQUOTE
1430 			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.
1431 
1432 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1433 		)
1434 
1435 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1436 
1437 		History:
1438 			Added February 23, 2021 but not yet stabilized.
1439 	+/
1440 	transient = 64,
1441 	/++
1442 		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.
1443 
1444 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1445 
1446 		History:
1447 			Added April 1, 2022
1448 	+/
1449 	managesChildWindowFocus = 128,
1450 
1451 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1452 }
1453 
1454 /++
1455 	When creating a window, you can pass a type to SimpleWindow's constructor,
1456 	then further customize the window by changing `WindowFlags`.
1457 
1458 
1459 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1460 	use. The others are there to build a foundation for a higher level GUI toolkit,
1461 	but are themselves not as high level as you might think from their names.
1462 
1463 	This list is based on the EMWH spec for X11.
1464 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1465 +/
1466 enum WindowTypes : int {
1467 	/// An ordinary application window.
1468 	normal,
1469 	/// 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.
1470 	undecorated,
1471 	/// 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.
1472 	eventOnly,
1473 	/// A drop down menu, such as from a menu bar
1474 	dropdownMenu,
1475 	/// A popup menu, such as from a right click
1476 	popupMenu,
1477 	/// A popup bubble notification
1478 	notification,
1479 	/*
1480 	menu, /// a tearable menu bar
1481 	splashScreen, /// a loading splash screen for your application
1482 	tooltip, /// A tiny window showing temporary help text or something.
1483 	comboBoxDropdown,
1484 	dialog,
1485 	toolbar
1486 	*/
1487 	/// a child nested inside the parent. You must pass a parent window to the ctor
1488 	nestedChild,
1489 
1490 	/++
1491 		The type you get when you pass in an existing browser handle, which means most
1492 		of simpledisplay's fancy things will not be done since they were never set up.
1493 
1494 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1495 		failure; you should use the existing handle constructor.
1496 
1497 		History:
1498 			Added November 17, 2022 (previously it would have type `normal`)
1499 	+/
1500 	minimallyWrapped
1501 }
1502 
1503 
1504 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1505 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1506 private __gshared char* sdpyWindowClassStr = null;
1507 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1508 
1509 /**
1510 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1511 	You may want to change context version if you want to use advanced shaders or
1512 	other modern OpenGL techinques. This setting doesn't affect already created
1513 	windows. You may use version 2.1 as your default, which should be supported
1514 	by any box since 2006, so seems to be a reasonable choice.
1515 
1516 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1517 	old context creation code without any version specified. This is the safest
1518 	way to init OpenGL, but it may not give you access to advanced features.
1519 
1520 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1521 */
1522 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1523 
1524 /**
1525 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1526 	pipeline functions, and without "compatible" mode you won't be able to use
1527 	your old non-shader-based code with such contexts. By default SimpleDisplay
1528 	creates compatible context, so you can gradually upgrade your OpenGL code if
1529 	you want to (or leave it as is, as it should "just work").
1530 */
1531 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1532 
1533 /**
1534 	Set to `true` to allow creating OpenGL context with lower version than requested
1535 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1536 	`openGLContextFallbackActivated()` will return `true`.
1537 	*/
1538 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1539 
1540 /**
1541 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1542 	*/
1543 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1544 
1545 /++
1546 	History:
1547 		Added April 24, 2023  (dub v11.0)
1548 +/
1549 version(without_opengl) {} else
1550 auto openGLCurrentContext() {
1551 	version(Windows)
1552 		return wglGetCurrentContext();
1553 	else
1554 		return glXGetCurrentContext();
1555 }
1556 
1557 
1558 /**
1559 	Set window class name for all following `new SimpleWindow()` calls.
1560 
1561 	WARNING! For Windows, you should set your class name before creating any
1562 	window, and NEVER change it after that!
1563 */
1564 void sdpyWindowClass (const(char)[] v) {
1565 	import core.stdc.stdlib : realloc;
1566 	if (v.length == 0) v = "SimpleDisplayWindow";
1567 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1568 	if (sdpyWindowClassStr is null) return; // oops
1569 	sdpyWindowClassStr[0..v.length+1] = 0;
1570 	sdpyWindowClassStr[0..v.length] = v[];
1571 }
1572 
1573 /**
1574 	Get current window class name.
1575 */
1576 string sdpyWindowClass () {
1577 	if (sdpyWindowClassStr is null) return null;
1578 	foreach (immutable idx; 0..size_t.max-1) {
1579 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1580 	}
1581 	return null;
1582 }
1583 
1584 /++
1585 	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.
1586 
1587 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1588 +/
1589 float[2] getDpi() {
1590 	float[2] dpi;
1591 	version(Windows) {
1592 		HDC screen = GetDC(null);
1593 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1594 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1595 	} else version(X11) {
1596 		auto display = XDisplayConnection.get;
1597 		auto screen = DefaultScreen(display);
1598 
1599 		void fallback() {
1600 			/+
1601 			// 25.4 millimeters in an inch...
1602 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1603 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1604 			+/
1605 
1606 			// the physical size isn't actually as important as the logical size since this is
1607 			// all about scaling really
1608 			dpi[0] = 96;
1609 			dpi[1] = 96;
1610 		}
1611 
1612 		auto xft = getXftDpi();
1613 		if(xft is float.init)
1614 			fallback();
1615 		else {
1616 			dpi[0] = xft;
1617 			dpi[1] = xft;
1618 		}
1619 	}
1620 
1621 	return dpi;
1622 }
1623 
1624 version(X11)
1625 float getXftDpi() {
1626 	auto display = XDisplayConnection.get;
1627 
1628 	char* resourceString = XResourceManagerString(display);
1629 	XrmInitialize();
1630 
1631 	if (resourceString) {
1632 		auto db = XrmGetStringDatabase(resourceString);
1633 		XrmValue value;
1634 		char* type;
1635 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1636 			if (value.addr) {
1637 				import core.stdc.stdlib;
1638 				return atof(cast(char*) value.addr);
1639 			}
1640 		}
1641 	}
1642 
1643 	return float.init;
1644 }
1645 
1646 /++
1647 	Implementation used by [SimpleWindow.takeScreenshot].
1648 
1649 	Params:
1650 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1651 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1652 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1653 		x = the x-offset of the image to capture, from the left.
1654 		y = the y-offset of the image to capture, from the top.
1655 
1656 	History:
1657 		Added on March 14, 2021
1658 
1659 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1660 
1661 +/
1662 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1663 	TrueColorImage got;
1664 	version(X11) {
1665 		auto display = XDisplayConnection.get;
1666 		if(handle == 0)
1667 			handle = RootWindow(display, DefaultScreen(display));
1668 
1669 		if(width == 0 || height == 0) {
1670 			Window root;
1671 			int xpos, ypos;
1672 			uint widthret, heightret, borderret, depthret;
1673 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1674 
1675 			if(width == 0)
1676 				width = widthret;
1677 			if(height == 0)
1678 				height = heightret;
1679 		}
1680 
1681 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1682 
1683 		// https://github.com/adamdruppe/arsd/issues/98
1684 
1685 		auto i = new Image(image);
1686 		got = i.toTrueColorImage();
1687 
1688 		XDestroyImage(image);
1689 	} else version(Windows) {
1690 		auto hdc = GetDC(handle);
1691 		scope(exit) ReleaseDC(handle, hdc);
1692 
1693 		if(width == 0 || height == 0) {
1694 			BITMAP bmHeader;
1695 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1696 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1697 			if(width == 0)
1698 				width = bmHeader.bmWidth;
1699 			if(height == 0)
1700 				height = bmHeader.bmHeight;
1701 		}
1702 
1703 		auto i = new Image(width, height);
1704 		HDC hdcMem = CreateCompatibleDC(hdc);
1705 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1706 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1707 		SelectObject(hdcMem, hbmOld);
1708 		DeleteDC(hdcMem);
1709 
1710 		got = i.toTrueColorImage();
1711 	} else featureNotImplemented();
1712 
1713 	return got;
1714 }
1715 
1716 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1717 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1718 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1719 
1720 version(Windows)
1721 shared static this() {
1722 	auto lib = LoadLibrary("User32.dll");
1723 	if(lib is null)
1724 		return;
1725 	//scope(exit)
1726 		//FreeLibrary(lib);
1727 
1728 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1729 
1730 	if(SetProcessDpiAwarenessContext is null)
1731 		return;
1732 
1733 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1734 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1735 		//writeln(GetLastError());
1736 	}
1737 
1738 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1739 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1740 }
1741 
1742 /++
1743 	Blocking mode for event loop calls associated with a window instance.
1744 
1745 	History:
1746 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1747 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1748 		is, all would block until the application quit.
1749 
1750 		That behavior can still be achieved here with `untilApplicationQuits`,
1751 		or explicitly calling the top-level `EventLoop.get.run` function.
1752 +/
1753 enum BlockingMode {
1754 	/++
1755 		The event loop call will block until the whole application is ready
1756 		to quit if it is the only one running, but if it is nested inside
1757 		another one, it will only block until the window you're calling it on
1758 		closes.
1759 	+/
1760 	automatic             = 0x00,
1761 	/++
1762 		The event loop call will only return when the whole application
1763 		is ready to quit. This usually means all windows have been closed.
1764 
1765 		This is appropriate for your main application event loop.
1766 	+/
1767 	untilApplicationQuits = 0x01,
1768 	/++
1769 		The event loop will return when the window you're calling it on
1770 		closes. If there are other windows still open, they may be destroyed
1771 		unless you have another event loop running later.
1772 
1773 		This might be appropriate for a modal dialog box loop. Remember that
1774 		other windows are still processing input though, so you can end up
1775 		with a lengthy call stack if this happens in a loop, similar to a
1776 		recursive function (well, it literally is a recursive function, just
1777 		not an obvious looking one).
1778 	+/
1779 	untilWindowCloses     = 0x02,
1780 	/++
1781 		If an event loop is already running, this call will immediately
1782 		return, allowing the existing loop to handle it. If not, this call
1783 		will block until the condition you bitwise-or into the flag.
1784 
1785 		The default is to block until the application quits, same as with
1786 		the `automatic` setting (since if it were nested, which triggers until
1787 		window closes in automatic, this flag would instead not block at all),
1788 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1789 		it will only nest until the window closes. You might want that if you are
1790 		going to open two windows simultaneously and want closing just one of them
1791 		to trigger the event loop return.
1792 	+/
1793 	onlyIfNotNested       = 0x10,
1794 }
1795 
1796 /++
1797 	The flagship window class.
1798 
1799 
1800 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1801 	out of more advanced or complex features of the underlying windowing system.
1802 
1803 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1804 	and get a suitable window to work with.
1805 
1806 	From there, you can opt into additional features, like custom resizability and OpenGL support
1807 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1808 	and customization flags with the final two constructor arguments.
1809 
1810 	If none of that works for you, you can also create a window using native function calls, then
1811 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1812 	though, if you do this, managing the window is still your own responsibility! Notably, you
1813 	will need to destroy it yourself.
1814 +/
1815 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1816 
1817 	/++
1818 		Copies the window's current state into a [TrueColorImage].
1819 
1820 		Be warned: this can be a very slow operation
1821 
1822 		History:
1823 			Actually implemented on March 14, 2021
1824 	+/
1825 	TrueColorImage takeScreenshot() {
1826 		version(Windows)
1827 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1828 		else version(OSXCocoa)
1829 			throw new NotYetImplementedException();
1830 		else
1831 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
1832 	}
1833 
1834 	/++
1835 		Returns the actual logical DPI for the window on its current display monitor. If the window
1836 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1837 
1838 		Please note this function may return zero if it doesn't know the answer!
1839 
1840 
1841 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1842 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1843 
1844 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1845 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1846 		window primarily resides on by checking the center point of the window against the monitor map.
1847 
1848 		Returns:
1849 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1850 			assumes the X and Y dpi are the same.
1851 
1852 		History:
1853 			Added November 26, 2021 (dub v10.4)
1854 
1855 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1856 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1857 			that.
1858 
1859 		Bugs:
1860 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1861 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1862 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1863 			and 1.5 on the secondary monitor.
1864 
1865 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1866 			is a historical misnomer - the real thing of interest is the scale factor and due to
1867 			compatibility concerns the scale would modify dpi values to trick applications. But since
1868 			that's the terminology common out there, I used it too.
1869 
1870 		See_Also:
1871 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1872 			as this since the window many be on a different monitor, but it is a reasonable fallback
1873 			to use if `actualDpi` returns 0.
1874 
1875 			[onDpiChanged] is changed when `actualDpi` has changed.
1876 	+/
1877 	int actualDpi() {
1878 		version(X11) bool useFallbackDpi = false;
1879 		if(!actualDpiLoadAttempted) {
1880 			// FIXME: do the actual monitor we are on
1881 			// and on X this is a good chance to load the monitor map.
1882 			version(Windows) {
1883 				if(GetDpiForWindow)
1884 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1885 			} else version(X11) {
1886 				if(!xRandrInfoLoadAttemped) {
1887 					xRandrInfoLoadAttemped = true;
1888 					if(!XRandrLibrary.attempted) {
1889 						XRandrLibrary.loadDynamicLibrary();
1890 					}
1891 
1892 					if(XRandrLibrary.loadSuccessful) {
1893 						auto display = XDisplayConnection.get;
1894 						int scratch;
1895 						int major, minor;
1896 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1897 							goto fallback;
1898 
1899 						XRRQueryVersion(display, &major, &minor);
1900 						if(major <= 1 && minor < 5)
1901 							goto fallback;
1902 
1903 						int count;
1904 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1905 						if(monitors is null)
1906 							goto fallback;
1907 						scope(exit) XRRFreeMonitors(monitors);
1908 
1909 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1910 						MonitorInfo.info.assumeSafeAppend();
1911 						foreach(idx, monitor; monitors[0 .. count]) {
1912 							MonitorInfo.info ~= MonitorInfo(
1913 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1914 								Size(monitor.mwidth, monitor.mheight),
1915 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1916 							);
1917 
1918 							/+
1919 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1920 							// unknown physical size, just guess 96 to avoid divide by zero
1921 							MonitorInfo.info ~= MonitorInfo(
1922 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1923 								Size(monitor.mwidth, monitor.mheight),
1924 								96
1925 							);
1926 							else
1927 							// and actual thing
1928 							MonitorInfo.info ~= MonitorInfo(
1929 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1930 								Size(monitor.mwidth, monitor.mheight),
1931 								minInternal(
1932 									// millimeter to int then rounding up.
1933 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1934 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1935 								)
1936 							);
1937 							+/
1938 						}
1939 					// writeln("Here", MonitorInfo.info);
1940 					}
1941 				}
1942 
1943 				if(XRandrLibrary.loadSuccessful) {
1944 					updateActualDpi(true);
1945 					// writeln("updated");
1946 
1947 					if(!requestedInput) {
1948 						// this is what requests live updates should the configuration change
1949 						// each time you select input, it sends an initial event, so very important
1950 						// to not get into a loop of selecting input, getting event, updating data,
1951 						// and reselecting input...
1952 						requestedInput = true;
1953 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1954 						// writeln("requested input");
1955 					}
1956 				} else {
1957 					fallback:
1958 					// make sure we disable events that aren't coming
1959 					xrrEventBase = -1;
1960 					// best guess... respect the custom scaling user command to some extent at least though
1961 					useFallbackDpi = true;
1962 				}
1963 			}
1964 			actualDpiLoadAttempted = true;
1965 		} else version(X11) if(MonitorInfo.info.length == 0) {
1966 			useFallbackDpi = true;
1967 		}
1968 
1969 		version(X11)
1970 		if(useFallbackDpi)
1971 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1972 
1973 		return actualDpi_;
1974 	}
1975 
1976 	private int actualDpi_;
1977 	private bool actualDpiLoadAttempted;
1978 
1979 	version(X11) private {
1980 		bool requestedInput;
1981 		static bool xRandrInfoLoadAttemped;
1982 		struct MonitorInfo {
1983 			Rectangle position;
1984 			Size size;
1985 			int dpi;
1986 
1987 			static MonitorInfo[] info;
1988 		}
1989 		bool screenPositionKnown;
1990 		int screenPositionX;
1991 		int screenPositionY;
1992 		void updateActualDpi(bool loadingNow = false) {
1993 			if(!loadingNow && !actualDpiLoadAttempted)
1994 				actualDpi(); // just to make it do the load
1995 			foreach(idx, m; MonitorInfo.info) {
1996 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1997 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1998 					actualDpi_ = m.dpi;
1999 					// writeln("monitor ", idx);
2000 					if(changed && onDpiChanged)
2001 						onDpiChanged();
2002 					break;
2003 				}
2004 			}
2005 		}
2006 	}
2007 
2008 	/++
2009 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2010 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2011 
2012 		History:
2013 			Added November 26, 2021 (dub v10.4)
2014 
2015 		See_Also:
2016 			[actualDpi]
2017 	+/
2018 	void delegate() onDpiChanged;
2019 
2020 	version(X11) {
2021 		void recreateAfterDisconnect() {
2022 			if(!stateDiscarded) return;
2023 
2024 			if(_parent !is null && _parent.stateDiscarded)
2025 				_parent.recreateAfterDisconnect();
2026 
2027 			bool wasHidden = hidden;
2028 
2029 			activeScreenPainter = null; // should already be done but just to confirm
2030 
2031 			actualDpi_ = 0;
2032 			actualDpiLoadAttempted = false;
2033 			xRandrInfoLoadAttemped = false;
2034 
2035 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2036 
2037 			if(auto dh = dropHandler) {
2038 				dropHandler = null;
2039 				enableDragAndDrop(this, dh);
2040 			}
2041 
2042 			if(recreateAdditionalConnectionState)
2043 				recreateAdditionalConnectionState();
2044 
2045 			hidden = wasHidden;
2046 			stateDiscarded = false;
2047 		}
2048 
2049 		bool stateDiscarded;
2050 		void discardConnectionState() {
2051 			if(XDisplayConnection.display)
2052 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2053 			if(discardAdditionalConnectionState)
2054 				discardAdditionalConnectionState();
2055 			stateDiscarded = true;
2056 		}
2057 
2058 		void delegate() discardAdditionalConnectionState;
2059 		void delegate() recreateAdditionalConnectionState;
2060 
2061 	}
2062 
2063 	private DropHandler dropHandler;
2064 
2065 	SimpleWindow _parent;
2066 	bool beingOpenKeepsAppOpen = true;
2067 	/++
2068 		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.
2069 
2070 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2071 
2072 		Params:
2073 
2074 		width = the width of the window's client area, in pixels
2075 		height = the height of the window's client area, in pixels
2076 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2077 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2078 		resizable = [Resizability] has three options:
2079 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2080 			$(P `fixedSize` will not allow the user to resize the window.)
2081 			$(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.)
2082 		windowType = The type of window you want to make.
2083 		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.
2084 		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".
2085 	+/
2086 	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) {
2087 		claimGuiThread();
2088 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2089 		this._width = this._virtualWidth = width;
2090 		this._height = this._virtualHeight = height;
2091 		this.openglMode = opengl;
2092 		version(X11) {
2093 			// auto scale not implemented except with opengl and even there it is kinda weird
2094 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2095 				resizable = Resizability.fixedSize;
2096 		}
2097 		this.resizability = resizable;
2098 		this.windowType = windowType;
2099 		this.customizationFlags = customizationFlags;
2100 		this._title = (title is null ? "D Application" : title);
2101 		this._parent = parent;
2102 		impl.createWindow(width, height, this._title, opengl, parent);
2103 
2104 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2105 			beingOpenKeepsAppOpen = false;
2106 	}
2107 
2108 	/// ditto
2109 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2110 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2111 	}
2112 
2113 	/// Same as above, except using the `Size` struct instead of separate width and height.
2114 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2115 		this(size.width, size.height, title, opengl, resizable);
2116 	}
2117 
2118 	/// ditto
2119 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2120 		this(size, title, opengl, resizable);
2121 	}
2122 
2123 
2124 	/++
2125 		Creates a window based on the given [Image]. It's client area
2126 		width and height is equal to the image. (A window's client area
2127 		is the drawable space inside; it excludes the title bar, etc.)
2128 
2129 		Windows based on images will not be resizable and do not use OpenGL.
2130 
2131 		It will draw the image in upon creation, but this will be overwritten
2132 		upon any draws, including the initial window visible event.
2133 
2134 		You probably do not want to use this and it may be removed from
2135 		the library eventually, or I might change it to be a "permanent"
2136 		background image; one that is automatically drawn on it before any
2137 		other drawing event. idk.
2138 	+/
2139 	this(Image image, string title = null) {
2140 		this(image.width, image.height, title);
2141 		this.image = image;
2142 	}
2143 
2144 	/++
2145 		Wraps a native window handle with very little additional processing - notably no destruction
2146 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2147 		windows created through the low level API (so you can use platform-specific options and
2148 		other details SimpleWindow does not expose) available to the event loop wrappers.
2149 	+/
2150 	this(NativeWindowHandle nativeWindow) {
2151 		windowType = WindowTypes.minimallyWrapped;
2152 		version(Windows)
2153 			impl.hwnd = nativeWindow;
2154 		else version(X11) {
2155 			impl.window = nativeWindow;
2156 			if(nativeWindow)
2157 				display = XDisplayConnection.get(); // get initial display to not segfault
2158 		} else version(OSXCocoa) {
2159 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2160 		} else featureNotImplemented();
2161 		// FIXME: set the size correctly
2162 		_width = 1;
2163 		_height = 1;
2164 		if(nativeWindow)
2165 			nativeMapping[cast(void*) nativeWindow] = this;
2166 
2167 		beingOpenKeepsAppOpen = false;
2168 
2169 		if(nativeWindow)
2170 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2171 		_suppressDestruction = true; // so it doesn't try to close
2172 	}
2173 
2174 	/++
2175 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2176 		The delegate will be called when the window manager asks you to take focus.
2177 
2178 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2179 
2180 		History:
2181 			Added April 1, 2022 (dub v10.8)
2182 	+/
2183 	SimpleWindow delegate() setRequestedInputFocus;
2184 
2185 	/// Experimental, do not use yet
2186 	/++
2187 		Grabs exclusive input from the user until you release it with
2188 		[releaseInputGrab].
2189 
2190 
2191 		Note: it is extremely rude to do this without good reason.
2192 		Reasons may include doing some kind of mouse drag operation
2193 		or popping up a temporary menu that should get events and will
2194 		be dismissed at ease by the user clicking away.
2195 
2196 		Params:
2197 			keyboard = do you want to grab keyboard input?
2198 			mouse = grab mouse input?
2199 			confine = confine the mouse cursor to inside this window?
2200 
2201 		History:
2202 			Prior to March 11, 2021, grabbing the keyboard would always also
2203 			set the X input focus. Now, it only focuses if it is a non-transient
2204 			window and otherwise manages the input direction internally.
2205 
2206 			This means spurious focus/blur events will no longer be sent and the
2207 			application will not steal focus from other applications (which the
2208 			window manager may have rejected anyway).
2209 	+/
2210 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2211 		static if(UsingSimpledisplayX11) {
2212 			XSync(XDisplayConnection.get, 0);
2213 			if(keyboard) {
2214 				if(isTransient && _parent) {
2215 					/*
2216 					FIXME:
2217 						setting the keyboard focus is not actually that helpful, what I more likely want
2218 						is the events from the parent window to be sent over here if we're transient.
2219 					*/
2220 
2221 					_parent.inputProxy = this;
2222 				} else {
2223 
2224 					SimpleWindow setTo;
2225 					if(setRequestedInputFocus !is null)
2226 						setTo = setRequestedInputFocus();
2227 					if(setTo is null)
2228 						setTo = this;
2229 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2230 				}
2231 			}
2232 			if(mouse) {
2233 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2234 				EventMask.PointerMotionMask // FIXME: not efficient
2235 				| EventMask.ButtonPressMask
2236 				| EventMask.ButtonReleaseMask
2237 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2238 				)
2239 			{
2240 				XSync(XDisplayConnection.get, 0);
2241 				import core.stdc.stdio;
2242 				printf("Grab input failed %d\n", res);
2243 				//throw new Exception("Grab input failed");
2244 			} else {
2245 				// cool
2246 			}
2247 			}
2248 
2249 		} else version(Windows) {
2250 			// FIXME: keyboard?
2251 			SetCapture(impl.hwnd);
2252 			if(confine) {
2253 				RECT rcClip;
2254 				//RECT rcOldClip;
2255 				//GetClipCursor(&rcOldClip);
2256 				GetWindowRect(hwnd, &rcClip);
2257 				ClipCursor(&rcClip);
2258 			}
2259 		} else version(OSXCocoa) {
2260 			// throw new NotYetImplementedException();
2261 		} else static assert(0);
2262 	}
2263 
2264 	private Point imePopupLocation = Point(0, 0);
2265 
2266 	/++
2267 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2268 
2269 		Bugs:
2270 			Not implemented outside X11.
2271 	+/
2272 	void setIMEPopupLocation(Point location) {
2273 		static if(UsingSimpledisplayX11) {
2274 			imePopupLocation = location;
2275 			updateIMEPopupLocation();
2276 		} else {
2277 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2278 			// throw new NotYetImplementedException();
2279 		}
2280 	}
2281 
2282 	/// ditto
2283 	void setIMEPopupLocation(int x, int y) {
2284 		return setIMEPopupLocation(Point(x, y));
2285 	}
2286 
2287 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2288 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2289 	// receives a ConfigureNotify event
2290 	private void updateIMEPopupLocation() {
2291 		static if(UsingSimpledisplayX11) {
2292 			if (xic is null) {
2293 				return;
2294 			}
2295 
2296 			XPoint nspot;
2297 			nspot.x = cast(short) imePopupLocation.x;
2298 			nspot.y = cast(short) imePopupLocation.y;
2299 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2300 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2301 			XFree(preeditAttr);
2302 		}
2303 	}
2304 
2305 	private bool imeFocused = true;
2306 
2307 	/++
2308 		Tells the IME whether or not an input field is currently focused in the window.
2309 
2310 		Bugs:
2311 			Not implemented outside X11.
2312 	+/
2313 	void setIMEFocused(bool value) {
2314 		imeFocused = value;
2315 		updateIMEFocused();
2316 	}
2317 
2318 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2319 	private void updateIMEFocused() {
2320 		static if(UsingSimpledisplayX11) {
2321 			if (xic is null) {
2322 				return;
2323 			}
2324 
2325 			if (focused && imeFocused) {
2326 				XSetICFocus(xic);
2327 			} else {
2328 				XUnsetICFocus(xic);
2329 			}
2330 		}
2331 	}
2332 
2333 	/++
2334 		Returns the native window.
2335 
2336 		History:
2337 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2338 			to access it through the `impl` member (which is semi-supported
2339 			but platform specific and here it is simple enough to offer an accessor).
2340 
2341 		Bugs:
2342 			Not implemented outside Windows or X11.
2343 	+/
2344 	NativeWindowHandle nativeWindowHandle() {
2345 		version(X11)
2346 			return impl.window;
2347 		else version(Windows)
2348 			return impl.hwnd;
2349 		else
2350 			throw new NotYetImplementedException();
2351 	}
2352 
2353 	private bool isTransient() {
2354 		with(WindowTypes)
2355 		final switch(windowType) {
2356 			case normal, undecorated, eventOnly:
2357 			case nestedChild, minimallyWrapped:
2358 				return (customizationFlags & WindowFlags.transient) ? true : false;
2359 			case dropdownMenu, popupMenu, notification:
2360 				return true;
2361 		}
2362 	}
2363 
2364 	private SimpleWindow inputProxy;
2365 
2366 	/++
2367 		Releases the grab acquired by [grabInput].
2368 	+/
2369 	void releaseInputGrab() {
2370 		static if(UsingSimpledisplayX11) {
2371 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2372 			if(_parent)
2373 				_parent.inputProxy = null;
2374 		} else version(Windows) {
2375 			ReleaseCapture();
2376 			ClipCursor(null);
2377 		} else version(OSXCocoa) {
2378 			// throw new NotYetImplementedException();
2379 		} else static assert(0);
2380 	}
2381 
2382 	/++
2383 		Sets the input focus to this window.
2384 
2385 		You shouldn't call this very often - please let the user control the input focus.
2386 	+/
2387 	void focus() {
2388 		static if(UsingSimpledisplayX11) {
2389 			SimpleWindow setTo;
2390 			if(setRequestedInputFocus !is null)
2391 				setTo = setRequestedInputFocus();
2392 			if(setTo is null)
2393 				setTo = this;
2394 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2395 		} else version(Windows) {
2396 			SetFocus(this.impl.hwnd);
2397 		} else version(OSXCocoa) {
2398 			throw new NotYetImplementedException();
2399 		} else static assert(0);
2400 	}
2401 
2402 	/++
2403 		Requests attention from the user for this window.
2404 
2405 
2406 		The typical result of this function is to change the color
2407 		of the taskbar icon, though it may be tweaked on specific
2408 		platforms.
2409 
2410 		It is meant to unobtrusively tell the user that something
2411 		relevant to them happened in the background and they should
2412 		check the window when they get a chance. Upon receiving the
2413 		keyboard focus, the window will automatically return to its
2414 		natural state.
2415 
2416 		If the window already has the keyboard focus, this function
2417 		may do nothing, because the user is presumed to already be
2418 		giving the window attention.
2419 
2420 		Implementation_note:
2421 
2422 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2423 		atom on X11 and the FlashWindow function on Windows.
2424 	+/
2425 	void requestAttention() {
2426 		if(_focused)
2427 			return;
2428 
2429 		version(Windows) {
2430 			FLASHWINFO info;
2431 			info.cbSize = info.sizeof;
2432 			info.hwnd = impl.hwnd;
2433 			info.dwFlags = FLASHW_TRAY;
2434 			info.uCount = 1;
2435 
2436 			FlashWindowEx(&info);
2437 
2438 		} else version(X11) {
2439 			demandingAttention = true;
2440 			demandAttention(this, true);
2441 		} else version(OSXCocoa) {
2442 			throw new NotYetImplementedException();
2443 		} else static assert(0);
2444 	}
2445 
2446 	private bool _focused;
2447 
2448 	version(X11) private bool demandingAttention;
2449 
2450 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2451 	/// You'll have to call `close()` manually if you set this delegate.
2452 	void delegate () closeQuery;
2453 
2454 	/// This will be called when window visibility was changed.
2455 	void delegate (bool becomesVisible) visibilityChanged;
2456 
2457 	/// This will be called when window becomes visible for the first time.
2458 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2459 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2460 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2461 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2462 	private bool _visibleForTheFirstTimeCalled;
2463 	void delegate () visibleForTheFirstTime;
2464 
2465 	/// Returns true if the window has been closed.
2466 	final @property bool closed() { return _closed; }
2467 
2468 	private final @property bool notClosed() { return !_closed; }
2469 
2470 	/// Returns true if the window is focused.
2471 	final @property bool focused() { return _focused; }
2472 
2473 	private bool _visible;
2474 	/// Returns true if the window is visible (mapped).
2475 	final @property bool visible() { return _visible; }
2476 
2477 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2478 	void close() {
2479 		if (!_closed) {
2480 			runInGuiThread( {
2481 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2482 				if (onClosing !is null) onClosing();
2483 				impl.closeWindow();
2484 				_closed = true;
2485 			} );
2486 		}
2487 	}
2488 
2489 	/++
2490 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2491 
2492 		History:
2493 			Overload added on March 7, 2021.
2494 	+/
2495 	void close() shared {
2496 		(cast() this).close();
2497 	}
2498 
2499 	/++
2500 
2501 	+/
2502 	void maximize() {
2503 		version(Windows)
2504 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2505 		else version(X11) {
2506 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2507 
2508 			// also note _NET_WM_STATE_FULLSCREEN
2509 		}
2510 
2511 	}
2512 
2513 	private bool _fullscreen;
2514 	version(Windows)
2515 	private WINDOWPLACEMENT g_wpPrev;
2516 
2517 	/// not fully implemented but planned for a future release
2518 	void fullscreen(bool yes) {
2519 		version(Windows) {
2520 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2521 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2522 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2523 				MONITORINFO mi;
2524 				mi.cbSize = MONITORINFO.sizeof;
2525 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2526 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2527 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2528 					SetWindowLong(hwnd, GWL_STYLE,
2529 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2530 					SetWindowPos(hwnd, HWND_TOP,
2531 						     mi.rcMonitor.left, mi.rcMonitor.top,
2532 						     mi.rcMonitor.right - mi.rcMonitor.left,
2533 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2534 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2535 				}
2536 			} else {
2537 				SetWindowLong(hwnd, GWL_STYLE,
2538 					      dwStyle | WS_OVERLAPPEDWINDOW);
2539 				SetWindowPlacement(hwnd, &g_wpPrev);
2540 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2541 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2542 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2543 			}
2544 
2545 		} else version(X11) {
2546 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2547 		}
2548 
2549 		_fullscreen = yes;
2550 
2551 	}
2552 
2553 	bool fullscreen() {
2554 		return _fullscreen;
2555 	}
2556 
2557 	/++
2558 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2559 
2560 	+/
2561 	void minimize() {
2562 		version(Windows)
2563 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2564 		//else version(X11)
2565 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2566 	}
2567 
2568 	/// Alias for `hidden = false`
2569 	void show() {
2570 		hidden = false;
2571 	}
2572 
2573 	/// Alias for `hidden = true`
2574 	void hide() {
2575 		hidden = true;
2576 	}
2577 
2578 	/// Hide cursor when it enters the window.
2579 	void hideCursor() {
2580 		version(OSXCocoa) throw new NotYetImplementedException(); else
2581 		if (!_closed) impl.hideCursor();
2582 	}
2583 
2584 	/// Don't hide cursor when it enters the window.
2585 	void showCursor() {
2586 		version(OSXCocoa) throw new NotYetImplementedException(); else
2587 		if (!_closed) impl.showCursor();
2588 	}
2589 
2590 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2591 	 *
2592 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2593 	 * control. Try to think for other approaches before using this function.
2594 	 *
2595 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2596 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2597 	 *       receive "mouse moved here" event.
2598 	 */
2599 	bool warpMouse (int x, int y) {
2600 		version(X11) {
2601 			if (!_closed) { impl.warpMouse(x, y); return true; }
2602 		} else version(Windows) {
2603 			if (!_closed) {
2604 				POINT point;
2605 				point.x = x;
2606 				point.y = y;
2607 				if(ClientToScreen(impl.hwnd, &point)) {
2608 					SetCursorPos(point.x, point.y);
2609 					return true;
2610 				}
2611 			}
2612 		}
2613 		return false;
2614 	}
2615 
2616 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2617 	void sendDummyEvent () {
2618 		version(X11) {
2619 			if (!_closed) { impl.sendDummyEvent(); }
2620 		}
2621 	}
2622 
2623 	/// Set window minimal size.
2624 	void setMinSize (int minwidth, int minheight) {
2625 		version(OSXCocoa) throw new NotYetImplementedException(); else
2626 		if (!_closed) impl.setMinSize(minwidth, minheight);
2627 	}
2628 
2629 	/// Set window maximal size.
2630 	void setMaxSize (int maxwidth, int maxheight) {
2631 		version(OSXCocoa) throw new NotYetImplementedException(); else
2632 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2633 	}
2634 
2635 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2636 	/// Currently only supported on X11.
2637 	void setResizeGranularity (int granx, int grany) {
2638 		version(OSXCocoa) throw new NotYetImplementedException(); else
2639 		if (!_closed) impl.setResizeGranularity(granx, grany);
2640 	}
2641 
2642 	/// Move window.
2643 	void move(int x, int y) {
2644 		version(OSXCocoa) throw new NotYetImplementedException(); else
2645 		if (!_closed) impl.move(x, y);
2646 	}
2647 
2648 	/// ditto
2649 	void move(Point p) {
2650 		version(OSXCocoa) throw new NotYetImplementedException(); else
2651 		if (!_closed) impl.move(p.x, p.y);
2652 	}
2653 
2654 	/++
2655 		Resize window.
2656 
2657 		Note that the width and height of the window are NOT instantly
2658 		updated - it waits for the window manager to approve the resize
2659 		request, which means you must return to the event loop before the
2660 		width and height are actually changed.
2661 	+/
2662 	void resize(int w, int h) {
2663 		if(!_closed && _fullscreen) fullscreen = false;
2664 		version(OSXCocoa) throw new NotYetImplementedException(); else
2665 		if (!_closed) impl.resize(w, h);
2666 	}
2667 
2668 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2669 	void moveResize (int x, int y, int w, int h) {
2670 		if(!_closed && _fullscreen) fullscreen = false;
2671 		version(OSXCocoa) throw new NotYetImplementedException(); else
2672 		if (!_closed) impl.moveResize(x, y, w, h);
2673 	}
2674 
2675 	private bool _hidden;
2676 
2677 	/// Returns true if the window is hidden.
2678 	final @property bool hidden() {
2679 		return _hidden;
2680 	}
2681 
2682 	/// Shows or hides the window based on the bool argument.
2683 	final @property void hidden(bool b) {
2684 		_hidden = b;
2685 		version(Windows) {
2686 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2687 		} else version(X11) {
2688 			if(b)
2689 				//XUnmapWindow(impl.display, impl.window);
2690 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2691 			else
2692 				XMapWindow(impl.display, impl.window);
2693 		} else version(OSXCocoa) {
2694 			// throw new NotYetImplementedException();
2695 		} else static assert(0);
2696 	}
2697 
2698 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2699 	void opacity(double opacity) @property
2700 	in {
2701 		assert(opacity >= 0 && opacity <= 1);
2702 	} do {
2703 		version (Windows) {
2704 			impl.setOpacity(cast(ubyte)(255 * opacity));
2705 		} else version (X11) {
2706 			impl.setOpacity(cast(uint)(uint.max * opacity));
2707 		} else throw new NotYetImplementedException();
2708 	}
2709 
2710 	/++
2711 		Sets your event handlers, without entering the event loop. Useful if you
2712 		have multiple windows - set the handlers on each window, then only do
2713 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2714 
2715 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2716 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2717 		delegate signatures.
2718 	+/
2719 	void setEventHandlers(T...)(T eventHandlers) {
2720 		// FIXME: add more events
2721 		foreach(handler; eventHandlers) {
2722 			static if(__traits(compiles, handleKeyEvent = handler)) {
2723 				handleKeyEvent = handler;
2724 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2725 				handleCharEvent = handler;
2726 			} else static if(__traits(compiles, handlePulse = handler)) {
2727 				handlePulse = handler;
2728 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2729 				handleMouseEvent = handler;
2730 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2731 		}
2732 	}
2733 
2734 	/++
2735 		The event loop automatically returns when the window is closed
2736 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2737 		pulse timer is created. The event loop will block until an event
2738 		arrives or the pulse timer goes off.
2739 
2740 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2741 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2742 		[handleMouseEvent], based on the signature of delegates you provide.
2743 
2744 		Give one with no parameters to set a timer pulse handler. Give one that
2745 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2746 		and one that takes `dchar` for a char event handler. You can use as many
2747 		or as few handlers as you need for your application.
2748 
2749 		Bugs:
2750 
2751 		$(PITFALL
2752 			You should always have one event loop live for your application.
2753 			If you make two windows in sequence, the second call to eventLoop
2754 			might fail:
2755 
2756 			---
2757 			// don't do this!
2758 			auto window = new SimpleWindow();
2759 			window.eventLoop(0);
2760 
2761 			auto window2 = new SimpleWindow();
2762 			window2.eventLoop(0); // problematic! might crash
2763 			---
2764 
2765 			simpledisplay's current implementation assumes that final cleanup is
2766 			done when the event loop refcount reaches zero. So after the first
2767 			eventLoop returns, when there isn't already another one active, it assumes
2768 			the program will exit soon and cleans up.
2769 
2770 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2771 			it eventually, but in the mean time, there's an easy solution:
2772 
2773 			---
2774 			// do this
2775 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2776 
2777 			auto window = new SimpleWindow();
2778 			window.eventLoop(0);
2779 
2780 			auto window2 = new SimpleWindow();
2781 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2782 			---
2783 
2784 			By adding a top-level reference to the event loop, it ensures the final cleanup
2785 			is not performed until it goes out of scope too, letting the individual window loops
2786 			work without trouble despite the bug.
2787 		)
2788 
2789 		History:
2790 			The overload without `pulseTimeout` was added on December 8, 2021.
2791 
2792 			On December 9, 2021, the default blocking mode (which is now configurable
2793 			because [eventLoopWithBlockingMode] was added) switched from
2794 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2795 			should almost never be noticeable to you since the typical simpledisplay
2796 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2797 
2798 		See_Also:
2799 			[eventLoopWithBlockingMode]
2800 	+/
2801 	final int eventLoop(T...)(
2802 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2803 		T eventHandlers) /// delegate list like std.concurrency.receive
2804 	{
2805 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2806 	}
2807 
2808 	/// ditto
2809 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2810 	{
2811 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2812 	}
2813 
2814 	/++
2815 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2816 
2817 		History:
2818 			Added December 8, 2021 (dub v10.5)
2819 
2820 			Previously, this implementation was right inside [eventLoop], but when I wanted
2821 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2822 			just renamed it instead of adding as an overload. Besides, the new name makes it
2823 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2824 
2825 		See_Also:
2826 			[SimpleWindow.eventLoop], [EventLoop]
2827 
2828 		Bugs:
2829 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2830 	+/
2831 	final int eventLoopWithBlockingMode(T...)(
2832 		BlockingMode blockingMode, /// when you want this function to block until
2833 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2834 		T eventHandlers) /// delegate list like std.concurrency.receive
2835 	{
2836 		setEventHandlers(eventHandlers);
2837 
2838 		version(with_eventloop) {
2839 			// delegates event loop to my other module
2840 			version(X11)
2841 				XFlush(display);
2842 
2843 			import arsd.eventloop;
2844 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2845 			scope(exit) clearInterval(handle);
2846 
2847 			loop();
2848 			return 0;
2849 		} else version(OSXCocoa) {
2850 			// FIXME
2851 			if (handlePulse !is null && pulseTimeout != 0) {
2852 				timer = NSTimer.schedule(pulseTimeout*1e-3,
2853 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
2854 					null, true);
2855 			}
2856 
2857 			view.setNeedsDisplay(true);
2858 
2859 			NSApp.run();
2860             		return 0;
2861         	} else {
2862 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2863 
2864 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2865 				return 0;
2866 
2867 			return el.run(
2868 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2869 					null :
2870 					&this.notClosed
2871 			);
2872 		}
2873 	}
2874 
2875 	/++
2876 		This lets you draw on the window (or its backing buffer) using basic
2877 		2D primitives.
2878 
2879 		Be sure to call this in a limited scope because your changes will not
2880 		actually appear on the window until ScreenPainter's destructor runs.
2881 
2882 		Returns: an instance of [ScreenPainter], which has the drawing methods
2883 		on it to draw on this window.
2884 
2885 		Params:
2886 			manualInvalidations = if you set this to true, you will need to
2887 			set the invalid rectangle on the painter yourself. If false, it
2888 			assumes the whole window has been redrawn each time you draw.
2889 
2890 			Only invalidated rectangles are blitted back to the window when
2891 			the destructor runs. Doing this yourself can reduce flickering
2892 			of child windows.
2893 
2894 		History:
2895 			The `manualInvalidations` parameter overload was added on
2896 			December 30, 2021 (dub v10.5)
2897 	+/
2898 	ScreenPainter draw() {
2899 		return draw(false);
2900 	}
2901 	/// ditto
2902 	ScreenPainter draw(bool manualInvalidations) {
2903 		return impl.getPainter(manualInvalidations);
2904 	}
2905 
2906 	// This is here to implement the interface we use for various native handlers.
2907 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2908 
2909 	// maps native window handles to SimpleWindow instances, if there are any
2910 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2911 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
2912 	version(OSXCocoa)
2913 	public __gshared SimpleWindow[void*] nativeMapping;
2914 	else
2915 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2916 
2917 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
2918 	private int _virtualWidth;
2919 	private int _virtualHeight;
2920 
2921 	/// Width of the window's drawable client area, in pixels.
2922 	@scriptable
2923 	final @property int width() const pure nothrow @safe @nogc {
2924 		if(resizability == Resizability.automaticallyScaleIfPossible)
2925 			return _virtualWidth;
2926 		else
2927 			return _width;
2928 	}
2929 
2930 	/// Height of the window's drawable client area, in pixels.
2931 	@scriptable
2932 	final @property int height() const pure nothrow @safe @nogc {
2933 		if(resizability == Resizability.automaticallyScaleIfPossible)
2934 			return _virtualHeight;
2935 		else
2936 			return _height;
2937 	}
2938 
2939 	/++
2940 		Returns the actual size of the window, bypassing the logical
2941 		illusions of [Resizability.automaticallyScaleIfPossible].
2942 
2943 		History:
2944 			Added November 11, 2022 (dub v10.10)
2945 	+/
2946 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
2947 		return Size(_width, _height);
2948 	}
2949 
2950 
2951 	private int _width;
2952 	private int _height;
2953 
2954 	// HACK: making the best of some copy constructor woes with refcounting
2955 	private ScreenPainterImplementation* activeScreenPainter_;
2956 
2957 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2958 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2959 
2960 	private OpenGlOptions openglMode;
2961 	private Resizability resizability;
2962 	private WindowTypes windowType;
2963 	private int customizationFlags;
2964 
2965 	/// `true` if OpenGL was initialized for this window.
2966 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2967 		version(without_opengl)
2968 			return false;
2969 		else
2970 			return (openglMode == OpenGlOptions.yes);
2971 	}
2972 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2973 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2974 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2975 
2976 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2977 	/// to call this, as it's not recommended to share window between threads.
2978 	void mtLock () {
2979 		version(X11) {
2980 			XLockDisplay(this.display);
2981 		}
2982 	}
2983 
2984 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2985 	/// to call this, as it's not recommended to share window between threads.
2986 	void mtUnlock () {
2987 		version(X11) {
2988 			XUnlockDisplay(this.display);
2989 		}
2990 	}
2991 
2992 	/// Emit a beep to get user's attention.
2993 	void beep () {
2994 		version(X11) {
2995 			XBell(this.display, 100);
2996 		} else version(Windows) {
2997 			MessageBeep(0xFFFFFFFF);
2998 		}
2999 	}
3000 
3001 
3002 
3003 	version(without_opengl) {} else {
3004 
3005 		/// 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`.
3006 		void delegate() redrawOpenGlScene;
3007 
3008 		/// This will allow you to change OpenGL vsync state.
3009 		final @property void vsync (bool wait) {
3010 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3011 		  version(X11) {
3012 		    setAsCurrentOpenGlContext();
3013 		    glxSetVSync(display, impl.window, wait);
3014 		  } else version(Windows) {
3015 		    setAsCurrentOpenGlContext();
3016                     wglSetVSync(wait);
3017 		  }
3018 		}
3019 
3020 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3021 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3022 		/// enough without waiting 'em to finish their frame business.
3023 		bool useGLFinish = true;
3024 
3025 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3026 		/// 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.
3027 		void redrawOpenGlSceneNow() {
3028 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3029 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3030 			if(redrawOpenGlScene is null)
3031 				return;
3032 
3033 			this.mtLock();
3034 			scope(exit) this.mtUnlock();
3035 
3036 			this.setAsCurrentOpenGlContext();
3037 
3038 			redrawOpenGlScene();
3039 
3040 			this.swapOpenGlBuffers();
3041 			// 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.
3042 			if (useGLFinish) glFinish();
3043 		}
3044 
3045 		private bool redrawOpenGlSceneSoonSet = false;
3046 		private static class RedrawOpenGlSceneEvent {
3047 			SimpleWindow w;
3048 			this(SimpleWindow w) { this.w = w; }
3049 		}
3050 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3051 		/++
3052 			Queues an opengl redraw as soon as the other pending events are cleared.
3053 		+/
3054 		void redrawOpenGlSceneSoon() {
3055 			if(redrawOpenGlScene is null)
3056 				return;
3057 
3058 			if(!redrawOpenGlSceneSoonSet) {
3059 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3060 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3061 				redrawOpenGlSceneSoonSet = true;
3062 			}
3063 			this.postEvent(redrawOpenGlSceneEvent, true);
3064 		}
3065 
3066 
3067 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3068 		void setAsCurrentOpenGlContext() {
3069 			assert(openglMode == OpenGlOptions.yes);
3070 			version(X11) {
3071 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3072 					throw new Exception("glXMakeCurrent");
3073 			} else version(Windows) {
3074 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3075 				if (!wglMakeCurrent(ghDC, ghRC))
3076 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3077 			}
3078 		}
3079 
3080 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3081 		/// This doesn't throw, returning success flag instead.
3082 		bool setAsCurrentOpenGlContextNT() nothrow {
3083 			assert(openglMode == OpenGlOptions.yes);
3084 			version(X11) {
3085 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3086 			} else version(Windows) {
3087 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3088 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3089 			}
3090 		}
3091 
3092 		/// 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.
3093 		/// This doesn't throw, returning success flag instead.
3094 		bool releaseCurrentOpenGlContext() nothrow {
3095 			assert(openglMode == OpenGlOptions.yes);
3096 			version(X11) {
3097 				return (glXMakeCurrent(display, 0, null) != 0);
3098 			} else version(Windows) {
3099 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3100 				return wglMakeCurrent(ghDC, null) ? true : false;
3101 			}
3102 		}
3103 
3104 		/++
3105 			simpledisplay always uses double buffering, usually automatically. This
3106 			manually swaps the OpenGL buffers.
3107 
3108 
3109 			You should not need to call this yourself because simpledisplay will do it
3110 			for you after calling your `redrawOpenGlScene`.
3111 
3112 			Remember that this may throw an exception, which you can catch in a multithreaded
3113 			application to keep your thread from dying from an unhandled exception.
3114 		+/
3115 		void swapOpenGlBuffers() {
3116 			assert(openglMode == OpenGlOptions.yes);
3117 			version(X11) {
3118 				if (!this._visible) return; // no need to do this if window is invisible
3119 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3120 				glXSwapBuffers(display, impl.window);
3121 			} else version(Windows) {
3122 				SwapBuffers(ghDC);
3123 			}
3124 		}
3125 	}
3126 
3127 	/++
3128 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3129 
3130 
3131 		---
3132 			auto window = new SimpleWindow(100, 100, "First title");
3133 			window.title = "A new title";
3134 		---
3135 
3136 		You may call this function at any time.
3137 	+/
3138 	@property void title(string title) {
3139 		_title = title;
3140 		version(OSXCocoa) throw new NotYetImplementedException(); else
3141 		impl.setTitle(title);
3142 	}
3143 
3144 	private string _title;
3145 
3146 	/// Gets the title
3147 	@property string title() {
3148 		if(_title is null)
3149 			_title = getRealTitle();
3150 		return _title;
3151 	}
3152 
3153 	/++
3154 		Get the title as set by the window manager.
3155 		May not match what you attempted to set.
3156 	+/
3157 	string getRealTitle() {
3158 		static if(is(typeof(impl.getTitle())))
3159 			return impl.getTitle();
3160 		else
3161 			return null;
3162 	}
3163 
3164 	// don't use this generally it is not yet really released
3165 	version(X11)
3166 	@property Image secret_icon() {
3167 		return secret_icon_inner;
3168 	}
3169 	private Image secret_icon_inner;
3170 
3171 
3172 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3173 	@property void icon(MemoryImage icon) {
3174 		if(icon is null)
3175 			return;
3176 		auto tci = icon.getAsTrueColorImage();
3177 		version(Windows) {
3178 			winIcon = new WindowsIcon(icon);
3179 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3180 		} else version(X11) {
3181 			secret_icon_inner = Image.fromMemoryImage(icon);
3182 			// FIXME: ensure this is correct
3183 			auto display = XDisplayConnection.get;
3184 			arch_ulong[] buffer;
3185 			buffer ~= icon.width;
3186 			buffer ~= icon.height;
3187 			foreach(c; tci.imageData.colors) {
3188 				arch_ulong b;
3189 				b |= c.a << 24;
3190 				b |= c.r << 16;
3191 				b |= c.g << 8;
3192 				b |= c.b;
3193 				buffer ~= b;
3194 			}
3195 
3196 			XChangeProperty(
3197 				display,
3198 				impl.window,
3199 				GetAtom!("_NET_WM_ICON", true)(display),
3200 				GetAtom!"CARDINAL"(display),
3201 				32 /* bits */,
3202 				0 /*PropModeReplace*/,
3203 				buffer.ptr,
3204 				cast(int) buffer.length);
3205 		} else version(OSXCocoa) {
3206 			throw new NotYetImplementedException();
3207 		} else static assert(0);
3208 	}
3209 
3210 	version(Windows)
3211 		private WindowsIcon winIcon;
3212 
3213 	bool _suppressDestruction;
3214 
3215 	~this() {
3216 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3217 		if(_suppressDestruction)
3218 			return;
3219 		impl.dispose();
3220 	}
3221 
3222 	private bool _closed;
3223 
3224 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3225 	/*
3226 	ScreenPainter drawTransiently() {
3227 		return impl.getPainter();
3228 	}
3229 	*/
3230 
3231 	/// Draws an image on the window. This is meant to provide quick look
3232 	/// of a static image generated elsewhere.
3233 	@property void image(Image i) {
3234 	/+
3235 		version(Windows) {
3236 			BITMAP bm;
3237 			HDC hdc = GetDC(hwnd);
3238 			HDC hdcMem = CreateCompatibleDC(hdc);
3239 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3240 
3241 			GetObject(i.handle, bm.sizeof, &bm);
3242 
3243 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3244 
3245 			SelectObject(hdcMem, hbmOld);
3246 			DeleteDC(hdcMem);
3247 			ReleaseDC(hwnd, hdc);
3248 
3249 			/*
3250 			RECT r;
3251 			r.right = i.width;
3252 			r.bottom = i.height;
3253 			InvalidateRect(hwnd, &r, false);
3254 			*/
3255 		} else
3256 		version(X11) {
3257 			if(!destroyed) {
3258 				if(i.usingXshm)
3259 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3260 				else
3261 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3262 			}
3263 		} else
3264 		version(OSXCocoa) {
3265 			draw().drawImage(Point(0, 0), i);
3266 			setNeedsDisplay(view, true);
3267 		} else static assert(0);
3268 	+/
3269 		auto painter = this.draw;
3270 		painter.drawImage(Point(0, 0), i);
3271 	}
3272 
3273 	/++
3274 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3275 
3276 		---
3277 		window.cursor = GenericCursor.Help;
3278 		// now the window mouse cursor is set to a generic help
3279 		---
3280 
3281 	+/
3282 	@property void cursor(MouseCursor cursor) {
3283 		version(OSXCocoa)
3284 			{} // featureNotImplemented();
3285 		else
3286 		if(this.impl.curHidden <= 0) {
3287 			static if(UsingSimpledisplayX11) {
3288 				auto ch = cursor.cursorHandle;
3289 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3290 			} else version(Windows) {
3291 				auto ch = cursor.cursorHandle;
3292 				impl.currentCursor = ch;
3293 				SetCursor(ch); // redraw without waiting for mouse movement to update
3294 			} else featureNotImplemented();
3295 		}
3296 
3297 	}
3298 
3299 	/// What follows are the event handlers. These are set automatically
3300 	/// by the eventLoop function, but are still public so you can change
3301 	/// them later. wasPressed == true means key down. false == key up.
3302 
3303 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3304 	void delegate(KeyEvent ke) handleKeyEvent;
3305 
3306 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3307 	void delegate(dchar c) handleCharEvent;
3308 
3309 	/// Handles a timer pulse. Settable through setEventHandlers.
3310 	void delegate() handlePulse;
3311 
3312 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3313 	void delegate(bool) onFocusChange;
3314 
3315 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3316 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3317 	void delegate() onClosing;
3318 
3319 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3320 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3321 	 * last minute cleanup. */
3322 	void delegate() onDestroyed;
3323 
3324 	static if (UsingSimpledisplayX11)
3325 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3326 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3327 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3328 	 *
3329 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3330 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3331 
3332 	//version(Windows)
3333 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3334 
3335 	private {
3336 		int lastMouseX = int.min;
3337 		int lastMouseY = int.min;
3338 		void mdx(ref MouseEvent ev) {
3339 			if(lastMouseX == int.min || lastMouseY == int.min) {
3340 				ev.dx = 0;
3341 				ev.dy = 0;
3342 			} else {
3343 				ev.dx = ev.x - lastMouseX;
3344 				ev.dy = ev.y - lastMouseY;
3345 			}
3346 
3347 			lastMouseX = ev.x;
3348 			lastMouseY = ev.y;
3349 		}
3350 	}
3351 
3352 	/// Mouse event handler. Settable through setEventHandlers.
3353 	void delegate(MouseEvent) handleMouseEvent;
3354 
3355 	/// use to redraw child widgets if you use system apis to add stuff
3356 	void delegate() paintingFinished;
3357 
3358 	void delegate() paintingFinishedDg() {
3359 		return paintingFinished;
3360 	}
3361 
3362 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3363 	/// for this to ever happen.
3364 	void delegate(int width, int height) windowResized;
3365 
3366 	/++
3367 		Platform specific - handle any native message this window gets.
3368 
3369 		Note: this is called *in addition to* other event handlers, unless you either:
3370 
3371 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3372 
3373 		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.
3374 
3375 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3376 
3377 		On X, it takes the form of `int delegate(XEvent)`.
3378 
3379 		History:
3380 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3381 
3382 			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.
3383 	+/
3384 	NativeEventHandler handleNativeEvent_;
3385 
3386 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3387 		return handleNativeEvent_;
3388 	}
3389 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3390 		handleNativeEvent_ = neh;
3391 	}
3392 
3393 	version(Windows)
3394 	// compatibility shim with the old deprecated way
3395 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3396 	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) {
3397 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3398 			auto ret = dg(h, m, w, l);
3399 			if(ret == 0)
3400 				r = 1;
3401 			return ret;
3402 		};
3403 	}
3404 
3405 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3406 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3407 	/// this instead and it will work the same way.
3408 	__gshared NativeEventHandler handleNativeGlobalEvent;
3409 
3410 //  private:
3411 	/// The native implementation is available, but you shouldn't use it unless you are
3412 	/// familiar with the underlying operating system, don't mind depending on it, and
3413 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3414 	/// do what you need to do with handleNativeEvent instead.
3415 	///
3416 	/// This is likely to eventually change to be just a struct holding platform-specific
3417 	/// handles instead of a template mixin at some point because I'm not happy with the
3418 	/// code duplication here (ironically).
3419 	mixin NativeSimpleWindowImplementation!() impl;
3420 
3421 	/**
3422 		This is in-process one-way (from anything to window) event sending mechanics.
3423 		It is thread-safe, so it can be used in multi-threaded applications to send,
3424 		for example, "wake up and repaint" events when thread completed some operation.
3425 		This will allow to avoid using timer pulse to check events with synchronization,
3426 		'cause event handler will be called in UI thread. You can stop guessing which
3427 		pulse frequency will be enough for your app.
3428 		Note that events handlers may be called in arbitrary order, i.e. last registered
3429 		handler can be called first, and vice versa.
3430 	*/
3431 public:
3432 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3433 	 * "spamming" window with events it can't cope with.
3434 	 * It is safe to call this from non-UI threads.
3435 	 */
3436 	@property bool eventQueueEmpty() () {
3437 		synchronized(this) {
3438 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3439 		}
3440 		return true;
3441 	}
3442 
3443 	/** Does our custom event queue contains at least one with the given type?
3444 	 * Can be used in simple cases to prevent "spamming" window with events
3445 	 * it can't cope with.
3446 	 * It is safe to call this from non-UI threads.
3447 	 */
3448 	@property bool eventQueued(ET:Object) () {
3449 		synchronized(this) {
3450 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3451 				if (!o.doProcess) {
3452 					if (cast(ET)(o.evt)) return true;
3453 				}
3454 			}
3455 		}
3456 		return false;
3457 	}
3458 
3459 	/++
3460 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3461 
3462 		History:
3463 			Added May 12, 2021
3464 	+/
3465 	void delegate(Exception e) nothrow eventUncaughtException;
3466 
3467 	/** Add listener for custom event. Can be used like this:
3468 	 *
3469 	 * ---------------------
3470 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3471 	 *   ...
3472 	 *   win.removeEventListener(eid);
3473 	 * ---------------------
3474 	 *
3475 	 * Returns: 0 on failure (should never happen, so ignore it)
3476 	 *
3477 	 * $(WARNING Don't use this method in object destructors!)
3478 	 *
3479 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3480 	 *           'cause if event handler id counter will overflow, you won't be able
3481 	 *           to register any more events.)
3482 	 */
3483 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3484 		if (dg is null) return 0; // ignore empty handlers
3485 		synchronized(this) {
3486 			//FIXME: abort on overflow?
3487 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3488 			EventHandlerEntry e;
3489 			e.dg = delegate (Object o) {
3490 				if (auto co = cast(ET)o) {
3491 					try {
3492 						dg(co);
3493 					} catch (Exception e) {
3494 						// sorry!
3495 						if(eventUncaughtException)
3496 							eventUncaughtException(e);
3497 					}
3498 					return true;
3499 				}
3500 				return false;
3501 			};
3502 			e.id = lastUsedHandlerId;
3503 			auto optr = eventHandlers.ptr;
3504 			eventHandlers ~= e;
3505 			if (eventHandlers.ptr !is optr) {
3506 				import core.memory : GC;
3507 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3508 			}
3509 			return lastUsedHandlerId;
3510 		}
3511 	}
3512 
3513 	/// Remove event listener. It is safe to pass invalid event id here.
3514 	/// $(WARNING Don't use this method in object destructors!)
3515 	void removeEventListener() (uint id) {
3516 		if (id == 0 || id > lastUsedHandlerId) return;
3517 		synchronized(this) {
3518 			foreach (immutable idx; 0..eventHandlers.length) {
3519 				if (eventHandlers[idx].id == id) {
3520 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3521 					eventHandlers[$-1].dg = null;
3522 					eventHandlers.length -= 1;
3523 					eventHandlers.assumeSafeAppend;
3524 					return;
3525 				}
3526 			}
3527 		}
3528 	}
3529 
3530 	/// Post event to queue. It is safe to call this from non-UI threads.
3531 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3532 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3533 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3534 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3535 		if (this.closed) return false; // closed windows can't handle events
3536 
3537 		// remove all events of type `ET`
3538 		void removeAllET () {
3539 			uint eidx = 0, ec = eventQueueUsed;
3540 			auto eptr = eventQueue.ptr;
3541 			while (eidx < ec) {
3542 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3543 				if (cast(ET)eptr.evt !is null) {
3544 					// i found her!
3545 					if (inCustomEventProcessor) {
3546 						// if we're in custom event processing loop, processor will clear it for us
3547 						eptr.evt = null;
3548 						++eidx;
3549 						++eptr;
3550 					} else {
3551 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3552 						ec = --eventQueueUsed;
3553 						// clear last event (it is already copied)
3554 						eventQueue.ptr[ec].evt = null;
3555 					}
3556 				} else {
3557 					++eidx;
3558 					++eptr;
3559 				}
3560 			}
3561 		}
3562 
3563 		if (evt is null) {
3564 			if (replace) { synchronized(this) removeAllET(); }
3565 			// ignore empty events, they can't be handled anyway
3566 			return false;
3567 		}
3568 
3569 		// add events even if no event FD/event object created yet
3570 		synchronized(this) {
3571 			if (replace) removeAllET();
3572 			if (eventQueueUsed == uint.max) return false; // just in case
3573 			if (eventQueueUsed < eventQueue.length) {
3574 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3575 			} else {
3576 				if (eventQueue.capacity == eventQueue.length) {
3577 					// need to reallocate; do a trick to ensure that old array is cleared
3578 					auto oarr = eventQueue;
3579 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3580 					// just in case, do yet another check
3581 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3582 					import core.memory : GC;
3583 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3584 				} else {
3585 					auto optr = eventQueue.ptr;
3586 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3587 					assert(eventQueue.ptr is optr);
3588 				}
3589 				++eventQueueUsed;
3590 				assert(eventQueueUsed == eventQueue.length);
3591 			}
3592 			if (!eventWakeUp()) {
3593 				// can't wake up event processor, so there is no reason to keep the event
3594 				assert(eventQueueUsed > 0);
3595 				eventQueue[--eventQueueUsed].evt = null;
3596 				return false;
3597 			}
3598 			return true;
3599 		}
3600 	}
3601 
3602 	/// Post event to queue. It is safe to call this from non-UI threads.
3603 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3604 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3605 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3606 		return postTimeout!ET(evt, 0, replace);
3607 	}
3608 
3609 private:
3610 	private import core.time : MonoTime;
3611 
3612 	version(Posix) {
3613 		__gshared int customEventFDRead = -1;
3614 		__gshared int customEventFDWrite = -1;
3615 		__gshared int customSignalFD = -1;
3616 	} else version(Windows) {
3617 		__gshared HANDLE customEventH = null;
3618 	}
3619 
3620 	// wake up event processor
3621 	static bool eventWakeUp () {
3622 		version(X11) {
3623 			import core.sys.posix.unistd : write;
3624 			ulong n = 1;
3625 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3626 			return true;
3627 		} else version(Windows) {
3628 			if (customEventH !is null) SetEvent(customEventH);
3629 			return true;
3630 		} else version(OSXCocoa) {
3631 			if(globalAppDelegate)
3632 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3633 			return true;
3634 		} else {
3635 			// not implemented for other OSes
3636 			return false;
3637 		}
3638 	}
3639 
3640 	static struct QueuedEvent {
3641 		Object evt;
3642 		bool timed = false;
3643 		MonoTime hittime = MonoTime.zero;
3644 		bool doProcess = false; // process event at the current iteration (internal flag)
3645 
3646 		this (Object aevt, uint toutmsecs) {
3647 			evt = aevt;
3648 			if (toutmsecs > 0) {
3649 				import core.time : msecs;
3650 				timed = true;
3651 				hittime = MonoTime.currTime+toutmsecs.msecs;
3652 			}
3653 		}
3654 	}
3655 
3656 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3657 	static struct EventHandlerEntry {
3658 		CustomEventHandler dg;
3659 		uint id;
3660 	}
3661 
3662 	uint lastUsedHandlerId;
3663 	EventHandlerEntry[] eventHandlers;
3664 	QueuedEvent[] eventQueue = null;
3665 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3666 	bool inCustomEventProcessor = false; // required to properly remove events
3667 
3668 	// process queued events and call custom event handlers
3669 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3670 	void processCustomEvents () {
3671 		bool hasSomethingToDo = false;
3672 		uint ecount;
3673 		bool ocep;
3674 		synchronized(this) {
3675 			ocep = inCustomEventProcessor;
3676 			inCustomEventProcessor = true;
3677 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3678 			auto ctt = MonoTime.currTime;
3679 			bool hasEmpty = false;
3680 			// mark events to process (this is required for `eventQueued()`)
3681 			foreach (ref qe; eventQueue[0..ecount]) {
3682 				if (qe.evt is null) { hasEmpty = true; continue; }
3683 				if (qe.timed) {
3684 					qe.doProcess = (qe.hittime <= ctt);
3685 				} else {
3686 					qe.doProcess = true;
3687 				}
3688 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3689 			}
3690 			if (!hasSomethingToDo) {
3691 				// remove empty events
3692 				if (hasEmpty) {
3693 					uint eidx = 0, ec = eventQueueUsed;
3694 					auto eptr = eventQueue.ptr;
3695 					while (eidx < ec) {
3696 						if (eptr.evt is null) {
3697 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3698 							ec = --eventQueueUsed;
3699 							eventQueue.ptr[ec].evt = null; // make GC life easier
3700 						} else {
3701 							++eidx;
3702 							++eptr;
3703 						}
3704 					}
3705 				}
3706 				inCustomEventProcessor = ocep;
3707 				return;
3708 			}
3709 		}
3710 		// process marked events
3711 		uint efree = 0; // non-processed events will be put at this index
3712 		EventHandlerEntry[] eh;
3713 		Object evt;
3714 		foreach (immutable eidx; 0..ecount) {
3715 			synchronized(this) {
3716 				if (!eventQueue[eidx].doProcess) {
3717 					// skip this event
3718 					assert(efree <= eidx);
3719 					if (efree != eidx) {
3720 						// copy this event to queue start
3721 						eventQueue[efree] = eventQueue[eidx];
3722 						eventQueue[eidx].evt = null; // just in case
3723 					}
3724 					++efree;
3725 					continue;
3726 				}
3727 				evt = eventQueue[eidx].evt;
3728 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3729 				if (evt is null) continue; // just in case
3730 				// try all handlers; this can be slow, but meh...
3731 				eh = eventHandlers;
3732 			}
3733 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3734 			evt = null;
3735 			eh = null;
3736 		}
3737 		synchronized(this) {
3738 			// move all unprocessed events to queue top; efree holds first "free index"
3739 			foreach (immutable eidx; ecount..eventQueueUsed) {
3740 				assert(efree <= eidx);
3741 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3742 				++efree;
3743 			}
3744 			eventQueueUsed = efree;
3745 			// wake up event processor on next event loop iteration if we have more queued events
3746 			// also, remove empty events
3747 			bool awaken = false;
3748 			uint eidx = 0, ec = eventQueueUsed;
3749 			auto eptr = eventQueue.ptr;
3750 			while (eidx < ec) {
3751 				if (eptr.evt is null) {
3752 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3753 					ec = --eventQueueUsed;
3754 					eventQueue.ptr[ec].evt = null; // make GC life easier
3755 				} else {
3756 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3757 					++eidx;
3758 					++eptr;
3759 				}
3760 			}
3761 			inCustomEventProcessor = ocep;
3762 		}
3763 	}
3764 
3765 	// for all windows in nativeMapping
3766 	package static void processAllCustomEvents () {
3767 
3768 		cleanupQueue.process();
3769 
3770 		justCommunication.processCustomEvents();
3771 
3772 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3773 			if (sw is null || sw.closed) continue;
3774 			sw.processCustomEvents();
3775 		}
3776 
3777 		runPendingRunInGuiThreadDelegates();
3778 	}
3779 
3780 	// 0: infinite (i.e. no scheduled events in queue)
3781 	uint eventQueueTimeoutMSecs () {
3782 		synchronized(this) {
3783 			if (eventQueueUsed == 0) return 0;
3784 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3785 			uint res = int.max;
3786 			auto ctt = MonoTime.currTime;
3787 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3788 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3789 				if (qe.doProcess) continue; // just in case
3790 				if (!qe.timed) return 1; // minimal
3791 				if (qe.hittime <= ctt) return 1; // minimal
3792 				auto tms = (qe.hittime-ctt).total!"msecs";
3793 				if (tms < 1) tms = 1; // safety net
3794 				if (tms >= int.max) tms = int.max-1; // and another safety net
3795 				if (res > tms) res = cast(uint)tms;
3796 			}
3797 			return (res >= int.max ? 0 : res);
3798 		}
3799 	}
3800 
3801 	// for all windows in nativeMapping
3802 	static uint eventAllQueueTimeoutMSecs () {
3803 		uint res = uint.max;
3804 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3805 			if (sw is null || sw.closed) continue;
3806 			uint to = sw.eventQueueTimeoutMSecs();
3807 			if (to && to < res) {
3808 				res = to;
3809 				if (to == 1) break; // can't have less than this
3810 			}
3811 		}
3812 		return (res >= int.max ? 0 : res);
3813 	}
3814 
3815 	version(X11) {
3816 		ResizeEvent pendingResizeEvent;
3817 	}
3818 
3819 	/++
3820 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3821 
3822 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3823 		worth so you can disable it by setting this to `true`.
3824 
3825 		History:
3826 			Added November 13, 2022.
3827 	+/
3828 	public bool suppressAutoOpenglViewport = false;
3829 	private void updateOpenglViewportIfNeeded(int width, int height) {
3830 		if(suppressAutoOpenglViewport) return;
3831 
3832 		version(without_opengl) {} else
3833 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
3834 		// writeln(width, " ", height);
3835 			setAsCurrentOpenGlContextNT();
3836 			glViewport(0, 0, width, height);
3837 		}
3838 	}
3839 }
3840 
3841 version(OSXCocoa)
3842 	enum NSWindow NullWindow = null;
3843 else
3844 	enum NullWindow = NativeWindowHandle.init;
3845 
3846 /++
3847 	Magic pseudo-window for just posting events to a global queue.
3848 
3849 	Not entirely supported, I might delete it at any time.
3850 
3851 	Added Nov 5, 2021.
3852 +/
3853 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
3854 
3855 /* Drag and drop support { */
3856 version(X11) {
3857 
3858 } else version(Windows) {
3859 	import core.sys.windows.uuid;
3860 	import core.sys.windows.ole2;
3861 	import core.sys.windows.oleidl;
3862 	import core.sys.windows.objidl;
3863 	import core.sys.windows.wtypes;
3864 
3865 	pragma(lib, "ole32");
3866 	void initDnd() {
3867 		auto err = OleInitialize(null);
3868 		if(err != S_OK && err != S_FALSE)
3869 			throw new Exception("init");//err);
3870 	}
3871 }
3872 /* } End drag and drop support */
3873 
3874 
3875 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3876 /// See [GenericCursor].
3877 class MouseCursor {
3878 	int osId;
3879 	bool isStockCursor;
3880 	private this(int osId) {
3881 		this.osId = osId;
3882 		this.isStockCursor = true;
3883 	}
3884 
3885 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3886 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3887 
3888 	version(Windows) {
3889 		HCURSOR cursor_;
3890 		HCURSOR cursorHandle() {
3891 			if(cursor_ is null)
3892 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3893 			return cursor_;
3894 		}
3895 
3896 	} else static if(UsingSimpledisplayX11) {
3897 		Cursor cursor_ = None;
3898 		int xDisplaySequence;
3899 
3900 		Cursor cursorHandle() {
3901 			if(this.osId == None)
3902 				return None;
3903 
3904 			// we need to reload if we on a new X connection
3905 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3906 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3907 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3908 			}
3909 			return cursor_;
3910 		}
3911 	}
3912 }
3913 
3914 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3915 // https://tronche.com/gui/x/xlib/appendix/b/
3916 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3917 /// 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.
3918 enum GenericCursorType {
3919 	Default, /// The default arrow pointer.
3920 	Wait, /// A cursor indicating something is loading and the user must wait.
3921 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3922 	Help, /// A cursor indicating the user can get help about the pointer location.
3923 	Cross, /// A crosshair.
3924 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3925 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3926 	UpArrow, /// An arrow pointing straight up.
3927 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3928 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3929 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3930 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3931 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3932 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3933 
3934 }
3935 
3936 /*
3937 	X_plus == css cell == Windows ?
3938 */
3939 
3940 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3941 static struct GenericCursor {
3942 	static:
3943 	///
3944 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3945 		static MouseCursor mc;
3946 
3947 		auto type = __traits(getMember, GenericCursorType, str);
3948 
3949 		if(mc is null) {
3950 
3951 			version(Windows) {
3952 				int osId;
3953 				final switch(type) {
3954 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3955 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3956 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3957 					case GenericCursorType.Help: osId = IDC_HELP; break;
3958 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3959 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3960 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3961 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3962 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3963 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3964 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3965 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3966 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3967 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3968 				}
3969 			} else static if(UsingSimpledisplayX11) {
3970 				int osId;
3971 				final switch(type) {
3972 					case GenericCursorType.Default: osId = None; break;
3973 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3974 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3975 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3976 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3977 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3978 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3979 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3980 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3981 
3982 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3983 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3984 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3985 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3986 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3987 				}
3988 
3989 			} else {
3990 				int osId;
3991 				// featureNotImplemented();
3992 			}
3993 
3994 			mc = new MouseCursor(osId);
3995 		}
3996 		return mc;
3997 	}
3998 }
3999 
4000 
4001 /++
4002 	If you want to get more control over the event loop, you can use this.
4003 
4004 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4005 	to `EventLoop.get.run`.
4006 +/
4007 struct EventLoop {
4008 	@disable this();
4009 
4010 	/// Gets a reference to an existing event loop
4011 	static EventLoop get() {
4012 		return EventLoop(0, null);
4013 	}
4014 
4015 	static void quitApplication() {
4016 		EventLoop.get().exit();
4017 	}
4018 
4019 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4020 
4021 	/// Construct an application-global event loop for yourself
4022 	/// See_Also: [SimpleWindow.setEventHandlers]
4023 	this(long pulseTimeout, void delegate() handlePulse) {
4024 		synchronized(monitor) {
4025 			if(impl is null) {
4026 				claimGuiThread();
4027 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4028 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4029 			} else {
4030 				if(pulseTimeout) {
4031 					impl.pulseTimeout = pulseTimeout;
4032 					impl.handlePulse = handlePulse;
4033 				}
4034 			}
4035 			impl.refcount++;
4036 		}
4037 	}
4038 
4039 	~this() {
4040 		if(impl is null)
4041 			return;
4042 		impl.refcount--;
4043 		if(impl.refcount == 0) {
4044 			impl.dispose();
4045 			if(thisIsGuiThread)
4046 				guiThreadFinalize();
4047 		}
4048 
4049 	}
4050 
4051 	this(this) {
4052 		if(impl is null)
4053 			return;
4054 		impl.refcount++;
4055 	}
4056 
4057 	/// Runs the event loop until the whileCondition, if present, returns false
4058 	int run(bool delegate() whileCondition = null) {
4059 		assert(impl !is null);
4060 		impl.notExited = true;
4061 		return impl.run(whileCondition);
4062 	}
4063 
4064 	/// Exits the event loop
4065 	void exit() {
4066 		assert(impl !is null);
4067 		impl.notExited = false;
4068 	}
4069 
4070 	version(linux)
4071 	ref void delegate(int) signalHandler() {
4072 		assert(impl !is null);
4073 		return impl.signalHandler;
4074 	}
4075 
4076 	__gshared static EventLoopImpl* impl;
4077 }
4078 
4079 version(linux)
4080 	void delegate(int, int) globalHupHandler;
4081 
4082 version(Posix)
4083 	void makeNonBlocking(int fd) {
4084 		import fcntl = core.sys.posix.fcntl;
4085 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4086 		if(flags == -1)
4087 			throw new Exception("fcntl get");
4088 		flags |= fcntl.O_NONBLOCK;
4089 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4090 		if(s == -1)
4091 			throw new Exception("fcntl set");
4092 	}
4093 
4094 struct EventLoopImpl {
4095 	int refcount;
4096 
4097 	bool notExited = true;
4098 
4099 	version(linux) {
4100 		static import ep = core.sys.linux.epoll;
4101 		static import unix = core.sys.posix.unistd;
4102 		static import err = core.stdc.errno;
4103 		import core.sys.linux.timerfd;
4104 
4105 		void delegate(int) signalHandler;
4106 	}
4107 
4108 	version(X11) {
4109 		int pulseFd = -1;
4110 		version(linux) ep.epoll_event[16] events = void;
4111 	} else version(Windows) {
4112 		Timer pulser;
4113 		HANDLE[] handles;
4114 	}
4115 
4116 
4117 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4118 	/// to call this, as it's not recommended to share window between threads.
4119 	void mtLock () {
4120 		version(X11) {
4121 			XLockDisplay(this.display);
4122 		}
4123 	}
4124 
4125 	version(X11)
4126 	auto display() { return XDisplayConnection.get; }
4127 
4128 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4129 	/// to call this, as it's not recommended to share window between threads.
4130 	void mtUnlock () {
4131 		version(X11) {
4132 			XUnlockDisplay(this.display);
4133 		}
4134 	}
4135 
4136 	version(with_eventloop)
4137 	void initialize(long pulseTimeout) {}
4138 	else
4139 	void initialize(long pulseTimeout) {
4140 		version(Windows) {
4141 			if(pulseTimeout && handlePulse !is null)
4142 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4143 
4144 			if (customEventH is null) {
4145 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4146 				if (customEventH !is null) {
4147 					handles ~= customEventH;
4148 				} else {
4149 					// this is something that should not be; better be safe than sorry
4150 					throw new Exception("can't create eventfd for custom event processing");
4151 				}
4152 			}
4153 
4154 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4155 		}
4156 
4157 		version(linux) {
4158 			prepareEventLoop();
4159 			{
4160 				auto display = XDisplayConnection.get;
4161 				// adding Xlib file
4162 				ep.epoll_event ev = void;
4163 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4164 				ev.events = ep.EPOLLIN;
4165 				ev.data.fd = display.fd;
4166 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4167 					throw new Exception("add x fd");// ~ to!string(epollFd));
4168 				displayFd = display.fd;
4169 			}
4170 
4171 			if(pulseTimeout && handlePulse !is null) {
4172 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4173 				if(pulseFd == -1)
4174 					throw new Exception("pulse timer create failed");
4175 
4176 				itimerspec value;
4177 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4178 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4179 
4180 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4181 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4182 
4183 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4184 					throw new Exception("couldn't make pulse timer");
4185 
4186 				ep.epoll_event ev = void;
4187 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4188 				ev.events = ep.EPOLLIN;
4189 				ev.data.fd = pulseFd;
4190 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4191 			}
4192 
4193 			// eventfd for custom events
4194 			if (customEventFDWrite == -1) {
4195 				customEventFDWrite = eventfd(0, 0);
4196 				customEventFDRead = customEventFDWrite;
4197 				if (customEventFDRead >= 0) {
4198 					ep.epoll_event ev = void;
4199 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4200 					ev.events = ep.EPOLLIN;
4201 					ev.data.fd = customEventFDRead;
4202 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4203 				} else {
4204 					// this is something that should not be; better be safe than sorry
4205 					throw new Exception("can't create eventfd for custom event processing");
4206 				}
4207 			}
4208 
4209 			if (customSignalFD == -1) {
4210 				import core.sys.linux.sys.signalfd;
4211 
4212 				sigset_t sigset;
4213 				auto err = sigemptyset(&sigset);
4214 				assert(!err);
4215 				err = sigaddset(&sigset, SIGINT);
4216 				assert(!err);
4217 				err = sigaddset(&sigset, SIGHUP);
4218 				assert(!err);
4219 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4220 				assert(!err);
4221 
4222 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4223 				assert(customSignalFD != -1);
4224 
4225 				ep.epoll_event ev = void;
4226 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4227 				ev.events = ep.EPOLLIN;
4228 				ev.data.fd = customSignalFD;
4229 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4230 			}
4231 		} else version(Posix) {
4232 			prepareEventLoop();
4233 			if (customEventFDRead == -1) {
4234 				int[2] bfr;
4235 				import core.sys.posix.unistd;
4236 				auto ret = pipe(bfr);
4237 				if(ret == -1) throw new Exception("pipe");
4238 				customEventFDRead = bfr[0];
4239 				customEventFDWrite = bfr[1];
4240 			}
4241 
4242 		}
4243 
4244 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4245 
4246 		version(linux) {
4247 			this.mtLock();
4248 			scope(exit) this.mtUnlock();
4249 			XPending(display); // no, really
4250 		}
4251 
4252 		disposed = false;
4253 	}
4254 
4255 	bool disposed = true;
4256 	version(X11)
4257 		int displayFd = -1;
4258 
4259 	version(with_eventloop)
4260 	void dispose() {}
4261 	else
4262 	void dispose() {
4263 		disposed = true;
4264 		version(X11) {
4265 			if(pulseFd != -1) {
4266 				import unix = core.sys.posix.unistd;
4267 				unix.close(pulseFd);
4268 				pulseFd = -1;
4269 			}
4270 
4271 				version(linux)
4272 				if(displayFd != -1) {
4273 					// 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
4274 					ep.epoll_event ev = void;
4275 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4276 					ev.events = ep.EPOLLIN;
4277 					ev.data.fd = displayFd;
4278 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4279 					displayFd = -1;
4280 				}
4281 
4282 		} else version(Windows) {
4283 			if(pulser !is null) {
4284 				pulser.destroy();
4285 				pulser = null;
4286 			}
4287 			if (customEventH !is null) {
4288 				CloseHandle(customEventH);
4289 				customEventH = null;
4290 			}
4291 		}
4292 	}
4293 
4294 	this(long pulseTimeout, void delegate() handlePulse) {
4295 		this.pulseTimeout = pulseTimeout;
4296 		this.handlePulse = handlePulse;
4297 		initialize(pulseTimeout);
4298 	}
4299 
4300 	private long pulseTimeout;
4301 	void delegate() handlePulse;
4302 
4303 	~this() {
4304 		dispose();
4305 	}
4306 
4307 	version(Posix)
4308 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4309 	version(Posix)
4310 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4311 	version(linux)
4312 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4313 	version(Windows)
4314 	ref auto customEventH() { return SimpleWindow.customEventH; }
4315 
4316 	version(with_eventloop) {
4317 		int loopHelper(bool delegate() whileCondition) {
4318 			// FIXME: whileCondition
4319 			import arsd.eventloop;
4320 			loop();
4321 			return 0;
4322 		}
4323 	} else
4324 	int loopHelper(bool delegate() whileCondition) {
4325 		version(X11) {
4326 			bool done = false;
4327 
4328 			XFlush(display);
4329 			insideXEventLoop = true;
4330 			scope(exit) insideXEventLoop = false;
4331 
4332 			version(linux) {
4333 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4334 					bool forceXPending = false;
4335 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4336 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4337 					{
4338 						this.mtLock();
4339 						scope(exit) this.mtUnlock();
4340 						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
4341 					}
4342 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4343 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4344 					if(nfds == -1) {
4345 						if(err.errno == err.EINTR) {
4346 							//if(forceXPending) goto xpending;
4347 							continue; // interrupted by signal, just try again
4348 						}
4349 						throw new Exception("epoll wait failure");
4350 					}
4351 					// writeln(nfds, " ", events[0].data.fd);
4352 
4353 					SimpleWindow.processAllCustomEvents(); // anyway
4354 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4355 					foreach(idx; 0 .. nfds) {
4356 						if(done) break;
4357 						auto fd = events[idx].data.fd;
4358 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4359 						auto flags = events[idx].events;
4360 						if(flags & ep.EPOLLIN) {
4361 							if (fd == customSignalFD) {
4362 								version(linux) {
4363 									import core.sys.linux.sys.signalfd;
4364 									import core.sys.posix.unistd : read;
4365 									signalfd_siginfo info;
4366 									read(customSignalFD, &info, info.sizeof);
4367 
4368 									auto sig = info.ssi_signo;
4369 
4370 									if(EventLoop.get.signalHandler !is null) {
4371 										EventLoop.get.signalHandler()(sig);
4372 									} else {
4373 										EventLoop.get.exit();
4374 									}
4375 								}
4376 							} else if(fd == display.fd) {
4377 								version(sdddd) { writeln("X EVENT PENDING!"); }
4378 								this.mtLock();
4379 								scope(exit) this.mtUnlock();
4380 								while(!done && XPending(display)) {
4381 									done = doXNextEvent(this.display);
4382 								}
4383 								forceXPending = false;
4384 							} else if(fd == pulseFd) {
4385 								long expirationCount;
4386 								// 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...
4387 
4388 								handlePulse();
4389 
4390 								// read just to clear the buffer so poll doesn't trigger again
4391 								// BTW I read AFTER the pulse because if the pulse handler takes
4392 								// a lot of time to execute, we don't want the app to get stuck
4393 								// in a loop of timer hits without a chance to do anything else
4394 								//
4395 								// IOW handlePulse happens at most once per pulse interval.
4396 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4397 								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
4398 							} else if (fd == customEventFDRead) {
4399 								// we have some custom events; process 'em
4400 								import core.sys.posix.unistd : read;
4401 								ulong n;
4402 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4403 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4404 								//SimpleWindow.processAllCustomEvents();
4405 
4406 								forceXPending = true;
4407 							} else {
4408 								// some other timer
4409 								version(sdddd) { writeln("unknown fd: ", fd); }
4410 
4411 								if(Timer* t = fd in Timer.mapping)
4412 									(*t).trigger();
4413 
4414 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4415 									(*pfr).ready(flags);
4416 
4417 								// we don't know what the user did in this timer, so we need to assume that
4418 								// there's X data to be flushed and potentially processed
4419 								forceXPending = true;
4420 
4421 								// or i might add support for other FDs too
4422 								// but for now it is just timer
4423 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4424 							}
4425 						}
4426 						if(flags & ep.EPOLLHUP) {
4427 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4428 								(*pfr).hup(flags);
4429 							if(globalHupHandler)
4430 								globalHupHandler(fd, flags);
4431 						}
4432 						/+
4433 						} else {
4434 							// not interested in OUT, we are just reading here.
4435 							//
4436 							// error or hup might also be reported
4437 							// but it shouldn't here since we are only
4438 							// using a few types of FD and Xlib will report
4439 							// if it dies.
4440 							// so instead of thoughtfully handling it, I'll
4441 							// just throw. for now at least
4442 
4443 							throw new Exception("epoll did something else");
4444 						}
4445 						+/
4446 					}
4447 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4448 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4449 					xpending:
4450 					if (!done && forceXPending) {
4451 						this.mtLock();
4452 						scope(exit) this.mtUnlock();
4453 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4454 						while(!done && XPending(display)) {
4455 							done = doXNextEvent(this.display);
4456 						}
4457 					}
4458 				}
4459 			} else {
4460 				// Generic fallback: yes to simple pulse support,
4461 				// but NO timer support!
4462 
4463 				// FIXME: we could probably support the POSIX timer_create
4464 				// signal-based option, but I'm in no rush to write it since
4465 				// I prefer the fd-based functions.
4466 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4467 
4468 					import core.sys.posix.poll;
4469 
4470 					pollfd[] pfds;
4471 					pollfd[32] pfdsBuffer;
4472 					auto len = PosixFdReader.mapping.length + 2;
4473 					// FIXME: i should just reuse the buffer
4474 					if(len < pfdsBuffer.length)
4475 						pfds = pfdsBuffer[0 .. len];
4476 					else
4477 						pfds = new pollfd[](len);
4478 
4479 					pfds[0].fd = display.fd;
4480 					pfds[0].events = POLLIN;
4481 					pfds[0].revents = 0;
4482 
4483 					int slot = 1;
4484 
4485 					if(customEventFDRead != -1) {
4486 						pfds[slot].fd = customEventFDRead;
4487 						pfds[slot].events = POLLIN;
4488 						pfds[slot].revents = 0;
4489 
4490 						slot++;
4491 					}
4492 
4493 					foreach(fd, obj; PosixFdReader.mapping) {
4494 						if(!obj.enabled) continue;
4495 						pfds[slot].fd = fd;
4496 						pfds[slot].events = POLLIN;
4497 						pfds[slot].revents = 0;
4498 
4499 						slot++;
4500 					}
4501 
4502 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4503 					if(ret == -1) throw new Exception("poll");
4504 
4505 					if(ret == 0) {
4506 						// FIXME it may not necessarily time out if events keep coming
4507 						if(handlePulse !is null)
4508 							handlePulse();
4509 					} else {
4510 						foreach(s; 0 .. slot) {
4511 							if(pfds[s].revents == 0) continue;
4512 
4513 							if(pfds[s].fd == display.fd) {
4514 								while(!done && XPending(display)) {
4515 									this.mtLock();
4516 									scope(exit) this.mtUnlock();
4517 									done = doXNextEvent(this.display);
4518 								}
4519 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4520 
4521 								import core.sys.posix.unistd : read;
4522 								ulong n;
4523 								read(customEventFDRead, &n, n.sizeof);
4524 								SimpleWindow.processAllCustomEvents();
4525 							} else {
4526 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4527 								if(pfds[s].revents & POLLNVAL) {
4528 									obj.dispose();
4529 								} else {
4530 									obj.ready(pfds[s].revents);
4531 								}
4532 							}
4533 
4534 							ret--;
4535 							if(ret == 0) break;
4536 						}
4537 					}
4538 				}
4539 			}
4540 		}
4541 
4542 		version(Windows) {
4543 			int ret = -1;
4544 			MSG message;
4545 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4546 				eventLoopRound++;
4547 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4548 				auto waitResult = MsgWaitForMultipleObjectsEx(
4549 					cast(int) handles.length, handles.ptr,
4550 					(wto == 0 ? INFINITE : wto), /* timeout */
4551 					0x04FF, /* QS_ALLINPUT */
4552 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4553 
4554 				SimpleWindow.processAllCustomEvents(); // anyway
4555 				enum WAIT_OBJECT_0 = 0;
4556 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4557 					auto h = handles[waitResult - WAIT_OBJECT_0];
4558 					if(auto e = h in WindowsHandleReader.mapping) {
4559 						(*e).ready();
4560 					}
4561 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4562 					// message ready
4563 					int count;
4564 					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
4565 						ret = GetMessage(&message, null, 0, 0);
4566 						if(ret == -1)
4567 							throw new WindowsApiException("GetMessage", GetLastError());
4568 						TranslateMessage(&message);
4569 						DispatchMessage(&message);
4570 
4571 						count++;
4572 						if(count > 10)
4573 							break; // take the opportunity to catch up on other events
4574 
4575 						if(ret == 0) { // WM_QUIT
4576 							EventLoop.quitApplication();
4577 							break;
4578 						}
4579 					}
4580 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4581 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4582 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4583 					// timeout, should never happen since we aren't using it
4584 				} else if(waitResult == 0xFFFFFFFF) {
4585 						// failed
4586 						throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4587 				} else {
4588 					// idk....
4589 				}
4590 			}
4591 
4592 			// return message.wParam;
4593 			return 0;
4594 		} else {
4595 			return 0;
4596 		}
4597 	}
4598 
4599 	int run(bool delegate() whileCondition = null) {
4600 		if(disposed)
4601 			initialize(this.pulseTimeout);
4602 
4603 		version(X11) {
4604 			try {
4605 				return loopHelper(whileCondition);
4606 			} catch(XDisconnectException e) {
4607 				if(e.userRequested) {
4608 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4609 						item.discardConnectionState();
4610 					XCloseDisplay(XDisplayConnection.display);
4611 				}
4612 
4613 				XDisplayConnection.display = null;
4614 
4615 				this.dispose();
4616 
4617 				throw e;
4618 			}
4619 		} else {
4620 			return loopHelper(whileCondition);
4621 		}
4622 	}
4623 }
4624 
4625 
4626 /++
4627 	Provides an icon on the system notification area (also known as the system tray).
4628 
4629 
4630 	If a notification area is not available with the NotificationIcon object is created,
4631 	it will silently succeed and simply attempt to create one when an area becomes available.
4632 
4633 
4634 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
4635 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
4636 	with true color was added at that time. I was just too lazy to write the fallback.
4637 
4638 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
4639 	you use arsd 10.x when targeting Windows XP.
4640 +/
4641 version(OSXCocoa) {} else // NotYetImplementedException
4642 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4643 
4644 	version(X11) {
4645 		void recreateAfterDisconnect() {
4646 			stateDiscarded = false;
4647 			clippixmap = None;
4648 			throw new Exception("NOT IMPLEMENTED");
4649 		}
4650 
4651 		bool stateDiscarded;
4652 		void discardConnectionState() {
4653 			stateDiscarded = true;
4654 		}
4655 	}
4656 
4657 
4658 	version(X11) {
4659 		Image img;
4660 
4661 		NativeEventHandler getNativeEventHandler() {
4662 			return delegate int(XEvent e) {
4663 				switch(e.type) {
4664 					case EventType.Expose:
4665 					//case EventType.VisibilityNotify:
4666 						redraw();
4667 					break;
4668 					case EventType.ClientMessage:
4669 						version(sddddd) {
4670 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4671 						writeln("\t", e.xclient.format);
4672 						writeln("\t", e.xclient.data.l);
4673 						}
4674 					break;
4675 					case EventType.ButtonPress:
4676 						auto event = e.xbutton;
4677 						if (onClick !is null || onClickEx !is null) {
4678 							MouseButton mb = cast(MouseButton)0;
4679 							switch (event.button) {
4680 								case 1: mb = MouseButton.left; break; // left
4681 								case 2: mb = MouseButton.middle; break; // middle
4682 								case 3: mb = MouseButton.right; break; // right
4683 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4684 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4685 								case 6: break; // scroll left...
4686 								case 7: break; // scroll right...
4687 								case 8: mb = MouseButton.backButton; break;
4688 								case 9: mb = MouseButton.forwardButton; break;
4689 								default:
4690 							}
4691 							if (mb) {
4692 								try { onClick()(mb); } catch (Exception) {}
4693 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4694 							}
4695 						}
4696 					break;
4697 					case EventType.EnterNotify:
4698 						if (onEnter !is null) {
4699 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4700 						}
4701 						break;
4702 					case EventType.LeaveNotify:
4703 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4704 						break;
4705 					case EventType.DestroyNotify:
4706 						active = false;
4707 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4708 					break;
4709 					case EventType.ConfigureNotify:
4710 						auto event = e.xconfigure;
4711 						this.width = event.width;
4712 						this.height = event.height;
4713 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4714 						redraw();
4715 					break;
4716 					default: return 1;
4717 				}
4718 				return 1;
4719 			};
4720 		}
4721 
4722 		/* private */ void hideBalloon() {
4723 			balloon.close();
4724 			version(with_timer)
4725 				timer.destroy();
4726 			balloon = null;
4727 			version(with_timer)
4728 				timer = null;
4729 		}
4730 
4731 		void redraw() {
4732 			if (!active) return;
4733 
4734 			auto display = XDisplayConnection.get;
4735 			GC gc;
4736 
4737 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
4738 
4739 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
4740 				Visual *visual;
4741 				XVisualInfo vis_info;
4742 				XSetWindowAttributes win_attr;
4743 				c_ulong win_mask;
4744 
4745 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
4746 					assert(0);
4747 					// return 1;
4748 				}
4749 
4750 				visual = vis_info.visual;
4751 
4752 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
4753 				win_attr.background_pixel = 0;
4754 				win_attr.border_pixel = 0;
4755 
4756 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
4757 
4758 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
4759 
4760 				return 0;
4761 			}
4762 
4763 			if(useAlpha)
4764 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
4765 			else
4766 				gc = DefaultGC(display, DefaultScreen(display));
4767 
4768 			XClearWindow(display, nativeHandle);
4769 
4770 			if(!useAlpha && img !is null)
4771 				XSetClipMask(display, gc, clippixmap);
4772 
4773 			/+
4774 			XSetForeground(display, gc,
4775 				cast(uint) 0 << 16 |
4776 				cast(uint) 0 << 8 |
4777 				cast(uint) 0);
4778 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4779 			+/
4780 
4781 			if (img is null) {
4782 				XSetForeground(display, gc,
4783 					cast(uint) 0 << 16 |
4784 					cast(uint) 127 << 8 |
4785 					cast(uint) 0);
4786 				XFillArc(display, nativeHandle,
4787 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4788 			} else {
4789 				int dx = 0;
4790 				int dy = 0;
4791 				if(width > img.width)
4792 					dx = (width - img.width) / 2;
4793 				if(height > img.height)
4794 					dy = (height - img.height) / 2;
4795 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
4796 				XSetClipOrigin(display, gc, dx, dy);
4797 
4798 				int max(int a, int b) {
4799 					if(a > b) return a; else return b;
4800 				}
4801 
4802 				if (img.usingXshm)
4803 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
4804 				else
4805 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
4806 			}
4807 			XSetClipMask(display, gc, None);
4808 			flushGui();
4809 		}
4810 
4811 		static Window getTrayOwner() {
4812 			auto display = XDisplayConnection.get;
4813 			auto i = cast(int) DefaultScreen(display);
4814 			if(i < 10 && i >= 0) {
4815 				static Atom atom;
4816 				if(atom == None)
4817 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4818 				return XGetSelectionOwner(display, atom);
4819 			}
4820 			return None;
4821 		}
4822 
4823 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4824 			auto to = getTrayOwner();
4825 			auto display = XDisplayConnection.get;
4826 			XEvent ev;
4827 			ev.xclient.type = EventType.ClientMessage;
4828 			ev.xclient.window = to;
4829 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4830 			ev.xclient.format = 32;
4831 			ev.xclient.data.l[0] = CurrentTime;
4832 			ev.xclient.data.l[1] = message;
4833 			ev.xclient.data.l[2] = d1;
4834 			ev.xclient.data.l[3] = d2;
4835 			ev.xclient.data.l[4] = d3;
4836 
4837 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4838 		}
4839 
4840 		private static NotificationAreaIcon[] activeIcons;
4841 
4842 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4843 		private void newManager() {
4844 			close();
4845 			createXWin();
4846 
4847 			if(this.clippixmap)
4848 				XFreePixmap(XDisplayConnection.get, clippixmap);
4849 			if(this.originalMemoryImage)
4850 				this.icon = this.originalMemoryImage;
4851 			else if(this.img)
4852 				this.icon = this.img;
4853 		}
4854 
4855 		private bool useAlpha = false;
4856 
4857 		private void createXWin () {
4858 			// create window
4859 			auto display = XDisplayConnection.get;
4860 
4861 			// to check for MANAGER on root window to catch new/changed tray owners
4862 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4863 			// so if a thing does appear, we can handle it
4864 			foreach(ai; activeIcons)
4865 				if(ai is this)
4866 					goto alreadythere;
4867 			activeIcons ~= this;
4868 			alreadythere:
4869 
4870 			// and check for an existing tray
4871 			auto trayOwner = getTrayOwner();
4872 			if(trayOwner == None)
4873 				return;
4874 				//throw new Exception("No notification area found");
4875 
4876 			Visual* v = cast(Visual*) CopyFromParent;
4877 
4878 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
4879 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
4880 			// a resize event later.
4881 			width = 22;
4882 			height = 22;
4883 
4884 			// if they system gave us a 32 bit visual we need to switch to it too
4885 			int depth = 24;
4886 
4887 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4888 			if(visualProp !is null) {
4889 				c_ulong[] info = cast(c_ulong[]) visualProp;
4890 				if(info.length == 1) {
4891 					auto vid = info[0];
4892 					int returned;
4893 					XVisualInfo t;
4894 					t.visualid = vid;
4895 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4896 					if(got !is null) {
4897 						if(returned == 1) {
4898 							v = got.visual;
4899 							depth = got.depth;
4900 							// writeln("using special visual ", got.depth);
4901 							// writeln(depth);
4902 						}
4903 						XFree(got);
4904 					}
4905 				}
4906 			}
4907 
4908 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
4909 			XSetWindowAttributes attr;
4910 			attr.background_pixel = 0;
4911 			attr.border_pixel = 0;
4912 			attr.override_redirect = 0;
4913 			if(v !is cast(Visual*) CopyFromParent) {
4914 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
4915 				CWFlags |= CWColormap;
4916 				if(depth == 32)
4917 					useAlpha = true;
4918 				else
4919 					goto plain;
4920 			} else {
4921 				plain:
4922 				attr.background_pixmap = 1 /* ParentRelative */;
4923 				CWFlags |= CWBackPixmap;
4924 			}
4925 
4926 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
4927 
4928 			assert(nativeWindow);
4929 
4930 			if(!useAlpha)
4931 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4932 
4933 			nativeHandle = nativeWindow;
4934 
4935 			///+
4936 			arch_ulong[2] info;
4937 			info[0] = 0;
4938 			info[1] = 1;
4939 
4940 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4941 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4942 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4943 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4944 
4945 			XChangeProperty(
4946 				display,
4947 				nativeWindow,
4948 				GetAtom!("_XEMBED_INFO", true)(display),
4949 				GetAtom!("_XEMBED_INFO", true)(display),
4950 				32 /* bits */,
4951 				0 /*PropModeReplace*/,
4952 				info.ptr,
4953 				2);
4954 
4955 			import core.sys.posix.unistd;
4956 			arch_ulong pid = getpid();
4957 
4958 			XChangeProperty(
4959 				display,
4960 				nativeWindow,
4961 				GetAtom!("_NET_WM_PID", true)(display),
4962 				XA_CARDINAL,
4963 				32 /* bits */,
4964 				0 /*PropModeReplace*/,
4965 				&pid,
4966 				1);
4967 
4968 			updateNetWmIcon();
4969 
4970 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4971 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4972 				XClassHint klass;
4973 				XWMHints wh;
4974 				XSizeHints size;
4975 				klass.res_name = sdpyWindowClassStr;
4976 				klass.res_class = sdpyWindowClassStr;
4977 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4978 			}
4979 
4980 				// believe it or not, THIS is what xfce needed for the 9999 issue
4981 				XSizeHints sh;
4982 				c_long spr;
4983 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4984 				sh.flags |= PMaxSize | PMinSize;
4985 				// FIXME maybe nicer resizing
4986 				sh.min_width = 16;
4987 				sh.min_height = 16;
4988 				sh.max_width = 22;
4989 				sh.max_height = 22;
4990 				XSetWMNormalHints(display, nativeWindow, &sh);
4991 
4992 
4993 			//+/
4994 
4995 
4996 			XSelectInput(display, nativeWindow,
4997 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4998 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4999 
5000 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5001 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5002 
5003 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5004 			active = true;
5005 		}
5006 
5007 		void updateNetWmIcon() {
5008 			if(img is null) return;
5009 			auto display = XDisplayConnection.get;
5010 			// FIXME: ensure this is correct
5011 			arch_ulong[] buffer;
5012 			auto imgMi = img.toTrueColorImage;
5013 			buffer ~= imgMi.width;
5014 			buffer ~= imgMi.height;
5015 			foreach(c; imgMi.imageData.colors) {
5016 				arch_ulong b;
5017 				b |= c.a << 24;
5018 				b |= c.r << 16;
5019 				b |= c.g << 8;
5020 				b |= c.b;
5021 				buffer ~= b;
5022 			}
5023 
5024 			XChangeProperty(
5025 				display,
5026 				nativeHandle,
5027 				GetAtom!"_NET_WM_ICON"(display),
5028 				GetAtom!"CARDINAL"(display),
5029 				32 /* bits */,
5030 				0 /*PropModeReplace*/,
5031 				buffer.ptr,
5032 				cast(int) buffer.length);
5033 		}
5034 
5035 
5036 
5037 		private SimpleWindow balloon;
5038 		version(with_timer)
5039 		private Timer timer;
5040 
5041 		private Window nativeHandle;
5042 		private Pixmap clippixmap = None;
5043 		private int width = 16;
5044 		private int height = 16;
5045 		private bool active = false;
5046 
5047 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5048 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5049 		void delegate () onLeave; /// X11 only.
5050 
5051 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5052 
5053 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5054 		void getWindowRect (out int x, out int y, out int width, out int height) {
5055 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5056 			Window dummyw;
5057 			auto dpy = XDisplayConnection.get;
5058 			//XWindowAttributes xwa;
5059 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5060 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5061 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5062 			width = this.width;
5063 			height = this.height;
5064 		}
5065 	}
5066 
5067 	/+
5068 		What I actually want from this:
5069 
5070 		* set / change: icon, tooltip
5071 		* handle: mouse click, right click
5072 		* show: notification bubble.
5073 	+/
5074 
5075 	version(Windows) {
5076 		WindowsIcon win32Icon;
5077 		HWND hwnd;
5078 
5079 		NOTIFYICONDATAW data;
5080 
5081 		NativeEventHandler getNativeEventHandler() {
5082 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5083 				if(msg == WM_USER) {
5084 					auto event = LOWORD(lParam);
5085 					auto iconId = HIWORD(lParam);
5086 					//auto x = GET_X_LPARAM(wParam);
5087 					//auto y = GET_Y_LPARAM(wParam);
5088 					switch(event) {
5089 						case WM_LBUTTONDOWN:
5090 							onClick()(MouseButton.left);
5091 						break;
5092 						case WM_RBUTTONDOWN:
5093 							onClick()(MouseButton.right);
5094 						break;
5095 						case WM_MBUTTONDOWN:
5096 							onClick()(MouseButton.middle);
5097 						break;
5098 						case WM_MOUSEMOVE:
5099 							// sent, we could use it.
5100 						break;
5101 						case WM_MOUSEWHEEL:
5102 							// NOT SENT
5103 						break;
5104 						//case NIN_KEYSELECT:
5105 						//case NIN_SELECT:
5106 						//break;
5107 						default: {}
5108 					}
5109 				}
5110 				return 0;
5111 			};
5112 		}
5113 
5114 		enum NIF_SHOWTIP = 0x00000080;
5115 
5116 		private static struct NOTIFYICONDATAW {
5117 			DWORD cbSize;
5118 			HWND  hWnd;
5119 			UINT  uID;
5120 			UINT  uFlags;
5121 			UINT  uCallbackMessage;
5122 			HICON hIcon;
5123 			WCHAR[128] szTip;
5124 			DWORD dwState;
5125 			DWORD dwStateMask;
5126 			WCHAR[256] szInfo;
5127 			union {
5128 				UINT uTimeout;
5129 				UINT uVersion;
5130 			}
5131 			WCHAR[64] szInfoTitle;
5132 			DWORD dwInfoFlags;
5133 			GUID  guidItem;
5134 			HICON hBalloonIcon;
5135 		}
5136 
5137 	}
5138 
5139 	/++
5140 		Note that on Windows, only left, right, and middle buttons are sent.
5141 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5142 		program is meant to be used on Windows too.
5143 	+/
5144 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5145 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5146 		// but on X, we need an Image, so its canonical ctor is there. They should
5147 		// forward to each other though.
5148 		version(X11) {
5149 			this.name = name;
5150 			this.onClick = onClick;
5151 			createXWin();
5152 			this.icon = icon;
5153 		} else version(Windows) {
5154 			this.onClick = onClick;
5155 			this.win32Icon = new WindowsIcon(icon);
5156 
5157 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5158 
5159 			static bool registered = false;
5160 			if(!registered) {
5161 				WNDCLASSEX wc;
5162 				wc.cbSize = wc.sizeof;
5163 				wc.hInstance = hInstance;
5164 				wc.lpfnWndProc = &WndProc;
5165 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5166 				if(!RegisterClassExW(&wc))
5167 					throw new WindowsApiException("RegisterClass", GetLastError());
5168 				registered = true;
5169 			}
5170 
5171 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5172 			if(hwnd is null)
5173 				throw new WindowsApiException("CreateWindow", GetLastError());
5174 
5175 			data.cbSize = data.sizeof;
5176 			data.hWnd = hwnd;
5177 			data.uID = cast(uint) cast(void*) this;
5178 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5179 				// NIF_INFO means show balloon
5180 			data.uCallbackMessage = WM_USER;
5181 			data.hIcon = this.win32Icon.hIcon;
5182 			data.szTip = ""; // FIXME
5183 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5184 			data.dwStateMask = NIS_HIDDEN; // windows vista
5185 
5186 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5187 
5188 
5189 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5190 
5191 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5192 		} else version(OSXCocoa) {
5193 			throw new NotYetImplementedException();
5194 		} else static assert(0);
5195 	}
5196 
5197 	/// ditto
5198 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5199 		version(X11) {
5200 			this.onClick = onClick;
5201 			this.name = name;
5202 			createXWin();
5203 			this.icon = icon;
5204 		} else version(Windows) {
5205 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5206 		} else version(OSXCocoa) {
5207 			throw new NotYetImplementedException();
5208 		} else static assert(0);
5209 	}
5210 
5211 	version(X11) {
5212 		/++
5213 			X-specific extension (for now at least)
5214 		+/
5215 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5216 			this.onClickEx = onClickEx;
5217 			createXWin();
5218 			if (icon !is null) this.icon = icon;
5219 		}
5220 
5221 		/// ditto
5222 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5223 			this.onClickEx = onClickEx;
5224 			createXWin();
5225 			this.icon = icon;
5226 		}
5227 	}
5228 
5229 	private void delegate (MouseButton button) onClick_;
5230 
5231 	///
5232 	@property final void delegate(MouseButton) onClick() {
5233 		if(onClick_ is null)
5234 			onClick_ = delegate void(MouseButton) {};
5235 		return onClick_;
5236 	}
5237 
5238 	/// ditto
5239 	@property final void onClick(void delegate(MouseButton) handler) {
5240 		// I made this a property setter so we can wrap smaller arg
5241 		// delegates and just forward all to onClickEx or something.
5242 		onClick_ = handler;
5243 	}
5244 
5245 
5246 	string name_;
5247 	@property void name(string n) {
5248 		name_ = n;
5249 	}
5250 
5251 	@property string name() {
5252 		return name_;
5253 	}
5254 
5255 	private MemoryImage originalMemoryImage;
5256 
5257 	///
5258 	@property void icon(MemoryImage i) {
5259 		version(X11) {
5260 			this.originalMemoryImage = i;
5261 			if (!active) return;
5262 			if (i !is null) {
5263 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5264 				if(!useAlpha)
5265 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5266 				// writeln("using pixmap ", clippixmap);
5267 				updateNetWmIcon();
5268 				redraw();
5269 			} else {
5270 				if (this.img !is null) {
5271 					this.img = null;
5272 					redraw();
5273 				}
5274 			}
5275 		} else version(Windows) {
5276 			this.win32Icon = new WindowsIcon(i);
5277 
5278 			data.uFlags = NIF_ICON;
5279 			data.hIcon = this.win32Icon.hIcon;
5280 
5281 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5282 		} else version(OSXCocoa) {
5283 			throw new NotYetImplementedException();
5284 		} else static assert(0);
5285 	}
5286 
5287 	/// ditto
5288 	@property void icon (Image i) {
5289 		version(X11) {
5290 			if (!active) return;
5291 			if (i !is img) {
5292 				originalMemoryImage = null;
5293 				img = i;
5294 				redraw();
5295 			}
5296 		} else version(Windows) {
5297 			this.icon(i is null ? null : i.toTrueColorImage());
5298 		} else version(OSXCocoa) {
5299 			throw new NotYetImplementedException();
5300 		} else static assert(0);
5301 	}
5302 
5303 	/++
5304 		Shows a balloon notification. You can only show one balloon at a time, if you call
5305 		it twice while one is already up, the first balloon will be replaced.
5306 
5307 
5308 		The user is free to block notifications and they will automatically disappear after
5309 		a timeout period.
5310 
5311 		Params:
5312 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5313 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5314 			icon = the icon to display with the notification. If null, it uses your existing icon.
5315 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5316 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5317 	+/
5318 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5319 		bool useCustom = true;
5320 		version(libnotify) {
5321 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5322 			try {
5323 				if(!active) return;
5324 
5325 				if(libnotify is null) {
5326 					libnotify = new C_DynamicLibrary("libnotify.so");
5327 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5328 				}
5329 
5330 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5331 
5332 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5333 
5334 				if(onclick) {
5335 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5336 					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);
5337 					libnotify_action_delegates_count++;
5338 				}
5339 
5340 				// FIXME icon
5341 
5342 				// set hint image-data
5343 				// set default action for onclick
5344 
5345 				void* error;
5346 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5347 
5348 				useCustom = false;
5349 			} catch(Exception e) {
5350 
5351 			}
5352 		}
5353 
5354 		version(X11) {
5355 		if(useCustom) {
5356 			if(!active) return;
5357 			if(balloon) {
5358 				hideBalloon();
5359 			}
5360 			// I know there are two specs for this, but one is never
5361 			// implemented by any window manager I have ever seen, and
5362 			// the other is a bloated mess and too complicated for simpledisplay...
5363 			// so doing my own little window instead.
5364 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5365 
5366 			int x, y, width, height;
5367 			getWindowRect(x, y, width, height);
5368 
5369 			int bx = x - balloon.width;
5370 			int by = y - balloon.height;
5371 			if(bx < 0)
5372 				bx = x + width + balloon.width;
5373 			if(by < 0)
5374 				by = y + height;
5375 
5376 			// just in case, make sure it is actually on scren
5377 			if(bx < 0)
5378 				bx = 0;
5379 			if(by < 0)
5380 				by = 0;
5381 
5382 			balloon.move(bx, by);
5383 			auto painter = balloon.draw();
5384 			painter.fillColor = Color(220, 220, 220);
5385 			painter.outlineColor = Color.black;
5386 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5387 			auto iconWidth = icon is null ? 0 : icon.width;
5388 			if(icon)
5389 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5390 			iconWidth += 6; // margin around the icon
5391 
5392 			// draw a close button
5393 			painter.outlineColor = Color(44, 44, 44);
5394 			painter.fillColor = Color(255, 255, 255);
5395 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5396 			painter.pen = Pen(Color.black, 3);
5397 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5398 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5399 			painter.pen = Pen(Color.black, 1);
5400 			painter.fillColor = Color(220, 220, 220);
5401 
5402 			// Draw the title and message
5403 			painter.drawText(Point(4 + iconWidth, 4), title);
5404 			painter.drawLine(
5405 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5406 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5407 			);
5408 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5409 
5410 			balloon.setEventHandlers(
5411 				(MouseEvent ev) {
5412 					if(ev.type == MouseEventType.buttonPressed) {
5413 						if(ev.x > balloon.width - 16 && ev.y < 16)
5414 							hideBalloon();
5415 						else if(onclick)
5416 							onclick();
5417 					}
5418 				}
5419 			);
5420 			balloon.show();
5421 
5422 			version(with_timer)
5423 			timer = new Timer(timeout, &hideBalloon);
5424 			else {} // FIXME
5425 		}
5426 		} else version(Windows) {
5427 			enum NIF_INFO = 0x00000010;
5428 
5429 			data.uFlags = NIF_INFO;
5430 
5431 			// FIXME: go back to the last valid unicode code point
5432 			if(title.length > 40)
5433 				title = title[0 .. 40];
5434 			if(message.length > 220)
5435 				message = message[0 .. 220];
5436 
5437 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5438 			enum NIIF_LARGE_ICON  = 0x00000020;
5439 			enum NIIF_NOSOUND = 0x00000010;
5440 			enum NIIF_USER = 0x00000004;
5441 			enum NIIF_ERROR = 0x00000003;
5442 			enum NIIF_WARNING = 0x00000002;
5443 			enum NIIF_INFO = 0x00000001;
5444 			enum NIIF_NONE = 0;
5445 
5446 			WCharzBuffer t = WCharzBuffer(title);
5447 			WCharzBuffer m = WCharzBuffer(message);
5448 
5449 			t.copyInto(data.szInfoTitle);
5450 			m.copyInto(data.szInfo);
5451 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5452 
5453 			if(icon !is null) {
5454 				auto i = new WindowsIcon(icon);
5455 				data.hBalloonIcon = i.hIcon;
5456 				data.dwInfoFlags |= NIIF_USER;
5457 			}
5458 
5459 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5460 		} else version(OSXCocoa) {
5461 			throw new NotYetImplementedException();
5462 		} else static assert(0);
5463 	}
5464 
5465 	///
5466 	//version(Windows)
5467 	void show() {
5468 		version(X11) {
5469 			if(!hidden)
5470 				return;
5471 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5472 			hidden = false;
5473 		} else version(Windows) {
5474 			data.uFlags = NIF_STATE;
5475 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5476 			data.dwStateMask = NIS_HIDDEN; // windows vista
5477 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5478 		} else version(OSXCocoa) {
5479 			throw new NotYetImplementedException();
5480 		} else static assert(0);
5481 	}
5482 
5483 	version(X11)
5484 		bool hidden = false;
5485 
5486 	///
5487 	//version(Windows)
5488 	void hide() {
5489 		version(X11) {
5490 			if(hidden)
5491 				return;
5492 			hidden = true;
5493 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5494 		} else version(Windows) {
5495 			data.uFlags = NIF_STATE;
5496 			data.dwState = NIS_HIDDEN; // windows vista
5497 			data.dwStateMask = NIS_HIDDEN; // windows vista
5498 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5499 		} else version(OSXCocoa) {
5500 			throw new NotYetImplementedException();
5501 		} else static assert(0);
5502 	}
5503 
5504 	///
5505 	void close () {
5506 		version(X11) {
5507 			if (active) {
5508 				active = false; // event handler will set this too, but meh
5509 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5510 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5511 				flushGui();
5512 			}
5513 		} else version(Windows) {
5514 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5515 		} else version(OSXCocoa) {
5516 			throw new NotYetImplementedException();
5517 		} else static assert(0);
5518 	}
5519 
5520 	~this() {
5521 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5522 		version(X11)
5523 			if(clippixmap != None)
5524 				XFreePixmap(XDisplayConnection.get, clippixmap);
5525 		close();
5526 	}
5527 }
5528 
5529 version(X11)
5530 /// Call `XFreePixmap` on the return value.
5531 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5532 	char[] data = new char[](i.width * i.height / 8 + 2);
5533 	data[] = 0;
5534 
5535 	int bitOffset = 0;
5536 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5537 		ubyte v = c.a > 128 ? 1 : 0;
5538 		data[bitOffset / 8] |= v << (bitOffset%8);
5539 		bitOffset++;
5540 	}
5541 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5542 	return handle;
5543 }
5544 
5545 
5546 // basic functions to make timers
5547 /**
5548 	A timer that will trigger your function on a given interval.
5549 
5550 
5551 	You create a timer with an interval and a callback. It will continue
5552 	to fire on the interval until it is destroyed.
5553 
5554 	There are currently no one-off timers (instead, just create one and
5555 	destroy it when it is triggered) nor are there pause/resume functions -
5556 	the timer must again be destroyed and recreated if you want to pause it.
5557 
5558 	---
5559 	auto timer = new Timer(50, { it happened!; });
5560 	timer.destroy();
5561 	---
5562 
5563 	Timers can only be expected to fire when the event loop is running and only
5564 	once per iteration through the event loop.
5565 
5566 	History:
5567 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5568 		slow could lock up the event loop. It now guarantees other things will
5569 		get a chance to run between timer calls, even if that means not keeping up
5570 		with the requested interval.
5571 */
5572 version(with_timer) {
5573 class Timer {
5574 // FIXME: needs pause and unpause
5575 	// FIXME: I might add overloads for ones that take a count of
5576 	// how many elapsed since last time (on Windows, it will divide
5577 	// the ticks thing given, on Linux it is just available) and
5578 	// maybe one that takes an instance of the Timer itself too
5579 	/// Create a timer with a callback when it triggers.
5580 	this(int intervalInMilliseconds, void delegate() onPulse) {
5581 		assert(onPulse !is null);
5582 
5583 		this.intervalInMilliseconds = intervalInMilliseconds;
5584 		this.onPulse = onPulse;
5585 
5586 		version(Windows) {
5587 			/*
5588 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5589 			if(handle == 0)
5590 				throw new WindowsApiException("SetTimer", GetLastError());
5591 			*/
5592 
5593 			// thanks to Archival 998 for the WaitableTimer blocks
5594 			handle = CreateWaitableTimer(null, false, null);
5595 			long initialTime = -intervalInMilliseconds;
5596 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5597 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5598 
5599 			mapping[handle] = this;
5600 
5601 		} else version(linux) {
5602 			static import ep = core.sys.linux.epoll;
5603 
5604 			import core.sys.linux.timerfd;
5605 
5606 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5607 			if(fd == -1)
5608 				throw new Exception("timer create failed");
5609 
5610 			mapping[fd] = this;
5611 
5612 			itimerspec value = makeItimerspec(intervalInMilliseconds);
5613 
5614 			if(timerfd_settime(fd, 0, &value, null) == -1)
5615 				throw new Exception("couldn't make pulse timer");
5616 
5617 			version(with_eventloop) {
5618 				import arsd.eventloop;
5619 				addFileEventListeners(fd, &trigger, null, null);
5620 			} else {
5621 				prepareEventLoop();
5622 
5623 				ep.epoll_event ev = void;
5624 				ev.events = ep.EPOLLIN;
5625 				ev.data.fd = fd;
5626 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5627 			}
5628 		} else featureNotImplemented();
5629 	}
5630 
5631 	private int intervalInMilliseconds;
5632 
5633 	// just cuz I sometimes call it this.
5634 	alias dispose = destroy;
5635 
5636 	/// Stop and destroy the timer object.
5637 	void destroy() {
5638 		version(Windows) {
5639 			staticDestroy(handle);
5640 			handle = null;
5641 		} else version(linux) {
5642 			staticDestroy(fd);
5643 			fd = -1;
5644 		} else featureNotImplemented();
5645 	}
5646 
5647 	version(Windows)
5648 	static void staticDestroy(HANDLE handle) {
5649 		if(handle) {
5650 			// KillTimer(null, handle);
5651 			CancelWaitableTimer(cast(void*)handle);
5652 			mapping.remove(handle);
5653 			CloseHandle(handle);
5654 		}
5655 	}
5656 	else version(linux)
5657 	static void staticDestroy(int fd) {
5658 		if(fd != -1) {
5659 			import unix = core.sys.posix.unistd;
5660 			static import ep = core.sys.linux.epoll;
5661 
5662 			version(with_eventloop) {
5663 				import arsd.eventloop;
5664 				removeFileEventListeners(fd);
5665 			} else {
5666 				ep.epoll_event ev = void;
5667 				ev.events = ep.EPOLLIN;
5668 				ev.data.fd = fd;
5669 
5670 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5671 			}
5672 			unix.close(fd);
5673 			mapping.remove(fd);
5674 		}
5675 	}
5676 
5677 	~this() {
5678 		version(Windows) { if(handle)
5679 			cleanupQueue.queue!staticDestroy(handle);
5680 		} else version(linux) { if(fd != -1)
5681 			cleanupQueue.queue!staticDestroy(fd);
5682 		}
5683 	}
5684 
5685 	void changeTime(int intervalInMilliseconds)
5686 	{
5687 		this.intervalInMilliseconds = intervalInMilliseconds;
5688 		version(Windows)
5689 		{
5690 			if(handle)
5691 			{
5692 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5693 				long initialTime = -intervalInMilliseconds;
5694 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5695 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
5696 			}
5697 		} else version(linux) {
5698 			import core.sys.linux.timerfd;
5699 
5700 			itimerspec value = makeItimerspec(intervalInMilliseconds);
5701 			if(timerfd_settime(fd, 0, &value, null) == -1) {
5702 				throw new Exception("couldn't change pulse timer");
5703 			}
5704 		} else {
5705 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
5706 		}
5707 	}
5708 
5709 
5710 	private:
5711 
5712 	void delegate() onPulse;
5713 
5714 	int lastEventLoopRoundTriggered;
5715 
5716 	version(linux) {
5717 		static auto makeItimerspec(int intervalInMilliseconds) {
5718 			import core.sys.linux.timerfd;
5719 
5720 			itimerspec value;
5721 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5722 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5723 
5724 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5725 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5726 
5727 			return value;
5728 		}
5729 	}
5730 
5731 	void trigger() {
5732 		version(linux) {
5733 			import unix = core.sys.posix.unistd;
5734 			long val;
5735 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5736 		} else version(Windows) {
5737 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5738 				return; // never try to actually run faster than the event loop
5739 			lastEventLoopRoundTriggered = eventLoopRound;
5740 		} else featureNotImplemented();
5741 
5742 		onPulse();
5743 	}
5744 
5745 	version(Windows)
5746 	void rearm() {
5747 
5748 	}
5749 
5750 	version(Windows)
5751 		extern(Windows)
5752 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5753 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5754 			if(Timer* t = timer in mapping) {
5755 				try
5756 				(*t).trigger();
5757 				catch(Exception e) { sdpy_abort(e); assert(0); }
5758 			}
5759 		}
5760 
5761 	version(Windows) {
5762 		//UINT_PTR handle;
5763 		//static Timer[UINT_PTR] mapping;
5764 		HANDLE handle;
5765 		__gshared Timer[HANDLE] mapping;
5766 	} else version(linux) {
5767 		int fd = -1;
5768 		__gshared Timer[int] mapping;
5769 	} else version(OSXCocoa) {
5770 	} else static assert(0, "timer not supported");
5771 }
5772 }
5773 
5774 version(Windows)
5775 private int eventLoopRound;
5776 
5777 version(Windows)
5778 /// 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
5779 class WindowsHandleReader {
5780 	///
5781 	this(void delegate() onReady, HANDLE handle) {
5782 		this.onReady = onReady;
5783 		this.handle = handle;
5784 
5785 		mapping[handle] = this;
5786 
5787 		enable();
5788 	}
5789 
5790 	///
5791 	void enable() {
5792 		auto el = EventLoop.get().impl;
5793 		el.handles ~= handle;
5794 	}
5795 
5796 	///
5797 	void disable() {
5798 		auto el = EventLoop.get().impl;
5799 		for(int i = 0; i < el.handles.length; i++) {
5800 			if(el.handles[i] is handle) {
5801 				el.handles[i] = el.handles[$-1];
5802 				el.handles = el.handles[0 .. $-1];
5803 				return;
5804 			}
5805 		}
5806 	}
5807 
5808 	void dispose() {
5809 		disable();
5810 		if(handle)
5811 			mapping.remove(handle);
5812 		handle = null;
5813 	}
5814 
5815 	void ready() {
5816 		if(onReady)
5817 			onReady();
5818 	}
5819 
5820 	HANDLE handle;
5821 	void delegate() onReady;
5822 
5823 	__gshared WindowsHandleReader[HANDLE] mapping;
5824 }
5825 
5826 version(Posix)
5827 /// Lets you add files to the event loop for reading. Use at your own risk.
5828 class PosixFdReader {
5829 	///
5830 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5831 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5832 	}
5833 
5834 	///
5835 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5836 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5837 	}
5838 
5839 	///
5840 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5841 		this.onReady = onReady;
5842 		this.fd = fd;
5843 		this.captureWrites = captureWrites;
5844 		this.captureReads = captureReads;
5845 
5846 		mapping[fd] = this;
5847 
5848 		version(with_eventloop) {
5849 			import arsd.eventloop;
5850 			addFileEventListeners(fd, &readyel);
5851 		} else {
5852 			enable();
5853 		}
5854 	}
5855 
5856 	bool captureReads;
5857 	bool captureWrites;
5858 
5859 	version(with_eventloop) {} else
5860 	///
5861 	void enable() {
5862 		prepareEventLoop();
5863 
5864 		enabled = true;
5865 
5866 		version(linux) {
5867 			static import ep = core.sys.linux.epoll;
5868 			ep.epoll_event ev = void;
5869 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5870 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5871 			ev.data.fd = fd;
5872 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5873 		} else {
5874 
5875 		}
5876 	}
5877 
5878 	version(with_eventloop) {} else
5879 	///
5880 	void disable() {
5881 		prepareEventLoop();
5882 
5883 		enabled = false;
5884 
5885 		version(linux) {
5886 			static import ep = core.sys.linux.epoll;
5887 			ep.epoll_event ev = void;
5888 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5889 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5890 			ev.data.fd = fd;
5891 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5892 		}
5893 	}
5894 
5895 	version(with_eventloop) {} else
5896 	///
5897 	void dispose() {
5898 		if(enabled)
5899 			disable();
5900 		if(fd != -1)
5901 			mapping.remove(fd);
5902 		fd = -1;
5903 	}
5904 
5905 	void delegate(int, bool, bool) onReady;
5906 
5907 	version(with_eventloop)
5908 	void readyel() {
5909 		onReady(fd, true, true);
5910 	}
5911 
5912 	void ready(uint flags) {
5913 		version(linux) {
5914 			static import ep = core.sys.linux.epoll;
5915 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5916 		} else {
5917 			import core.sys.posix.poll;
5918 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5919 		}
5920 	}
5921 
5922 	void hup(uint flags) {
5923 		if(onHup)
5924 			onHup();
5925 	}
5926 
5927 	void delegate() onHup;
5928 
5929 	int fd = -1;
5930 	private bool enabled;
5931 	__gshared PosixFdReader[int] mapping;
5932 }
5933 
5934 // basic functions to access the clipboard
5935 /+
5936 
5937 
5938 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5939 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5940 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5941 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5942 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5943 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5944 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5945 
5946 +/
5947 
5948 /++
5949 	this does a delegate because it is actually an async call on X...
5950 	the receiver may never be called if the clipboard is empty or unavailable
5951 	gets plain text from the clipboard.
5952 +/
5953 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5954 	version(Windows) {
5955 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5956 		if(OpenClipboard(hwndOwner) == 0)
5957 			throw new WindowsApiException("OpenClipboard", GetLastError());
5958 		scope(exit)
5959 			CloseClipboard();
5960 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5961 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5962 
5963 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5964 				scope(exit)
5965 					GlobalUnlock(dataHandle);
5966 
5967 				// FIXME: CR/LF conversions
5968 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5969 				int len = 0;
5970 				auto d = data;
5971 				while(*d) {
5972 					d++;
5973 					len++;
5974 				}
5975 				string s;
5976 				s.reserve(len);
5977 				foreach(dchar ch; data[0 .. len]) {
5978 					s ~= ch;
5979 				}
5980 				receiver(s);
5981 			}
5982 		}
5983 	} else version(X11) {
5984 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5985 	} else version(OSXCocoa) {
5986 		throw new NotYetImplementedException();
5987 	} else static assert(0);
5988 }
5989 
5990 // FIXME: a clipboard listener might be cool btw
5991 
5992 /++
5993 	this does a delegate because it is actually an async call on X...
5994 	the receiver may never be called if the clipboard is empty or unavailable
5995 	gets image from the clipboard.
5996 
5997 	templated because it introduces an optional dependency on arsd.bmp
5998 +/
5999 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6000 	version(Windows) {
6001 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6002 		if(OpenClipboard(hwndOwner) == 0)
6003 			throw new WindowsApiException("OpenClipboard", GetLastError());
6004 		scope(exit)
6005 			CloseClipboard();
6006 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6007 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6008 				scope(exit)
6009 					GlobalUnlock(dataHandle);
6010 
6011 				auto len = GlobalSize(dataHandle);
6012 
6013 				import arsd.bmp;
6014 				auto img = readBmp(data[0 .. len], false);
6015 				receiver(img);
6016 			}
6017 		}
6018 	} else version(X11) {
6019 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6020 	} else version(OSXCocoa) {
6021 		throw new NotYetImplementedException();
6022 	} else static assert(0);
6023 }
6024 
6025 /// Copies some text to the clipboard.
6026 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6027 	assert(clipboardOwner !is null);
6028 	version(Windows) {
6029 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6030 			throw new WindowsApiException("OpenClipboard", GetLastError());
6031 		scope(exit)
6032 			CloseClipboard();
6033 		EmptyClipboard();
6034 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6035 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6036 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6037 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6038 			auto slice = data[0 .. sz];
6039 			scope(failure)
6040 				GlobalUnlock(handle);
6041 
6042 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6043 
6044 			GlobalUnlock(handle);
6045 			SetClipboardData(CF_UNICODETEXT, handle);
6046 		}
6047 	} else version(X11) {
6048 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6049 	} else version(OSXCocoa) {
6050 		throw new NotYetImplementedException();
6051 	} else static assert(0);
6052 }
6053 
6054 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6055 	assert(clipboardOwner !is null);
6056 	version(Windows) {
6057 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6058 			throw new WindowsApiException("OpenClipboard", GetLastError());
6059 		scope(exit)
6060 			CloseClipboard();
6061 		EmptyClipboard();
6062 
6063 
6064 		import arsd.bmp;
6065 		ubyte[] mdata;
6066 		mdata.reserve(img.width * img.height);
6067 		void sink(ubyte b) {
6068 			mdata ~= b;
6069 		}
6070 		writeBmpIndirect(img, &sink, false);
6071 
6072 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6073 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6074 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6075 			auto slice = data[0 .. mdata.length];
6076 			scope(failure)
6077 				GlobalUnlock(handle);
6078 
6079 			slice[] = mdata[];
6080 
6081 			GlobalUnlock(handle);
6082 			SetClipboardData(CF_DIB, handle);
6083 		}
6084 	} else version(X11) {
6085 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6086 			mixin X11SetSelectionHandler_Basics;
6087 			private const(ubyte)[] mdata;
6088 			private const(ubyte)[] mdata_original;
6089 			this(MemoryImage img) {
6090 				import arsd.bmp;
6091 
6092 				mdata.reserve(img.width * img.height);
6093 				void sink(ubyte b) {
6094 					mdata ~= b;
6095 				}
6096 				writeBmpIndirect(img, &sink, true);
6097 
6098 				mdata_original = mdata;
6099 			}
6100 
6101 			Atom[] availableFormats() {
6102 				auto display = XDisplayConnection.get;
6103 				return [
6104 					GetAtom!"image/bmp"(display),
6105 					GetAtom!"TARGETS"(display)
6106 				];
6107 			}
6108 
6109 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6110 				if(mdata.length < data.length) {
6111 					data[0 .. mdata.length] = mdata[];
6112 					auto ret = data[0 .. mdata.length];
6113 					mdata = mdata[$..$];
6114 					return ret;
6115 				} else {
6116 					data[] = mdata[0 .. data.length];
6117 					mdata = mdata[data.length .. $];
6118 					return data[];
6119 				}
6120 			}
6121 
6122 			void done() {
6123 				mdata = mdata_original;
6124 			}
6125 		}
6126 
6127 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6128 	} else version(OSXCocoa) {
6129 		throw new NotYetImplementedException();
6130 	} else static assert(0);
6131 }
6132 
6133 
6134 version(X11) {
6135 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6136 
6137 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6138 
6139 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6140 	/// Platform-specific for X11.
6141 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6142 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6143 		__gshared static Atom a;
6144 		if(!a) {
6145 			a = XInternAtom(display, name, !create);
6146 			// FIXME: might need to synchronize this and attach it to the actual object
6147 			interredAtoms ~= &a;
6148 		}
6149 		if(a == None)
6150 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6151 		return a;
6152 	}
6153 
6154 	/// Platform-specific for X11 - gets atom names as a string.
6155 	string getAtomName(Atom atom, Display* display) {
6156 		auto got = XGetAtomName(display, atom);
6157 		scope(exit) XFree(got);
6158 		import core.stdc.string;
6159 		string s = got[0 .. strlen(got)].idup;
6160 		return s;
6161 	}
6162 
6163 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6164 	void setPrimarySelection(SimpleWindow window, string text) {
6165 		setX11Selection!"PRIMARY"(window, text);
6166 	}
6167 
6168 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6169 	void setSecondarySelection(SimpleWindow window, string text) {
6170 		setX11Selection!"SECONDARY"(window, text);
6171 	}
6172 
6173 	interface X11SetSelectionHandler {
6174 		// should include TARGETS right now
6175 		Atom[] availableFormats();
6176 		// Return the slice of data you filled, empty slice if done.
6177 		// this is to support the incremental thing
6178 		ubyte[] getData(Atom format, return scope ubyte[] data);
6179 
6180 		void done();
6181 
6182 		void handleRequest(XEvent);
6183 
6184 		bool matchesIncr(Window, Atom);
6185 		void sendMoreIncr(XPropertyEvent*);
6186 	}
6187 
6188 	mixin template X11SetSelectionHandler_Basics() {
6189 		Window incrWindow;
6190 		Atom incrAtom;
6191 		Atom selectionAtom;
6192 		Atom formatAtom;
6193 		ubyte[] toSend;
6194 		bool matchesIncr(Window w, Atom a) {
6195 			return incrAtom && incrAtom == a && w == incrWindow;
6196 		}
6197 		void sendMoreIncr(XPropertyEvent* event) {
6198 			auto display = XDisplayConnection.get;
6199 
6200 			XChangeProperty (display,
6201 				incrWindow,
6202 				incrAtom,
6203 				formatAtom,
6204 				8 /* bits */, PropModeReplace,
6205 				toSend.ptr, cast(int) toSend.length);
6206 
6207 			if(toSend.length != 0) {
6208 				toSend = this.getData(formatAtom, toSend[]);
6209 			} else {
6210 				this.done();
6211 				incrWindow = None;
6212 				incrAtom = None;
6213 				selectionAtom = None;
6214 				formatAtom = None;
6215 				toSend = null;
6216 			}
6217 		}
6218 		void handleRequest(XEvent ev) {
6219 
6220 			auto display = XDisplayConnection.get;
6221 
6222 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6223 			XSelectionEvent selectionEvent;
6224 			selectionEvent.type = EventType.SelectionNotify;
6225 			selectionEvent.display = event.display;
6226 			selectionEvent.requestor = event.requestor;
6227 			selectionEvent.selection = event.selection;
6228 			selectionEvent.time = event.time;
6229 			selectionEvent.target = event.target;
6230 
6231 			bool supportedType() {
6232 				foreach(t; this.availableFormats())
6233 					if(t == event.target)
6234 						return true;
6235 				return false;
6236 			}
6237 
6238 			if(event.property == None) {
6239 				selectionEvent.property = event.target;
6240 
6241 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6242 				XFlush(display);
6243 			} if(event.target == GetAtom!"TARGETS"(display)) {
6244 				/* respond with the supported types */
6245 				auto tlist = this.availableFormats();
6246 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6247 				selectionEvent.property = event.property;
6248 
6249 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6250 				XFlush(display);
6251 			} else if(supportedType()) {
6252 				auto buffer = new ubyte[](1024 * 64);
6253 				auto toSend = this.getData(event.target, buffer[]);
6254 
6255 				if(toSend.length < 32 * 1024) {
6256 					// small enough to send directly...
6257 					selectionEvent.property = event.property;
6258 					XChangeProperty (display,
6259 						selectionEvent.requestor,
6260 						selectionEvent.property,
6261 						event.target,
6262 						8 /* bits */, 0 /* PropModeReplace */,
6263 						toSend.ptr, cast(int) toSend.length);
6264 
6265 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6266 					XFlush(display);
6267 				} else {
6268 					// large, let's send incrementally
6269 					arch_ulong l = toSend.length;
6270 
6271 					// if I wanted other events from this window don't want to clear that out....
6272 					XWindowAttributes xwa;
6273 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6274 
6275 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6276 
6277 					incrWindow = event.requestor;
6278 					incrAtom = event.property;
6279 					formatAtom = event.target;
6280 					selectionAtom = event.selection;
6281 					this.toSend = toSend;
6282 
6283 					selectionEvent.property = event.property;
6284 					XChangeProperty (display,
6285 						selectionEvent.requestor,
6286 						selectionEvent.property,
6287 						GetAtom!"INCR"(display),
6288 						32 /* bits */, PropModeReplace,
6289 						&l, 1);
6290 
6291 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6292 					XFlush(display);
6293 				}
6294 				//if(after)
6295 					//after();
6296 			} else {
6297 				debug(sdpy_clip) {
6298 					writeln("Unsupported data ", getAtomName(event.target, display));
6299 				}
6300 				selectionEvent.property = None; // I don't know how to handle this type...
6301 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6302 				XFlush(display);
6303 			}
6304 		}
6305 	}
6306 
6307 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6308 		mixin X11SetSelectionHandler_Basics;
6309 		private const(ubyte)[] text;
6310 		private const(ubyte)[] text_original;
6311 		this(string text) {
6312 			this.text = cast(const ubyte[]) text;
6313 			this.text_original = this.text;
6314 		}
6315 		Atom[] availableFormats() {
6316 			auto display = XDisplayConnection.get;
6317 			return [
6318 				GetAtom!"UTF8_STRING"(display),
6319 				GetAtom!"text/plain"(display),
6320 				XA_STRING,
6321 				GetAtom!"TARGETS"(display)
6322 			];
6323 		}
6324 
6325 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6326 			if(text.length < data.length) {
6327 				data[0 .. text.length] = text[];
6328 				return data[0 .. text.length];
6329 			} else {
6330 				data[] = text[0 .. data.length];
6331 				text = text[data.length .. $];
6332 				return data[];
6333 			}
6334 		}
6335 
6336 		void done() {
6337 			text = text_original;
6338 		}
6339 	}
6340 
6341 	/// 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?!)
6342 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6343 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6344 	}
6345 
6346 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6347 		assert(window !is null);
6348 
6349 		auto display = XDisplayConnection.get();
6350 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6351 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6352 		else Atom a = GetAtom!atomName(display);
6353 
6354 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6355 
6356 		window.impl.setSelectionHandlers[a] = data;
6357 	}
6358 
6359 	///
6360 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6361 		getX11Selection!"PRIMARY"(window, handler);
6362 	}
6363 
6364 	// added July 28, 2020
6365 	// undocumented as experimental tho
6366 	interface X11GetSelectionHandler {
6367 		void handleData(Atom target, in ubyte[] data);
6368 		Atom findBestFormat(Atom[] answer);
6369 
6370 		void prepareIncremental(Window, Atom);
6371 		bool matchesIncr(Window, Atom);
6372 		void handleIncrData(Atom, in ubyte[] data);
6373 	}
6374 
6375 	mixin template X11GetSelectionHandler_Basics() {
6376 		Window incrWindow;
6377 		Atom incrAtom;
6378 
6379 		void prepareIncremental(Window w, Atom a) {
6380 			incrWindow = w;
6381 			incrAtom = a;
6382 		}
6383 		bool matchesIncr(Window w, Atom a) {
6384 			return incrWindow == w && incrAtom == a;
6385 		}
6386 
6387 		Atom incrFormatAtom;
6388 		ubyte[] incrData;
6389 		void handleIncrData(Atom format, in ubyte[] data) {
6390 			incrFormatAtom = format;
6391 
6392 			if(data.length)
6393 				incrData ~= data;
6394 			else
6395 				handleData(incrFormatAtom, incrData);
6396 
6397 		}
6398 	}
6399 
6400 	///
6401 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6402 		assert(window !is null);
6403 
6404 		auto display = XDisplayConnection.get();
6405 		auto atom = GetAtom!atomName(display);
6406 
6407 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6408 			this(void delegate(in char[]) handler) {
6409 				this.handler = handler;
6410 			}
6411 
6412 			mixin X11GetSelectionHandler_Basics;
6413 
6414 			void delegate(in char[]) handler;
6415 
6416 			void handleData(Atom target, in ubyte[] data) {
6417 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6418 					handler(cast(const char[]) data);
6419 			}
6420 
6421 			Atom findBestFormat(Atom[] answer) {
6422 				Atom best = None;
6423 				foreach(option; answer) {
6424 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6425 						best = option;
6426 						break;
6427 					} else if(option == XA_STRING) {
6428 						best = option;
6429 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6430 						best = option;
6431 					}
6432 				}
6433 				return best;
6434 			}
6435 		}
6436 
6437 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6438 
6439 		auto target = GetAtom!"TARGETS"(display);
6440 
6441 		// SDD_DATA is "simpledisplay.d data"
6442 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6443 	}
6444 
6445 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6446 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6447 		assert(window !is null);
6448 
6449 		auto display = XDisplayConnection.get();
6450 		auto atom = GetAtom!atomName(display);
6451 
6452 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6453 			this(void delegate(MemoryImage) handler) {
6454 				this.handler = handler;
6455 			}
6456 
6457 			mixin X11GetSelectionHandler_Basics;
6458 
6459 			void delegate(MemoryImage) handler;
6460 
6461 			void handleData(Atom target, in ubyte[] data) {
6462 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6463 					import arsd.bmp;
6464 					handler(readBmp(data));
6465 				}
6466 			}
6467 
6468 			Atom findBestFormat(Atom[] answer) {
6469 				Atom best = None;
6470 				foreach(option; answer) {
6471 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6472 						best = option;
6473 					}
6474 				}
6475 				return best;
6476 			}
6477 
6478 		}
6479 
6480 
6481 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6482 
6483 		auto target = GetAtom!"TARGETS"(display);
6484 
6485 		// SDD_DATA is "simpledisplay.d data"
6486 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6487 	}
6488 
6489 
6490 	///
6491 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6492 		Atom actualType;
6493 		int actualFormat;
6494 		arch_ulong actualItems;
6495 		arch_ulong bytesRemaining;
6496 		void* data;
6497 
6498 		auto display = XDisplayConnection.get();
6499 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6500 			if(actualFormat == 0)
6501 				return null;
6502 			else {
6503 				int byteLength;
6504 				if(actualFormat == 32) {
6505 					// 32 means it is a C long... which is variable length
6506 					actualFormat = cast(int) arch_long.sizeof * 8;
6507 				}
6508 
6509 				// then it is just a bit count
6510 				byteLength = cast(int) (actualItems * actualFormat / 8);
6511 
6512 				auto d = new ubyte[](byteLength);
6513 				d[] = cast(ubyte[]) data[0 .. byteLength];
6514 				XFree(data);
6515 				return d;
6516 			}
6517 		}
6518 		return null;
6519 	}
6520 
6521 	/* defined in the systray spec */
6522 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6523 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6524 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6525 
6526 
6527 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6528 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6529 	public class GlobalHotkey {
6530 		KeyEvent key;
6531 		void delegate () handler;
6532 
6533 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6534 
6535 		/// Create from initialzed KeyEvent object
6536 		this (KeyEvent akey, void delegate () ahandler=null) {
6537 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6538 			key = akey;
6539 			handler = ahandler;
6540 		}
6541 
6542 		/// Create from emacs-like key name ("C-M-Y", etc.)
6543 		this (const(char)[] akey, void delegate () ahandler=null) {
6544 			key = KeyEvent.parse(akey);
6545 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6546 			handler = ahandler;
6547 		}
6548 
6549 	}
6550 
6551 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6552 		//conwriteln("failed to grab key");
6553 		GlobalHotkeyManager.ghfailed = true;
6554 		return 0;
6555 	}
6556 
6557 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6558 		Image.impl.xshmfailed = true;
6559 		return 0;
6560 	}
6561 
6562 	private __gshared int errorHappened;
6563 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6564 		import core.stdc.stdio;
6565 		char[265] buffer;
6566 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6567 		debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, evt.serial, evt.request_code, evt.minor_code, evt.resourceid);
6568 		errorHappened = true;
6569 		return 0;
6570 	}
6571 
6572 	/++
6573 		Global hotkey manager. It contains static methods to manage global hotkeys.
6574 
6575 		---
6576 		 try {
6577 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6578 		} catch (Exception e) {
6579 			conwriteln("ERROR registering hotkey!");
6580 		}
6581 		EventLoop.get.run();
6582 		---
6583 
6584 		The key strings are based on Emacs. In practical terms,
6585 		`M` means `alt` and `H` means the Windows logo key. `C`
6586 		is `ctrl`.
6587 
6588 		$(WARNING
6589 			This is X-specific right now. If you are on
6590 			Windows, try [registerHotKey] instead.
6591 
6592 			We will probably merge these into a single
6593 			interface later.
6594 		)
6595 	+/
6596 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6597 		version(X11) {
6598 			void recreateAfterDisconnect() {
6599 				throw new Exception("NOT IMPLEMENTED");
6600 			}
6601 			void discardConnectionState() {
6602 				throw new Exception("NOT IMPLEMENTED");
6603 			}
6604 		}
6605 
6606 		private static immutable uint[8] masklist = [ 0,
6607 			KeyOrButtonMask.LockMask,
6608 			KeyOrButtonMask.Mod2Mask,
6609 			KeyOrButtonMask.Mod3Mask,
6610 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6611 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6612 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6613 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6614 		];
6615 		private __gshared GlobalHotkeyManager ghmanager;
6616 		private __gshared bool ghfailed = false;
6617 
6618 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6619 			if (modmask == 0) return false;
6620 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6621 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6622 			return true;
6623 		}
6624 
6625 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6626 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6627 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6628 			return modmask;
6629 		}
6630 
6631 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6632 			uint keycode = cast(uint)ke.key;
6633 			auto dpy = XDisplayConnection.get;
6634 			return XKeysymToKeycode(dpy, keycode);
6635 		}
6636 
6637 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6638 
6639 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6640 
6641 		NativeEventHandler getNativeEventHandler () {
6642 			return delegate int (XEvent e) {
6643 				if (e.type != EventType.KeyPress) return 1;
6644 				auto kev = cast(const(XKeyEvent)*)&e;
6645 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6646 				if (auto ghkp = hash in globalHotkeyList) {
6647 					try {
6648 						ghkp.doHandle();
6649 					} catch (Exception e) {
6650 						import core.stdc.stdio : stderr, fprintf;
6651 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6652 					}
6653 				}
6654 				return 1;
6655 			};
6656 		}
6657 
6658 		private this () {
6659 			auto dpy = XDisplayConnection.get;
6660 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6661 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6662 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6663 		}
6664 
6665 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6666 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6667 		static void register (GlobalHotkey gh) {
6668 			if (gh is null) return;
6669 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6670 
6671 			auto dpy = XDisplayConnection.get;
6672 			immutable keycode = keyEvent2KeyCode(gh.key);
6673 
6674 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6675 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6676 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6677 			XSync(dpy, 0/*False*/);
6678 
6679 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6680 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6681 			ghfailed = false;
6682 			foreach (immutable uint ormask; masklist[]) {
6683 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6684 			}
6685 			XSync(dpy, 0/*False*/);
6686 			XSetErrorHandler(savedErrorHandler);
6687 
6688 			if (ghfailed) {
6689 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6690 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6691 				XSync(dpy, 0/*False*/);
6692 				XSetErrorHandler(savedErrorHandler);
6693 				throw new Exception("cannot register global hotkey");
6694 			}
6695 
6696 			globalHotkeyList[hash] = gh;
6697 		}
6698 
6699 		/// Ditto
6700 		static void register (const(char)[] akey, void delegate () ahandler) {
6701 			register(new GlobalHotkey(akey, ahandler));
6702 		}
6703 
6704 		private static void removeByHash (ulong hash) {
6705 			if (auto ghp = hash in globalHotkeyList) {
6706 				auto dpy = XDisplayConnection.get;
6707 				immutable keycode = keyEvent2KeyCode(ghp.key);
6708 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6709 				XSync(dpy, 0/*False*/);
6710 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6711 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6712 				XSync(dpy, 0/*False*/);
6713 				XSetErrorHandler(savedErrorHandler);
6714 				globalHotkeyList.remove(hash);
6715 			}
6716 		}
6717 
6718 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6719 		/// It is safe to unregister unknown or invalid hotkey.
6720 		static void unregister (GlobalHotkey gh) {
6721 			//TODO: add second AA for faster search? prolly doesn't worth it.
6722 			if (gh is null) return;
6723 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6724 				if (kv.value is gh) {
6725 					removeByHash(kv.key);
6726 					return;
6727 				}
6728 			}
6729 		}
6730 
6731 		/// Ditto.
6732 		static void unregister (const(char)[] key) {
6733 			auto kev = KeyEvent.parse(key);
6734 			immutable keycode = keyEvent2KeyCode(kev);
6735 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6736 		}
6737 	}
6738 }
6739 
6740 version(Windows) {
6741 	/++
6742 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6743 
6744 		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).
6745 	+/
6746 	void sendSyntheticInput(wstring s) {
6747 			INPUT[] inputs;
6748 			inputs.reserve(s.length * 2);
6749 
6750 			foreach(wchar c; s) {
6751 				INPUT input;
6752 				input.type = INPUT_KEYBOARD;
6753 				input.ki.wScan = c;
6754 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6755 				inputs ~= input;
6756 
6757 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6758 				inputs ~= input;
6759 			}
6760 
6761 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6762 				throw new WindowsApiException("SendInput", GetLastError());
6763 			}
6764 
6765 	}
6766 
6767 
6768 	// global hotkey helper function
6769 
6770 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6771 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6772 		__gshared int hotkeyId = 0;
6773 		int id = ++hotkeyId;
6774 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6775 			throw new Exception("RegisterHotKey");
6776 
6777 		__gshared void delegate()[WPARAM][HWND] handlers;
6778 
6779 		handlers[window.impl.hwnd][id] = handler;
6780 
6781 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6782 
6783 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6784 			switch(msg) {
6785 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6786 				case WM_HOTKEY:
6787 					if(auto list = hwnd in handlers) {
6788 						if(auto h = wParam in *list) {
6789 							(*h)();
6790 							return 0;
6791 						}
6792 					}
6793 				goto default;
6794 				default:
6795 			}
6796 			if(oldHandler)
6797 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6798 			return 1; // pass it on
6799 		};
6800 
6801 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6802 			oldHandler = window.handleNativeEvent;
6803 			window.handleNativeEvent = nativeEventHandler;
6804 		}
6805 
6806 		return id;
6807 	}
6808 
6809 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6810 	void unregisterHotKey(SimpleWindow window, int id) {
6811 		if(!UnregisterHotKey(window.impl.hwnd, id))
6812 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
6813 	}
6814 }
6815 
6816 version (X11) {
6817 	pragma(lib, "dl");
6818 	import core.sys.posix.dlfcn;
6819 }
6820 
6821 /++
6822 	Allows for sending synthetic input to the X server via the Xtst
6823 	extension or on Windows using SendInput.
6824 
6825 	Please remember user input is meant to be user - don't use this
6826 	if you have some other alternative!
6827 
6828 	History:
6829 		Added May 17, 2020 with the X implementation.
6830 
6831 		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.)
6832 	Bugs:
6833 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6834 +/
6835 struct SyntheticInput {
6836 	@disable this();
6837 
6838 	private int* refcount;
6839 
6840 	version(X11) {
6841 		private void* lib;
6842 
6843 		private extern(C) {
6844 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6845 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6846 		}
6847 	}
6848 
6849 	/// The dummy param must be 0.
6850 	this(int dummy) {
6851 		version(X11) {
6852 			lib = dlopen("libXtst.so", RTLD_NOW);
6853 			if(lib is null)
6854 				throw new Exception("cannot load xtest lib extension");
6855 			scope(failure)
6856 				dlclose(lib);
6857 
6858 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6859 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6860 
6861 			if(XTestFakeKeyEvent is null)
6862 				throw new Exception("No XTestFakeKeyEvent");
6863 			if(XTestFakeButtonEvent is null)
6864 				throw new Exception("No XTestFakeButtonEvent");
6865 		}
6866 
6867 		refcount = new int;
6868 		*refcount = 1;
6869 	}
6870 
6871 	this(this) {
6872 		if(refcount)
6873 			*refcount += 1;
6874 	}
6875 
6876 	~this() {
6877 		if(refcount) {
6878 			*refcount -= 1;
6879 			if(*refcount == 0)
6880 				// I commented this because if I close the lib before
6881 				// XCloseDisplay, it is liable to segfault... so just
6882 				// gonna keep it loaded if it is loaded, no big deal
6883 				// anyway.
6884 				{} // dlclose(lib);
6885 		}
6886 	}
6887 
6888 	/++
6889 		Simulates typing a string into the keyboard.
6890 
6891 		Bugs:
6892 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6893 
6894 			Not implemented except on Windows and X11.
6895 	+/
6896 	void sendSyntheticInput(string s) {
6897 		version(Windows) {
6898 			INPUT[] inputs;
6899 			inputs.reserve(s.length * 2);
6900 
6901 			auto ei = GetMessageExtraInfo();
6902 
6903 			foreach(wchar c; s) {
6904 				INPUT input;
6905 				input.type = INPUT_KEYBOARD;
6906 				input.ki.wScan = c;
6907 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6908 				input.ki.dwExtraInfo = ei;
6909 				inputs ~= input;
6910 
6911 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6912 				inputs ~= input;
6913 			}
6914 
6915 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6916 				throw new WindowsApiException("SendInput", GetLastError());
6917 			}
6918 		} else version(X11) {
6919 			int delay = 0;
6920 			foreach(ch; s) {
6921 				pressKey(cast(Key) ch, true, delay);
6922 				pressKey(cast(Key) ch, false, delay);
6923 				delay += 5;
6924 			}
6925 		} else throw new NotYetImplementedException();
6926 	}
6927 
6928 	/++
6929 		Sends a fake press or release key event.
6930 
6931 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6932 
6933 		Bugs:
6934 			The `delay` parameter is not implemented yet on Windows.
6935 
6936 			Not implemented except on Windows and X11.
6937 	+/
6938 	void pressKey(Key key, bool pressed, int delay = 0) {
6939 		version(Windows) {
6940 			INPUT input;
6941 			input.type = INPUT_KEYBOARD;
6942 			input.ki.wVk = cast(ushort) key;
6943 
6944 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6945 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6946 
6947 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6948 				throw new WindowsApiException("SendInput", GetLastError());
6949 			}
6950 		} else version(X11) {
6951 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6952 		} else throw new NotYetImplementedException();
6953 	}
6954 
6955 	/++
6956 		Sends a fake mouse button press or release event.
6957 
6958 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6959 
6960 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6961 
6962 		Bugs:
6963 			The `delay` parameter is not implemented yet on Windows.
6964 
6965 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6966 
6967 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6968 	+/
6969 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6970 		version(Windows) {
6971 			INPUT input;
6972 			input.type = INPUT_MOUSE;
6973 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6974 
6975 			// input.mi.mouseData for a wheel event
6976 
6977 			switch(button) {
6978 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6979 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6980 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6981 				case MouseButton.wheelUp:
6982 				case MouseButton.wheelDown:
6983 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6984 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6985 				break;
6986 				case MouseButton.backButton: throw new NotYetImplementedException();
6987 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6988 				default:
6989 			}
6990 
6991 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6992 				throw new WindowsApiException("SendInput", GetLastError());
6993 			}
6994 		} else version(X11) {
6995 			int btn;
6996 
6997 			switch(button) {
6998 				case MouseButton.left: btn = 1; break;
6999 				case MouseButton.middle: btn = 2; break;
7000 				case MouseButton.right: btn = 3; break;
7001 				case MouseButton.wheelUp: btn = 4; break;
7002 				case MouseButton.wheelDown: btn = 5; break;
7003 				case MouseButton.backButton: btn = 8; break;
7004 				case MouseButton.forwardButton: btn = 9; break;
7005 				default:
7006 			}
7007 
7008 			assert(btn);
7009 
7010 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7011 		} else throw new NotYetImplementedException();
7012 	}
7013 
7014 	///
7015 	static void moveMouseArrowBy(int dx, int dy) {
7016 		version(Windows) {
7017 			INPUT input;
7018 			input.type = INPUT_MOUSE;
7019 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7020 			input.mi.dx = dx;
7021 			input.mi.dy = dy;
7022 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7023 
7024 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7025 				throw new WindowsApiException("SendInput", GetLastError());
7026 			}
7027 		} else version(X11) {
7028 			auto disp = XDisplayConnection.get();
7029 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7030 			XFlush(disp);
7031 		} else throw new NotYetImplementedException();
7032 	}
7033 
7034 	///
7035 	static void moveMouseArrowTo(int x, int y) {
7036 		version(Windows) {
7037 			INPUT input;
7038 			input.type = INPUT_MOUSE;
7039 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7040 			input.mi.dx = x;
7041 			input.mi.dy = y;
7042 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7043 
7044 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7045 				throw new WindowsApiException("SendInput", GetLastError());
7046 			}
7047 		} else version(X11) {
7048 			auto disp = XDisplayConnection.get();
7049 			auto root = RootWindow(disp, DefaultScreen(disp));
7050 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7051 			XFlush(disp);
7052 		} else throw new NotYetImplementedException();
7053 	}
7054 }
7055 
7056 
7057 
7058 /++
7059 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7060 
7061 	See_Also:
7062 	$(LIST
7063 		*[ScreenPainter]
7064 		*[ScreenPainter.rasterOp]
7065 	)
7066 +/
7067 enum RasterOp {
7068 	normal, /// Replaces the pixel.
7069 	xor, /// Uses bitwise xor to draw.
7070 }
7071 
7072 // being phobos-free keeps the size WAY down
7073 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7074 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7075 package(arsd) const(wchar)* toWStringz(string s) {
7076 	wstring r;
7077 	foreach(dchar c; s)
7078 		r ~= c;
7079 	r ~= '\0';
7080 	return r.ptr;
7081 }
7082 private string[] split(in void[] a, char c) {
7083 		string[] ret;
7084 		size_t previous = 0;
7085 		foreach(i, char ch; cast(ubyte[]) a) {
7086 			if(ch == c) {
7087 				ret ~= cast(string) a[previous .. i];
7088 				previous = i + 1;
7089 			}
7090 		}
7091 		if(previous != a.length)
7092 			ret ~= cast(string) a[previous .. $];
7093 		return ret;
7094 	}
7095 
7096 version(without_opengl) {
7097 	enum OpenGlOptions {
7098 		no,
7099 	}
7100 } else {
7101 	/++
7102 		Determines if you want an OpenGL context created on the new window.
7103 
7104 
7105 		See more: [#topics-3d|in the 3d topic].
7106 
7107 		---
7108 		import arsd.simpledisplay;
7109 		void main() {
7110 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7111 
7112 			// Set up the matrix
7113 			window.setAsCurrentOpenGlContext(); // make this window active
7114 
7115 			// This is called on each frame, we will draw our scene
7116 			window.redrawOpenGlScene = delegate() {
7117 
7118 			};
7119 
7120 			window.eventLoop(0);
7121 		}
7122 		---
7123 	+/
7124 	enum OpenGlOptions {
7125 		no, /// No OpenGL context is created
7126 		yes, /// Yes, create an OpenGL context
7127 	}
7128 
7129 	version(X11) {
7130 		static if (!SdpyIsUsingIVGLBinds) {
7131 
7132 
7133 			struct __GLXFBConfigRec {}
7134 			alias GLXFBConfig = __GLXFBConfigRec*;
7135 
7136 			//pragma(lib, "GL");
7137 			//pragma(lib, "GLU");
7138 			interface GLX {
7139 			extern(C) nothrow @nogc {
7140 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7141 						const int *attrib_list);
7142 
7143 				 void glXCopyContext(Display *dpy, GLXContext src,
7144 						GLXContext dst, arch_ulong mask);
7145 
7146 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7147 						GLXContext share_list, Bool direct);
7148 
7149 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7150 						Pixmap pixmap);
7151 
7152 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7153 
7154 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7155 
7156 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7157 						int attrib, int *value);
7158 
7159 				 GLXContext glXGetCurrentContext();
7160 
7161 				 GLXDrawable glXGetCurrentDrawable();
7162 
7163 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7164 
7165 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7166 						GLXContext ctx);
7167 
7168 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7169 
7170 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7171 
7172 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7173 
7174 				 void glXUseXFont(Font font, int first, int count, int list_base);
7175 
7176 				 void glXWaitGL();
7177 
7178 				 void glXWaitX();
7179 
7180 
7181 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7182 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7183 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7184 
7185 				char* glXQueryExtensionsString (Display*, int);
7186 				void* glXGetProcAddress (const(char)*);
7187 
7188 			}
7189 			}
7190 
7191 			version(OSX)
7192 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7193 			else
7194 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7195 			shared static this() {
7196 				glx.loadDynamicLibrary();
7197 			}
7198 
7199 			alias glbindGetProcAddress = glXGetProcAddress;
7200 		}
7201 	} else version(Windows) {
7202 		/* it is done below by interface GL */
7203 	} else
7204 		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.");
7205 }
7206 
7207 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7208 alias Resizablity = Resizability;
7209 
7210 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7211 enum Resizability {
7212 	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.
7213 	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.
7214 	/++
7215 		$(PITFALL
7216 			Planned for the future but not implemented.
7217 		)
7218 
7219 		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.
7220 
7221 		History:
7222 			Added November 11, 2022, but not yet implemented and may not be for some time.
7223 	+/
7224 	/*@__future*/ allowResizingMaintainingAspectRatio,
7225 	/++
7226 		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.
7227 
7228 		History:
7229 			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.
7230 
7231 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7232 	+/
7233 	automaticallyScaleIfPossible,
7234 }
7235 /// ditto
7236 alias Resizeability = Resizability;
7237 
7238 
7239 /++
7240 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7241 +/
7242 enum TextAlignment : uint {
7243 	Left = 0, ///
7244 	Center = 1, ///
7245 	Right = 2, ///
7246 
7247 	VerticalTop = 0, ///
7248 	VerticalCenter = 4, ///
7249 	VerticalBottom = 8, ///
7250 }
7251 
7252 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7253 alias Rectangle = arsd.color.Rectangle;
7254 
7255 
7256 /++
7257 	Keyboard press and release events.
7258 +/
7259 struct KeyEvent {
7260 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7261 	Key key;
7262 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7263 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7264 
7265 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7266 
7267 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7268 
7269 	SimpleWindow window; /// associated Window
7270 
7271 	/++
7272 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7273 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7274 		to predict if char events are actually coming..
7275 
7276 		Only available on X systems since this information is not given ahead of time elsewhere.
7277 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7278 
7279 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7280 		and potential quirks I'd recommend avoiding it.
7281 
7282 		History:
7283 			Added April 26, 2021 (dub v9.5)
7284 	+/
7285 	version(X11)
7286 		dchar[] charsPossible;
7287 
7288 	// convert key event to simplified string representation a-la emacs
7289 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7290 		uint dpos = 0;
7291 		void put (const(char)[] s...) nothrow @trusted {
7292 			static if (growdest) {
7293 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7294 			} else {
7295 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7296 			}
7297 		}
7298 
7299 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7300 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7301 		}
7302 
7303 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7304 
7305 		// put modifiers
7306 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7307 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7308 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7309 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7310 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7311 
7312 		if (this.key) {
7313 			foreach (string kn; __traits(allMembers, Key)) {
7314 				if (this.key == __traits(getMember, Key, kn)) {
7315 					// HACK!
7316 					static if (kn == "N0") put("0");
7317 					else static if (kn == "N1") put("1");
7318 					else static if (kn == "N2") put("2");
7319 					else static if (kn == "N3") put("3");
7320 					else static if (kn == "N4") put("4");
7321 					else static if (kn == "N5") put("5");
7322 					else static if (kn == "N6") put("6");
7323 					else static if (kn == "N7") put("7");
7324 					else static if (kn == "N8") put("8");
7325 					else static if (kn == "N9") put("9");
7326 					else put(kn);
7327 					return dest[0..dpos];
7328 				}
7329 			}
7330 			put("Unknown");
7331 		} else {
7332 			if (dpos && dest[dpos-1] == '+') --dpos;
7333 		}
7334 		return dest[0..dpos];
7335 	}
7336 
7337 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7338 
7339 	/** Parse string into key name with modifiers. It accepts things like:
7340 	 *
7341 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7342 	 *
7343 	 * Ctrl+Win+1 -- windows style
7344 	 *
7345 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7346 	 *
7347 	 * Ctrl Win 1 -- and space
7348 	 *
7349 	 * and even "Win + 1 + Ctrl".
7350 	 */
7351 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7352 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7353 
7354 		// remove trailing spaces
7355 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7356 
7357 		// tokens delimited by blank, '+', or '-'
7358 		// null on eol
7359 		const(char)[] getToken () nothrow @trusted @nogc {
7360 			// remove leading spaces and delimiters
7361 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7362 			if (name.length == 0) return null; // oops, no more tokens
7363 			// get token
7364 			size_t epos = 0;
7365 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7366 			assert(epos > 0 && epos <= name.length);
7367 			auto res = name[0..epos];
7368 			name = name[epos..$];
7369 			return res;
7370 		}
7371 
7372 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7373 			if (s0.length != s1.length) return false;
7374 			foreach (immutable ci, char c0; s0) {
7375 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7376 				char c1 = s1[ci];
7377 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7378 				if (c0 != c1) return false;
7379 			}
7380 			return true;
7381 		}
7382 
7383 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7384 		if (updown !is null) *updown = -1;
7385 		KeyEvent res;
7386 		res.key = cast(Key)0; // just in case
7387 		const(char)[] tk, tkn; // last token
7388 		bool allowEmascStyle = true;
7389 		bool ignoreModifiers = false;
7390 		tokenloop: for (;;) {
7391 			tk = tkn;
7392 			tkn = getToken();
7393 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7394 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7395 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7396 			if (allowEmascStyle && tkn.length != 0) {
7397 				if (tk.length == 1) {
7398 					char mdc = tk[0];
7399 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7400 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7401 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7402 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7403 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7404 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7405 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7406 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7407 				}
7408 			}
7409 			allowEmascStyle = false;
7410 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7411 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7412 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7413 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7414 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7415 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7416 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7417 			if (tk.length == 0) continue;
7418 			// try key name
7419 			if (res.key == 0) {
7420 				// little hack
7421 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7422 					final switch (tk[0]) {
7423 						case '0': tk = "N0"; break;
7424 						case '1': tk = "N1"; break;
7425 						case '2': tk = "N2"; break;
7426 						case '3': tk = "N3"; break;
7427 						case '4': tk = "N4"; break;
7428 						case '5': tk = "N5"; break;
7429 						case '6': tk = "N6"; break;
7430 						case '7': tk = "N7"; break;
7431 						case '8': tk = "N8"; break;
7432 						case '9': tk = "N9"; break;
7433 					}
7434 				}
7435 				foreach (string kn; __traits(allMembers, Key)) {
7436 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7437 				}
7438 			}
7439 			// unknown or duplicate key name, get out of here
7440 			break;
7441 		}
7442 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7443 		return res; // something
7444 	}
7445 
7446 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7447 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7448 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7449 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7450 		}
7451 		bool ignoreMods;
7452 		int updown;
7453 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7454 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7455 		if (this.key != ke.key) {
7456 			// things like "ctrl+alt" are complicated
7457 			uint tkm = this.modifierState&modmask;
7458 			uint kkm = ke.modifierState&modmask;
7459 			Key tk = this.key;
7460 			// ke
7461 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7462 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7463 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7464 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7465 			// this
7466 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7467 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7468 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7469 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7470 			return (tk == ke.key && tkm == kkm);
7471 		}
7472 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7473 	}
7474 }
7475 
7476 /// Sets the application name.
7477 @property string ApplicationName(string name) {
7478 	return _applicationName = name;
7479 }
7480 
7481 string _applicationName;
7482 
7483 /// ditto
7484 @property string ApplicationName() {
7485 	if(_applicationName is null) {
7486 		import core.runtime;
7487 		return Runtime.args[0];
7488 	}
7489 	return _applicationName;
7490 }
7491 
7492 
7493 /// Type of a [MouseEvent].
7494 enum MouseEventType : int {
7495 	motion = 0, /// The mouse moved inside the window
7496 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7497 	buttonReleased = 2, /// A mouse button was released
7498 }
7499 
7500 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7501 /++
7502 	Listen for this on your event listeners if you are interested in mouse action.
7503 
7504 	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.
7505 
7506 	Examples:
7507 
7508 	This will draw boxes on the window with the mouse as you hold the left button.
7509 	---
7510 	import arsd.simpledisplay;
7511 
7512 	void main() {
7513 		auto window = new SimpleWindow();
7514 
7515 		window.eventLoop(0,
7516 			(MouseEvent ev) {
7517 				if(ev.modifierState & ModifierState.leftButtonDown) {
7518 					auto painter = window.draw();
7519 					painter.fillColor = Color.red;
7520 					painter.outlineColor = Color.black;
7521 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7522 				}
7523 			}
7524 		);
7525 	}
7526 	---
7527 +/
7528 struct MouseEvent {
7529 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7530 
7531 	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.
7532 	int y; /// Current Y position of the cursor when the event fired.
7533 
7534 	int dx; /// Change in X position since last report
7535 	int dy; /// Change in Y position since last report
7536 
7537 	MouseButton button; /// See [MouseButton]
7538 	int modifierState; /// See [ModifierState]
7539 
7540 	version(X11)
7541 		private Time timestamp;
7542 
7543 	/// Returns a linear representation of mouse button,
7544 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7545 	///
7546 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7547 	@property ubyte buttonLinear() const {
7548 		import core.bitop;
7549 		if(button == 0)
7550 			return 0;
7551 		return (bsf(button) + 1) & 0b1111;
7552 	}
7553 
7554 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7555 
7556 	SimpleWindow window; /// The window in which the event happened.
7557 
7558 	Point globalCoordinates() {
7559 		Point p;
7560 		if(window is null)
7561 			throw new Exception("wtf");
7562 		static if(UsingSimpledisplayX11) {
7563 			Window child;
7564 			XTranslateCoordinates(
7565 				XDisplayConnection.get,
7566 				window.impl.window,
7567 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7568 				x, y, &p.x, &p.y, &child);
7569 			return p;
7570 		} else version(Windows) {
7571 			POINT[1] points;
7572 			points[0].x = x;
7573 			points[0].y = y;
7574 			MapWindowPoints(
7575 				window.impl.hwnd,
7576 				null,
7577 				points.ptr,
7578 				points.length
7579 			);
7580 			p.x = points[0].x;
7581 			p.y = points[0].y;
7582 
7583 			return p;
7584 		} else version(OSXCocoa) {
7585 			throw new NotYetImplementedException();
7586 		} else static assert(0);
7587 	}
7588 
7589 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7590 
7591 	/**
7592 	can contain emacs-like modifier prefix
7593 	case-insensitive names:
7594 		lmbX/leftX
7595 		rmbX/rightX
7596 		mmbX/middleX
7597 		wheelX
7598 		motion (no prefix allowed)
7599 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7600 	*/
7601 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7602 		if (str.length == 0) return false; // just in case
7603 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7604 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7605 		auto anchor = str;
7606 		uint mods = 0; // uint.max == any
7607 		// interesting bits in kmod
7608 		uint kmodmask =
7609 			ModifierState.shift|
7610 			ModifierState.ctrl|
7611 			ModifierState.alt|
7612 			ModifierState.windows|
7613 			ModifierState.leftButtonDown|
7614 			ModifierState.middleButtonDown|
7615 			ModifierState.rightButtonDown|
7616 			0;
7617 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7618 		bool wasButtons = false;
7619 		while (str.length) {
7620 			if (str.ptr[0] <= ' ') {
7621 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7622 				continue;
7623 			}
7624 			// one-letter modifier?
7625 			if (str.length >= 2 && str.ptr[1] == '-') {
7626 				switch (str.ptr[0]) {
7627 					case '*': // "any" modifier (cannot be undone)
7628 						mods = mods.max;
7629 						break;
7630 					case 'C': case 'c': // emacs "ctrl"
7631 						if (mods != mods.max) mods |= ModifierState.ctrl;
7632 						break;
7633 					case 'M': case 'm': // emacs "meta"
7634 						if (mods != mods.max) mods |= ModifierState.alt;
7635 						break;
7636 					case 'S': case 's': // emacs "shift"
7637 						if (mods != mods.max) mods |= ModifierState.shift;
7638 						break;
7639 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7640 						if (mods != mods.max) mods |= ModifierState.windows;
7641 						break;
7642 					default:
7643 						return false; // unknown modifier
7644 				}
7645 				str = str[2..$];
7646 				continue;
7647 			}
7648 			// word
7649 			char[16] buf = void; // locased
7650 			auto wep = 0;
7651 			while (str.length) {
7652 				immutable char ch = str.ptr[0];
7653 				if (ch <= ' ' || ch == '-') break;
7654 				str = str[1..$];
7655 				if (wep > buf.length) return false; // too long
7656 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7657 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7658 				else return false; // invalid char
7659 			}
7660 			if (wep == 0) return false; // just in case
7661 			uint bnum;
7662 			enum UpDown { None = -1, Up, Down, Any }
7663 			auto updown = UpDown.None; // 0: up; 1: down
7664 			switch (buf[0..wep]) {
7665 				// left button
7666 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7667 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7668 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7669 				case "lmb": case "left": bnum = 0; break;
7670 				// middle button
7671 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7672 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7673 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7674 				case "mmb": case "middle": bnum = 1; break;
7675 				// right button
7676 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7677 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7678 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7679 				case "rmb": case "right": bnum = 2; break;
7680 				// wheel
7681 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7682 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7683 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7684 				case "wheel": bnum = 3; break;
7685 				// motion
7686 				case "motion": bnum = 7; break;
7687 				// unknown
7688 				default: return false;
7689 			}
7690 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7691 			// parse possible "-up" or "-down"
7692 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7693 				wep = 0;
7694 				foreach (immutable idx, immutable char ch; str[1..$]) {
7695 					if (ch <= ' ' || ch == '-') break;
7696 					assert(idx == wep); // for now; trick
7697 					if (wep > buf.length) { wep = 0; break; } // too long
7698 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7699 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7700 					else { wep = 0; break; } // invalid char
7701 				}
7702 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7703 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7704 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7705 				// remove parsed part
7706 				if (updown != UpDown.None) str = str[wep+1..$];
7707 			}
7708 			if (updown == UpDown.None) {
7709 				updown = UpDown.Down;
7710 			}
7711 			wasButtons = wasButtons || (bnum <= 2);
7712 			//assert(updown != UpDown.None);
7713 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7714 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7715 			if (lastButt != lastButt.max) {
7716 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7717 				if (mods != mods.max) {
7718 					uint butbit = 0;
7719 					final switch (lastButt&0x03) {
7720 						case 0: butbit = ModifierState.leftButtonDown; break;
7721 						case 1: butbit = ModifierState.middleButtonDown; break;
7722 						case 2: butbit = ModifierState.rightButtonDown; break;
7723 					}
7724 					     if (lastButt&Flag.Down) mods |= butbit;
7725 					else if (lastButt&Flag.Up) mods &= ~butbit;
7726 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7727 				}
7728 			}
7729 			// remember last button
7730 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7731 		}
7732 		// no button -- nothing to do
7733 		if (lastButt == lastButt.max) return false;
7734 		// done parsing, check if something's left
7735 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7736 		// remove action button from mask
7737 		if ((lastButt&0xff) < 3) {
7738 			final switch (lastButt&0x03) {
7739 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7740 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7741 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7742 			}
7743 		}
7744 		// special case: "Motion" means "ignore buttons"
7745 		if ((lastButt&0xff) == 7 && !wasButtons) {
7746 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7747 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7748 		}
7749 		uint kmod = event.modifierState&kmodmask;
7750 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7751 		// check modifier state
7752 		if (mods != mods.max) {
7753 			if (kmod != mods) return false;
7754 		}
7755 		// now check type
7756 		if ((lastButt&0xff) == 7) {
7757 			// motion
7758 			if (event.type != MouseEventType.motion) return false;
7759 		} else if ((lastButt&0xff) == 3) {
7760 			// wheel
7761 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7762 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7763 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7764 			return false;
7765 		} else {
7766 			// buttons
7767 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7768 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7769 			{
7770 				return false;
7771 			}
7772 			// button number
7773 			switch (lastButt&0x03) {
7774 				case 0: if (event.button != MouseButton.left) return false; break;
7775 				case 1: if (event.button != MouseButton.middle) return false; break;
7776 				case 2: if (event.button != MouseButton.right) return false; break;
7777 				default: return false;
7778 			}
7779 		}
7780 		return true;
7781 	}
7782 }
7783 
7784 version(arsd_mevent_strcmp_test) unittest {
7785 	MouseEvent event;
7786 	event.type = MouseEventType.buttonPressed;
7787 	event.button = MouseButton.left;
7788 	event.modifierState = ModifierState.ctrl;
7789 	assert(event == "C-LMB");
7790 	assert(event != "C-LMBUP");
7791 	assert(event != "C-LMB-UP");
7792 	assert(event != "C-S-LMB");
7793 	assert(event == "*-LMB");
7794 	assert(event != "*-LMB-UP");
7795 
7796 	event.type = MouseEventType.buttonReleased;
7797 	assert(event != "C-LMB");
7798 	assert(event == "C-LMBUP");
7799 	assert(event == "C-LMB-UP");
7800 	assert(event != "C-S-LMB");
7801 	assert(event != "*-LMB");
7802 	assert(event == "*-LMB-UP");
7803 
7804 	event.button = MouseButton.right;
7805 	event.modifierState |= ModifierState.shift;
7806 	event.type = MouseEventType.buttonPressed;
7807 	assert(event != "C-LMB");
7808 	assert(event != "C-LMBUP");
7809 	assert(event != "C-LMB-UP");
7810 	assert(event != "C-S-LMB");
7811 	assert(event != "*-LMB");
7812 	assert(event != "*-LMB-UP");
7813 
7814 	assert(event != "C-RMB");
7815 	assert(event != "C-RMBUP");
7816 	assert(event != "C-RMB-UP");
7817 	assert(event == "C-S-RMB");
7818 	assert(event == "*-RMB");
7819 	assert(event != "*-RMB-UP");
7820 }
7821 
7822 /// This gives a few more options to drawing lines and such
7823 struct Pen {
7824 	Color color; /// the foreground color
7825 	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.
7826 	Style style; /// See [Style]
7827 /+
7828 // From X.h
7829 
7830 #define LineSolid		0
7831 #define LineOnOffDash		1
7832 #define LineDoubleDash		2
7833        LineDou-        The full path of the line is drawn, but the
7834        bleDash         even dashes are filled differently from the
7835                        odd dashes (see fill-style) with CapButt
7836                        style used where even and odd dashes meet.
7837 
7838 
7839 
7840 /* capStyle */
7841 
7842 #define CapNotLast		0
7843 #define CapButt			1
7844 #define CapRound		2
7845 #define CapProjecting		3
7846 
7847 /* joinStyle */
7848 
7849 #define JoinMiter		0
7850 #define JoinRound		1
7851 #define JoinBevel		2
7852 
7853 /* fillStyle */
7854 
7855 #define FillSolid		0
7856 #define FillTiled		1
7857 #define FillStippled		2
7858 #define FillOpaqueStippled	3
7859 
7860 
7861 +/
7862 	/// Style of lines drawn
7863 	enum Style {
7864 		Solid, /// a solid line
7865 		Dashed, /// a dashed line
7866 		Dotted, /// a dotted line
7867 	}
7868 }
7869 
7870 
7871 /++
7872 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7873 
7874 
7875 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7876 
7877 	$(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.)
7878 
7879 	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.
7880 
7881 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7882 
7883 	$(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.
7884 
7885 	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!
7886 
7887 	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!)
7888 
7889 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7890 
7891 	---
7892 		auto image = new Image(256, 256);
7893 		scope(exit) destroy(image);
7894 	---
7895 
7896 	As long as you don't hold on to it outside the scope.
7897 
7898 	I might change it to be an owned pointer at some point in the future.
7899 
7900 	)
7901 
7902 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7903 	you can also often get a fair amount of speedup by getting the raw data format and
7904 	writing some custom code.
7905 
7906 	FIXME INSERT EXAMPLES HERE
7907 
7908 
7909 +/
7910 final class Image {
7911 	///
7912 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7913 		this.width = width;
7914 		this.height = height;
7915 		this.enableAlpha = enableAlpha;
7916 
7917 		impl.createImage(width, height, forcexshm, enableAlpha);
7918 	}
7919 
7920 	///
7921 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7922 		this(size.width, size.height, forcexshm, enableAlpha);
7923 	}
7924 
7925 	private bool suppressDestruction;
7926 
7927 	version(X11)
7928 	this(XImage* handle) {
7929 		this.handle = handle;
7930 		this.rawData = cast(ubyte*) handle.data;
7931 		this.width = handle.width;
7932 		this.height = handle.height;
7933 		this.enableAlpha = handle.depth == 32;
7934 		suppressDestruction = true;
7935 	}
7936 
7937 	~this() {
7938 		if(suppressDestruction) return;
7939 		impl.dispose();
7940 	}
7941 
7942 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7943 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7944 	pure const @system nothrow {
7945 		/*
7946 			To use these to draw a blue rectangle with size WxH at position X,Y...
7947 
7948 			// make certain that it will fit before we proceed
7949 			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!
7950 
7951 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7952 			// (though calculating them isn't really that expensive).
7953 			auto nextLineAdjustment = img.adjustmentForNextLine();
7954 			auto offR = img.redByteOffset();
7955 			auto offB = img.blueByteOffset();
7956 			auto offG = img.greenByteOffset();
7957 			auto bpp = img.bytesPerPixel();
7958 
7959 			auto data = img.getDataPointer();
7960 
7961 			// figure out the starting byte offset
7962 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7963 
7964 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7965 
7966 			// and now our drawing loop for the rectangle
7967 			foreach(y; 0 .. H) {
7968 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7969 				foreach(x; 0 .. W) {
7970 					// write our color
7971 					data[offR] = 0;
7972 					data[offG] = 0;
7973 					data[offB] = 255;
7974 
7975 					data += bpp; // moving to the next pixel is just an addition...
7976 				}
7977 				startOfLine += nextLineAdjustment;
7978 			}
7979 
7980 
7981 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7982 
7983 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7984 			can be made into a bitmask or something so we can write them as *uint...
7985 		*/
7986 
7987 		///
7988 		int offsetForTopLeftPixel() {
7989 			version(X11) {
7990 				return 0;
7991 			} else version(Windows) {
7992 				if(enableAlpha) {
7993 					return (width * 4) * (height - 1);
7994 				} else {
7995 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7996 				}
7997 			} else version(OSXCocoa) {
7998 				return 0 ; //throw new NotYetImplementedException();
7999 			} else static assert(0, "fill in this info for other OSes");
8000 		}
8001 
8002 		///
8003 		int offsetForPixel(int x, int y) {
8004 			version(X11) {
8005 				auto offset = (y * width + x) * 4;
8006 				return offset;
8007 			} else version(Windows) {
8008 				if(enableAlpha) {
8009 					auto itemsPerLine = width * 4;
8010 					// remember, bmps are upside down
8011 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8012 					return offset;
8013 				} else {
8014 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8015 					// remember, bmps are upside down
8016 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8017 					return offset;
8018 				}
8019 			} else version(OSXCocoa) {
8020 				return 0 ; //throw new NotYetImplementedException();
8021 			} else static assert(0, "fill in this info for other OSes");
8022 		}
8023 
8024 		///
8025 		int adjustmentForNextLine() {
8026 			version(X11) {
8027 				return width * 4;
8028 			} else version(Windows) {
8029 				// windows bmps are upside down, so the adjustment is actually negative
8030 				if(enableAlpha)
8031 					return - (cast(int) width * 4);
8032 				else
8033 					return -((cast(int) width * 3 + 3) / 4) * 4;
8034 			} else version(OSXCocoa) {
8035 				return 0 ; //throw new NotYetImplementedException();
8036 			} else static assert(0, "fill in this info for other OSes");
8037 		}
8038 
8039 		/// once you have the position of a pixel, use these to get to the proper color
8040 		int redByteOffset() {
8041 			version(X11) {
8042 				return 2;
8043 			} else version(Windows) {
8044 				return 2;
8045 			} else version(OSXCocoa) {
8046 				return 0 ; //throw new NotYetImplementedException();
8047 			} else static assert(0, "fill in this info for other OSes");
8048 		}
8049 
8050 		///
8051 		int greenByteOffset() {
8052 			version(X11) {
8053 				return 1;
8054 			} else version(Windows) {
8055 				return 1;
8056 			} else version(OSXCocoa) {
8057 				return 0 ; //throw new NotYetImplementedException();
8058 			} else static assert(0, "fill in this info for other OSes");
8059 		}
8060 
8061 		///
8062 		int blueByteOffset() {
8063 			version(X11) {
8064 				return 0;
8065 			} else version(Windows) {
8066 				return 0;
8067 			} else version(OSXCocoa) {
8068 				return 0 ; //throw new NotYetImplementedException();
8069 			} else static assert(0, "fill in this info for other OSes");
8070 		}
8071 
8072 		/// Only valid if [enableAlpha] is true
8073 		int alphaByteOffset() {
8074 			version(X11) {
8075 				return 3;
8076 			} else version(Windows) {
8077 				return 3;
8078 			} else version(OSXCocoa) {
8079 				return 3; //throw new NotYetImplementedException();
8080 			} else static assert(0, "fill in this info for other OSes");
8081 		}
8082 	}
8083 
8084 	///
8085 	final void putPixel(int x, int y, Color c) {
8086 		if(x < 0 || x >= width)
8087 			return;
8088 		if(y < 0 || y >= height)
8089 			return;
8090 
8091 		impl.setPixel(x, y, c);
8092 	}
8093 
8094 	///
8095 	final Color getPixel(int x, int y) {
8096 		if(x < 0 || x >= width)
8097 			return Color.transparent;
8098 		if(y < 0 || y >= height)
8099 			return Color.transparent;
8100 
8101 		version(OSXCocoa) throw new NotYetImplementedException(); else
8102 		return impl.getPixel(x, y);
8103 	}
8104 
8105 	///
8106 	final void opIndexAssign(Color c, int x, int y) {
8107 		putPixel(x, y, c);
8108 	}
8109 
8110 	///
8111 	TrueColorImage toTrueColorImage() {
8112 		auto tci = new TrueColorImage(width, height);
8113 		convertToRgbaBytes(tci.imageData.bytes);
8114 		return tci;
8115 	}
8116 
8117 	///
8118 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8119 		auto tci = i.getAsTrueColorImage();
8120 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8121 		static if(UsingSimpledisplayX11)
8122 			img.premultiply = premultiply;
8123 		img.setRgbaBytes(tci.imageData.bytes);
8124 		return img;
8125 	}
8126 
8127 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8128 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8129 	/// if you pass null, it will allocate a new one.
8130 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8131 		if(where is null)
8132 			where = new ubyte[this.width*this.height*4];
8133 		convertToRgbaBytes(where);
8134 		return where;
8135 	}
8136 
8137 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8138 	void setRgbaBytes(in ubyte[] from ) {
8139 		assert(from.length == this.width * this.height * 4);
8140 		setFromRgbaBytes(from);
8141 	}
8142 
8143 	// FIXME: make properly cross platform by getting rgba right
8144 
8145 	/// warning: this is not portable across platforms because the data format can change
8146 	ubyte* getDataPointer() {
8147 		return impl.rawData;
8148 	}
8149 
8150 	/// for use with getDataPointer
8151 	final int bytesPerLine() const pure @safe nothrow {
8152 		version(Windows)
8153 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8154 		else version(X11)
8155 			return 4 * width;
8156 		else version(OSXCocoa)
8157 			return 4 * width;
8158 		else static assert(0);
8159 	}
8160 
8161 	/// for use with getDataPointer
8162 	final int bytesPerPixel() const pure @safe nothrow {
8163 		version(Windows)
8164 			return enableAlpha ? 4 : 3;
8165 		else version(X11)
8166 			return 4;
8167 		else version(OSXCocoa)
8168 			return 4;
8169 		else static assert(0);
8170 	}
8171 
8172 	///
8173 	immutable int width;
8174 
8175 	///
8176 	immutable int height;
8177 
8178 	///
8179 	immutable bool enableAlpha;
8180     //private:
8181 	mixin NativeImageImplementation!() impl;
8182 }
8183 
8184 /++
8185 	A convenience function to pop up a window displaying the image.
8186 	If you pass a win, it will draw the image in it. Otherwise, it will
8187 	create a window with the size of the image and run its event loop, closing
8188 	when a key is pressed.
8189 
8190 	History:
8191 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8192 		always block until the application quit which could cause bizarre behavior
8193 		inside a more complex application. Now, the default is to block until
8194 		this window closes if it is the only event loop running, and otherwise,
8195 		not to block at all and just pop up the display window asynchronously.
8196 +/
8197 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8198 	if(win is null) {
8199 		win = new SimpleWindow(image);
8200 		{
8201 			auto p = win.draw;
8202 			p.drawImage(Point(0, 0), image);
8203 		}
8204 		win.eventLoopWithBlockingMode(
8205 			bm, 0,
8206 			(KeyEvent ev) {
8207 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8208 			} );
8209 	} else {
8210 		win.image = image;
8211 	}
8212 }
8213 
8214 enum FontWeight : int {
8215 	dontcare = 0,
8216 	thin = 100,
8217 	extralight = 200,
8218 	light = 300,
8219 	regular = 400,
8220 	medium = 500,
8221 	semibold = 600,
8222 	bold = 700,
8223 	extrabold = 800,
8224 	heavy = 900
8225 }
8226 
8227 /++
8228 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8229 
8230 	History:
8231 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8232 +/
8233 interface MeasurableFont {
8234 	/++
8235 		Returns true if it is a monospace font, meaning each of the
8236 		glyphs (at least the ascii characters) have matching width
8237 		and no kerning, so you can determine the display width of some
8238 		strings by simply multiplying the string width by [averageWidth].
8239 
8240 		(Please note that multiply doesn't $(I actually) work in general,
8241 		consider characters like tab and newline, but it does sometimes.)
8242 	+/
8243 	bool isMonospace();
8244 
8245 	/++
8246 		The average width of glyphs in the font, traditionally equal to the
8247 		width of the lowercase x. Can be used to estimate bounding boxes,
8248 		especially if the font [isMonospace].
8249 
8250 		Given in pixels.
8251 	+/
8252 	int averageWidth();
8253 	/++
8254 		The height of the bounding box of a line.
8255 	+/
8256 	int height();
8257 	/++
8258 		The maximum ascent of a glyph above the baseline.
8259 
8260 		Given in pixels.
8261 	+/
8262 	int ascent();
8263 	/++
8264 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8265 
8266 		Given in pixels.
8267 	+/
8268 	int descent();
8269 	/++
8270 		The display width of the given string, and if you provide a window, it will use it to
8271 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8272 
8273 		Given in pixels.
8274 	+/
8275 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8276 
8277 }
8278 
8279 // FIXME: i need a font cache and it needs to handle disconnects.
8280 
8281 /++
8282 	Represents a font loaded off the operating system or the X server.
8283 
8284 
8285 	While the api here is unified cross platform, the fonts are not necessarily
8286 	available, even across machines of the same platform, so be sure to always check
8287 	for null (using [isNull]) and have a fallback plan.
8288 
8289 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8290 
8291 	Worst case, a null font will automatically fall back to the default font loaded
8292 	for your system.
8293 +/
8294 class OperatingSystemFont : MeasurableFont {
8295 	// FIXME: when the X Connection is lost, these need to be invalidated!
8296 	// that means I need to store the original stuff again to reconstruct it too.
8297 
8298 	version(X11) {
8299 		XFontStruct* font;
8300 		XFontSet fontset;
8301 
8302 		version(with_xft) {
8303 			XftFont* xftFont;
8304 			bool isXft;
8305 		}
8306 	} else version(Windows) {
8307 		HFONT font;
8308 		int width_;
8309 		int height_;
8310 	} else version(OSXCocoa) {
8311 		NSFont font;
8312 	} else static assert(0);
8313 
8314 	/++
8315 		Constructs the class and immediately calls [load].
8316 	+/
8317 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8318 		load(name, size, weight, italic);
8319 	}
8320 
8321 	/++
8322 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8323 
8324 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8325 
8326 		History:
8327 			Added January 24, 2021.
8328 	+/
8329 	this() {
8330 		// this space intentionally left blank
8331 	}
8332 
8333 	/++
8334 		Constructs a copy of the given font object.
8335 
8336 		History:
8337 			Added January 7, 2023.
8338 	+/
8339 	this(OperatingSystemFont font) {
8340 		if(font is null || font.loadedInfo is LoadedInfo.init)
8341 			loadDefault();
8342 		else
8343 			load(font.loadedInfo.tupleof);
8344 	}
8345 
8346 	/++
8347 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8348 
8349 		History:
8350 			Added November 13, 2020.
8351 	+/
8352 	version(with_xft)
8353 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8354 		unload();
8355 
8356 		if(!XftLibrary.attempted) {
8357 			XftLibrary.loadDynamicLibrary();
8358 		}
8359 
8360 		if(!XftLibrary.loadSuccessful)
8361 			return false;
8362 
8363 		auto display = XDisplayConnection.get;
8364 
8365 		char[256] nameBuffer = void;
8366 		int nbp = 0;
8367 
8368 		void add(in char[] a) {
8369 			nameBuffer[nbp .. nbp + a.length] = a[];
8370 			nbp += a.length;
8371 		}
8372 		add(name);
8373 
8374 		if(size) {
8375 			add(":size=");
8376 			add(toInternal!string(size));
8377 		}
8378 		if(weight != FontWeight.dontcare) {
8379 			add(":weight=");
8380 			add(weightToString(weight));
8381 		}
8382 		if(italic)
8383 			add(":slant=100");
8384 
8385 		nameBuffer[nbp] = 0;
8386 
8387 		this.xftFont = XftFontOpenName(
8388 			display,
8389 			DefaultScreen(display),
8390 			nameBuffer.ptr
8391 		);
8392 
8393 		this.isXft = true;
8394 
8395 		if(xftFont !is null) {
8396 			isMonospace_ = stringWidth("x") == stringWidth("M");
8397 			ascent_ = xftFont.ascent;
8398 			descent_ = xftFont.descent;
8399 		}
8400 
8401 		return !isNull();
8402 	}
8403 
8404 	/++
8405 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8406 
8407 
8408 		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.
8409 
8410 		If `pattern` is null, it returns all available font families.
8411 
8412 		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.
8413 
8414 		The format of the pattern is platform-specific.
8415 
8416 		History:
8417 			Added May 1, 2021 (dub v9.5)
8418 	+/
8419 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8420 		version(Windows) {
8421 			auto hdc = GetDC(null);
8422 			scope(exit) ReleaseDC(null, hdc);
8423 			LOGFONT logfont;
8424 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8425 				auto localHandler = *(cast(typeof(handler)*) p);
8426 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8427 			}
8428 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8429 		} else version(X11) {
8430 			//import core.stdc.stdio;
8431 			bool done = false;
8432 			version(with_xft) {
8433 				if(!XftLibrary.attempted) {
8434 					XftLibrary.loadDynamicLibrary();
8435 				}
8436 
8437 				if(!XftLibrary.loadSuccessful)
8438 					goto skipXft;
8439 
8440 				if(!FontConfigLibrary.attempted)
8441 					FontConfigLibrary.loadDynamicLibrary();
8442 				if(!FontConfigLibrary.loadSuccessful)
8443 					goto skipXft;
8444 
8445 				{
8446 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8447 					if(got is null)
8448 						goto skipXft;
8449 					scope(exit) FcFontSetDestroy(got);
8450 
8451 					auto fontPatterns = got.fonts[0 .. got.nfont];
8452 					foreach(candidate; fontPatterns) {
8453 						char* where, whereStyle;
8454 
8455 						char* pmg = FcNameUnparse(candidate);
8456 
8457 						//FcPatternGetString(candidate, "family", 0, &where);
8458 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8459 						//if(where && whereStyle) {
8460 						if(pmg) {
8461 							if(!handler(pmg.sliceCString))
8462 								return;
8463 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8464 						}
8465 					}
8466 				}
8467 			}
8468 
8469 			skipXft:
8470 
8471 			if(pattern is null)
8472 				pattern = "*";
8473 
8474 			int count;
8475 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8476 			scope(exit) XFreeFontNames(coreFontsRaw);
8477 
8478 			auto coreFonts = coreFontsRaw[0 .. count];
8479 
8480 			foreach(font; coreFonts) {
8481 				char[128] tmp;
8482 				tmp[0 ..5] = "core:";
8483 				auto cf = font.sliceCString;
8484 				if(5 + cf.length > tmp.length)
8485 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8486 				tmp[5 .. 5 + cf.length] = cf;
8487 				if(!handler(tmp[0 .. 5 + cf.length]))
8488 					return;
8489 			}
8490 		}
8491 	}
8492 
8493 	/++
8494 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8495 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8496 
8497 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8498 		underlying system doesn't support returning the raw bytes.
8499 
8500 		History:
8501 			Added September 10, 2021 (dub v10.3)
8502 	+/
8503 	ubyte[] getTtfBytes() {
8504 		if(isNull)
8505 			return null;
8506 
8507 		version(Windows) {
8508 			auto dc = GetDC(null);
8509 			auto orig = SelectObject(dc, font);
8510 
8511 			scope(exit) {
8512 				SelectObject(dc, orig);
8513 				ReleaseDC(null, dc);
8514 			}
8515 
8516 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8517 			if(res == GDI_ERROR)
8518 				return null;
8519 
8520 			ubyte[] buffer = new ubyte[](res);
8521 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8522 			if(res == GDI_ERROR)
8523 				return null; // wtf really tbh
8524 
8525 			return buffer;
8526 		} else version(with_xft) {
8527 			if(isXft && xftFont) {
8528 				if(!FontConfigLibrary.attempted)
8529 					FontConfigLibrary.loadDynamicLibrary();
8530 				if(!FontConfigLibrary.loadSuccessful)
8531 					return null;
8532 
8533 				char* file;
8534 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8535 					if (file !is null && file[0]) {
8536 						import core.stdc.stdio;
8537 						auto fp = fopen(file, "rb");
8538 						if(fp is null)
8539 							return null;
8540 						scope(exit)
8541 							fclose(fp);
8542 						fseek(fp, 0, SEEK_END);
8543 						ubyte[] buffer = new ubyte[](ftell(fp));
8544 						fseek(fp, 0, SEEK_SET);
8545 
8546 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8547 						if(got != buffer.length)
8548 							return null;
8549 
8550 						return buffer;
8551 					}
8552 				}
8553 			}
8554 			return null;
8555 		} else throw new NotYetImplementedException();
8556 	}
8557 
8558 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8559 
8560 	private string weightToString(FontWeight weight) {
8561 		with(FontWeight)
8562 		final switch(weight) {
8563 			case dontcare: return "*";
8564 			case thin: return "extralight";
8565 			case extralight: return "extralight";
8566 			case light: return "light";
8567 			case regular: return "regular";
8568 			case medium: return "medium";
8569 			case semibold: return "demibold";
8570 			case bold: return "bold";
8571 			case extrabold: return "demibold";
8572 			case heavy: return "black";
8573 		}
8574 	}
8575 
8576 	/++
8577 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8578 
8579 		History:
8580 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8581 	+/
8582 	version(X11)
8583 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8584 		unload();
8585 
8586 		string xfontstr;
8587 
8588 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8589 			// this is kinda a disgusting hack but if the user sends an exact
8590 			// string I'd like to honor it...
8591 			xfontstr = name;
8592 		} else {
8593 			string weightstr = weightToString(weight);
8594 			string sizestr;
8595 			if(size == 0)
8596 				sizestr = "*";
8597 			else
8598 				sizestr = toInternal!string(size);
8599 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8600 		}
8601 
8602 		// writeln(xfontstr);
8603 
8604 		auto display = XDisplayConnection.get;
8605 
8606 		font = XLoadQueryFont(display, xfontstr.ptr);
8607 		if(font is null)
8608 			return false;
8609 
8610 		char** lol;
8611 		int lol2;
8612 		char* lol3;
8613 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8614 
8615 		prepareFontInfo();
8616 
8617 		return !isNull();
8618 	}
8619 
8620 	version(X11)
8621 	private void prepareFontInfo() {
8622 		if(font !is null) {
8623 			isMonospace_ = stringWidth("l") == stringWidth("M");
8624 			ascent_ = font.max_bounds.ascent;
8625 			descent_ = font.max_bounds.descent;
8626 		}
8627 	}
8628 
8629 	version(OSXCocoa)
8630 	private void prepareFontInfo() {
8631 		if(font !is null) {
8632 			isMonospace_ = font.isFixedPitch;
8633 			ascent_ = cast(int) font.ascender;
8634 			descent_ = cast(int) - font.descender;
8635 		}
8636 	}
8637 
8638 
8639 	/++
8640 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8641 
8642 		History:
8643 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8644 	+/
8645 	version(Windows)
8646 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8647 		unload();
8648 
8649 		WCharzBuffer buffer = WCharzBuffer(name);
8650 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8651 
8652 		prepareFontInfo(hdc);
8653 
8654 		return !isNull();
8655 	}
8656 
8657 	version(Windows)
8658 	void prepareFontInfo(HDC hdc = null) {
8659 		if(font is null)
8660 			return;
8661 
8662 		TEXTMETRIC tm;
8663 		auto dc = hdc ? hdc : GetDC(null);
8664 		auto orig = SelectObject(dc, font);
8665 		GetTextMetrics(dc, &tm);
8666 		SelectObject(dc, orig);
8667 		if(hdc is null)
8668 			ReleaseDC(null, dc);
8669 
8670 		width_ = tm.tmAveCharWidth;
8671 		height_ = tm.tmHeight;
8672 		ascent_ = tm.tmAscent;
8673 		descent_ = tm.tmDescent;
8674 		// 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.
8675 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8676 	}
8677 
8678 
8679 	/++
8680 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8681 
8682 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8683 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8684 
8685 		On Windows, it forwards directly to [loadWin32].
8686 
8687 		Params:
8688 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8689 			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.
8690 			weight = approximate boldness, results may vary.
8691 			italic = try to get a slanted version of the given font.
8692 
8693 		History:
8694 			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.
8695 	+/
8696 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8697 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
8698 		version(X11) {
8699 			version(with_xft) {
8700 				if(name.length > 5 && name[0 .. 5] == "core:") {
8701 					goto core;
8702 				}
8703 
8704 				if(loadXft(name, size, weight, italic))
8705 					return true;
8706 				// if xft fails, fallback to core to avoid breaking
8707 				// code that already depended on this.
8708 			}
8709 
8710 			core:
8711 
8712 			if(name.length > 5 && name[0 .. 5] == "core:") {
8713 				name = name[5 .. $];
8714 			}
8715 
8716 			return loadCoreX(name, size, weight, italic);
8717 		} else version(Windows) {
8718 			return loadWin32(name, size, weight, italic);
8719 		} else version(OSXCocoa) {
8720 			return loadCocoa(name, size, weight, italic);
8721 		} else static assert(0);
8722 	}
8723 
8724 	version(OSXCocoa)
8725 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
8726 		unload();
8727 
8728 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
8729 		prepareFontInfo();
8730 
8731 		return !isNull();
8732 	}
8733 
8734 	private struct LoadedInfo {
8735 		string name;
8736 		int size;
8737 		FontWeight weight;
8738 		bool italic;
8739 	}
8740 	private LoadedInfo loadedInfo;
8741 
8742 	///
8743 	void unload() {
8744 		if(isNull())
8745 			return;
8746 
8747 		version(X11) {
8748 			auto display = XDisplayConnection.display;
8749 
8750 			if(display is null)
8751 				return;
8752 
8753 			version(with_xft) {
8754 				if(isXft) {
8755 					if(xftFont)
8756 						XftFontClose(display, xftFont);
8757 					isXft = false;
8758 					xftFont = null;
8759 					return;
8760 				}
8761 			}
8762 
8763 			if(font && font !is ScreenPainterImplementation.defaultfont)
8764 				XFreeFont(display, font);
8765 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8766 				XFreeFontSet(display, fontset);
8767 
8768 			font = null;
8769 			fontset = null;
8770 		} else version(Windows) {
8771 			DeleteObject(font);
8772 			font = null;
8773 		} else version(OSXCocoa) {
8774 			font.release();
8775 			font = null;
8776 		} else static assert(0);
8777 	}
8778 
8779 	private bool isMonospace_;
8780 
8781 	/++
8782 		History:
8783 			Added January 16, 2021
8784 	+/
8785 	bool isMonospace() {
8786 		return isMonospace_;
8787 	}
8788 
8789 	/++
8790 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8791 
8792 		History:
8793 			Added March 26, 2020
8794 			Documented January 16, 2021
8795 	+/
8796 	int averageWidth() {
8797 		version(X11) {
8798 			return stringWidth("x");
8799 		} version(OSXCocoa) {
8800 			return stringWidth("x");
8801 		} else version(Windows)
8802 			return width_;
8803 		else assert(0);
8804 	}
8805 
8806 	/++
8807 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8808 
8809 		History:
8810 			Added January 16, 2021
8811 	+/
8812 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8813 	// FIXME: what about tab?
8814 		if(isNull)
8815 			return 0;
8816 
8817 		version(X11) {
8818 			version(with_xft)
8819 				if(isXft && xftFont !is null) {
8820 					//return xftFont.max_advance_width;
8821 					XGlyphInfo extents;
8822 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8823 					// writeln(extents);
8824 					return extents.xOff;
8825 				}
8826 			if(font is null)
8827 				return 0;
8828 			else if(fontset) {
8829 				XRectangle rect;
8830 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8831 
8832 				return rect.width;
8833 			} else {
8834 				return XTextWidth(font, s.ptr, cast(int) s.length);
8835 			}
8836 		} else version(Windows) {
8837 			WCharzBuffer buffer = WCharzBuffer(s);
8838 
8839 			return stringWidth(buffer.slice, window);
8840 		} else version(OSXCocoa) {
8841 			/+
8842 			int charCount = [string length];
8843 			CGGlyph glyphs[charCount];
8844 			CGRect rects[charCount];
8845 
8846 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
8847 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
8848 
8849 			int totalwidth = 0, maxheight = 0;
8850 			for (int i=0; i < charCount; i++)
8851 			{
8852 				totalwidth += rects[i].size.width;
8853 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
8854 			}
8855 
8856 			dim = CGSizeMake(totalwidth, maxheight);
8857 			+/
8858 
8859 			return 16; // FIXME
8860 		}
8861 		else assert(0);
8862 	}
8863 
8864 	version(Windows)
8865 	/// ditto
8866 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8867 		if(isNull)
8868 			return 0;
8869 		version(Windows) {
8870 			SIZE size;
8871 
8872 			prepareContext(window);
8873 			scope(exit) releaseContext();
8874 
8875 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8876 
8877 			return size.cx;
8878 		} else {
8879 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8880 			static assert(0, "not implemented yet");
8881 			//return stringWidth(s, window);
8882 		}
8883 	}
8884 
8885 	private {
8886 		int prepRefcount;
8887 
8888 		version(Windows) {
8889 			HDC dc;
8890 			HANDLE orig;
8891 			HWND hwnd;
8892 		}
8893 	}
8894 	/++
8895 		[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.
8896 
8897 		History:
8898 			Added January 23, 2021
8899 	+/
8900 	void prepareContext(SimpleWindow window = null) {
8901 		prepRefcount++;
8902 		if(prepRefcount == 1) {
8903 			version(Windows) {
8904 				hwnd = window is null ? null : window.impl.hwnd;
8905 				dc = GetDC(hwnd);
8906 				orig = SelectObject(dc, font);
8907 			}
8908 		}
8909 	}
8910 	/// ditto
8911 	void releaseContext() {
8912 		prepRefcount--;
8913 		if(prepRefcount == 0) {
8914 			version(Windows) {
8915 				SelectObject(dc, orig);
8916 				ReleaseDC(hwnd, dc);
8917 				hwnd = null;
8918 				dc = null;
8919 				orig = null;
8920 			}
8921 		}
8922 	}
8923 
8924 	/+
8925 		FIXME: I think I need advance and kerning pair
8926 
8927 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8928 	+/
8929 
8930 	/++
8931 		Returns the height of the font.
8932 
8933 		History:
8934 			Added March 26, 2020
8935 			Documented January 16, 2021
8936 	+/
8937 	int height() {
8938 		version(X11) {
8939 			version(with_xft)
8940 				if(isXft && xftFont !is null) {
8941 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8942 				}
8943 			if(font is null)
8944 				return 0;
8945 			return font.max_bounds.ascent + font.max_bounds.descent;
8946 		} else version(Windows) {
8947 			return height_;
8948 		} else version(OSXCocoa) {
8949 			if(font is null)
8950 				return 0;
8951 			return cast(int) font.capHeight;
8952 		}
8953 		else assert(0);
8954 	}
8955 
8956 	private int ascent_;
8957 	private int descent_;
8958 
8959 	/++
8960 		Max ascent above the baseline.
8961 
8962 		History:
8963 			Added January 22, 2021
8964 	+/
8965 	int ascent() {
8966 		return ascent_;
8967 	}
8968 
8969 	/++
8970 		Max descent below the baseline.
8971 
8972 		History:
8973 			Added January 22, 2021
8974 	+/
8975 	int descent() {
8976 		return descent_;
8977 	}
8978 
8979 	/++
8980 		Loads the default font used by [ScreenPainter] if none others are loaded.
8981 
8982 		Returns:
8983 			This method mutates the `this` object, but then returns `this` for
8984 			easy chaining like:
8985 
8986 			---
8987 			auto font = foo.isNull ? foo : foo.loadDefault
8988 			---
8989 
8990 		History:
8991 			Added previously, but left unimplemented until January 24, 2021.
8992 	+/
8993 	OperatingSystemFont loadDefault() {
8994 		unload();
8995 
8996 		loadedInfo = LoadedInfo.init;
8997 
8998 		version(X11) {
8999 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9000 			// but meh since sdpy does its own thing, this should be ok too
9001 
9002 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9003 			this.font = ScreenPainterImplementation.defaultfont;
9004 			this.fontset = ScreenPainterImplementation.defaultfontset;
9005 
9006 			prepareFontInfo();
9007 			return this;
9008 		} else version(Windows) {
9009 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9010 			this.font = ScreenPainterImplementation.defaultGuiFont;
9011 
9012 			prepareFontInfo();
9013 			return this;
9014 		} else version(OSXCocoa) {
9015 			this.font = NSFont.systemFontOfSize(12);
9016 
9017 			prepareFontInfo();
9018 			return this;
9019 		} else throw new NotYetImplementedException();
9020 	}
9021 
9022 	///
9023 	bool isNull() {
9024 		version(with_xft)
9025 			if(isXft)
9026 				return xftFont is null;
9027 		return font is null;
9028 	}
9029 
9030 	/* Metrics */
9031 	/+
9032 		GetABCWidth
9033 		GetKerningPairs
9034 
9035 		if I do it right, I can size it all here, and match
9036 		what happens when I draw the full string with the OS functions.
9037 
9038 		subclasses might do the same thing while getting the glyphs on images
9039 	struct GlyphInfo {
9040 		int glyph;
9041 
9042 		size_t stringIdxStart;
9043 		size_t stringIdxEnd;
9044 
9045 		Rectangle boundingBox;
9046 	}
9047 	GlyphInfo[] getCharBoxes() {
9048 		// XftTextExtentsUtf8
9049 		return null;
9050 
9051 	}
9052 	+/
9053 
9054 	~this() {
9055 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9056 		unload();
9057 	}
9058 }
9059 
9060 version(Windows)
9061 private string sliceCString(const(wchar)[] w) {
9062 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9063 }
9064 
9065 private inout(char)[] sliceCString(inout(char)* s) {
9066 	import core.stdc.string;
9067 	auto len = strlen(s);
9068 	return s[0 .. len];
9069 }
9070 
9071 version(OSXCocoa)
9072 	alias PaintingHandle = NSObject;
9073 else
9074 	alias PaintingHandle = NativeWindowHandle;
9075 
9076 /**
9077 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9078 	than constructing it directly. Then, it is reference counted so you can pass it
9079 	at around and when the last ref goes out of scope, the buffered drawing activities
9080 	are all carried out.
9081 
9082 
9083 	Most functions use the outlineColor instead of taking a color themselves.
9084 	ScreenPainter is reference counted and draws its buffer to the screen when its
9085 	final reference goes out of scope.
9086 */
9087 struct ScreenPainter {
9088 	CapableOfBeingDrawnUpon window;
9089 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9090 		this.window = window;
9091 		if(window.closed)
9092 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9093 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9094 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9095 		if(window.activeScreenPainter !is null) {
9096 			impl = window.activeScreenPainter;
9097 			if(impl.referenceCount == 0) {
9098 				impl.window = window;
9099 				impl.create(handle);
9100 			}
9101 			impl.manualInvalidations = manualInvalidations;
9102 			impl.referenceCount++;
9103 		//	writeln("refcount ++ ", impl.referenceCount);
9104 		} else {
9105 			impl = new ScreenPainterImplementation;
9106 			impl.window = window;
9107 			impl.create(handle);
9108 			impl.referenceCount = 1;
9109 			impl.manualInvalidations = manualInvalidations;
9110 			window.activeScreenPainter = impl;
9111 			// writeln("constructed");
9112 		}
9113 
9114 		copyActiveOriginals();
9115 	}
9116 
9117 	/++
9118 		EXPERIMENTAL. subject to change.
9119 
9120 		When you draw a cursor, you can draw this to notify your window of where it is,
9121 		for IME systems to use.
9122 	+/
9123 	void notifyCursorPosition(int x, int y, int width, int height) {
9124 		if(auto w = cast(SimpleWindow) window) {
9125 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9126 		}
9127 	}
9128 
9129 	/++
9130 		If you are using manual invalidations, this informs the
9131 		window system that a section needs to be redrawn.
9132 
9133 		If you didn't opt into manual invalidation, you don't
9134 		have to call this.
9135 
9136 		History:
9137 			Added December 30, 2021 (dub v10.5)
9138 	+/
9139 	void invalidateRect(Rectangle rect) {
9140 		if(impl is null) return;
9141 
9142 		// transform(rect)
9143 		rect.left += _originX;
9144 		rect.right += _originX;
9145 		rect.top += _originY;
9146 		rect.bottom += _originY;
9147 
9148 		impl.invalidateRect(rect);
9149 	}
9150 
9151 	private Pen originalPen;
9152 	private Color originalFillColor;
9153 	private arsd.color.Rectangle originalClipRectangle;
9154 	private OperatingSystemFont originalFont;
9155 	void copyActiveOriginals() {
9156 		if(impl is null) return;
9157 		originalPen = impl._activePen;
9158 		originalFillColor = impl._fillColor;
9159 		originalClipRectangle = impl._clipRectangle;
9160 		version(OSXCocoa) {} else
9161 		originalFont = impl._activeFont;
9162 	}
9163 
9164 	~this() {
9165 		if(impl is null) return;
9166 		impl.referenceCount--;
9167 		//writeln("refcount -- ", impl.referenceCount);
9168 		if(impl.referenceCount == 0) {
9169 			// writeln("destructed");
9170 			impl.dispose();
9171 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9172 			// writeln("paint finished");
9173 		} else {
9174 			// there is still an active reference, reset stuff so the
9175 			// next user doesn't get weirdness via the reference
9176 			this.rasterOp = RasterOp.normal;
9177 			pen = originalPen;
9178 			fillColor = originalFillColor;
9179 			if(originalFont)
9180 				setFont(originalFont);
9181 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9182 		}
9183 	}
9184 
9185 	this(this) {
9186 		if(impl is null) return;
9187 		impl.referenceCount++;
9188 		//writeln("refcount ++ ", impl.referenceCount);
9189 
9190 		copyActiveOriginals();
9191 	}
9192 
9193 	private int _originX;
9194 	private int _originY;
9195 	@property int originX() { return _originX; }
9196 	@property int originY() { return _originY; }
9197 	@property int originX(int a) {
9198 		_originX = a;
9199 		return _originX;
9200 	}
9201 	@property int originY(int a) {
9202 		_originY = a;
9203 		return _originY;
9204 	}
9205 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9206 	private void transform(ref Point p) {
9207 		if(impl is null) return;
9208 		p.x += _originX;
9209 		p.y += _originY;
9210 	}
9211 
9212 	// this needs to be checked BEFORE the originX/Y transformation
9213 	private bool isClipped(Point p) {
9214 		return !currentClipRectangle.contains(p);
9215 	}
9216 	private bool isClipped(Point p, int width, int height) {
9217 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9218 	}
9219 	private bool isClipped(Point p, Size s) {
9220 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9221 	}
9222 	private bool isClipped(Point p, Point p2) {
9223 		// need to ensure the end points are actually included inside, so the +1 does that
9224 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9225 	}
9226 
9227 
9228 	/++
9229 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9230 
9231 		Returns:
9232 			The old clip rectangle.
9233 
9234 		History:
9235 			Return value was `void` prior to May 10, 2021.
9236 
9237 	+/
9238 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9239 		if(impl is null) return currentClipRectangle;
9240 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9241 			return currentClipRectangle; // no need to do anything
9242 		auto old = currentClipRectangle;
9243 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9244 		transform(pt);
9245 
9246 		impl.setClipRectangle(pt.x, pt.y, width, height);
9247 
9248 		return old;
9249 	}
9250 
9251 	/// ditto
9252 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9253 		if(impl is null) return currentClipRectangle;
9254 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9255 	}
9256 
9257 	///
9258 	void setFont(OperatingSystemFont font) {
9259 		if(impl is null) return;
9260 		impl.setFont(font);
9261 	}
9262 
9263 	///
9264 	int fontHeight() {
9265 		if(impl is null) return 0;
9266 		return impl.fontHeight();
9267 	}
9268 
9269 	private Pen activePen;
9270 
9271 	///
9272 	@property void pen(Pen p) {
9273 		if(impl is null) return;
9274 		activePen = p;
9275 		impl.pen(p);
9276 	}
9277 
9278 	///
9279 	@scriptable
9280 	@property void outlineColor(Color c) {
9281 		if(impl is null) return;
9282 		if(activePen.color == c)
9283 			return;
9284 		activePen.color = c;
9285 		impl.pen(activePen);
9286 	}
9287 
9288 	///
9289 	@scriptable
9290 	@property void fillColor(Color c) {
9291 		if(impl is null) return;
9292 		impl.fillColor(c);
9293 	}
9294 
9295 	///
9296 	@property void rasterOp(RasterOp op) {
9297 		if(impl is null) return;
9298 		impl.rasterOp(op);
9299 	}
9300 
9301 
9302 	void updateDisplay() {
9303 		// FIXME this should do what the dtor does
9304 	}
9305 
9306 	/// 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)
9307 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9308 		if(impl is null) return;
9309 		if(isClipped(upperLeft, width, height)) return;
9310 		transform(upperLeft);
9311 		version(Windows) {
9312 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9313 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9314 			RECT clip = scroll;
9315 			RECT uncovered;
9316 			HRGN hrgn;
9317 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9318 				throw new WindowsApiException("ScrollDC", GetLastError());
9319 
9320 		} else version(X11) {
9321 			// FIXME: clip stuff outside this rectangle
9322 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9323 		} else version(OSXCocoa) {
9324 			throw new NotYetImplementedException();
9325 		} else static assert(0);
9326 	}
9327 
9328 	///
9329 	void clear(Color color = Color.white()) {
9330 		if(impl is null) return;
9331 		fillColor = color;
9332 		outlineColor = color;
9333 		drawRectangle(Point(0, 0), window.width, window.height);
9334 	}
9335 
9336 	/++
9337 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9338 
9339 		Params:
9340 			upperLeft = point on the window where the upper left corner of the image will be drawn
9341 			imageUpperLeft = point on the image to start the slice to draw
9342 			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.
9343 		History:
9344 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9345 	+/
9346 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9347 		if(impl is null) return;
9348 		if(isClipped(upperLeft, s.width, s.height)) return;
9349 		transform(upperLeft);
9350 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9351 	}
9352 
9353 	///
9354 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9355 		if(impl is null) return;
9356 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9357 		transform(upperLeft);
9358 		if(w == 0 || w > i.width)
9359 			w = i.width;
9360 		if(h == 0 || h > i.height)
9361 			h = i.height;
9362 		if(upperLeftOfImage.x < 0)
9363 			upperLeftOfImage.x = 0;
9364 		if(upperLeftOfImage.y < 0)
9365 			upperLeftOfImage.y = 0;
9366 
9367 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9368 	}
9369 
9370 	///
9371 	Size textSize(in char[] text) {
9372 		if(impl is null) return Size(0, 0);
9373 		return impl.textSize(text);
9374 	}
9375 
9376 	/++
9377 		Draws a string in the window with the set font (see [setFont] to change it).
9378 
9379 		Params:
9380 			upperLeft = the upper left point of the bounding box of the text
9381 			text = the string to draw
9382 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9383 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9384 	+/
9385 	@scriptable
9386 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9387 		if(impl is null) return;
9388 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9389 			if(isClipped(upperLeft, lowerRight)) return;
9390 			transform(lowerRight);
9391 		} else {
9392 			if(isClipped(upperLeft, textSize(text))) return;
9393 		}
9394 		transform(upperLeft);
9395 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9396 	}
9397 
9398 	/++
9399 		Draws text using a custom font.
9400 
9401 		This is still MAJOR work in progress.
9402 
9403 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9404 	+/
9405 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9406 		if(impl is null) return;
9407 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9408 		transform(upperLeft);
9409 		font.drawString(this, upperLeft, text);
9410 	}
9411 
9412 	version(Windows)
9413 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9414 		if(impl is null) return;
9415 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9416 		transform(upperLeft);
9417 
9418 		if(text.length && text[$-1] == '\n')
9419 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9420 
9421 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9422 	}
9423 
9424 	static struct TextDrawingContext {
9425 		Point boundingBoxUpperLeft;
9426 		Point boundingBoxLowerRight;
9427 
9428 		Point currentLocation;
9429 
9430 		Point lastDrewUpperLeft;
9431 		Point lastDrewLowerRight;
9432 
9433 		// how do i do right aligned rich text?
9434 		// i kinda want to do a pre-made drawing then right align
9435 		// draw the whole block.
9436 		//
9437 		// That's exactly the diff: inline vs block stuff.
9438 
9439 		// I need to get coordinates of an inline section out too,
9440 		// not just a bounding box, but a series of bounding boxes
9441 		// should be ok. Consider what's needed to detect a click
9442 		// on a link in the middle of a paragraph breaking a line.
9443 		//
9444 		// Generally, we should be able to get the rectangles of
9445 		// any portion we draw.
9446 		//
9447 		// It also needs to tell what text is left if it overflows
9448 		// out of the box, so we can do stuff like float images around
9449 		// it. It should not attempt to draw a letter that would be
9450 		// clipped.
9451 		//
9452 		// I might also turn off word wrap stuff.
9453 	}
9454 
9455 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9456 		if(impl is null) return;
9457 		// FIXME
9458 	}
9459 
9460 	/// Drawing an individual pixel is slow. Avoid it if possible.
9461 	void drawPixel(Point where) {
9462 		if(impl is null) return;
9463 		if(isClipped(where)) return;
9464 		transform(where);
9465 		impl.drawPixel(where.x, where.y);
9466 	}
9467 
9468 
9469 	/// Draws a pen using the current pen / outlineColor
9470 	@scriptable
9471 	void drawLine(Point starting, Point ending) {
9472 		if(impl is null) return;
9473 		if(isClipped(starting, ending)) return;
9474 		transform(starting);
9475 		transform(ending);
9476 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9477 	}
9478 
9479 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9480 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9481 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9482 	@scriptable
9483 	void drawRectangle(Point upperLeft, int width, int height) {
9484 		if(impl is null) return;
9485 		if(isClipped(upperLeft, width, height)) return;
9486 		transform(upperLeft);
9487 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9488 	}
9489 
9490 	/// ditto
9491 	void drawRectangle(Point upperLeft, Size size) {
9492 		if(impl is null) return;
9493 		if(isClipped(upperLeft, size.width, size.height)) return;
9494 		transform(upperLeft);
9495 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9496 	}
9497 
9498 	/// ditto
9499 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9500 		if(impl is null) return;
9501 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9502 		transform(upperLeft);
9503 		transform(lowerRightInclusive);
9504 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9505 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9506 	}
9507 
9508 	// overload added on May 12, 2021
9509 	/// ditto
9510 	void drawRectangle(Rectangle rect) {
9511 		drawRectangle(rect.upperLeft, rect.size);
9512 	}
9513 
9514 	/// Arguments are the points of the bounding rectangle
9515 	void drawEllipse(Point upperLeft, Point lowerRight) {
9516 		if(impl is null) return;
9517 		if(isClipped(upperLeft, lowerRight)) return;
9518 		transform(upperLeft);
9519 		transform(lowerRight);
9520 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9521 	}
9522 
9523 	/++
9524 		start and finish are units of degrees * 64
9525 
9526 		History:
9527 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9528 
9529 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9530 	+/
9531 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9532 		if(impl is null) return;
9533 		// FIXME: not actually implemented
9534 		if(isClipped(upperLeft, width, height)) return;
9535 		transform(upperLeft);
9536 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9537 	}
9538 
9539 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9540 	void drawCircle(Point upperLeft, int diameter) {
9541 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9542 	}
9543 
9544 	/// .
9545 	void drawPolygon(Point[] vertexes) {
9546 		if(impl is null) return;
9547 		assert(vertexes.length);
9548 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9549 		foreach(ref vertex; vertexes) {
9550 			if(vertex.x < minX)
9551 				minX = vertex.x;
9552 			if(vertex.y < minY)
9553 				minY = vertex.y;
9554 			if(vertex.x > maxX)
9555 				maxX = vertex.x;
9556 			if(vertex.y > maxY)
9557 				maxY = vertex.y;
9558 			transform(vertex);
9559 		}
9560 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9561 		impl.drawPolygon(vertexes);
9562 	}
9563 
9564 	/// ditto
9565 	void drawPolygon(Point[] vertexes...) {
9566 		if(impl is null) return;
9567 		drawPolygon(vertexes);
9568 	}
9569 
9570 
9571 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9572 
9573 	//mixin NativeScreenPainterImplementation!() impl;
9574 
9575 
9576 	// HACK: if I mixin the impl directly, it won't let me override the copy
9577 	// constructor! The linker complains about there being multiple definitions.
9578 	// I'll make the best of it and reference count it though.
9579 	ScreenPainterImplementation* impl;
9580 }
9581 
9582 	// HACK: I need a pointer to the implementation so it's separate
9583 	struct ScreenPainterImplementation {
9584 		CapableOfBeingDrawnUpon window;
9585 		int referenceCount;
9586 		mixin NativeScreenPainterImplementation!();
9587 	}
9588 
9589 // FIXME: i haven't actually tested the sprite class on MS Windows
9590 
9591 /**
9592 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9593 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9594 
9595 
9596 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9597 	though I'm not sure that's ideal and the implementation might change.
9598 
9599 	You create one by giving a window and an image. It optimizes for that window,
9600 	and copies the image into it to use as the initial picture. Creating a sprite
9601 	can be quite slow (especially over a network connection) so you should do it
9602 	as little as possible and just hold on to your sprite handles after making them.
9603 	simpledisplay does try to do its best though, using the XSHM extension if available,
9604 	but you should still write your code as if it will always be slow.
9605 
9606 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9607 	a fast operation - much faster than drawing the Image itself every time.
9608 
9609 	`Sprite` represents a scarce resource which should be freed when you
9610 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9611 	after it has been disposed. If you are unsure about this, don't take chances,
9612 	just let the garbage collector do it for you. But ideally, you can manage its
9613 	lifetime more efficiently.
9614 
9615 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9616 	support alpha blending in its drawing at this time. That might change in the
9617 	future, but if you need alpha blending right now, use OpenGL instead. See
9618 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9619 
9620 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9621 	in by setting the enableAlpha = true in the constructor.
9622 */
9623 class Sprite : CapableOfBeingDrawnUpon {
9624 
9625 	///
9626 	ScreenPainter draw() {
9627 		return ScreenPainter(this, handle, false);
9628 	}
9629 
9630 	/++
9631 		Copies the sprite's current state into a [TrueColorImage].
9632 
9633 		Be warned: this can be a very slow operation
9634 
9635 		History:
9636 			Actually implemented on March 14, 2021
9637 	+/
9638 	TrueColorImage takeScreenshot() {
9639 		return trueColorImageFromNativeHandle(handle, width, height);
9640 	}
9641 
9642 	void delegate() paintingFinishedDg() { return null; }
9643 	bool closed() { return false; }
9644 	ScreenPainterImplementation* activeScreenPainter_;
9645 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9646 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9647 
9648 	version(Windows)
9649 		private ubyte* rawData;
9650 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9651 	// ditto on the XPicture stuff
9652 
9653 	version(X11) {
9654 		private static XRenderPictFormat* RGB24;
9655 		private static XRenderPictFormat* ARGB32;
9656 
9657 		private Picture xrenderPicture;
9658 	}
9659 
9660 	version(X11)
9661 	private static void requireXRender() {
9662 		if(!XRenderLibrary.loadAttempted) {
9663 			XRenderLibrary.loadDynamicLibrary();
9664 		}
9665 
9666 		if(!XRenderLibrary.loadSuccessful)
9667 			throw new Exception("XRender library load failure");
9668 
9669 		auto display = XDisplayConnection.get;
9670 
9671 		// FIXME: if we migrate X displays, these need to be changed
9672 		if(RGB24 is null)
9673 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9674 		if(ARGB32 is null)
9675 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9676 	}
9677 
9678 	protected this() {}
9679 
9680 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9681 		this._width = width;
9682 		this._height = height;
9683 		this.enableAlpha = enableAlpha;
9684 
9685 		version(X11) {
9686 			auto display = XDisplayConnection.get();
9687 
9688 			if(enableAlpha) {
9689 				requireXRender();
9690 			}
9691 
9692 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9693 
9694 			if(enableAlpha) {
9695 				XRenderPictureAttributes attrs;
9696 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9697 			}
9698 		} else version(Windows) {
9699 			version(CRuntime_DigitalMars) {
9700 				//if(enableAlpha)
9701 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9702 			}
9703 
9704 			BITMAPINFO infoheader;
9705 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9706 			infoheader.bmiHeader.biWidth = width;
9707 			infoheader.bmiHeader.biHeight = height;
9708 			infoheader.bmiHeader.biPlanes = 1;
9709 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9710 			infoheader.bmiHeader.biCompression = BI_RGB;
9711 
9712 			// FIXME: this should prolly be a device dependent bitmap...
9713 			handle = CreateDIBSection(
9714 				null,
9715 				&infoheader,
9716 				DIB_RGB_COLORS,
9717 				cast(void**) &rawData,
9718 				null,
9719 				0);
9720 
9721 			if(handle is null)
9722 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
9723 		}
9724 	}
9725 
9726 	/// Makes a sprite based on the image with the initial contents from the Image
9727 	this(SimpleWindow win, Image i) {
9728 		this(win, i.width, i.height, i.enableAlpha);
9729 
9730 		version(X11) {
9731 			auto display = XDisplayConnection.get();
9732 			auto gc = XCreateGC(display, this.handle, 0, null);
9733 			scope(exit) XFreeGC(display, gc);
9734 			if(i.usingXshm)
9735 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9736 			else
9737 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9738 		} else version(Windows) {
9739 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9740 			auto arrLength = itemsPerLine * height;
9741 			rawData[0..arrLength] = i.rawData[0..arrLength];
9742 		} else version(OSXCocoa) {
9743 			// FIXME: I have no idea if this is even any good
9744 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9745 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
9746 				colorSpace,
9747 				kCGImageAlphaPremultipliedLast
9748 				|kCGBitmapByteOrder32Big);
9749 			CGColorSpaceRelease(colorSpace);
9750 			auto rawData = CGBitmapContextGetData(handle);
9751 
9752 			auto rdl = (width * height * 4);
9753 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9754 		} else static assert(0);
9755 	}
9756 
9757 	/++
9758 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9759 
9760 		Params:
9761 			where = point on the window where the upper left corner of the image will be drawn
9762 			imageUpperLeft = point on the image to start the slice to draw
9763 			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.
9764 		History:
9765 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9766 	+/
9767 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9768 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9769 	}
9770 
9771 	/// Call this when you're ready to get rid of it
9772 	void dispose() {
9773 		version(X11) {
9774 			staticDispose(xrenderPicture, handle);
9775 			xrenderPicture = None;
9776 			handle = None;
9777 		} else version(Windows) {
9778 			staticDispose(handle);
9779 			handle = null;
9780 		} else version(OSXCocoa) {
9781 			staticDispose(handle);
9782 			handle = null;
9783 		} else static assert(0);
9784 
9785 	}
9786 
9787 	version(X11)
9788 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9789 		if(xrenderPicture)
9790 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9791 		if(handle)
9792 			XFreePixmap(XDisplayConnection.get(), handle);
9793 	}
9794 	else version(Windows)
9795 	static void staticDispose(HBITMAP handle) {
9796 		if(handle)
9797 			DeleteObject(handle);
9798 	}
9799 	else version(OSXCocoa)
9800 	static void staticDispose(CGContextRef context) {
9801 		if(context)
9802 			CGContextRelease(context);
9803 	}
9804 
9805 	~this() {
9806 		version(X11) { if(xrenderPicture || handle)
9807 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9808 		} else version(Windows) { if(handle)
9809 			cleanupQueue.queue!staticDispose(handle);
9810 		} else version(OSXCocoa) { if(handle)
9811 			cleanupQueue.queue!staticDispose(handle);
9812 		} else static assert(0);
9813 	}
9814 
9815 	///
9816 	final @property int width() { return _width; }
9817 
9818 	///
9819 	final @property int height() { return _height; }
9820 
9821 	///
9822 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9823 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9824 	}
9825 
9826 	auto nativeHandle() {
9827 		return handle;
9828 	}
9829 
9830 	private:
9831 
9832 	int _width;
9833 	int _height;
9834 	bool enableAlpha;
9835 	version(X11)
9836 		Pixmap handle;
9837 	else version(Windows)
9838 		HBITMAP handle;
9839 	else version(OSXCocoa)
9840 		CGContextRef handle;
9841 	else static assert(0);
9842 }
9843 
9844 /++
9845 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9846 
9847 	History:
9848 		Added November 20, 2021 (dub v10.4)
9849 +/
9850 version(OSXCocoa) {} else // NotYetImplementedException
9851 abstract class Gradient : Sprite {
9852 	protected this(int w, int h) {
9853 		version(X11) {
9854 			Sprite.requireXRender();
9855 
9856 			super();
9857 			enableAlpha = true;
9858 			_width = w;
9859 			_height = h;
9860 		} else version(Windows) {
9861 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9862 		}
9863 	}
9864 
9865 	version(Windows)
9866 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9867 		auto ptr = rawData;
9868 		foreach(j; 0 .. _height)
9869 		foreach(i; 0 .. _width) {
9870 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9871 			*rawData = (color.a * color.b) / 255; rawData++;
9872 			*rawData = (color.a * color.g) / 255; rawData++;
9873 			*rawData = (color.a * color.r) / 255; rawData++;
9874 			*rawData = color.a; rawData++;
9875 		}
9876 	}
9877 
9878 	version(X11)
9879 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9880 		assert(stops.length > 0);
9881 		assert(stops.length <= 16, "I got lazy with buffers");
9882 
9883 		XFixed[16] stopsPositions = void;
9884 		XRenderColor[16] colors = void;
9885 
9886 		foreach(idx, stop; stops) {
9887 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9888 			auto c = stop.c;
9889 			colors[idx] = XRenderColor(
9890 				cast(ushort)(c.r * ushort.max / 255),
9891 				cast(ushort)(c.g * ushort.max / 255),
9892 				cast(ushort)(c.b * ushort.max / 255),
9893 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9894 			);
9895 		}
9896 
9897 		xrenderPicture = dg(stopsPositions, colors);
9898 	}
9899 
9900 	///
9901 	static struct Stop {
9902 		float percentage; /// between 0 and 1.0
9903 		Color c;
9904 	}
9905 }
9906 
9907 /++
9908 	Creates a linear gradient between p1 and p2.
9909 
9910 	X ONLY RIGHT NOW
9911 
9912 	History:
9913 		Added November 20, 2021 (dub v10.4)
9914 
9915 	Bugs:
9916 		Not yet implemented on Windows.
9917 +/
9918 version(OSXCocoa) {} else // NotYetImplementedException
9919 class LinearGradient : Gradient {
9920 	/++
9921 
9922 	+/
9923 	this(Point p1, Point p2, Stop[] stops...) {
9924 		super(p2.x, p2.y);
9925 
9926 		version(X11) {
9927 			XLinearGradient gradient;
9928 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9929 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9930 
9931 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9932 				return XRenderCreateLinearGradient(
9933 					XDisplayConnection.get,
9934 					&gradient,
9935 					stopsPositions.ptr,
9936 					colors.ptr,
9937 					cast(int) stops.length);
9938 			});
9939 		} else version(Windows) {
9940 			// FIXME
9941 			forEachPixel((int x, int y) {
9942 				import core.stdc.math;
9943 
9944 				//sqrtf(
9945 
9946 				return Color.transparent;
9947 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9948 			});
9949 		}
9950 	}
9951 }
9952 
9953 /++
9954 	A conical gradient goes from color to color around a circumference from a center point.
9955 
9956 	X ONLY RIGHT NOW
9957 
9958 	History:
9959 		Added November 20, 2021 (dub v10.4)
9960 
9961 	Bugs:
9962 		Not yet implemented on Windows.
9963 +/
9964 version(OSXCocoa) {} else // NotYetImplementedException
9965 class ConicalGradient : Gradient {
9966 	/++
9967 
9968 	+/
9969 	this(Point center, float angleInDegrees, Stop[] stops...) {
9970 		super(center.x * 2, center.y * 2);
9971 
9972 		version(X11) {
9973 			XConicalGradient gradient;
9974 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9975 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9976 
9977 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9978 				return XRenderCreateConicalGradient(
9979 					XDisplayConnection.get,
9980 					&gradient,
9981 					stopsPositions.ptr,
9982 					colors.ptr,
9983 					cast(int) stops.length);
9984 			});
9985 		} else version(Windows) {
9986 			// FIXME
9987 			forEachPixel((int x, int y) {
9988 				import core.stdc.math;
9989 
9990 				//sqrtf(
9991 
9992 				return Color.transparent;
9993 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9994 			});
9995 
9996 		}
9997 	}
9998 }
9999 
10000 /++
10001 	A radial gradient goes from color to color based on distance from the center.
10002 	It is like rings of color.
10003 
10004 	X ONLY RIGHT NOW
10005 
10006 
10007 	More specifically, you create two circles: an inner circle and an outer circle.
10008 	The gradient is only drawn in the area outside the inner circle but inside the outer
10009 	circle. The closest line between those two circles forms the line for the gradient
10010 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10011 
10012 	History:
10013 		Added November 20, 2021 (dub v10.4)
10014 
10015 	Bugs:
10016 		Not yet implemented on Windows.
10017 +/
10018 version(OSXCocoa) {} else // NotYetImplementedException
10019 class RadialGradient : Gradient {
10020 	/++
10021 
10022 	+/
10023 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10024 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10025 
10026 		version(X11) {
10027 			XRadialGradient gradient;
10028 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10029 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10030 
10031 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10032 				return XRenderCreateRadialGradient(
10033 					XDisplayConnection.get,
10034 					&gradient,
10035 					stopsPositions.ptr,
10036 					colors.ptr,
10037 					cast(int) stops.length);
10038 			});
10039 		} else version(Windows) {
10040 			// FIXME
10041 			forEachPixel((int x, int y) {
10042 				import core.stdc.math;
10043 
10044 				//sqrtf(
10045 
10046 				return Color.transparent;
10047 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10048 			});
10049 		}
10050 	}
10051 }
10052 
10053 
10054 
10055 /+
10056 	NOT IMPLEMENTED
10057 
10058 	A display-stored image optimized for relatively quick drawing, like
10059 	[Sprite], but this one supports alpha channel blending and does NOT
10060 	support direct drawing upon it with a [ScreenPainter].
10061 
10062 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10063 	plain [ScreenPainter]... sort of.
10064 
10065 	On X11, it requires the Xrender extension and library. This is available
10066 	almost everywhere though.
10067 
10068 	History:
10069 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10070 +/
10071 version(none)
10072 class AlphaSprite {
10073 	/++
10074 		Copies the given image into it.
10075 	+/
10076 	this(MemoryImage img) {
10077 
10078 		if(!XRenderLibrary.loadAttempted) {
10079 			XRenderLibrary.loadDynamicLibrary();
10080 
10081 			// FIXME: this needs to be reconstructed when the X server changes
10082 			repopulateX();
10083 		}
10084 		if(!XRenderLibrary.loadSuccessful)
10085 			throw new Exception("XRender library load failure");
10086 
10087 		// I probably need to put the alpha mask in a separate Picture
10088 		// ugh
10089 		// maybe the Sprite itself can have an alpha bitmask anyway
10090 
10091 
10092 		auto display = XDisplayConnection.get();
10093 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10094 
10095 
10096 		XRenderPictureAttributes attrs;
10097 
10098 		handle = XRenderCreatePicture(
10099 			XDisplayConnection.get,
10100 			pixmap,
10101 			RGBA,
10102 			0,
10103 			&attrs
10104 		);
10105 
10106 	}
10107 
10108 	// maybe i'll use the create gradient functions too with static factories..
10109 
10110 	void drawAt(ScreenPainter painter, Point where) {
10111 		//painter.drawPixmap(this, where);
10112 
10113 		XRenderPictureAttributes attrs;
10114 
10115 		auto pic = XRenderCreatePicture(
10116 			XDisplayConnection.get,
10117 			painter.impl.d,
10118 			RGB,
10119 			0,
10120 			&attrs
10121 		);
10122 
10123 		XRenderComposite(
10124 			XDisplayConnection.get,
10125 			3, // PictOpOver
10126 			handle,
10127 			None,
10128 			pic,
10129 			0, // src
10130 			0,
10131 			0, // mask
10132 			0,
10133 			10, // dest
10134 			10,
10135 			100, // width
10136 			100
10137 		);
10138 
10139 		/+
10140 		XRenderFreePicture(
10141 			XDisplayConnection.get,
10142 			pic
10143 		);
10144 
10145 		XRenderFreePicture(
10146 			XDisplayConnection.get,
10147 			fill
10148 		);
10149 		+/
10150 		// on Windows you can stretch but Xrender still can't :(
10151 	}
10152 
10153 	static XRenderPictFormat* RGB;
10154 	static XRenderPictFormat* RGBA;
10155 	static void repopulateX() {
10156 		auto display = XDisplayConnection.get;
10157 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10158 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10159 	}
10160 
10161 	XPixmap pixmap;
10162 	Picture handle;
10163 }
10164 
10165 ///
10166 interface CapableOfBeingDrawnUpon {
10167 	///
10168 	ScreenPainter draw();
10169 	///
10170 	int width();
10171 	///
10172 	int height();
10173 	protected ScreenPainterImplementation* activeScreenPainter();
10174 	protected void activeScreenPainter(ScreenPainterImplementation*);
10175 	bool closed();
10176 
10177 	void delegate() paintingFinishedDg();
10178 
10179 	/// Be warned: this can be a very slow operation
10180 	TrueColorImage takeScreenshot();
10181 }
10182 
10183 /// 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].
10184 void flushGui() {
10185 	version(X11) {
10186 		auto dpy = XDisplayConnection.get();
10187 		XLockDisplay(dpy);
10188 		scope(exit) XUnlockDisplay(dpy);
10189 		XFlush(dpy);
10190 	}
10191 }
10192 
10193 /++
10194 	Runs the given code in the GUI thread when its event loop
10195 	is available, blocking until it completes. This allows you
10196 	to create and manipulate windows from another thread without
10197 	invoking undefined behavior.
10198 
10199 	If this is the gui thread, it runs the code immediately.
10200 
10201 	If no gui thread exists yet, the current thread is assumed
10202 	to be it. Attempting to create windows or run the event loop
10203 	in any other thread will cause an assertion failure.
10204 
10205 
10206 	$(TIP
10207 		Did you know you can use UFCS on delegate literals?
10208 
10209 		() {
10210 			// code here
10211 		}.runInGuiThread;
10212 	)
10213 
10214 	Returns:
10215 		`true` if the function was called, `false` if it was not.
10216 		The function may not be called because the gui thread had
10217 		already terminated by the time you called this.
10218 
10219 	History:
10220 		Added April 10, 2020 (v7.2.0)
10221 
10222 		Return value added and implementation tweaked to avoid locking
10223 		at program termination on February 24, 2021 (v9.2.1).
10224 +/
10225 bool runInGuiThread(scope void delegate() dg) @trusted {
10226 	claimGuiThread();
10227 
10228 	if(thisIsGuiThread) {
10229 		dg();
10230 		return true;
10231 	}
10232 
10233 	if(guiThreadTerminating)
10234 		return false;
10235 
10236 	import core.sync.semaphore;
10237 	static Semaphore sc;
10238 	if(sc is null)
10239 		sc = new Semaphore();
10240 
10241 	static RunQueueMember* rqm;
10242 	if(rqm is null)
10243 		rqm = new RunQueueMember;
10244 	rqm.dg = cast(typeof(rqm.dg)) dg;
10245 	rqm.signal = sc;
10246 	rqm.thrown = null;
10247 
10248 	synchronized(runInGuiThreadLock) {
10249 		runInGuiThreadQueue ~= rqm;
10250 	}
10251 
10252 	if(!SimpleWindow.eventWakeUp())
10253 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10254 
10255 	rqm.signal.wait();
10256 	auto t = rqm.thrown;
10257 
10258 	if(t)
10259 		throw t;
10260 
10261 	return true;
10262 }
10263 
10264 // note it runs sync if this is the gui thread....
10265 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10266 	claimGuiThread();
10267 
10268 	try {
10269 
10270 		if(thisIsGuiThread) {
10271 			dg();
10272 			return;
10273 		}
10274 
10275 		if(guiThreadTerminating)
10276 			return;
10277 
10278 		RunQueueMember* rqm = new RunQueueMember;
10279 		rqm.dg = cast(typeof(rqm.dg)) dg;
10280 		rqm.signal = null;
10281 		rqm.thrown = null;
10282 
10283 		synchronized(runInGuiThreadLock) {
10284 			runInGuiThreadQueue ~= rqm;
10285 		}
10286 
10287 		if(!SimpleWindow.eventWakeUp())
10288 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10289 	} catch(Exception e) {
10290 		if(handleError)
10291 			handleError(e);
10292 	}
10293 }
10294 
10295 private void runPendingRunInGuiThreadDelegates() {
10296 	more:
10297 	RunQueueMember* next;
10298 	synchronized(runInGuiThreadLock) {
10299 		if(runInGuiThreadQueue.length) {
10300 			next = runInGuiThreadQueue[0];
10301 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10302 		} else {
10303 			next = null;
10304 		}
10305 	}
10306 
10307 	if(next) {
10308 		try {
10309 			next.dg();
10310 			next.thrown = null;
10311 		} catch(Throwable t) {
10312 			next.thrown = t;
10313 		}
10314 
10315 		if(next.signal)
10316 			next.signal.notify();
10317 
10318 		goto more;
10319 	}
10320 }
10321 
10322 private void claimGuiThread() nothrow {
10323 	import core.atomic;
10324 	if(cas(&guiThreadExists_, false, true))
10325 		thisIsGuiThread = true;
10326 }
10327 
10328 private struct RunQueueMember {
10329 	void delegate() dg;
10330 	import core.sync.semaphore;
10331 	Semaphore signal;
10332 	Throwable thrown;
10333 }
10334 
10335 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10336 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10337 private bool thisIsGuiThread = false;
10338 private shared bool guiThreadExists_ = false;
10339 private shared bool guiThreadTerminating = false;
10340 
10341 /++
10342 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10343 	event loop. All windows must be exclusively created and managed by a single thread.
10344 
10345 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10346 	when you call one of its constructors.
10347 
10348 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10349 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10350 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10351 
10352 	The reason this function is available is in case you want to message pass between a gui
10353 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10354 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10355 
10356 	History:
10357 		Added December 3, 2021 (dub v10.5)
10358 +/
10359 public bool guiThreadExists() {
10360 	return guiThreadExists_;
10361 }
10362 
10363 /++
10364 	Returns `true` if this thread is either running or set to be running the
10365 	simpledisplay.d gui core event loop because it owns windows.
10366 
10367 	It is important to keep gui-related functionality in the right thread, so you will
10368 	want to `runInGuiThread` when you call them (with some specific exceptions called
10369 	out in those specific functions' documentation). Notably, all windows must be
10370 	created and managed only from the gui thread.
10371 
10372 	Will return false if simpledisplay's other functions haven't been called
10373 	yet; check [guiThreadExists] in addition to this.
10374 
10375 	History:
10376 		Added December 3, 2021 (dub v10.5)
10377 +/
10378 public bool thisThreadRunningGui() {
10379 	return thisIsGuiThread;
10380 }
10381 
10382 /++
10383 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10384 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10385 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10386 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10387 	file instead if you are in one of those situations).
10388 
10389 	It does not support outputting very many types; just strings and ints are likely to actually work.
10390 
10391 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10392 	is unspecified meaning I can change it at any time. The only point of this function is to help
10393 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10394 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10395 	in those contexts.
10396 
10397 	$(WARNING
10398 		I reserve the right to change this function at any time. You can use it if it helps you
10399 		but do not rely on it for anything permanent.
10400 	)
10401 
10402 	History:
10403 		Added December 3, 2021. Not formally supported under any stable tag.
10404 +/
10405 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10406 	try {
10407 		version(Windows) {
10408 			import core.sys.windows.wincon;
10409 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10410 				AllocConsole();
10411 			const(char)* fn = "CONOUT$";
10412 		} else version(Posix) {
10413 			const(char)* fn = "/dev/tty";
10414 		} else static assert(0, "Function not implemented for your system");
10415 
10416 		if(fileOverride.length)
10417 			fn = fileOverride.ptr;
10418 
10419 		import core.stdc.stdio;
10420 		auto fp = fopen(fn, "wt");
10421 		if(fp is null) return;
10422 		scope(exit) fclose(fp);
10423 
10424 		string str;
10425 		foreach(item; t) {
10426 			static if(is(typeof(item) : const(char)[]))
10427 				str ~= item;
10428 			else
10429 				str ~= toInternal!string(item);
10430 			str ~= " ";
10431 		}
10432 		str ~= "\n";
10433 
10434 		fwrite(str.ptr, 1, str.length, fp);
10435 		fflush(fp);
10436 	} catch(Exception e) {
10437 		// sorry no hope
10438 	}
10439 }
10440 
10441 private void guiThreadFinalize() {
10442 	assert(thisIsGuiThread);
10443 
10444 	guiThreadTerminating = true; // don't add any more from this point on
10445 	runPendingRunInGuiThreadDelegates();
10446 }
10447 
10448 /+
10449 interface IPromise {
10450 	void reportProgress(int current, int max, string message);
10451 
10452 	/+ // not formally in cuz of templates but still
10453 	IPromise Then();
10454 	IPromise Catch();
10455 	IPromise Finally();
10456 	+/
10457 }
10458 
10459 /+
10460 	auto promise = async({ ... });
10461 	promise.Then(whatever).
10462 		Then(whateverelse).
10463 		Catch((exception) { });
10464 
10465 
10466 	A promise is run inside a fiber and it looks something like:
10467 
10468 	try {
10469 		auto res = whatever();
10470 		auto res2 = whateverelse(res);
10471 	} catch(Exception e) {
10472 		{ }(e);
10473 	}
10474 
10475 	When a thing succeeds, it is passed as an arg to the next
10476 +/
10477 class Promise(T) : IPromise {
10478 	auto Then() { return null; }
10479 	auto Catch() { return null; }
10480 	auto Finally() { return null; }
10481 
10482 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10483 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10484 	T await();
10485 }
10486 
10487 interface Task {
10488 }
10489 
10490 interface Resolvable(T) : Task {
10491 	void run();
10492 
10493 	void resolve(T);
10494 
10495 	Resolvable!T then(void delegate(T)); // returns a new promise
10496 	Resolvable!T error(Throwable); // js catch
10497 	Resolvable!T completed(); // js finally
10498 
10499 }
10500 
10501 /++
10502 	Runs `work` in a helper thread and sends its return value back to the main gui
10503 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10504 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10505 	kill the program.
10506 
10507 	You can call reportProgress(position, max, message) to update your parent window
10508 	on your progress.
10509 
10510 	I should also use `shared` methods. FIXME
10511 
10512 	History:
10513 		Added March 6, 2021 (dub version 9.3).
10514 +/
10515 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10516 	uponCompletion(work(null));
10517 }
10518 
10519 +/
10520 
10521 /// Used internal to dispatch events to various classes.
10522 interface CapableOfHandlingNativeEvent {
10523 	NativeEventHandler getNativeEventHandler();
10524 
10525 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10526 
10527 	version(X11) {
10528 		// if this is impossible, you are allowed to just throw from it
10529 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10530 		void recreateAfterDisconnect();
10531 		// discard any *connection specific* state, but keep enough that you
10532 		// can be recreated if possible. discardConnectionState() is always called immediately
10533 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10534 		// you need initialization order
10535 		void discardConnectionState();
10536 	}
10537 }
10538 
10539 version(X11)
10540 /++
10541 	State of keys on mouse events, especially motion.
10542 
10543 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10544 +/
10545 enum ModifierState : uint {
10546 	shift = 1, ///
10547 	capsLock = 2, ///
10548 	ctrl = 4, ///
10549 	alt = 8, /// Not always available on Windows
10550 	windows = 64, /// ditto
10551 	numLock = 16, ///
10552 
10553 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10554 	middleButtonDown = 512, /// ditto
10555 	rightButtonDown = 1024, /// ditto
10556 }
10557 else version(Windows)
10558 /// ditto
10559 enum ModifierState : uint {
10560 	shift = 4, ///
10561 	ctrl = 8, ///
10562 
10563 	// i'm not sure if the next two are available
10564 	alt = 256, /// not always available on Windows
10565 	windows = 512, /// ditto
10566 
10567 	capsLock = 1024, ///
10568 	numLock = 2048, ///
10569 
10570 	leftButtonDown = 1, /// not available on key events
10571 	middleButtonDown = 16, /// ditto
10572 	rightButtonDown = 2, /// ditto
10573 
10574 	backButtonDown = 0x20, /// not available on X
10575 	forwardButtonDown = 0x40, /// ditto
10576 }
10577 else version(OSXCocoa)
10578 // FIXME FIXME NotYetImplementedException
10579 enum ModifierState : uint {
10580 	shift = 1, ///
10581 	capsLock = 2, ///
10582 	ctrl = 4, ///
10583 	alt = 8, /// Not always available on Windows
10584 	windows = 64, /// ditto
10585 	numLock = 16, ///
10586 
10587 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10588 	middleButtonDown = 512, /// ditto
10589 	rightButtonDown = 1024, /// ditto
10590 }
10591 
10592 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10593 enum MouseButton : int {
10594 	none = 0,
10595 	left = 1, ///
10596 	right = 2, ///
10597 	middle = 4, ///
10598 	wheelUp = 8, ///
10599 	wheelDown = 16, ///
10600 	backButton = 32, /// often found on the thumb and used for back in browsers
10601 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10602 }
10603 
10604 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
10605 enum MouseButtonLinear : ubyte {
10606 	left = 1, ///
10607 	right, ///
10608 	middle, ///
10609 	wheelUp, ///
10610 	wheelDown, ///
10611 	backButton, /// often found on the thumb and used for back in browsers
10612 	forwardButton, /// often found on the thumb and used for forward in browsers
10613 }
10614 
10615 version(X11) {
10616 	// FIXME: match ASCII whenever we can. Most of it is already there,
10617 	// but there's a few exceptions and mismatches with Windows
10618 
10619 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10620 	enum Key {
10621 		Escape = 0xff1b, ///
10622 		F1 = 0xffbe, ///
10623 		F2 = 0xffbf, ///
10624 		F3 = 0xffc0, ///
10625 		F4 = 0xffc1, ///
10626 		F5 = 0xffc2, ///
10627 		F6 = 0xffc3, ///
10628 		F7 = 0xffc4, ///
10629 		F8 = 0xffc5, ///
10630 		F9 = 0xffc6, ///
10631 		F10 = 0xffc7, ///
10632 		F11 = 0xffc8, ///
10633 		F12 = 0xffc9, ///
10634 		PrintScreen = 0xff61, ///
10635 		ScrollLock = 0xff14, ///
10636 		Pause = 0xff13, ///
10637 		Grave = 0x60, /// The $(BACKTICK) ~ key
10638 		// number keys across the top of the keyboard
10639 		N1 = 0x31, /// Number key atop the keyboard
10640 		N2 = 0x32, ///
10641 		N3 = 0x33, ///
10642 		N4 = 0x34, ///
10643 		N5 = 0x35, ///
10644 		N6 = 0x36, ///
10645 		N7 = 0x37, ///
10646 		N8 = 0x38, ///
10647 		N9 = 0x39, ///
10648 		N0 = 0x30, ///
10649 		Dash = 0x2d, ///
10650 		Equals = 0x3d, ///
10651 		Backslash = 0x5c, /// The \ | key
10652 		Backspace = 0xff08, ///
10653 		Insert = 0xff63, ///
10654 		Home = 0xff50, ///
10655 		PageUp = 0xff55, ///
10656 		Delete = 0xffff, ///
10657 		End = 0xff57, ///
10658 		PageDown = 0xff56, ///
10659 		Up = 0xff52, ///
10660 		Down = 0xff54, ///
10661 		Left = 0xff51, ///
10662 		Right = 0xff53, ///
10663 
10664 		Tab = 0xff09, ///
10665 		Q = 0x71, ///
10666 		W = 0x77, ///
10667 		E = 0x65, ///
10668 		R = 0x72, ///
10669 		T = 0x74, ///
10670 		Y = 0x79, ///
10671 		U = 0x75, ///
10672 		I = 0x69, ///
10673 		O = 0x6f, ///
10674 		P = 0x70, ///
10675 		LeftBracket = 0x5b, /// the [ { key
10676 		RightBracket = 0x5d, /// the ] } key
10677 		CapsLock = 0xffe5, ///
10678 		A = 0x61, ///
10679 		S = 0x73, ///
10680 		D = 0x64, ///
10681 		F = 0x66, ///
10682 		G = 0x67, ///
10683 		H = 0x68, ///
10684 		J = 0x6a, ///
10685 		K = 0x6b, ///
10686 		L = 0x6c, ///
10687 		Semicolon = 0x3b, ///
10688 		Apostrophe = 0x27, ///
10689 		Enter = 0xff0d, ///
10690 		Shift = 0xffe1, ///
10691 		Z = 0x7a, ///
10692 		X = 0x78, ///
10693 		C = 0x63, ///
10694 		V = 0x76, ///
10695 		B = 0x62, ///
10696 		N = 0x6e, ///
10697 		M = 0x6d, ///
10698 		Comma = 0x2c, ///
10699 		Period = 0x2e, ///
10700 		Slash = 0x2f, /// the / ? key
10701 		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
10702 		Ctrl = 0xffe3, ///
10703 		Windows = 0xffeb, ///
10704 		Alt = 0xffe9, ///
10705 		Space = 0x20, ///
10706 		Alt_r = 0xffea, /// ditto of shift_r
10707 		Windows_r = 0xffec, ///
10708 		Menu = 0xff67, ///
10709 		Ctrl_r = 0xffe4, ///
10710 
10711 		NumLock = 0xff7f, ///
10712 		Divide = 0xffaf, /// The / key on the number pad
10713 		Multiply = 0xffaa, /// The * key on the number pad
10714 		Minus = 0xffad, /// The - key on the number pad
10715 		Plus = 0xffab, /// The + key on the number pad
10716 		PadEnter = 0xff8d, /// Numberpad enter key
10717 		Pad1 = 0xff9c, /// Numberpad keys
10718 		Pad2 = 0xff99, ///
10719 		Pad3 = 0xff9b, ///
10720 		Pad4 = 0xff96, ///
10721 		Pad5 = 0xff9d, ///
10722 		Pad6 = 0xff98, ///
10723 		Pad7 = 0xff95, ///
10724 		Pad8 = 0xff97, ///
10725 		Pad9 = 0xff9a, ///
10726 		Pad0 = 0xff9e, ///
10727 		PadDot = 0xff9f, ///
10728 	}
10729 } else version(Windows) {
10730 	// the character here is for en-us layouts and for illustration only
10731 	// if you actually want to get characters, wait for character events
10732 	// (the argument to your event handler is simply a dchar)
10733 	// those will be converted by the OS for the right locale.
10734 
10735 	enum Key {
10736 		Escape = 0x1b,
10737 		F1 = 0x70,
10738 		F2 = 0x71,
10739 		F3 = 0x72,
10740 		F4 = 0x73,
10741 		F5 = 0x74,
10742 		F6 = 0x75,
10743 		F7 = 0x76,
10744 		F8 = 0x77,
10745 		F9 = 0x78,
10746 		F10 = 0x79,
10747 		F11 = 0x7a,
10748 		F12 = 0x7b,
10749 		PrintScreen = 0x2c,
10750 		ScrollLock = 0x91,
10751 		Pause = 0x13,
10752 		Grave = 0xc0,
10753 		// number keys across the top of the keyboard
10754 		N1 = 0x31,
10755 		N2 = 0x32,
10756 		N3 = 0x33,
10757 		N4 = 0x34,
10758 		N5 = 0x35,
10759 		N6 = 0x36,
10760 		N7 = 0x37,
10761 		N8 = 0x38,
10762 		N9 = 0x39,
10763 		N0 = 0x30,
10764 		Dash = 0xbd,
10765 		Equals = 0xbb,
10766 		Backslash = 0xdc,
10767 		Backspace = 0x08,
10768 		Insert = 0x2d,
10769 		Home = 0x24,
10770 		PageUp = 0x21,
10771 		Delete = 0x2e,
10772 		End = 0x23,
10773 		PageDown = 0x22,
10774 		Up = 0x26,
10775 		Down = 0x28,
10776 		Left = 0x25,
10777 		Right = 0x27,
10778 
10779 		Tab = 0x09,
10780 		Q = 0x51,
10781 		W = 0x57,
10782 		E = 0x45,
10783 		R = 0x52,
10784 		T = 0x54,
10785 		Y = 0x59,
10786 		U = 0x55,
10787 		I = 0x49,
10788 		O = 0x4f,
10789 		P = 0x50,
10790 		LeftBracket = 0xdb,
10791 		RightBracket = 0xdd,
10792 		CapsLock = 0x14,
10793 		A = 0x41,
10794 		S = 0x53,
10795 		D = 0x44,
10796 		F = 0x46,
10797 		G = 0x47,
10798 		H = 0x48,
10799 		J = 0x4a,
10800 		K = 0x4b,
10801 		L = 0x4c,
10802 		Semicolon = 0xba,
10803 		Apostrophe = 0xde,
10804 		Enter = 0x0d,
10805 		Shift = 0x10,
10806 		Z = 0x5a,
10807 		X = 0x58,
10808 		C = 0x43,
10809 		V = 0x56,
10810 		B = 0x42,
10811 		N = 0x4e,
10812 		M = 0x4d,
10813 		Comma = 0xbc,
10814 		Period = 0xbe,
10815 		Slash = 0xbf,
10816 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10817 		Ctrl = 0x11,
10818 		Windows = 0x5b,
10819 		Alt = -5, // FIXME
10820 		Space = 0x20,
10821 		Alt_r = 0xffea, // ditto of shift_r
10822 		Windows_r = 0x5c, // ditto of shift_r
10823 		Menu = 0x5d,
10824 		Ctrl_r = 0xa3, // ditto of shift_r
10825 
10826 		NumLock = 0x90,
10827 		Divide = 0x6f,
10828 		Multiply = 0x6a,
10829 		Minus = 0x6d,
10830 		Plus = 0x6b,
10831 		PadEnter = -8, // FIXME
10832 		Pad1 = 0x61,
10833 		Pad2 = 0x62,
10834 		Pad3 = 0x63,
10835 		Pad4 = 0x64,
10836 		Pad5 = 0x65,
10837 		Pad6 = 0x66,
10838 		Pad7 = 0x67,
10839 		Pad8 = 0x68,
10840 		Pad9 = 0x69,
10841 		Pad0 = 0x60,
10842 		PadDot = 0x6e,
10843 	}
10844 
10845 	// I'm keeping this around for reference purposes
10846 	// ideally all these buttons will be listed for all platforms,
10847 	// but now now I'm just focusing on my US keyboard
10848 	version(none)
10849 	enum Key {
10850 		LBUTTON = 0x01,
10851 		RBUTTON = 0x02,
10852 		CANCEL = 0x03,
10853 		MBUTTON = 0x04,
10854 		//static if (_WIN32_WINNT > =  0x500) {
10855 		XBUTTON1 = 0x05,
10856 		XBUTTON2 = 0x06,
10857 		//}
10858 		BACK = 0x08,
10859 		TAB = 0x09,
10860 		CLEAR = 0x0C,
10861 		RETURN = 0x0D,
10862 		SHIFT = 0x10,
10863 		CONTROL = 0x11,
10864 		MENU = 0x12,
10865 		PAUSE = 0x13,
10866 		CAPITAL = 0x14,
10867 		KANA = 0x15,
10868 		HANGEUL = 0x15,
10869 		HANGUL = 0x15,
10870 		JUNJA = 0x17,
10871 		FINAL = 0x18,
10872 		HANJA = 0x19,
10873 		KANJI = 0x19,
10874 		ESCAPE = 0x1B,
10875 		CONVERT = 0x1C,
10876 		NONCONVERT = 0x1D,
10877 		ACCEPT = 0x1E,
10878 		MODECHANGE = 0x1F,
10879 		SPACE = 0x20,
10880 		PRIOR = 0x21,
10881 		NEXT = 0x22,
10882 		END = 0x23,
10883 		HOME = 0x24,
10884 		LEFT = 0x25,
10885 		UP = 0x26,
10886 		RIGHT = 0x27,
10887 		DOWN = 0x28,
10888 		SELECT = 0x29,
10889 		PRINT = 0x2A,
10890 		EXECUTE = 0x2B,
10891 		SNAPSHOT = 0x2C,
10892 		INSERT = 0x2D,
10893 		DELETE = 0x2E,
10894 		HELP = 0x2F,
10895 		LWIN = 0x5B,
10896 		RWIN = 0x5C,
10897 		APPS = 0x5D,
10898 		SLEEP = 0x5F,
10899 		NUMPAD0 = 0x60,
10900 		NUMPAD1 = 0x61,
10901 		NUMPAD2 = 0x62,
10902 		NUMPAD3 = 0x63,
10903 		NUMPAD4 = 0x64,
10904 		NUMPAD5 = 0x65,
10905 		NUMPAD6 = 0x66,
10906 		NUMPAD7 = 0x67,
10907 		NUMPAD8 = 0x68,
10908 		NUMPAD9 = 0x69,
10909 		MULTIPLY = 0x6A,
10910 		ADD = 0x6B,
10911 		SEPARATOR = 0x6C,
10912 		SUBTRACT = 0x6D,
10913 		DECIMAL = 0x6E,
10914 		DIVIDE = 0x6F,
10915 		F1 = 0x70,
10916 		F2 = 0x71,
10917 		F3 = 0x72,
10918 		F4 = 0x73,
10919 		F5 = 0x74,
10920 		F6 = 0x75,
10921 		F7 = 0x76,
10922 		F8 = 0x77,
10923 		F9 = 0x78,
10924 		F10 = 0x79,
10925 		F11 = 0x7A,
10926 		F12 = 0x7B,
10927 		F13 = 0x7C,
10928 		F14 = 0x7D,
10929 		F15 = 0x7E,
10930 		F16 = 0x7F,
10931 		F17 = 0x80,
10932 		F18 = 0x81,
10933 		F19 = 0x82,
10934 		F20 = 0x83,
10935 		F21 = 0x84,
10936 		F22 = 0x85,
10937 		F23 = 0x86,
10938 		F24 = 0x87,
10939 		NUMLOCK = 0x90,
10940 		SCROLL = 0x91,
10941 		LSHIFT = 0xA0,
10942 		RSHIFT = 0xA1,
10943 		LCONTROL = 0xA2,
10944 		RCONTROL = 0xA3,
10945 		LMENU = 0xA4,
10946 		RMENU = 0xA5,
10947 		//static if (_WIN32_WINNT > =  0x500) {
10948 		BROWSER_BACK = 0xA6,
10949 		BROWSER_FORWARD = 0xA7,
10950 		BROWSER_REFRESH = 0xA8,
10951 		BROWSER_STOP = 0xA9,
10952 		BROWSER_SEARCH = 0xAA,
10953 		BROWSER_FAVORITES = 0xAB,
10954 		BROWSER_HOME = 0xAC,
10955 		VOLUME_MUTE = 0xAD,
10956 		VOLUME_DOWN = 0xAE,
10957 		VOLUME_UP = 0xAF,
10958 		MEDIA_NEXT_TRACK = 0xB0,
10959 		MEDIA_PREV_TRACK = 0xB1,
10960 		MEDIA_STOP = 0xB2,
10961 		MEDIA_PLAY_PAUSE = 0xB3,
10962 		LAUNCH_MAIL = 0xB4,
10963 		LAUNCH_MEDIA_SELECT = 0xB5,
10964 		LAUNCH_APP1 = 0xB6,
10965 		LAUNCH_APP2 = 0xB7,
10966 		//}
10967 		OEM_1 = 0xBA,
10968 		//static if (_WIN32_WINNT > =  0x500) {
10969 		OEM_PLUS = 0xBB,
10970 		OEM_COMMA = 0xBC,
10971 		OEM_MINUS = 0xBD,
10972 		OEM_PERIOD = 0xBE,
10973 		//}
10974 		OEM_2 = 0xBF,
10975 		OEM_3 = 0xC0,
10976 		OEM_4 = 0xDB,
10977 		OEM_5 = 0xDC,
10978 		OEM_6 = 0xDD,
10979 		OEM_7 = 0xDE,
10980 		OEM_8 = 0xDF,
10981 		//static if (_WIN32_WINNT > =  0x500) {
10982 		OEM_102 = 0xE2,
10983 		//}
10984 		PROCESSKEY = 0xE5,
10985 		//static if (_WIN32_WINNT > =  0x500) {
10986 		PACKET = 0xE7,
10987 		//}
10988 		ATTN = 0xF6,
10989 		CRSEL = 0xF7,
10990 		EXSEL = 0xF8,
10991 		EREOF = 0xF9,
10992 		PLAY = 0xFA,
10993 		ZOOM = 0xFB,
10994 		NONAME = 0xFC,
10995 		PA1 = 0xFD,
10996 		OEM_CLEAR = 0xFE,
10997 	}
10998 
10999 } else version(OSXCocoa) {
11000 	enum Key {
11001 		Escape = 53,
11002 		F1 = 122,
11003 		F2 = 120,
11004 		F3 = 99,
11005 		F4 = 118,
11006 		F5 = 96,
11007 		F6 = 97,
11008 		F7 = 98,
11009 		F8 = 100,
11010 		F9 = 101,
11011 		F10 = 109,
11012 		F11 = 103,
11013 		F12 = 111,
11014 		PrintScreen = 105,
11015 		ScrollLock = 107,
11016 		Pause = 113,
11017 		Grave = 50,
11018 		// number keys across the top of the keyboard
11019 		N1 = 18,
11020 		N2 = 19,
11021 		N3 = 20,
11022 		N4 = 21,
11023 		N5 = 23,
11024 		N6 = 22,
11025 		N7 = 26,
11026 		N8 = 28,
11027 		N9 = 25,
11028 		N0 = 29,
11029 		Dash = 27,
11030 		Equals = 24,
11031 		Backslash = 42,
11032 		Backspace = 51,
11033 		Insert = 114,
11034 		Home = 115,
11035 		PageUp = 116,
11036 		Delete = 117,
11037 		End = 119,
11038 		PageDown = 121,
11039 		Up = 126,
11040 		Down = 125,
11041 		Left = 123,
11042 		Right = 124,
11043 
11044 		Tab = 48,
11045 		Q = 12,
11046 		W = 13,
11047 		E = 14,
11048 		R = 15,
11049 		T = 17,
11050 		Y = 16,
11051 		U = 32,
11052 		I = 34,
11053 		O = 31,
11054 		P = 35,
11055 		LeftBracket = 33,
11056 		RightBracket = 30,
11057 		CapsLock = 57,
11058 		A = 0,
11059 		S = 1,
11060 		D = 2,
11061 		F = 3,
11062 		G = 5,
11063 		H = 4,
11064 		J = 38,
11065 		K = 40,
11066 		L = 37,
11067 		Semicolon = 41,
11068 		Apostrophe = 39,
11069 		Enter = 36,
11070 		Shift = 56,
11071 		Z = 6,
11072 		X = 7,
11073 		C = 8,
11074 		V = 9,
11075 		B = 11,
11076 		N = 45,
11077 		M = 46,
11078 		Comma = 43,
11079 		Period = 47,
11080 		Slash = 44,
11081 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11082 		Ctrl = 59,
11083 		Windows = 55,
11084 		Alt = 58,
11085 		Space = 49,
11086 		Alt_r = -3, // ditto of shift_r
11087 		Windows_r = -2,
11088 		Menu = 110,
11089 		Ctrl_r = -1,
11090 
11091 		NumLock = 1,
11092 		Divide = 75,
11093 		Multiply = 67,
11094 		Minus = 78,
11095 		Plus = 69,
11096 		PadEnter = 76,
11097 		Pad1 = 83,
11098 		Pad2 = 84,
11099 		Pad3 = 85,
11100 		Pad4 = 86,
11101 		Pad5 = 87,
11102 		Pad6 = 88,
11103 		Pad7 = 89,
11104 		Pad8 = 91,
11105 		Pad9 = 92,
11106 		Pad0 = 82,
11107 		PadDot = 65,
11108 	}
11109 
11110 }
11111 
11112 /* Additional utilities */
11113 
11114 
11115 Color fromHsl(real h, real s, real l) {
11116 	return arsd.color.fromHsl([h,s,l]);
11117 }
11118 
11119 
11120 
11121 /* ********** What follows is the system-specific implementations *********/
11122 version(Windows) {
11123 
11124 
11125 	// helpers for making HICONs from MemoryImages
11126 	class WindowsIcon {
11127 		struct Win32Icon {
11128 			align(1):
11129 			uint biSize;
11130 			int biWidth;
11131 			int biHeight;
11132 			ushort biPlanes;
11133 			ushort biBitCount;
11134 			uint biCompression;
11135 			uint biSizeImage;
11136 			int biXPelsPerMeter;
11137 			int biYPelsPerMeter;
11138 			uint biClrUsed;
11139 			uint biClrImportant;
11140 			// RGBQUAD[colorCount] biColors;
11141 			/* Pixels:
11142 			Uint8 pixels[]
11143 			*/
11144 			/* Mask:
11145 			Uint8 mask[]
11146 			*/
11147 		}
11148 
11149 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11150 
11151 			assert(mi.width <= 256, "image too wide");
11152 			assert(mi.height <= 256, "image too tall");
11153 			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
11154 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11155 
11156 			int icon_plen = mi.width * mi.height * 4;
11157 			int icon_mlen = mi.width * mi.height / 8;
11158 
11159 			int colorCount = 0;
11160 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11161 
11162 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11163 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11164 
11165 			auto data = memory[Win32Icon.sizeof .. $];
11166 
11167 			width = mi.width;
11168 			height = mi.height;
11169 
11170 			auto trueColorImage = mi.getAsTrueColorImage();
11171 
11172 			icon_win32.biSize = 40;
11173 			icon_win32.biWidth = mi.width;
11174 			icon_win32.biHeight = mi.height*2;
11175 			icon_win32.biPlanes = 1;
11176 			icon_win32.biBitCount = 32;
11177 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11178 
11179 			int offset = 0;
11180 			int andOff = icon_plen * 8; // the and offset is in bits
11181 
11182 			// leaving the and mask as the default 0 so the rgba alpha blend
11183 			// does its thing instead
11184 			for(int y = height - 1; y >= 0; y--) {
11185 				int off2 = y * width * 4;
11186 				foreach(x; 0 .. width) {
11187 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11188 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11189 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11190 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11191 
11192 					offset += 4;
11193 					off2 += 4;
11194 				}
11195 			}
11196 
11197 			return memory;
11198 		}
11199 
11200 		this(MemoryImage mi) {
11201 			int icon_len, width, height;
11202 
11203 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11204 
11205 			/*
11206 			PNG* png = readPnpngData);
11207 			PNGHeader pngh = getHeader(png);
11208 			void* icon_win32;
11209 			if(pngh.depth == 4) {
11210 				auto i = new Win32Icon!(16);
11211 				i.fromPNG(png, pngh, icon_len, width, height);
11212 				icon_win32 = i;
11213 			}
11214 			else if(pngh.depth == 8) {
11215 				auto i = new Win32Icon!(256);
11216 				i.fromPNG(png, pngh, icon_len, width, height);
11217 				icon_win32 = i;
11218 			} else assert(0);
11219 			*/
11220 
11221 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11222 
11223 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11224 		}
11225 
11226 		~this() {
11227 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11228 			DestroyIcon(hIcon);
11229 		}
11230 
11231 		HICON hIcon;
11232 	}
11233 
11234 
11235 
11236 
11237 
11238 
11239 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11240 	alias HWND NativeWindowHandle;
11241 
11242 	extern(Windows)
11243 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11244 		try {
11245 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11246 				// it returns zero if the message is handled, so we won't do anything more there
11247 				// do I like that though?
11248 				int mustReturn;
11249 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11250 				if(mustReturn)
11251 					return ret;
11252 			}
11253 
11254 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11255 				if(window.getNativeEventHandler !is null) {
11256 					int mustReturn;
11257 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11258 					if(mustReturn)
11259 						return ret;
11260 				}
11261 				if(auto w = cast(SimpleWindow) (*window))
11262 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11263 				else
11264 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11265 			} else {
11266 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11267 			}
11268 		} catch (Exception e) {
11269 			try {
11270 				sdpy_abort(e);
11271 				return 0;
11272 			} catch(Exception e) { assert(0); }
11273 		}
11274 	}
11275 
11276 	void sdpy_abort(Throwable e) nothrow {
11277 		try
11278 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11279 		catch(Exception e)
11280 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11281 		ExitProcess(1);
11282 	}
11283 
11284 	mixin template NativeScreenPainterImplementation() {
11285 		HDC hdc;
11286 		HWND hwnd;
11287 		//HDC windowHdc;
11288 		HBITMAP oldBmp;
11289 
11290 		void create(PaintingHandle window) {
11291 			hwnd = window;
11292 
11293 			if(auto sw = cast(SimpleWindow) this.window) {
11294 				// drawing on a window, double buffer
11295 				auto windowHdc = GetDC(hwnd);
11296 
11297 				auto buffer = sw.impl.buffer;
11298 				if(buffer is null) {
11299 					hdc = windowHdc;
11300 					windowDc = true;
11301 				} else {
11302 					hdc = CreateCompatibleDC(windowHdc);
11303 
11304 					ReleaseDC(hwnd, windowHdc);
11305 
11306 					oldBmp = SelectObject(hdc, buffer);
11307 				}
11308 			} else {
11309 				// drawing on something else, draw directly
11310 				hdc = CreateCompatibleDC(null);
11311 				SelectObject(hdc, window);
11312 			}
11313 
11314 			// X doesn't draw a text background, so neither should we
11315 			SetBkMode(hdc, TRANSPARENT);
11316 
11317 			ensureDefaultFontLoaded();
11318 
11319 			if(defaultGuiFont) {
11320 				SelectObject(hdc, defaultGuiFont);
11321 				// DeleteObject(defaultGuiFont);
11322 			}
11323 		}
11324 
11325 		static HFONT defaultGuiFont;
11326 		static void ensureDefaultFontLoaded() {
11327 			static bool triedDefaultGuiFont = false;
11328 			if(!triedDefaultGuiFont) {
11329 				NONCLIENTMETRICS params;
11330 				params.cbSize = params.sizeof;
11331 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11332 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11333 				}
11334 				triedDefaultGuiFont = true;
11335 			}
11336 		}
11337 
11338 		private OperatingSystemFont _activeFont;
11339 
11340 		void setFont(OperatingSystemFont font) {
11341 			_activeFont = font;
11342 			if(font && font.font) {
11343 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11344 					// error... how to handle tho?
11345 				} else {
11346 
11347 				}
11348 			}
11349 			else if(defaultGuiFont)
11350 				SelectObject(hdc, defaultGuiFont);
11351 		}
11352 
11353 		arsd.color.Rectangle _clipRectangle;
11354 
11355 		void setClipRectangle(int x, int y, int width, int height) {
11356 			auto old = _clipRectangle;
11357 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11358 			if(old == _clipRectangle)
11359 				return;
11360 
11361 			if(width == 0 || height == 0) {
11362 				SelectClipRgn(hdc, null);
11363 			} else {
11364 				auto region = CreateRectRgn(x, y, x + width, y + height);
11365 				SelectClipRgn(hdc, region);
11366 				DeleteObject(region);
11367 			}
11368 		}
11369 
11370 
11371 		// just because we can on Windows...
11372 		//void create(Image image);
11373 
11374 		void invalidateRect(Rectangle invalidRect) {
11375 			RECT rect;
11376 			rect.left = invalidRect.left;
11377 			rect.right = invalidRect.right;
11378 			rect.top = invalidRect.top;
11379 			rect.bottom = invalidRect.bottom;
11380 			InvalidateRect(hwnd, &rect, false);
11381 		}
11382 		bool manualInvalidations;
11383 
11384 		void dispose() {
11385 			// FIXME: this.window.width/height is probably wrong
11386 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11387 			// ReleaseDC(hwnd, windowHdc);
11388 
11389 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11390 			if(cast(SimpleWindow) this.window) {
11391 				if(!manualInvalidations)
11392 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11393 			}
11394 
11395 			if(originalPen !is null)
11396 				SelectObject(hdc, originalPen);
11397 			if(currentPen !is null)
11398 				DeleteObject(currentPen);
11399 			if(originalBrush !is null)
11400 				SelectObject(hdc, originalBrush);
11401 			if(currentBrush !is null)
11402 				DeleteObject(currentBrush);
11403 
11404 			SelectObject(hdc, oldBmp);
11405 
11406 			if(windowDc)
11407 				ReleaseDC(hwnd, hdc);
11408 			else
11409 				DeleteDC(hdc);
11410 
11411 			if(window.paintingFinishedDg !is null)
11412 				window.paintingFinishedDg()();
11413 		}
11414 
11415 		bool windowDc;
11416 		HPEN originalPen;
11417 		HPEN currentPen;
11418 
11419 		Pen _activePen;
11420 
11421 		Color _outlineColor;
11422 
11423 		@property void pen(Pen p) {
11424 			_activePen = p;
11425 			_outlineColor = p.color;
11426 
11427 			HPEN pen;
11428 			if(p.color.a == 0) {
11429 				pen = GetStockObject(NULL_PEN);
11430 			} else {
11431 				int style = PS_SOLID;
11432 				final switch(p.style) {
11433 					case Pen.Style.Solid:
11434 						style = PS_SOLID;
11435 					break;
11436 					case Pen.Style.Dashed:
11437 						style = PS_DASH;
11438 					break;
11439 					case Pen.Style.Dotted:
11440 						style = PS_DOT;
11441 					break;
11442 				}
11443 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11444 			}
11445 			auto orig = SelectObject(hdc, pen);
11446 			if(originalPen is null)
11447 				originalPen = orig;
11448 
11449 			if(currentPen !is null)
11450 				DeleteObject(currentPen);
11451 
11452 			currentPen = pen;
11453 
11454 			// the outline is like a foreground since it's done that way on X
11455 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11456 
11457 		}
11458 
11459 		@property void rasterOp(RasterOp op) {
11460 			int mode;
11461 			final switch(op) {
11462 				case RasterOp.normal:
11463 					mode = R2_COPYPEN;
11464 				break;
11465 				case RasterOp.xor:
11466 					mode = R2_XORPEN;
11467 				break;
11468 			}
11469 			SetROP2(hdc, mode);
11470 		}
11471 
11472 		HBRUSH originalBrush;
11473 		HBRUSH currentBrush;
11474 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11475 		@property void fillColor(Color c) {
11476 			if(c == _fillColor)
11477 				return;
11478 			_fillColor = c;
11479 			HBRUSH brush;
11480 			if(c.a == 0) {
11481 				brush = GetStockObject(HOLLOW_BRUSH);
11482 			} else {
11483 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11484 			}
11485 			auto orig = SelectObject(hdc, brush);
11486 			if(originalBrush is null)
11487 				originalBrush = orig;
11488 
11489 			if(currentBrush !is null)
11490 				DeleteObject(currentBrush);
11491 
11492 			currentBrush = brush;
11493 
11494 			// background color is NOT set because X doesn't draw text backgrounds
11495 			//   SetBkColor(hdc, RGB(255, 255, 255));
11496 		}
11497 
11498 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11499 			BITMAP bm;
11500 
11501 			HDC hdcMem = CreateCompatibleDC(hdc);
11502 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11503 
11504 			GetObject(i.handle, bm.sizeof, &bm);
11505 
11506 			// or should I AlphaBlend!??!?!
11507 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11508 
11509 			SelectObject(hdcMem, hbmOld);
11510 			DeleteDC(hdcMem);
11511 		}
11512 
11513 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11514 			BITMAP bm;
11515 
11516 			HDC hdcMem = CreateCompatibleDC(hdc);
11517 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11518 
11519 			GetObject(s.handle, bm.sizeof, &bm);
11520 
11521 			version(CRuntime_DigitalMars) goto noalpha;
11522 
11523 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11524 			if(s.enableAlpha) {
11525 				auto dw = w ? w : bm.bmWidth;
11526 				auto dh = h ? h : bm.bmHeight;
11527 				BLENDFUNCTION bf;
11528 				bf.BlendOp = AC_SRC_OVER;
11529 				bf.SourceConstantAlpha = 255;
11530 				bf.AlphaFormat = AC_SRC_ALPHA;
11531 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11532 			} else {
11533 				noalpha:
11534 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11535 			}
11536 
11537 			SelectObject(hdcMem, hbmOld);
11538 			DeleteDC(hdcMem);
11539 		}
11540 
11541 		Size textSize(scope const(char)[] text) {
11542 			bool dummyX;
11543 			if(text.length == 0) {
11544 				text = " ";
11545 				dummyX = true;
11546 			}
11547 			RECT rect;
11548 			WCharzBuffer buffer = WCharzBuffer(text);
11549 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11550 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11551 		}
11552 
11553 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11554 			if(text.length && text[$-1] == '\n')
11555 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11556 			if(text.length && text[$-1] == '\r')
11557 				text = text[0 .. $-1];
11558 
11559 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11560 			if(x2 == 0 && y2 == 0) {
11561 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11562 			} else {
11563 				RECT rect;
11564 				rect.left = x;
11565 				rect.top = y;
11566 				rect.right = x2;
11567 				rect.bottom = y2;
11568 
11569 				uint mode = DT_LEFT;
11570 				if(alignment & TextAlignment.Right)
11571 					mode = DT_RIGHT;
11572 				else if(alignment & TextAlignment.Center)
11573 					mode = DT_CENTER;
11574 
11575 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11576 				if(alignment & TextAlignment.VerticalCenter)
11577 					mode |= DT_VCENTER | DT_SINGLELINE;
11578 
11579 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11580 			}
11581 
11582 			/*
11583 			uint mode;
11584 
11585 			if(alignment & TextAlignment.Center)
11586 				mode = TA_CENTER;
11587 
11588 			SetTextAlign(hdc, mode);
11589 			*/
11590 		}
11591 
11592 		int fontHeight() {
11593 			TEXTMETRIC metric;
11594 			if(GetTextMetricsW(hdc, &metric)) {
11595 				return metric.tmHeight;
11596 			}
11597 
11598 			return 16; // idk just guessing here, maybe we should throw
11599 		}
11600 
11601 		void drawPixel(int x, int y) {
11602 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11603 		}
11604 
11605 		// The basic shapes, outlined
11606 
11607 		void drawLine(int x1, int y1, int x2, int y2) {
11608 			MoveToEx(hdc, x1, y1, null);
11609 			LineTo(hdc, x2, y2);
11610 		}
11611 
11612 		void drawRectangle(int x, int y, int width, int height) {
11613 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11614 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11615 		}
11616 
11617 		/// Arguments are the points of the bounding rectangle
11618 		void drawEllipse(int x1, int y1, int x2, int y2) {
11619 			Ellipse(hdc, x1, y1, x2, y2);
11620 		}
11621 
11622 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11623 			if((start % (360*64)) == (finish % (360*64)))
11624 				drawEllipse(x1, y1, x1 + width, y1 + height);
11625 			else {
11626 				import core.stdc.math;
11627 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11628 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11629 
11630 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11631 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11632 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11633 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11634 
11635 				if(_activePen.color.a)
11636 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11637 				if(_fillColor.a)
11638 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11639 			}
11640 		}
11641 
11642 		void drawPolygon(Point[] vertexes) {
11643 			POINT[] points;
11644 			points.length = vertexes.length;
11645 
11646 			foreach(i, p; vertexes) {
11647 				points[i].x = p.x;
11648 				points[i].y = p.y;
11649 			}
11650 
11651 			Polygon(hdc, points.ptr, cast(int) points.length);
11652 		}
11653 	}
11654 
11655 
11656 	// Mix this into the SimpleWindow class
11657 	mixin template NativeSimpleWindowImplementation() {
11658 		int curHidden = 0; // counter
11659 		__gshared static bool[string] knownWinClasses;
11660 		static bool altPressed = false;
11661 
11662 		HANDLE oldCursor;
11663 
11664 		void hideCursor () {
11665 			if(curHidden == 0)
11666 				oldCursor = SetCursor(null);
11667 			++curHidden;
11668 		}
11669 
11670 		void showCursor () {
11671 			--curHidden;
11672 			if(curHidden == 0) {
11673 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11674 			}
11675 		}
11676 
11677 
11678 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11679 
11680 		void setMinSize (int minwidth, int minheight) {
11681 			minWidth = minwidth;
11682 			minHeight = minheight;
11683 		}
11684 		void setMaxSize (int maxwidth, int maxheight) {
11685 			maxWidth = maxwidth;
11686 			maxHeight = maxheight;
11687 		}
11688 
11689 		// FIXME i'm not sure that Windows has this functionality
11690 		// though it is nonessential anyway.
11691 		void setResizeGranularity (int granx, int grany) {}
11692 
11693 		ScreenPainter getPainter(bool manualInvalidations) {
11694 			return ScreenPainter(this, hwnd, manualInvalidations);
11695 		}
11696 
11697 		HBITMAP buffer;
11698 
11699 		void setTitle(string title) {
11700 			WCharzBuffer bfr = WCharzBuffer(title);
11701 			SetWindowTextW(hwnd, bfr.ptr);
11702 		}
11703 
11704 		string getTitle() {
11705 			auto len = GetWindowTextLengthW(hwnd);
11706 			if (!len)
11707 				return null;
11708 			wchar[256] tmpBuffer;
11709 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11710 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11711 			auto str = buffer[0 .. len2];
11712 			return makeUtf8StringFromWindowsString(str);
11713 		}
11714 
11715 		void move(int x, int y) {
11716 			RECT rect;
11717 			GetWindowRect(hwnd, &rect);
11718 			// move it while maintaining the same size...
11719 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11720 		}
11721 
11722 		void resize(int w, int h) {
11723 			RECT rect;
11724 			GetWindowRect(hwnd, &rect);
11725 
11726 			RECT client;
11727 			GetClientRect(hwnd, &client);
11728 
11729 			rect.right = rect.right - client.right + w;
11730 			rect.bottom = rect.bottom - client.bottom + h;
11731 
11732 			// same position, new size for the client rectangle
11733 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11734 
11735 			updateOpenglViewportIfNeeded(w, h);
11736 		}
11737 
11738 		void moveResize (int x, int y, int w, int h) {
11739 			// what's given is the client rectangle, we need to adjust
11740 
11741 			RECT rect;
11742 			rect.left = x;
11743 			rect.top = y;
11744 			rect.right = w + x;
11745 			rect.bottom = h + y;
11746 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11747 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
11748 
11749 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11750 			updateOpenglViewportIfNeeded(w, h);
11751 			if (windowResized !is null) windowResized(w, h);
11752 		}
11753 
11754 		version(without_opengl) {} else {
11755 			HGLRC ghRC;
11756 			HDC ghDC;
11757 		}
11758 
11759 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11760 			string cnamec;
11761 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11762 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11763 				cnamec = "DSimpleWindow";
11764 			} else {
11765 				cnamec = sdpyWindowClass;
11766 			}
11767 
11768 			WCharzBuffer cn = WCharzBuffer(cnamec);
11769 
11770 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11771 
11772 			if(cnamec !in knownWinClasses) {
11773 				WNDCLASSEX wc;
11774 
11775 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11776 				// to the object. Maybe.
11777 				wc.cbSize = wc.sizeof;
11778 				wc.cbClsExtra = 0;
11779 				wc.cbWndExtra = 0;
11780 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11781 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11782 				wc.hIcon = LoadIcon(hInstance, null);
11783 				wc.hInstance = hInstance;
11784 				wc.lpfnWndProc = &WndProc;
11785 				wc.lpszClassName = cn.ptr;
11786 				wc.hIconSm = null;
11787 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11788 				if(!RegisterClassExW(&wc))
11789 					throw new WindowsApiException("RegisterClassExW", GetLastError());
11790 				knownWinClasses[cnamec] = true;
11791 			}
11792 
11793 			int style;
11794 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11795 
11796 			// FIXME: windowType and customizationFlags
11797 			final switch(windowType) {
11798 				case WindowTypes.normal:
11799 					if(resizability == Resizability.fixedSize) {
11800 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11801 					} else {
11802 						style = WS_OVERLAPPEDWINDOW;
11803 					}
11804 				break;
11805 				case WindowTypes.undecorated:
11806 					style = WS_POPUP | WS_SYSMENU;
11807 				break;
11808 				case WindowTypes.eventOnly:
11809 					_hidden = true;
11810 				break;
11811 				case WindowTypes.dropdownMenu:
11812 				case WindowTypes.popupMenu:
11813 				case WindowTypes.notification:
11814 					style = WS_POPUP;
11815 					flags |= WS_EX_NOACTIVATE;
11816 				break;
11817 				case WindowTypes.nestedChild:
11818 					style = WS_CHILD;
11819 				break;
11820 				case WindowTypes.minimallyWrapped:
11821 					assert(0, "construct minimally wrapped through the other ctor overlad");
11822 			}
11823 
11824 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11825 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11826 
11827 			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
11828 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11829 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11830 
11831 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11832 				setOpacity(255);
11833 
11834 			SimpleWindow.nativeMapping[hwnd] = this;
11835 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11836 
11837 			if(windowType == WindowTypes.eventOnly)
11838 				return;
11839 
11840 			HDC hdc = GetDC(hwnd);
11841 
11842 
11843 			version(without_opengl) {}
11844 			else {
11845 				if(opengl == OpenGlOptions.yes) {
11846 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11847 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11848 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11849 					ghDC = hdc;
11850 					PIXELFORMATDESCRIPTOR pfd;
11851 
11852 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11853 					pfd.nVersion = 1;
11854 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11855 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11856 					pfd.iPixelType = PFD_TYPE_RGBA;
11857 					pfd.cColorBits = 24;
11858 					pfd.cDepthBits = 24;
11859 					pfd.cAccumBits = 0;
11860 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11861 
11862 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11863 
11864 					if (pixelformat == 0)
11865 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
11866 
11867 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11868 						throw new WindowsApiException("SetPixelFormat", GetLastError());
11869 
11870 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11871 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11872 						// so we will create fake context to get that stupid address
11873 						auto tmpcc = wglCreateContext(ghDC);
11874 						if (tmpcc !is null) {
11875 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11876 							wglMakeCurrent(ghDC, tmpcc);
11877 							wglInitOtherFunctions();
11878 						}
11879 					}
11880 
11881 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11882 						int[9] contextAttribs = [
11883 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11884 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11885 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11886 							// for modern context, set "forward compatibility" flag too
11887 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11888 							0/*None*/,
11889 						];
11890 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11891 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11892 							// activate fallback mode
11893 							// 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;
11894 							ghRC = wglCreateContext(ghDC);
11895 						}
11896 						if (ghRC is null)
11897 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
11898 					} else {
11899 						// try to do at least something
11900 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11901 							sdpyOpenGLContextVersion = 0;
11902 							ghRC = wglCreateContext(ghDC);
11903 						}
11904 						if (ghRC is null)
11905 							throw new WindowsApiException("wglCreateContext", GetLastError());
11906 					}
11907 				}
11908 			}
11909 
11910 			if(opengl == OpenGlOptions.no) {
11911 				buffer = CreateCompatibleBitmap(hdc, width, height);
11912 
11913 				auto hdcBmp = CreateCompatibleDC(hdc);
11914 				// make sure it's filled with a blank slate
11915 				auto oldBmp = SelectObject(hdcBmp, buffer);
11916 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11917 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11918 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11919 				SelectObject(hdcBmp, oldBmp);
11920 				SelectObject(hdcBmp, oldBrush);
11921 				SelectObject(hdcBmp, oldPen);
11922 				DeleteDC(hdcBmp);
11923 
11924 				bmpWidth = width;
11925 				bmpHeight = height;
11926 
11927 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11928 			}
11929 
11930 			// We want the window's client area to match the image size
11931 			RECT rcClient, rcWindow;
11932 			POINT ptDiff;
11933 			GetClientRect(hwnd, &rcClient);
11934 			GetWindowRect(hwnd, &rcWindow);
11935 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11936 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11937 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11938 
11939 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11940 				ShowWindow(hwnd, SW_SHOWNORMAL);
11941 			} else {
11942 				_hidden = true;
11943 			}
11944 			this._visibleForTheFirstTimeCalled = false; // hack!
11945 		}
11946 
11947 
11948 		void dispose() {
11949 			if(buffer)
11950 				DeleteObject(buffer);
11951 		}
11952 
11953 		void closeWindow() {
11954 			if(ghRC) {
11955 				wglDeleteContext(ghRC);
11956 				ghRC = null;
11957 			}
11958 			DestroyWindow(hwnd);
11959 		}
11960 
11961 		bool setOpacity(ubyte alpha) {
11962 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11963 		}
11964 
11965 		HANDLE currentCursor;
11966 
11967 		// returns zero if it recognized the event
11968 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11969 			MouseEvent mouse;
11970 
11971 			void mouseEvent(bool isScreen, ulong mods) {
11972 				auto x = LOWORD(lParam);
11973 				auto y = HIWORD(lParam);
11974 				if(isScreen) {
11975 					POINT p;
11976 					p.x = x;
11977 					p.y = y;
11978 					ScreenToClient(hwnd, &p);
11979 					x = cast(ushort) p.x;
11980 					y = cast(ushort) p.y;
11981 				}
11982 
11983 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11984 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11985 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11986 				}
11987 
11988 				mouse.x = x + offsetX;
11989 				mouse.y = y + offsetY;
11990 
11991 				wind.mdx(mouse);
11992 				mouse.modifierState = cast(int) mods;
11993 				mouse.window = wind;
11994 
11995 				if(wind.handleMouseEvent)
11996 					wind.handleMouseEvent(mouse);
11997 			}
11998 
11999 			switch(msg) {
12000 				case WM_GETMINMAXINFO:
12001 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
12002 
12003 					if(wind.minWidth > 0) {
12004 						RECT rect;
12005 						rect.left = 100;
12006 						rect.top = 100;
12007 						rect.right = wind.minWidth + 100;
12008 						rect.bottom = wind.minHeight + 100;
12009 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12010 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12011 
12012 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12013 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12014 					}
12015 
12016 					if(wind.maxWidth < int.max) {
12017 						RECT rect;
12018 						rect.left = 100;
12019 						rect.top = 100;
12020 						rect.right = wind.maxWidth + 100;
12021 						rect.bottom = wind.maxHeight + 100;
12022 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12023 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12024 
12025 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12026 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12027 					}
12028 				break;
12029 				case WM_CHAR:
12030 					wchar c = cast(wchar) wParam;
12031 					if(wind.handleCharEvent)
12032 						wind.handleCharEvent(cast(dchar) c);
12033 				break;
12034 				  case WM_SETFOCUS:
12035 				  case WM_KILLFOCUS:
12036 					wind._focused = (msg == WM_SETFOCUS);
12037 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12038 					if(wind.onFocusChange)
12039 						wind.onFocusChange(msg == WM_SETFOCUS);
12040 				  break;
12041 
12042 				case WM_SYSKEYDOWN:
12043 					goto case;
12044 				case WM_SYSKEYUP:
12045 					if(lParam & (1 << 29)) {
12046 						goto case;
12047 					} else {
12048 						// no window has keyboard focus
12049 						goto default;
12050 					}
12051 				case WM_KEYDOWN:
12052 				case WM_KEYUP:
12053 					KeyEvent ev;
12054 					ev.key = cast(Key) wParam;
12055 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12056 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12057 
12058 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12059 
12060 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12061 						ev.modifierState |= ModifierState.shift;
12062 					//k8: this doesn't work; thanks for nothing, windows
12063 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12064 						ev.modifierState |= ModifierState.alt;*/
12065 					// this never seems to actually be set
12066 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12067 
12068 					if (wParam == 0x12) {
12069 						altPressed = (msg == WM_SYSKEYDOWN);
12070 					}
12071 
12072 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12073 						altPressed = false;
12074 					}
12075 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12076 
12077 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12078 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12079 						ev.modifierState |= ModifierState.ctrl;
12080 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12081 						ev.modifierState |= ModifierState.windows;
12082 					if(GetKeyState(Key.NumLock))
12083 						ev.modifierState |= ModifierState.numLock;
12084 					if(GetKeyState(Key.CapsLock))
12085 						ev.modifierState |= ModifierState.capsLock;
12086 
12087 					/+
12088 					// we always want to send the character too, so let's convert it
12089 					ubyte[256] state;
12090 					wchar[16] buffer;
12091 					GetKeyboardState(state.ptr);
12092 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12093 
12094 					foreach(dchar d; buffer) {
12095 						ev.character = d;
12096 						break;
12097 					}
12098 					+/
12099 
12100 					ev.window = wind;
12101 					if(wind.handleKeyEvent)
12102 						wind.handleKeyEvent(ev);
12103 				break;
12104 				case 0x020a /*WM_MOUSEWHEEL*/:
12105 					// send click
12106 					mouse.type = cast(MouseEventType) 1;
12107 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12108 					mouseEvent(true, LOWORD(wParam));
12109 
12110 					// also send release
12111 					mouse.type = cast(MouseEventType) 2;
12112 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12113 					mouseEvent(true, LOWORD(wParam));
12114 				break;
12115 				case WM_MOUSEMOVE:
12116 					mouse.type = cast(MouseEventType) 0;
12117 					mouseEvent(false, wParam);
12118 				break;
12119 				case WM_LBUTTONDOWN:
12120 				case WM_LBUTTONDBLCLK:
12121 					mouse.type = cast(MouseEventType) 1;
12122 					mouse.button = MouseButton.left;
12123 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12124 					mouseEvent(false, wParam);
12125 				break;
12126 				case WM_LBUTTONUP:
12127 					mouse.type = cast(MouseEventType) 2;
12128 					mouse.button = MouseButton.left;
12129 					mouseEvent(false, wParam);
12130 				break;
12131 				case WM_RBUTTONDOWN:
12132 				case WM_RBUTTONDBLCLK:
12133 					mouse.type = cast(MouseEventType) 1;
12134 					mouse.button = MouseButton.right;
12135 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12136 					mouseEvent(false, wParam);
12137 				break;
12138 				case WM_RBUTTONUP:
12139 					mouse.type = cast(MouseEventType) 2;
12140 					mouse.button = MouseButton.right;
12141 					mouseEvent(false, wParam);
12142 				break;
12143 				case WM_MBUTTONDOWN:
12144 				case WM_MBUTTONDBLCLK:
12145 					mouse.type = cast(MouseEventType) 1;
12146 					mouse.button = MouseButton.middle;
12147 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12148 					mouseEvent(false, wParam);
12149 				break;
12150 				case WM_MBUTTONUP:
12151 					mouse.type = cast(MouseEventType) 2;
12152 					mouse.button = MouseButton.middle;
12153 					mouseEvent(false, wParam);
12154 				break;
12155 				case WM_XBUTTONDOWN:
12156 				case WM_XBUTTONDBLCLK:
12157 					mouse.type = cast(MouseEventType) 1;
12158 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12159 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12160 					mouseEvent(false, wParam);
12161 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12162 				case WM_XBUTTONUP:
12163 					mouse.type = cast(MouseEventType) 2;
12164 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12165 					mouseEvent(false, wParam);
12166 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12167 
12168 				default: return 1;
12169 			}
12170 			return 0;
12171 		}
12172 
12173 		HWND hwnd;
12174 		private int oldWidth;
12175 		private int oldHeight;
12176 		private bool inSizeMove;
12177 
12178 		/++
12179 			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.
12180 
12181 			History:
12182 				Added November 23, 2021
12183 
12184 				Not fully stable, may be moved out of the impl struct.
12185 
12186 				Default value changed to `true` on February 15, 2021
12187 		+/
12188 		bool doLiveResizing = true;
12189 
12190 		package int bmpWidth;
12191 		package int bmpHeight;
12192 
12193 		// the extern(Windows) wndproc should just forward to this
12194 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12195 		try {
12196 			assert(hwnd is this.hwnd);
12197 
12198 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12199 			switch(msg) {
12200 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12201 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12202 					// The main things we can do are select, execute, close, or ignore
12203 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12204 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12205 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12206 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12207 
12208 					// returns the value in the *high order word* of the return value
12209 					// hence the << 16
12210 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12211 				case WM_SETCURSOR:
12212 					if(cast(HWND) wParam !is hwnd)
12213 						return 0; // further processing elsewhere
12214 
12215 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12216 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12217 						return 1;
12218 					} else {
12219 						return DefWindowProc(hwnd, msg, wParam, lParam);
12220 					}
12221 				//break;
12222 
12223 				case WM_CLOSE:
12224 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12225 				break;
12226 				case WM_DESTROY:
12227 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12228 					SimpleWindow.nativeMapping.remove(hwnd);
12229 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12230 
12231 					bool anyImportant = false;
12232 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12233 						if(w.beingOpenKeepsAppOpen) {
12234 							anyImportant = true;
12235 							break;
12236 						}
12237 					if(!anyImportant) {
12238 						PostQuitMessage(0);
12239 					}
12240 				break;
12241 				case 0x02E0 /*WM_DPICHANGED*/:
12242 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12243 
12244 					RECT* prcNewWindow = cast(RECT*)lParam;
12245 					// docs say this is the recommended position and we should honor it
12246 					SetWindowPos(hwnd,
12247 							null,
12248 							prcNewWindow.left,
12249 							prcNewWindow.top,
12250 							prcNewWindow.right - prcNewWindow.left,
12251 							prcNewWindow.bottom - prcNewWindow.top,
12252 							SWP_NOZORDER | SWP_NOACTIVATE);
12253 
12254 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12255 					// im not sure it is completely correct
12256 					// but without it the tabs and such do look weird as things change.
12257 					if(SystemParametersInfoForDpi) {
12258 						LOGFONT lfText;
12259 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12260 						HFONT hFontNew = CreateFontIndirect(&lfText);
12261 						if (hFontNew)
12262 						{
12263 							//DeleteObject(hFontOld);
12264 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12265 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12266 								return TRUE;
12267 							}
12268 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12269 						}
12270 					}
12271 
12272 					if(this.onDpiChanged)
12273 						this.onDpiChanged();
12274 				break;
12275 				case WM_ENTERIDLE:
12276 					// when a menu is up, it stops normal event processing (modal message loop)
12277 					// but this at least gives us a chance to SOMETIMES catch up
12278 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12279 					SimpleWindow.processAllCustomEvents;
12280 					SimpleWindow.processAllCustomEvents;
12281 					SleepEx(0, true);
12282 					break;
12283 				case WM_SIZE:
12284 					if(wParam == 1 /* SIZE_MINIMIZED */)
12285 						break;
12286 					_width = LOWORD(lParam);
12287 					_height = HIWORD(lParam);
12288 
12289 					// I want to avoid tearing in the windows (my code is inefficient
12290 					// so this is a hack around that) so while sizing, we don't trigger,
12291 					// but we do want to trigger on events like mazimize.
12292 					if(!inSizeMove || doLiveResizing)
12293 						goto size_changed;
12294 				break;
12295 				/+
12296 				case WM_SIZING:
12297 					writeln("size");
12298 				break;
12299 				+/
12300 				// I don't like the tearing I get when redrawing on WM_SIZE
12301 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12302 				// so instead it is going to redraw only at the end of a size.
12303 				case 0x0231: /* WM_ENTERSIZEMOVE */
12304 					inSizeMove = true;
12305 				break;
12306 				case 0x0232: /* WM_EXITSIZEMOVE */
12307 					inSizeMove = false;
12308 
12309 					size_changed:
12310 
12311 					// nothing relevant changed, don't bother redrawing
12312 					if(oldWidth == _width && oldHeight == _height) {
12313 						if(msg == 0x0232)
12314 							goto finalize_resize;
12315 						break;
12316 					}
12317 
12318 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12319 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12320 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12321 						// gotta get the double buffer bmp to match the window
12322 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12323 
12324 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12325 						if(resizability != Resizability.automaticallyScaleIfPossible)
12326 						if(_width > bmpWidth || _height > bmpHeight) {
12327 							auto hdc = GetDC(hwnd);
12328 							auto oldBuffer = buffer;
12329 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12330 
12331 							auto hdcBmp = CreateCompatibleDC(hdc);
12332 							auto oldBmp = SelectObject(hdcBmp, buffer);
12333 
12334 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12335 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12336 
12337 							/+
12338 							RECT r;
12339 							r.left = 0;
12340 							r.top = 0;
12341 							r.right = width;
12342 							r.bottom = height;
12343 							auto c = Color.green;
12344 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12345 							FillRect(hdcBmp, &r, brush);
12346 							DeleteObject(brush);
12347 							+/
12348 
12349 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12350 
12351 							bmpWidth = _width;
12352 							bmpHeight = _height;
12353 
12354 							SelectObject(hdcOldBmp, oldOldBmp);
12355 							DeleteDC(hdcOldBmp);
12356 
12357 							SelectObject(hdcBmp, oldBmp);
12358 							DeleteDC(hdcBmp);
12359 
12360 							ReleaseDC(hwnd, hdc);
12361 
12362 							DeleteObject(oldBuffer);
12363 						}
12364 					}
12365 
12366 					updateOpenglViewportIfNeeded(_width, _height);
12367 
12368 					if(resizability != Resizability.automaticallyScaleIfPossible)
12369 					if(windowResized !is null)
12370 						windowResized(_width, _height);
12371 
12372 					/+
12373 					if(inSizeMove) {
12374 						// SimpleWindow.processAllCustomEvents();
12375 						// SimpleWindow.processAllCustomEvents();
12376 
12377 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12378 						//sdpyPrintDebugString("redraw b");
12379 					} else {
12380 					+/ {
12381 						finalize_resize:
12382 						// when it is all done, make sure everything is freshly drawn or there might be
12383 						// weird bugs left.
12384 						SimpleWindow.processAllCustomEvents();
12385 						SimpleWindow.processAllCustomEvents();
12386 
12387 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12388 						// sdpyPrintDebugString("redraw");
12389 					}
12390 
12391 					oldWidth = this._width;
12392 					oldHeight = this._height;
12393 				break;
12394 				case WM_ERASEBKGND:
12395 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12396 					if (!this._visibleForTheFirstTimeCalled) {
12397 						this._visibleForTheFirstTimeCalled = true;
12398 						if (this.visibleForTheFirstTime !is null) {
12399 							this.visibleForTheFirstTime();
12400 						}
12401 					}
12402 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12403 					version(without_opengl) {} else {
12404 						if (openglMode == OpenGlOptions.yes) return 1;
12405 					}
12406 					// call windows default handler, so it can paint standard controls
12407 					goto default;
12408 				case WM_CTLCOLORBTN:
12409 				case WM_CTLCOLORSTATIC:
12410 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12411 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12412 					GetSysColorBrush(COLOR_3DFACE);
12413 				//break;
12414 				case WM_SHOWWINDOW:
12415 					this._visible = (wParam != 0);
12416 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12417 						this._visibleForTheFirstTimeCalled = true;
12418 						if (this.visibleForTheFirstTime !is null) {
12419 							this.visibleForTheFirstTime();
12420 						}
12421 					}
12422 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12423 					break;
12424 				case WM_PAINT: {
12425 					if (!this._visibleForTheFirstTimeCalled) {
12426 						this._visibleForTheFirstTimeCalled = true;
12427 						if (this.visibleForTheFirstTime !is null) {
12428 							this.visibleForTheFirstTime();
12429 						}
12430 					}
12431 
12432 					BITMAP bm;
12433 					PAINTSTRUCT ps;
12434 
12435 					HDC hdc = BeginPaint(hwnd, &ps);
12436 
12437 					if(openglMode == OpenGlOptions.no) {
12438 
12439 						HDC hdcMem = CreateCompatibleDC(hdc);
12440 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12441 
12442 						GetObject(buffer, bm.sizeof, &bm);
12443 
12444 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12445 						if(resizability == Resizability.automaticallyScaleIfPossible)
12446 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12447 						else
12448 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12449 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12450 
12451 						SelectObject(hdcMem, hbmOld);
12452 						DeleteDC(hdcMem);
12453 						EndPaint(hwnd, &ps);
12454 					} else {
12455 						EndPaint(hwnd, &ps);
12456 						version(without_opengl) {} else
12457 							redrawOpenGlSceneSoon();
12458 					}
12459 				} break;
12460 				  default:
12461 					return DefWindowProc(hwnd, msg, wParam, lParam);
12462 			}
12463 			 return 0;
12464 
12465 		}
12466 		catch(Throwable t) {
12467 			sdpyPrintDebugString(t.toString);
12468 			return 0;
12469 		}
12470 		}
12471 	}
12472 
12473 	mixin template NativeImageImplementation() {
12474 		HBITMAP handle;
12475 		ubyte* rawData;
12476 
12477 	final:
12478 
12479 		Color getPixel(int x, int y) {
12480 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12481 			// remember, bmps are upside down
12482 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12483 
12484 			Color c;
12485 			if(enableAlpha)
12486 				c.a = rawData[offset + 3];
12487 			else
12488 				c.a = 255;
12489 			c.b = rawData[offset + 0];
12490 			c.g = rawData[offset + 1];
12491 			c.r = rawData[offset + 2];
12492 			c.unPremultiply();
12493 			return c;
12494 		}
12495 
12496 		void setPixel(int x, int y, Color c) {
12497 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12498 			// remember, bmps are upside down
12499 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12500 
12501 			if(enableAlpha)
12502 				c.premultiply();
12503 
12504 			rawData[offset + 0] = c.b;
12505 			rawData[offset + 1] = c.g;
12506 			rawData[offset + 2] = c.r;
12507 			if(enableAlpha)
12508 				rawData[offset + 3] = c.a;
12509 		}
12510 
12511 		void convertToRgbaBytes(ubyte[] where) {
12512 			assert(where.length == this.width * this.height * 4);
12513 
12514 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12515 			int idx = 0;
12516 			int offset = itemsPerLine * (height - 1);
12517 			// remember, bmps are upside down
12518 			for(int y = height - 1; y >= 0; y--) {
12519 				auto offsetStart = offset;
12520 				for(int x = 0; x < width; x++) {
12521 					where[idx + 0] = rawData[offset + 2]; // r
12522 					where[idx + 1] = rawData[offset + 1]; // g
12523 					where[idx + 2] = rawData[offset + 0]; // b
12524 					if(enableAlpha) {
12525 						where[idx + 3] = rawData[offset + 3]; // a
12526 						unPremultiplyRgba(where[idx .. idx + 4]);
12527 						offset++;
12528 					} else
12529 						where[idx + 3] = 255; // a
12530 					idx += 4;
12531 					offset += 3;
12532 				}
12533 
12534 				offset = offsetStart - itemsPerLine;
12535 			}
12536 		}
12537 
12538 		void setFromRgbaBytes(in ubyte[] what) {
12539 			assert(what.length == this.width * this.height * 4);
12540 
12541 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12542 			int idx = 0;
12543 			int offset = itemsPerLine * (height - 1);
12544 			// remember, bmps are upside down
12545 			for(int y = height - 1; y >= 0; y--) {
12546 				auto offsetStart = offset;
12547 				for(int x = 0; x < width; x++) {
12548 					if(enableAlpha) {
12549 						auto a = what[idx + 3];
12550 
12551 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12552 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12553 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12554 						rawData[offset + 3] = a; // a
12555 						//premultiplyBgra(rawData[offset .. offset + 4]);
12556 						offset++;
12557 					} else {
12558 						rawData[offset + 2] = what[idx + 0]; // r
12559 						rawData[offset + 1] = what[idx + 1]; // g
12560 						rawData[offset + 0] = what[idx + 2]; // b
12561 					}
12562 					idx += 4;
12563 					offset += 3;
12564 				}
12565 
12566 				offset = offsetStart - itemsPerLine;
12567 			}
12568 		}
12569 
12570 
12571 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12572 			BITMAPINFO infoheader;
12573 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12574 			infoheader.bmiHeader.biWidth = width;
12575 			infoheader.bmiHeader.biHeight = height;
12576 			infoheader.bmiHeader.biPlanes = 1;
12577 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12578 			infoheader.bmiHeader.biCompression = BI_RGB;
12579 
12580 			handle = CreateDIBSection(
12581 				null,
12582 				&infoheader,
12583 				DIB_RGB_COLORS,
12584 				cast(void**) &rawData,
12585 				null,
12586 				0);
12587 			if(handle is null)
12588 				throw new WindowsApiException("create image failed", GetLastError());
12589 
12590 		}
12591 
12592 		void dispose() {
12593 			DeleteObject(handle);
12594 		}
12595 	}
12596 
12597 	enum KEY_ESCAPE = 27;
12598 }
12599 version(X11) {
12600 	/// This is the default font used. You might change this before doing anything else with
12601 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12602 	/// for cross-platform compatibility.
12603 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12604 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12605 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12606 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12607 
12608 	alias int delegate(XEvent) NativeEventHandler;
12609 	alias Window NativeWindowHandle;
12610 
12611 	enum KEY_ESCAPE = 9;
12612 
12613 	mixin template NativeScreenPainterImplementation() {
12614 		Display* display;
12615 		Drawable d;
12616 		Drawable destiny;
12617 
12618 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12619 		GC gc;
12620 
12621 		__gshared bool fontAttempted;
12622 
12623 		__gshared XFontStruct* defaultfont;
12624 		__gshared XFontSet defaultfontset;
12625 
12626 		XFontStruct* font;
12627 		XFontSet fontset;
12628 
12629 		void create(PaintingHandle window) {
12630 			this.display = XDisplayConnection.get();
12631 
12632 			Drawable buffer = None;
12633 			if(auto sw = cast(SimpleWindow) this.window) {
12634 				buffer = sw.impl.buffer;
12635 				this.destiny = cast(Drawable) window;
12636 			} else {
12637 				buffer = cast(Drawable) window;
12638 				this.destiny = None;
12639 			}
12640 
12641 			this.d = cast(Drawable) buffer;
12642 
12643 			auto dgc = DefaultGC(display, DefaultScreen(display));
12644 
12645 			this.gc = XCreateGC(display, d, 0, null);
12646 
12647 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12648 
12649 			ensureDefaultFontLoaded();
12650 
12651 			font = defaultfont;
12652 			fontset = defaultfontset;
12653 
12654 			if(font) {
12655 				XSetFont(display, gc, font.fid);
12656 			}
12657 		}
12658 
12659 		static void ensureDefaultFontLoaded() {
12660 			if(!fontAttempted) {
12661 				auto display = XDisplayConnection.get;
12662 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12663 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12664 				if(font is null) {
12665 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12666 					font = XLoadQueryFont(display, xfontstr.ptr);
12667 				}
12668 
12669 				char** lol;
12670 				int lol2;
12671 				char* lol3;
12672 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12673 
12674 				fontAttempted = true;
12675 
12676 				defaultfont = font;
12677 				defaultfontset = fontset;
12678 			}
12679 		}
12680 
12681 		arsd.color.Rectangle _clipRectangle;
12682 		void setClipRectangle(int x, int y, int width, int height) {
12683 			auto old = _clipRectangle;
12684 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12685 			if(old == _clipRectangle)
12686 				return;
12687 
12688 			if(width == 0 || height == 0) {
12689 				XSetClipMask(display, gc, None);
12690 
12691 				if(xrenderPicturePainter) {
12692 
12693 					XRectangle[1] rects;
12694 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12695 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12696 				}
12697 
12698 				version(with_xft) {
12699 					if(xftFont is null || xftDraw is null)
12700 						return;
12701 					XftDrawSetClip(xftDraw, null);
12702 				}
12703 			} else {
12704 				XRectangle[1] rects;
12705 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12706 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12707 
12708 				if(xrenderPicturePainter)
12709 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12710 
12711 				version(with_xft) {
12712 					if(xftFont is null || xftDraw is null)
12713 						return;
12714 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12715 				}
12716 			}
12717 		}
12718 
12719 		version(with_xft) {
12720 			XftFont* xftFont;
12721 			XftDraw* xftDraw;
12722 
12723 			XftColor xftColor;
12724 
12725 			void updateXftColor() {
12726 				if(xftFont is null)
12727 					return;
12728 
12729 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12730 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12731 
12732 				XftColorAllocValue(
12733 					display,
12734 					DefaultVisual(display, DefaultScreen(display)),
12735 					DefaultColormap(display, 0),
12736 					&colorIn,
12737 					&xftColor
12738 				);
12739 			}
12740 		}
12741 
12742 		private OperatingSystemFont _activeFont;
12743 		void setFont(OperatingSystemFont font) {
12744 			_activeFont = font;
12745 			version(with_xft) {
12746 				if(font && font.isXft && font.xftFont)
12747 					this.xftFont = font.xftFont;
12748 				else
12749 					this.xftFont = null;
12750 
12751 				if(this.xftFont) {
12752 					if(xftDraw is null) {
12753 						xftDraw = XftDrawCreate(
12754 							display,
12755 							d,
12756 							DefaultVisual(display, DefaultScreen(display)),
12757 							DefaultColormap(display, 0)
12758 						);
12759 
12760 						updateXftColor();
12761 					}
12762 
12763 					return;
12764 				}
12765 			}
12766 
12767 			if(font && font.font) {
12768 				this.font = font.font;
12769 				this.fontset = font.fontset;
12770 				XSetFont(display, gc, font.font.fid);
12771 			} else {
12772 				this.font = defaultfont;
12773 				this.fontset = defaultfontset;
12774 			}
12775 
12776 		}
12777 
12778 		private Picture xrenderPicturePainter;
12779 
12780 		bool manualInvalidations;
12781 		void invalidateRect(Rectangle invalidRect) {
12782 			// FIXME if manualInvalidations
12783 		}
12784 
12785 		void dispose() {
12786 			this.rasterOp = RasterOp.normal;
12787 
12788 			if(xrenderPicturePainter) {
12789 				XRenderFreePicture(display, xrenderPicturePainter);
12790 				xrenderPicturePainter = None;
12791 			}
12792 
12793 			// FIXME: this.window.width/height is probably wrong
12794 
12795 			// src x,y     then dest x, y
12796 			if(destiny != None) {
12797 				// FIXME: if manual invalidations we can actually only copy some of the area.
12798 				// if(manualInvalidations)
12799 				XSetClipMask(display, gc, None);
12800 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12801 			}
12802 
12803 			XFreeGC(display, gc);
12804 
12805 			version(with_xft)
12806 			if(xftDraw) {
12807 				XftDrawDestroy(xftDraw);
12808 				xftDraw = null;
12809 			}
12810 
12811 			/+
12812 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12813 			if(font && font !is defaultfont) {
12814 				XFreeFont(display, font);
12815 				font = null;
12816 			}
12817 			if(fontset && fontset !is defaultfontset) {
12818 				XFreeFontSet(display, fontset);
12819 				fontset = null;
12820 			}
12821 			+/
12822 			XFlush(display);
12823 
12824 			if(window.paintingFinishedDg !is null)
12825 				window.paintingFinishedDg()();
12826 		}
12827 
12828 		bool backgroundIsNotTransparent = true;
12829 		bool foregroundIsNotTransparent = true;
12830 
12831 		bool _penInitialized = false;
12832 		Pen _activePen;
12833 
12834 		Color _outlineColor;
12835 		Color _fillColor;
12836 
12837 		@property void pen(Pen p) {
12838 			if(_penInitialized && p == _activePen) {
12839 				return;
12840 			}
12841 			_penInitialized = true;
12842 			_activePen = p;
12843 			_outlineColor = p.color;
12844 
12845 			int style;
12846 
12847 			byte dashLength;
12848 
12849 			final switch(p.style) {
12850 				case Pen.Style.Solid:
12851 					style = 0 /*LineSolid*/;
12852 				break;
12853 				case Pen.Style.Dashed:
12854 					style = 1 /*LineOnOffDash*/;
12855 					dashLength = 4;
12856 				break;
12857 				case Pen.Style.Dotted:
12858 					style = 1 /*LineOnOffDash*/;
12859 					dashLength = 1;
12860 				break;
12861 			}
12862 
12863 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
12864 			if(dashLength)
12865 				XSetDashes(display, gc, 0, &dashLength, 1);
12866 
12867 			if(p.color.a == 0) {
12868 				foregroundIsNotTransparent = false;
12869 				return;
12870 			}
12871 
12872 			foregroundIsNotTransparent = true;
12873 
12874 			XSetForeground(display, gc, colorToX(p.color, display));
12875 
12876 			version(with_xft)
12877 				updateXftColor();
12878 		}
12879 
12880 		RasterOp _currentRasterOp;
12881 		bool _currentRasterOpInitialized = false;
12882 		@property void rasterOp(RasterOp op) {
12883 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12884 				return;
12885 			_currentRasterOp = op;
12886 			_currentRasterOpInitialized = true;
12887 			int mode;
12888 			final switch(op) {
12889 				case RasterOp.normal:
12890 					mode = GXcopy;
12891 				break;
12892 				case RasterOp.xor:
12893 					mode = GXxor;
12894 				break;
12895 			}
12896 			XSetFunction(display, gc, mode);
12897 		}
12898 
12899 
12900 		bool _fillColorInitialized = false;
12901 
12902 		@property void fillColor(Color c) {
12903 			if(_fillColorInitialized && _fillColor == c)
12904 				return; // already good, no need to waste time calling it
12905 			_fillColor = c;
12906 			_fillColorInitialized = true;
12907 			if(c.a == 0) {
12908 				backgroundIsNotTransparent = false;
12909 				return;
12910 			}
12911 
12912 			backgroundIsNotTransparent = true;
12913 
12914 			XSetBackground(display, gc, colorToX(c, display));
12915 
12916 		}
12917 
12918 		void swapColors() {
12919 			auto tmp = _fillColor;
12920 			fillColor = _outlineColor;
12921 			auto newPen = _activePen;
12922 			newPen.color = tmp;
12923 			pen(newPen);
12924 		}
12925 
12926 		uint colorToX(Color c, Display* display) {
12927 			auto visual = DefaultVisual(display, DefaultScreen(display));
12928 			import core.bitop;
12929 			uint color = 0;
12930 			{
12931 			auto startBit = bsf(visual.red_mask);
12932 			auto lastBit = bsr(visual.red_mask);
12933 			auto r = cast(uint) c.r;
12934 			r >>= 7 - (lastBit - startBit);
12935 			r <<= startBit;
12936 			color |= r;
12937 			}
12938 			{
12939 			auto startBit = bsf(visual.green_mask);
12940 			auto lastBit = bsr(visual.green_mask);
12941 			auto g = cast(uint) c.g;
12942 			g >>= 7 - (lastBit - startBit);
12943 			g <<= startBit;
12944 			color |= g;
12945 			}
12946 			{
12947 			auto startBit = bsf(visual.blue_mask);
12948 			auto lastBit = bsr(visual.blue_mask);
12949 			auto b = cast(uint) c.b;
12950 			b >>= 7 - (lastBit - startBit);
12951 			b <<= startBit;
12952 			color |= b;
12953 			}
12954 
12955 
12956 
12957 			return color;
12958 		}
12959 
12960 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12961 			// source x, source y
12962 			if(ix >= i.width) return;
12963 			if(iy >= i.height) return;
12964 			if(ix + w > i.width) w = i.width - ix;
12965 			if(iy + h > i.height) h = i.height - iy;
12966 			if(i.usingXshm)
12967 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12968 			else
12969 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12970 		}
12971 
12972 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12973 			if(s.enableAlpha) {
12974 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12975 				if(this.xrenderPicturePainter == None) {
12976 					XRenderPictureAttributes attrs;
12977 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12978 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12979 
12980 					// need to initialize the clip
12981 					XRectangle[1] rects;
12982 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12983 
12984 					if(_clipRectangle != Rectangle.init)
12985 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12986 				}
12987 
12988 				XRenderComposite(
12989 					display,
12990 					3, // PicOpOver
12991 					s.xrenderPicture,
12992 					None,
12993 					this.xrenderPicturePainter,
12994 					ix,
12995 					iy,
12996 					0,
12997 					0,
12998 					x,
12999 					y,
13000 					w ? w : s.width,
13001 					h ? h : s.height
13002 				);
13003 			} else {
13004 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
13005 			}
13006 		}
13007 
13008 		int fontHeight() {
13009 			version(with_xft)
13010 				if(xftFont !is null)
13011 					return xftFont.height;
13012 			if(font)
13013 				return font.max_bounds.ascent + font.max_bounds.descent;
13014 			return 12; // pretty common default...
13015 		}
13016 
13017 		int textWidth(in char[] line) {
13018 			version(with_xft)
13019 			if(xftFont) {
13020 				if(line.length == 0)
13021 					return 0;
13022 				XGlyphInfo extents;
13023 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
13024 				return extents.width;
13025 			}
13026 
13027 			if(fontset) {
13028 				if(line.length == 0)
13029 					return 0;
13030 				XRectangle rect;
13031 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13032 
13033 				return rect.width;
13034 			}
13035 
13036 			if(font)
13037 				// FIXME: unicode
13038 				return XTextWidth( font, line.ptr, cast(int) line.length);
13039 			else
13040 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13041 		}
13042 
13043 		Size textSize(in char[] text) {
13044 			auto maxWidth = 0;
13045 			auto lineHeight = fontHeight;
13046 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13047 			foreach(line; text.split('\n')) {
13048 				int textWidth = this.textWidth(line);
13049 				if(textWidth > maxWidth)
13050 					maxWidth = textWidth;
13051 				h += lineHeight + 4;
13052 			}
13053 			return Size(maxWidth, h);
13054 		}
13055 
13056 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13057 			const(char)[] text;
13058 			version(with_xft)
13059 			if(xftFont) {
13060 				text = originalText;
13061 				goto loaded;
13062 			}
13063 
13064 			if(fontset)
13065 				text = originalText;
13066 			else {
13067 				text.reserve(originalText.length);
13068 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13069 				// then strip the rest so there isn't garbage
13070 				foreach(dchar ch; originalText)
13071 					if(ch < 256)
13072 						text ~= cast(ubyte) ch;
13073 					else
13074 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13075 			}
13076 			loaded:
13077 			if(text.length == 0)
13078 				return;
13079 
13080 			// FIXME: should we clip it to the bounding box?
13081 			int textHeight = fontHeight;
13082 
13083 			auto lines = text.split('\n');
13084 
13085 			const lineHeight = textHeight;
13086 			textHeight *= lines.length;
13087 
13088 			int cy = y;
13089 
13090 			if(alignment & TextAlignment.VerticalBottom) {
13091 				if(y2 <= 0)
13092 					return;
13093 				auto h = y2 - y;
13094 				if(h > textHeight) {
13095 					cy += h - textHeight;
13096 					cy -= lineHeight / 2;
13097 				}
13098 			} else if(alignment & TextAlignment.VerticalCenter) {
13099 				if(y2 <= 0)
13100 					return;
13101 				auto h = y2 - y;
13102 				if(textHeight < h) {
13103 					cy += (h - textHeight) / 2;
13104 					//cy -= lineHeight / 4;
13105 				}
13106 			}
13107 
13108 			foreach(line; text.split('\n')) {
13109 				int textWidth = this.textWidth(line);
13110 
13111 				int px = x, py = cy;
13112 
13113 				if(alignment & TextAlignment.Center) {
13114 					if(x2 <= 0)
13115 						return;
13116 					auto w = x2 - x;
13117 					if(w > textWidth)
13118 						px += (w - textWidth) / 2;
13119 				} else if(alignment & TextAlignment.Right) {
13120 					if(x2 <= 0)
13121 						return;
13122 					auto pos = x2 - textWidth;
13123 					if(pos > x)
13124 						px = pos;
13125 				}
13126 
13127 				version(with_xft)
13128 				if(xftFont) {
13129 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13130 
13131 					goto carry_on;
13132 				}
13133 
13134 				if(fontset)
13135 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13136 				else
13137 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13138 				carry_on:
13139 				cy += lineHeight + 4;
13140 			}
13141 		}
13142 
13143 		void drawPixel(int x, int y) {
13144 			XDrawPoint(display, d, gc, x, y);
13145 		}
13146 
13147 		// The basic shapes, outlined
13148 
13149 		void drawLine(int x1, int y1, int x2, int y2) {
13150 			if(foregroundIsNotTransparent)
13151 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13152 		}
13153 
13154 		void drawRectangle(int x, int y, int width, int height) {
13155 			if(backgroundIsNotTransparent) {
13156 				swapColors();
13157 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13158 				swapColors();
13159 			}
13160 			// 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
13161 			if(foregroundIsNotTransparent)
13162 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13163 		}
13164 
13165 		/// Arguments are the points of the bounding rectangle
13166 		void drawEllipse(int x1, int y1, int x2, int y2) {
13167 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13168 		}
13169 
13170 		// NOTE: start and finish are in units of degrees * 64
13171 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
13172 			if(backgroundIsNotTransparent) {
13173 				swapColors();
13174 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
13175 				swapColors();
13176 			}
13177 			if(foregroundIsNotTransparent) {
13178 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
13179 
13180 				// Windows draws the straight lines on the edges too so FIXME sort of
13181 			}
13182 		}
13183 
13184 		void drawPolygon(Point[] vertexes) {
13185 			XPoint[16] pointsBuffer;
13186 			XPoint[] points;
13187 			if(vertexes.length <= pointsBuffer.length)
13188 				points = pointsBuffer[0 .. vertexes.length];
13189 			else
13190 				points.length = vertexes.length;
13191 
13192 			foreach(i, p; vertexes) {
13193 				points[i].x = cast(short) p.x;
13194 				points[i].y = cast(short) p.y;
13195 			}
13196 
13197 			if(backgroundIsNotTransparent) {
13198 				swapColors();
13199 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13200 				swapColors();
13201 			}
13202 			if(foregroundIsNotTransparent) {
13203 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13204 			}
13205 		}
13206 	}
13207 
13208 	/* XRender { */
13209 
13210 	struct XRenderColor {
13211 		ushort red;
13212 		ushort green;
13213 		ushort blue;
13214 		ushort alpha;
13215 	}
13216 
13217 	alias Picture = XID;
13218 	alias PictFormat = XID;
13219 
13220 	struct XGlyphInfo {
13221 		ushort width;
13222 		ushort height;
13223 		short x;
13224 		short y;
13225 		short xOff;
13226 		short yOff;
13227 	}
13228 
13229 struct XRenderDirectFormat {
13230     short   red;
13231     short   redMask;
13232     short   green;
13233     short   greenMask;
13234     short   blue;
13235     short   blueMask;
13236     short   alpha;
13237     short   alphaMask;
13238 }
13239 
13240 struct XRenderPictFormat {
13241     PictFormat		id;
13242     int			type;
13243     int			depth;
13244     XRenderDirectFormat	direct;
13245     Colormap		colormap;
13246 }
13247 
13248 enum PictFormatID	=   (1 << 0);
13249 enum PictFormatType	=   (1 << 1);
13250 enum PictFormatDepth	=   (1 << 2);
13251 enum PictFormatRed	=   (1 << 3);
13252 enum PictFormatRedMask  =(1 << 4);
13253 enum PictFormatGreen	=   (1 << 5);
13254 enum PictFormatGreenMask=(1 << 6);
13255 enum PictFormatBlue	=   (1 << 7);
13256 enum PictFormatBlueMask =(1 << 8);
13257 enum PictFormatAlpha	=   (1 << 9);
13258 enum PictFormatAlphaMask=(1 << 10);
13259 enum PictFormatColormap =(1 << 11);
13260 
13261 struct XRenderPictureAttributes {
13262 	int 		repeat;
13263 	Picture		alpha_map;
13264 	int			alpha_x_origin;
13265 	int			alpha_y_origin;
13266 	int			clip_x_origin;
13267 	int			clip_y_origin;
13268 	Pixmap		clip_mask;
13269 	Bool		graphics_exposures;
13270 	int			subwindow_mode;
13271 	int			poly_edge;
13272 	int			poly_mode;
13273 	Atom		dither;
13274 	Bool		component_alpha;
13275 }
13276 
13277 alias int XFixed;
13278 
13279 struct XPointFixed {
13280     XFixed  x, y;
13281 }
13282 
13283 struct XCircle {
13284     XFixed x;
13285     XFixed y;
13286     XFixed radius;
13287 }
13288 
13289 struct XTransform {
13290     XFixed[3][3]  matrix;
13291 }
13292 
13293 struct XFilters {
13294     int	    nfilter;
13295     char    **filter;
13296     int	    nalias;
13297     short   *alias_;
13298 }
13299 
13300 struct XIndexValue {
13301     c_ulong    pixel;
13302     ushort   red, green, blue, alpha;
13303 }
13304 
13305 struct XAnimCursor {
13306     Cursor	    cursor;
13307     c_ulong   delay;
13308 }
13309 
13310 struct XLinearGradient {
13311     XPointFixed p1;
13312     XPointFixed p2;
13313 }
13314 
13315 struct XRadialGradient {
13316     XCircle inner;
13317     XCircle outer;
13318 }
13319 
13320 struct XConicalGradient {
13321     XPointFixed center;
13322     XFixed angle; /* in degrees */
13323 }
13324 
13325 enum PictStandardARGB32  = 0;
13326 enum PictStandardRGB24   = 1;
13327 enum PictStandardA8	 =  2;
13328 enum PictStandardA4	 =  3;
13329 enum PictStandardA1	 =  4;
13330 enum PictStandardNUM	 =  5;
13331 
13332 interface XRender {
13333 extern(C) @nogc:
13334 
13335 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13336 
13337 	Status XRenderQueryVersion (Display *dpy,
13338 			int     *major_versionp,
13339 			int     *minor_versionp);
13340 
13341 	Status XRenderQueryFormats (Display *dpy);
13342 
13343 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13344 
13345 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13346 
13347 	XRenderPictFormat *
13348 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13349 
13350 	XRenderPictFormat *
13351 		XRenderFindFormat (Display			*dpy,
13352 				c_ulong		mask,
13353 				const XRenderPictFormat	*templ,
13354 				int				count);
13355 	XRenderPictFormat *
13356 		XRenderFindStandardFormat (Display		*dpy,
13357 				int			format);
13358 
13359 	XIndexValue *
13360 		XRenderQueryPictIndexValues(Display			*dpy,
13361 				const XRenderPictFormat	*format,
13362 				int				*num);
13363 
13364 	Picture XRenderCreatePicture(
13365 		Display *dpy,
13366 		Drawable drawable,
13367 		const XRenderPictFormat *format,
13368 		c_ulong valuemask,
13369 		const XRenderPictureAttributes *attributes);
13370 
13371 	void XRenderChangePicture (Display				*dpy,
13372 				Picture				picture,
13373 				c_ulong			valuemask,
13374 				const XRenderPictureAttributes  *attributes);
13375 
13376 	void
13377 		XRenderSetPictureClipRectangles (Display	    *dpy,
13378 				Picture	    picture,
13379 				int		    xOrigin,
13380 				int		    yOrigin,
13381 				const XRectangle *rects,
13382 				int		    n);
13383 
13384 	void
13385 		XRenderSetPictureClipRegion (Display	    *dpy,
13386 				Picture	    picture,
13387 				Region	    r);
13388 
13389 	void
13390 		XRenderSetPictureTransform (Display	    *dpy,
13391 				Picture	    picture,
13392 				XTransform	    *transform);
13393 
13394 	void
13395 		XRenderFreePicture (Display                   *dpy,
13396 				Picture                   picture);
13397 
13398 	void
13399 		XRenderComposite (Display   *dpy,
13400 				int	    op,
13401 				Picture   src,
13402 				Picture   mask,
13403 				Picture   dst,
13404 				int	    src_x,
13405 				int	    src_y,
13406 				int	    mask_x,
13407 				int	    mask_y,
13408 				int	    dst_x,
13409 				int	    dst_y,
13410 				uint	width,
13411 				uint	height);
13412 
13413 
13414 	Picture XRenderCreateSolidFill (Display *dpy,
13415 			const XRenderColor *color);
13416 
13417 	Picture XRenderCreateLinearGradient (Display *dpy,
13418 			const XLinearGradient *gradient,
13419 			const XFixed *stops,
13420 			const XRenderColor *colors,
13421 			int nstops);
13422 
13423 	Picture XRenderCreateRadialGradient (Display *dpy,
13424 			const XRadialGradient *gradient,
13425 			const XFixed *stops,
13426 			const XRenderColor *colors,
13427 			int nstops);
13428 
13429 	Picture XRenderCreateConicalGradient (Display *dpy,
13430 			const XConicalGradient *gradient,
13431 			const XFixed *stops,
13432 			const XRenderColor *colors,
13433 			int nstops);
13434 
13435 
13436 
13437 	Cursor
13438 		XRenderCreateCursor (Display	    *dpy,
13439 				Picture	    source,
13440 				uint   x,
13441 				uint   y);
13442 
13443 	XFilters *
13444 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13445 
13446 	void
13447 		XRenderSetPictureFilter (Display    *dpy,
13448 				Picture    picture,
13449 				const char *filter,
13450 				XFixed	    *params,
13451 				int	    nparams);
13452 
13453 	Cursor
13454 		XRenderCreateAnimCursor (Display	*dpy,
13455 				int		ncursor,
13456 				XAnimCursor	*cursors);
13457 }
13458 
13459 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13460 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13461 
13462 	/* XRender } */
13463 
13464 	/* Xrandr { */
13465 
13466 struct XRRMonitorInfo {
13467     Atom name;
13468     Bool primary;
13469     Bool automatic;
13470     int noutput;
13471     int x;
13472     int y;
13473     int width;
13474     int height;
13475     int mwidth;
13476     int mheight;
13477     /*RROutput*/ void *outputs;
13478 }
13479 
13480 struct XRRScreenChangeNotifyEvent {
13481     int type;                   /* event base */
13482     c_ulong serial;       /* # of last request processed by server */
13483     Bool send_event;            /* true if this came from a SendEvent request */
13484     Display *display;           /* Display the event was read from */
13485     Window window;              /* window which selected for this event */
13486     Window root;                /* Root window for changed screen */
13487     Time timestamp;             /* when the screen change occurred */
13488     Time config_timestamp;      /* when the last configuration change */
13489     ushort/*SizeID*/ size_index;
13490     ushort/*SubpixelOrder*/ subpixel_order;
13491     ushort/*Rotation*/ rotation;
13492     int width;
13493     int height;
13494     int mwidth;
13495     int mheight;
13496 }
13497 
13498 enum RRScreenChangeNotify = 0;
13499 
13500 enum RRScreenChangeNotifyMask = 1;
13501 
13502 __gshared int xrrEventBase = -1;
13503 
13504 
13505 interface XRandr {
13506 extern(C) @nogc:
13507 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13508 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13509 
13510 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13511 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13512 
13513 	void XRRSelectInput(Display *dpy, Window window, int mask);
13514 }
13515 
13516 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13517 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13518 	/* Xrandr } */
13519 
13520 	/* Xft { */
13521 
13522 	// actually freetype
13523 	alias void FT_Face;
13524 
13525 	// actually fontconfig
13526 	private alias FcBool = int;
13527 	alias void FcCharSet;
13528 	alias void FcPattern;
13529 	alias void FcResult;
13530 	enum FcEndian { FcEndianBig, FcEndianLittle }
13531 	struct FcFontSet {
13532 		int nfont;
13533 		int sfont;
13534 		FcPattern** fonts;
13535 	}
13536 
13537 	// actually XRegion
13538 	struct BOX {
13539 		short x1, x2, y1, y2;
13540 	}
13541 	struct _XRegion {
13542 		c_long size;
13543 		c_long numRects;
13544 		BOX* rects;
13545 		BOX extents;
13546 	}
13547 
13548 	alias Region = _XRegion*;
13549 
13550 	// ok actually Xft
13551 
13552 	struct XftFontInfo;
13553 
13554 	struct XftFont {
13555 		int         ascent;
13556 		int         descent;
13557 		int         height;
13558 		int         max_advance_width;
13559 		FcCharSet*  charset;
13560 		FcPattern*  pattern;
13561 	}
13562 
13563 	struct XftDraw;
13564 
13565 	struct XftColor {
13566 		c_ulong pixel;
13567 		XRenderColor color;
13568 	}
13569 
13570 	struct XftCharSpec {
13571 		dchar           ucs4;
13572 		short           x;
13573 		short           y;
13574 	}
13575 
13576 	struct XftCharFontSpec {
13577 		XftFont         *font;
13578 		dchar           ucs4;
13579 		short           x;
13580 		short           y;
13581 	}
13582 
13583 	struct XftGlyphSpec {
13584 		uint            glyph;
13585 		short           x;
13586 		short           y;
13587 	}
13588 
13589 	struct XftGlyphFontSpec {
13590 		XftFont         *font;
13591 		uint            glyph;
13592 		short           x;
13593 		short           y;
13594 	}
13595 
13596 	interface Xft {
13597 	extern(C) @nogc pure:
13598 
13599 	Bool XftColorAllocName (Display  *dpy,
13600 				const Visual   *visual,
13601 				Colormap cmap,
13602 				const char     *name,
13603 				XftColor *result);
13604 
13605 	Bool XftColorAllocValue (Display         *dpy,
13606 				Visual          *visual,
13607 				Colormap        cmap,
13608 				const XRenderColor    *color,
13609 				XftColor        *result);
13610 
13611 	void XftColorFree (Display   *dpy,
13612 				Visual    *visual,
13613 				Colormap  cmap,
13614 				XftColor  *color);
13615 
13616 	Bool XftDefaultHasRender (Display *dpy);
13617 
13618 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13619 
13620 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13621 
13622 	XftDraw * XftDrawCreate (Display   *dpy,
13623 		       Drawable  drawable,
13624 		       Visual    *visual,
13625 		       Colormap  colormap);
13626 
13627 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13628 			     Pixmap   bitmap);
13629 
13630 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13631 			    Pixmap  pixmap,
13632 			    int     depth);
13633 
13634 	void XftDrawChange (XftDraw  *draw,
13635 		       Drawable drawable);
13636 
13637 	Display * XftDrawDisplay (XftDraw *draw);
13638 
13639 	Drawable XftDrawDrawable (XftDraw *draw);
13640 
13641 	Colormap XftDrawColormap (XftDraw *draw);
13642 
13643 	Visual * XftDrawVisual (XftDraw *draw);
13644 
13645 	void XftDrawDestroy (XftDraw *draw);
13646 
13647 	Picture XftDrawPicture (XftDraw *draw);
13648 
13649 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13650 
13651 	void XftDrawGlyphs (XftDraw          *draw,
13652 				const XftColor *color,
13653 				XftFont          *pub,
13654 				int              x,
13655 				int              y,
13656 				const uint  *glyphs,
13657 				int              nglyphs);
13658 
13659 	void XftDrawString8 (XftDraw             *draw,
13660 				const XftColor    *color,
13661 				XftFont             *pub,
13662 				int                 x,
13663 				int                 y,
13664 				const char     *string,
13665 				int                 len);
13666 
13667 	void XftDrawString16 (XftDraw            *draw,
13668 				const XftColor   *color,
13669 				XftFont            *pub,
13670 				int                x,
13671 				int                y,
13672 				const wchar   *string,
13673 				int                len);
13674 
13675 	void XftDrawString32 (XftDraw            *draw,
13676 				const XftColor   *color,
13677 				XftFont            *pub,
13678 				int                x,
13679 				int                y,
13680 				const dchar   *string,
13681 				int                len);
13682 
13683 	void XftDrawStringUtf8 (XftDraw          *draw,
13684 				const XftColor *color,
13685 				XftFont          *pub,
13686 				int              x,
13687 				int              y,
13688 				const char  *string,
13689 				int              len);
13690 	void XftDrawStringUtf16 (XftDraw             *draw,
13691 				const XftColor    *color,
13692 				XftFont             *pub,
13693 				int                 x,
13694 				int                 y,
13695 				const char     *string,
13696 				FcEndian            endian,
13697 				int                 len);
13698 
13699 	void XftDrawCharSpec (XftDraw                *draw,
13700 				const XftColor       *color,
13701 				XftFont                *pub,
13702 				const XftCharSpec    *chars,
13703 				int                    len);
13704 
13705 	void XftDrawCharFontSpec (XftDraw                    *draw,
13706 				const XftColor           *color,
13707 				const XftCharFontSpec    *chars,
13708 				int                        len);
13709 
13710 	void XftDrawGlyphSpec (XftDraw               *draw,
13711 				const XftColor      *color,
13712 				XftFont               *pub,
13713 				const XftGlyphSpec  *glyphs,
13714 				int                   len);
13715 
13716 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13717 				const XftColor          *color,
13718 				const XftGlyphFontSpec  *glyphs,
13719 				int                       len);
13720 
13721 	void XftDrawRect (XftDraw            *draw,
13722 				const XftColor   *color,
13723 				int                x,
13724 				int                y,
13725 				uint       width,
13726 				uint       height);
13727 
13728 	Bool XftDrawSetClip (XftDraw     *draw,
13729 				Region      r);
13730 
13731 
13732 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13733 				int                   xOrigin,
13734 				int                   yOrigin,
13735 				const XRectangle    *rects,
13736 				int                   n);
13737 
13738 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13739 				int        mode);
13740 
13741 	void XftGlyphExtents (Display            *dpy,
13742 				XftFont            *pub,
13743 				const uint    *glyphs,
13744 				int                nglyphs,
13745 				XGlyphInfo         *extents);
13746 
13747 	void XftTextExtents8 (Display            *dpy,
13748 				XftFont            *pub,
13749 				const char    *string,
13750 				int                len,
13751 				XGlyphInfo         *extents);
13752 
13753 	void XftTextExtents16 (Display           *dpy,
13754 				XftFont           *pub,
13755 				const wchar  *string,
13756 				int               len,
13757 				XGlyphInfo        *extents);
13758 
13759 	void XftTextExtents32 (Display           *dpy,
13760 				XftFont           *pub,
13761 				const dchar  *string,
13762 				int               len,
13763 				XGlyphInfo        *extents);
13764 
13765 	void XftTextExtentsUtf8 (Display         *dpy,
13766 				XftFont         *pub,
13767 				const char *string,
13768 				int             len,
13769 				XGlyphInfo      *extents);
13770 
13771 	void XftTextExtentsUtf16 (Display            *dpy,
13772 				XftFont            *pub,
13773 				const char    *string,
13774 				FcEndian           endian,
13775 				int                len,
13776 				XGlyphInfo         *extents);
13777 
13778 	FcPattern * XftFontMatch (Display           *dpy,
13779 				int               screen,
13780 				const FcPattern *pattern,
13781 				FcResult          *result);
13782 
13783 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13784 
13785 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13786 
13787 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13788 
13789 	FT_Face XftLockFace (XftFont *pub);
13790 
13791 	void XftUnlockFace (XftFont *pub);
13792 
13793 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13794 
13795 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13796 
13797 	dchar XftFontInfoHash (const XftFontInfo *fi);
13798 
13799 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13800 
13801 	XftFont * XftFontOpenInfo (Display        *dpy,
13802 				FcPattern      *pattern,
13803 				XftFontInfo    *fi);
13804 
13805 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13806 
13807 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13808 
13809 	void XftFontClose (Display *dpy, XftFont *pub);
13810 
13811 	FcBool XftInitFtLibrary();
13812 	void XftFontLoadGlyphs (Display          *dpy,
13813 				XftFont          *pub,
13814 				FcBool           need_bitmaps,
13815 				const uint  *glyphs,
13816 				int              nglyph);
13817 
13818 	void XftFontUnloadGlyphs (Display            *dpy,
13819 				XftFont            *pub,
13820 				const uint    *glyphs,
13821 				int                nglyph);
13822 
13823 	FcBool XftFontCheckGlyph (Display  *dpy,
13824 				XftFont  *pub,
13825 				FcBool   need_bitmaps,
13826 				uint  glyph,
13827 				uint  *missing,
13828 				int      *nmissing);
13829 
13830 	FcBool XftCharExists (Display      *dpy,
13831 				XftFont      *pub,
13832 				dchar    ucs4);
13833 
13834 	uint XftCharIndex (Display       *dpy,
13835 				XftFont       *pub,
13836 				dchar      ucs4);
13837 	FcBool XftInit (const char *config);
13838 
13839 	int XftGetVersion ();
13840 
13841 	FcFontSet * XftListFonts (Display   *dpy,
13842 				int       screen,
13843 				...);
13844 
13845 	FcPattern *XftNameParse (const char *name);
13846 
13847 	void XftGlyphRender (Display         *dpy,
13848 				int             op,
13849 				Picture         src,
13850 				XftFont         *pub,
13851 				Picture         dst,
13852 				int             srcx,
13853 				int             srcy,
13854 				int             x,
13855 				int             y,
13856 				const uint *glyphs,
13857 				int             nglyphs);
13858 
13859 	void XftGlyphSpecRender (Display                 *dpy,
13860 				int                     op,
13861 				Picture                 src,
13862 				XftFont                 *pub,
13863 				Picture                 dst,
13864 				int                     srcx,
13865 				int                     srcy,
13866 				const XftGlyphSpec    *glyphs,
13867 				int                     nglyphs);
13868 
13869 	void XftCharSpecRender (Display              *dpy,
13870 				int                  op,
13871 				Picture              src,
13872 				XftFont              *pub,
13873 				Picture              dst,
13874 				int                  srcx,
13875 				int                  srcy,
13876 				const XftCharSpec  *chars,
13877 				int                  len);
13878 	void XftGlyphFontSpecRender (Display                     *dpy,
13879 				int                         op,
13880 				Picture                     src,
13881 				Picture                     dst,
13882 				int                         srcx,
13883 				int                         srcy,
13884 				const XftGlyphFontSpec    *glyphs,
13885 				int                         nglyphs);
13886 
13887 	void XftCharFontSpecRender (Display                  *dpy,
13888 				int                      op,
13889 				Picture                  src,
13890 				Picture                  dst,
13891 				int                      srcx,
13892 				int                      srcy,
13893 				const XftCharFontSpec  *chars,
13894 				int                      len);
13895 
13896 	void XftTextRender8 (Display         *dpy,
13897 				int             op,
13898 				Picture         src,
13899 				XftFont         *pub,
13900 				Picture         dst,
13901 				int             srcx,
13902 				int             srcy,
13903 				int             x,
13904 				int             y,
13905 				const char *string,
13906 				int             len);
13907 	void XftTextRender16 (Display            *dpy,
13908 				int                op,
13909 				Picture            src,
13910 				XftFont            *pub,
13911 				Picture            dst,
13912 				int                srcx,
13913 				int                srcy,
13914 				int                x,
13915 				int                y,
13916 				const wchar   *string,
13917 				int                len);
13918 
13919 	void XftTextRender16BE (Display          *dpy,
13920 				int              op,
13921 				Picture          src,
13922 				XftFont          *pub,
13923 				Picture          dst,
13924 				int              srcx,
13925 				int              srcy,
13926 				int              x,
13927 				int              y,
13928 				const char  *string,
13929 				int              len);
13930 
13931 	void XftTextRender16LE (Display          *dpy,
13932 				int              op,
13933 				Picture          src,
13934 				XftFont          *pub,
13935 				Picture          dst,
13936 				int              srcx,
13937 				int              srcy,
13938 				int              x,
13939 				int              y,
13940 				const char  *string,
13941 				int              len);
13942 
13943 	void XftTextRender32 (Display            *dpy,
13944 				int                op,
13945 				Picture            src,
13946 				XftFont            *pub,
13947 				Picture            dst,
13948 				int                srcx,
13949 				int                srcy,
13950 				int                x,
13951 				int                y,
13952 				const dchar   *string,
13953 				int                len);
13954 
13955 	void XftTextRender32BE (Display          *dpy,
13956 				int              op,
13957 				Picture          src,
13958 				XftFont          *pub,
13959 				Picture          dst,
13960 				int              srcx,
13961 				int              srcy,
13962 				int              x,
13963 				int              y,
13964 				const char  *string,
13965 				int              len);
13966 
13967 	void XftTextRender32LE (Display          *dpy,
13968 				int              op,
13969 				Picture          src,
13970 				XftFont          *pub,
13971 				Picture          dst,
13972 				int              srcx,
13973 				int              srcy,
13974 				int              x,
13975 				int              y,
13976 				const char  *string,
13977 				int              len);
13978 
13979 	void XftTextRenderUtf8 (Display          *dpy,
13980 				int              op,
13981 				Picture          src,
13982 				XftFont          *pub,
13983 				Picture          dst,
13984 				int              srcx,
13985 				int              srcy,
13986 				int              x,
13987 				int              y,
13988 				const char  *string,
13989 				int              len);
13990 
13991 	void XftTextRenderUtf16 (Display         *dpy,
13992 				int             op,
13993 				Picture         src,
13994 				XftFont         *pub,
13995 				Picture         dst,
13996 				int             srcx,
13997 				int             srcy,
13998 				int             x,
13999 				int             y,
14000 				const char *string,
14001 				FcEndian        endian,
14002 				int             len);
14003 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
14004 
14005 	}
14006 
14007 	interface FontConfig {
14008 	extern(C) @nogc pure:
14009 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
14010 		void FcFontSetDestroy(FcFontSet*);
14011 		char* FcNameUnparse(const FcPattern *);
14012 	}
14013 
14014 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
14015 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
14016 
14017 
14018 	/* Xft } */
14019 
14020 	class XDisconnectException : Exception {
14021 		bool userRequested;
14022 		this(bool userRequested = true) {
14023 			this.userRequested = userRequested;
14024 			super("X disconnected");
14025 		}
14026 	}
14027 
14028 	/++
14029 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14030 
14031 		Please note that it returns
14032 	+/
14033 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14034 
14035 		static XErrorEvent[] errorBuffer;
14036 
14037 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14038 			errorBuffer ~= *evt;
14039 			return 0;
14040 		}
14041 
14042 		auto savedErrorHandler = XSetErrorHandler(&handler);
14043 
14044 		try {
14045 			dg();
14046 		} finally {
14047 			XSync(XDisplayConnection.get, 0/*False*/);
14048 			XSetErrorHandler(savedErrorHandler);
14049 		}
14050 
14051 		auto bfr = errorBuffer;
14052 		errorBuffer = null;
14053 
14054 		return bfr;
14055 	}
14056 
14057 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14058 	class XDisplayConnection {
14059 		private __gshared Display* display;
14060 		private __gshared XIM xim;
14061 		private __gshared char* displayName;
14062 
14063 		private __gshared int connectionSequence_;
14064 		private __gshared bool isLocal_;
14065 
14066 		/// use this for lazy caching when reconnection
14067 		static int connectionSequenceNumber() { return connectionSequence_; }
14068 
14069 		/++
14070 			Guesses if the connection appears to be local.
14071 
14072 			History:
14073 				Added June 3, 2021
14074 		+/
14075 		static @property bool isLocal() nothrow @trusted @nogc {
14076 			return isLocal_;
14077 		}
14078 
14079 		/// Attempts recreation of state, may require application assistance
14080 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14081 		/// then call this, and if successful, reenter the loop.
14082 		static void discardAndRecreate(string newDisplayString = null) {
14083 			if(insideXEventLoop)
14084 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14085 
14086 			// 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
14087 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14088 
14089 			foreach(handle; chnenhm) {
14090 				handle.discardConnectionState();
14091 			}
14092 
14093 			discardState();
14094 
14095 			if(newDisplayString !is null)
14096 				setDisplayName(newDisplayString);
14097 
14098 			auto display = get();
14099 
14100 			foreach(handle; chnenhm) {
14101 				handle.recreateAfterDisconnect();
14102 			}
14103 		}
14104 
14105 		private __gshared EventMask rootEventMask;
14106 
14107 		/++
14108 			Requests the specified input from the root window on the connection, in addition to any other request.
14109 
14110 
14111 			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.
14112 
14113 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14114 		+/
14115 		static void addRootInput(EventMask mask) {
14116 			auto old = rootEventMask;
14117 			rootEventMask |= mask;
14118 			get(); // to ensure display connected
14119 			if(display !is null && rootEventMask != old)
14120 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14121 		}
14122 
14123 		static void discardState() {
14124 			freeImages();
14125 
14126 			foreach(atomPtr; interredAtoms)
14127 				*atomPtr = 0;
14128 			interredAtoms = null;
14129 			interredAtoms.assumeSafeAppend();
14130 
14131 			ScreenPainterImplementation.fontAttempted = false;
14132 			ScreenPainterImplementation.defaultfont = null;
14133 			ScreenPainterImplementation.defaultfontset = null;
14134 
14135 			Image.impl.xshmQueryCompleted = false;
14136 			Image.impl._xshmAvailable = false;
14137 
14138 			SimpleWindow.nativeMapping = null;
14139 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14140 			// GlobalHotkeyManager
14141 
14142 			display = null;
14143 			xim = null;
14144 		}
14145 
14146 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14147 		private static void createXIM () {
14148 			import core.stdc.locale : setlocale, LC_ALL;
14149 			import core.stdc.stdio : stderr, fprintf;
14150 			import core.stdc.stdlib : free;
14151 			import core.stdc.string : strdup;
14152 
14153 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14154 
14155 			auto olocale = strdup(setlocale(LC_ALL, null));
14156 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14157 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14158 
14159 			//fprintf(stderr, "opening IM...\n");
14160 			foreach (string s; mtry) {
14161 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14162 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14163 			}
14164 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14165 		}
14166 
14167 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14168 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14169 		static struct ImgList {
14170 			size_t img; // class; hide it from GC
14171 			ImgList* next;
14172 		}
14173 
14174 		static __gshared ImgList* imglist = null;
14175 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14176 
14177 		static void registerImage (Image img) {
14178 			if (!imglistLocked && img !is null) {
14179 				import core.stdc.stdlib : malloc;
14180 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14181 				assert(it !is null); // do proper checks
14182 				it.img = cast(size_t)cast(void*)img;
14183 				it.next = imglist;
14184 				imglist = it;
14185 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14186 			}
14187 		}
14188 
14189 		static void unregisterImage (Image img) {
14190 			if (!imglistLocked && img !is null) {
14191 				import core.stdc.stdlib : free;
14192 				ImgList* prev = null;
14193 				ImgList* cur = imglist;
14194 				while (cur !is null) {
14195 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14196 					prev = cur;
14197 					cur = cur.next;
14198 				}
14199 				if (cur !is null) {
14200 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14201 					free(cur);
14202 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14203 				} else {
14204 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14205 				}
14206 			}
14207 		}
14208 
14209 		static void freeImages () { // needed for discardAndRecreate
14210 			imglistLocked = true;
14211 			scope(exit) imglistLocked = false;
14212 			ImgList* cur = imglist;
14213 			ImgList* next = null;
14214 			while (cur !is null) {
14215 				import core.stdc.stdlib : free;
14216 				next = cur.next;
14217 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14218 				(cast(Image)cast(void*)cur.img).dispose();
14219 				free(cur);
14220 				cur = next;
14221 			}
14222 			imglist = null;
14223 		}
14224 
14225 		/// can be used to override normal handling of display name
14226 		/// from environment and/or command line
14227 		static setDisplayName(string newDisplayName) {
14228 			displayName = cast(char*) (newDisplayName ~ '\0');
14229 		}
14230 
14231 		/// resets to the default display string
14232 		static resetDisplayName() {
14233 			displayName = null;
14234 		}
14235 
14236 		///
14237 		static Display* get() {
14238 			if(display is null) {
14239 				if(!librariesSuccessfullyLoaded)
14240 					throw new Exception("Unable to load X11 client libraries");
14241 				display = XOpenDisplay(displayName);
14242 
14243 				isLocal_ = false;
14244 
14245 				connectionSequence_++;
14246 				if(display is null)
14247 					throw new Exception("Unable to open X display");
14248 
14249 				auto str = display.display_name;
14250 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14251 				// and otherwise it probably isn't
14252 				if(str is null || (str[0] != ':' && str[0] != '/'))
14253 					isLocal_ = false;
14254 				else
14255 					isLocal_ = true;
14256 
14257 				debug(sdpy_x_errors) {
14258 					XSetErrorHandler(&adrlogger);
14259 					XSynchronize(display, true);
14260 
14261 					extern(C) int wtf() {
14262 						if(errorHappened) {
14263 							asm { int 3; }
14264 							errorHappened = false;
14265 						}
14266 						return 0;
14267 					}
14268 					XSetAfterFunction(display, &wtf);
14269 				}
14270 
14271 
14272 				XSetIOErrorHandler(&x11ioerrCB);
14273 				Bool sup;
14274 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14275 				createXIM();
14276 				version(with_eventloop) {
14277 					import arsd.eventloop;
14278 					addFileEventListeners(display.fd, &eventListener, null, null);
14279 				}
14280 			}
14281 
14282 			return display;
14283 		}
14284 
14285 		extern(C)
14286 		static int x11ioerrCB(Display* dpy) {
14287 			throw new XDisconnectException(false);
14288 		}
14289 
14290 		version(with_eventloop) {
14291 			import arsd.eventloop;
14292 			static void eventListener(OsFileHandle fd) {
14293 				//this.mtLock();
14294 				//scope(exit) this.mtUnlock();
14295 				while(XPending(display))
14296 					doXNextEvent(display);
14297 			}
14298 		}
14299 
14300 		// close connection on program exit -- we need this to properly free all images
14301 		static ~this () {
14302 			// the gui thread must clean up after itself or else Xlib might deadlock
14303 			// using this flag on any thread destruction is the easiest way i know of
14304 			// (shared static this is run by the LAST thread to exit, which may not be
14305 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14306 			if(thisIsGuiThread)
14307 				close();
14308 		}
14309 
14310 		///
14311 		static void close() {
14312 			if(display is null)
14313 				return;
14314 
14315 			version(with_eventloop) {
14316 				import arsd.eventloop;
14317 				removeFileEventListeners(display.fd);
14318 			}
14319 
14320 			// now remove all registered images to prevent shared memory leaks
14321 			freeImages();
14322 
14323 			// tbh I don't know why it is doing this but like if this happens to run
14324 			// from the other thread there's frequent hanging inside here.
14325 			if(thisIsGuiThread)
14326 				XCloseDisplay(display);
14327 			display = null;
14328 		}
14329 	}
14330 
14331 	mixin template NativeImageImplementation() {
14332 		XImage* handle;
14333 		ubyte* rawData;
14334 
14335 		XShmSegmentInfo shminfo;
14336 		bool premultiply = true;
14337 
14338 		__gshared bool xshmQueryCompleted;
14339 		__gshared bool _xshmAvailable;
14340 		public static @property bool xshmAvailable() {
14341 			if(!xshmQueryCompleted) {
14342 				int i1, i2, i3;
14343 				xshmQueryCompleted = true;
14344 
14345 				if(!XDisplayConnection.isLocal)
14346 					_xshmAvailable = false;
14347 				else
14348 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14349 			}
14350 			return _xshmAvailable;
14351 		}
14352 
14353 		bool usingXshm;
14354 	final:
14355 
14356 		private __gshared bool xshmfailed;
14357 
14358 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14359 			auto display = XDisplayConnection.get();
14360 			assert(display !is null);
14361 			auto screen = DefaultScreen(display);
14362 
14363 			// it will only use shared memory for somewhat largish images,
14364 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14365 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14366 
14367 
14368 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14369 				// the actual use still fails. For example, if the program is in a container and permission denied
14370 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14371 				//
14372 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14373 
14374 
14375 				// synchronize so preexisting buffers are clear
14376 				XSync(display, false);
14377 				xshmfailed = false;
14378 
14379 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14380 
14381 
14382 				usingXshm = true;
14383 				handle = XShmCreateImage(
14384 					display,
14385 					DefaultVisual(display, screen),
14386 					enableAlpha ? 32: 24,
14387 					ImageFormat.ZPixmap,
14388 					null,
14389 					&shminfo,
14390 					width, height);
14391 				if(handle is null)
14392 					goto abortXshm1;
14393 
14394 				if(handle.bytes_per_line != 4 * width)
14395 					goto abortXshm2;
14396 
14397 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14398 				if(shminfo.shmid < 0)
14399 					goto abortXshm3;
14400 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14401 				if(rawData == cast(ubyte*) -1)
14402 					goto abortXshm4;
14403 				shminfo.readOnly = 0;
14404 				XShmAttach(display, &shminfo);
14405 
14406 				// and now to the final error check to ensure it actually worked.
14407 				XSync(display, false);
14408 				if(xshmfailed)
14409 					goto abortXshm5;
14410 
14411 				XSetErrorHandler(oldErrorHandler);
14412 
14413 				XDisplayConnection.registerImage(this);
14414 				// if I don't flush here there's a chance the dtor will run before the
14415 				// ctor and lead to a bad value X error. While this hurts the efficiency
14416 				// it is local anyway so prolly better to keep it simple
14417 				XFlush(display);
14418 
14419 				return;
14420 
14421 				abortXshm5:
14422 					shmdt(shminfo.shmaddr);
14423 					rawData = null;
14424 
14425 				abortXshm4:
14426 					shmctl(shminfo.shmid, IPC_RMID, null);
14427 
14428 				abortXshm3:
14429 					// nothing needed, the shmget failed so there's nothing to free
14430 
14431 				abortXshm2:
14432 					XDestroyImage(handle);
14433 					handle = null;
14434 
14435 				abortXshm1:
14436 					XSetErrorHandler(oldErrorHandler);
14437 					usingXshm = false;
14438 					handle = null;
14439 
14440 					shminfo = typeof(shminfo).init;
14441 
14442 					_xshmAvailable = false; // don't try again in the future
14443 
14444 					// writeln("fallingback");
14445 
14446 					goto fallback;
14447 
14448 			} else {
14449 				fallback:
14450 
14451 				if (forcexshm) throw new Exception("can't create XShm Image");
14452 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14453 				import core.stdc.stdlib : malloc;
14454 				rawData = cast(ubyte*) malloc(width * height * 4);
14455 
14456 				handle = XCreateImage(
14457 					display,
14458 					DefaultVisual(display, screen),
14459 					enableAlpha ? 32 : 24, // bpp
14460 					ImageFormat.ZPixmap,
14461 					0, // offset
14462 					rawData,
14463 					width, height,
14464 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14465 			}
14466 		}
14467 
14468 		void dispose() {
14469 			// note: this calls free(rawData) for us
14470 			if(handle) {
14471 				if (usingXshm) {
14472 					XDisplayConnection.unregisterImage(this);
14473 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14474 				}
14475 				XDestroyImage(handle);
14476 				if(usingXshm) {
14477 					shmdt(shminfo.shmaddr);
14478 					shmctl(shminfo.shmid, IPC_RMID, null);
14479 				}
14480 				handle = null;
14481 			}
14482 		}
14483 
14484 		Color getPixel(int x, int y) {
14485 			auto offset = (y * width + x) * 4;
14486 			Color c;
14487 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14488 			c.b = rawData[offset + 0];
14489 			c.g = rawData[offset + 1];
14490 			c.r = rawData[offset + 2];
14491 			if(enableAlpha && premultiply)
14492 				c.unPremultiply;
14493 			return c;
14494 		}
14495 
14496 		void setPixel(int x, int y, Color c) {
14497 			if(enableAlpha && premultiply)
14498 				c.premultiply();
14499 			auto offset = (y * width + x) * 4;
14500 			rawData[offset + 0] = c.b;
14501 			rawData[offset + 1] = c.g;
14502 			rawData[offset + 2] = c.r;
14503 			if(enableAlpha)
14504 				rawData[offset + 3] = c.a;
14505 		}
14506 
14507 		void convertToRgbaBytes(ubyte[] where) {
14508 			assert(where.length == this.width * this.height * 4);
14509 
14510 			// if rawData had a length....
14511 			//assert(rawData.length == where.length);
14512 			for(int idx = 0; idx < where.length; idx += 4) {
14513 				where[idx + 0] = rawData[idx + 2]; // r
14514 				where[idx + 1] = rawData[idx + 1]; // g
14515 				where[idx + 2] = rawData[idx + 0]; // b
14516 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14517 
14518 				if(enableAlpha && premultiply)
14519 					unPremultiplyRgba(where[idx .. idx + 4]);
14520 			}
14521 		}
14522 
14523 		void setFromRgbaBytes(in ubyte[] where) {
14524 			assert(where.length == this.width * this.height * 4);
14525 
14526 			// if rawData had a length....
14527 			//assert(rawData.length == where.length);
14528 			for(int idx = 0; idx < where.length; idx += 4) {
14529 				rawData[idx + 2] = where[idx + 0]; // r
14530 				rawData[idx + 1] = where[idx + 1]; // g
14531 				rawData[idx + 0] = where[idx + 2]; // b
14532 				if(enableAlpha) {
14533 					rawData[idx + 3] = where[idx + 3]; // a
14534 					if(premultiply)
14535 						premultiplyBgra(rawData[idx .. idx + 4]);
14536 				}
14537 			}
14538 		}
14539 
14540 	}
14541 
14542 	mixin template NativeSimpleWindowImplementation() {
14543 		GC gc;
14544 		Window window;
14545 		Display* display;
14546 
14547 		Pixmap buffer;
14548 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14549 		XIC xic; // input context
14550 		int curHidden = 0; // counter
14551 		Cursor blankCurPtr = 0;
14552 		int cursorSequenceNumber = 0;
14553 		int warpEventCount = 0; // number of mouse movement events to eat
14554 
14555 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14556 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14557 
14558 		version(without_opengl) {} else
14559 		GLXContext glc;
14560 
14561 		private void fixFixedSize(bool forced=false) (int width, int height) {
14562 			if (forced || this.resizability == Resizability.fixedSize) {
14563 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14564 				XSizeHints sh;
14565 				static if (!forced) {
14566 					c_long spr;
14567 					XGetWMNormalHints(display, window, &sh, &spr);
14568 					sh.flags |= PMaxSize | PMinSize;
14569 				} else {
14570 					sh.flags = PMaxSize | PMinSize;
14571 				}
14572 				sh.min_width = width;
14573 				sh.min_height = height;
14574 				sh.max_width = width;
14575 				sh.max_height = height;
14576 				XSetWMNormalHints(display, window, &sh);
14577 				//XFlush(display);
14578 			}
14579 		}
14580 
14581 		ScreenPainter getPainter(bool manualInvalidations) {
14582 			return ScreenPainter(this, window, manualInvalidations);
14583 		}
14584 
14585 		void move(int x, int y) {
14586 			XMoveWindow(display, window, x, y);
14587 		}
14588 
14589 		void resize(int w, int h) {
14590 			if (w < 1) w = 1;
14591 			if (h < 1) h = 1;
14592 			XResizeWindow(display, window, w, h);
14593 
14594 			// calling this now to avoid waiting for the server to
14595 			// acknowledge the resize; draws without returning to the
14596 			// event loop will thus actually work. the server's event
14597 			// btw might overrule this and resize it again
14598 			recordX11Resize(display, this, w, h);
14599 
14600 			updateOpenglViewportIfNeeded(w, h);
14601 		}
14602 
14603 		void moveResize (int x, int y, int w, int h) {
14604 			if (w < 1) w = 1;
14605 			if (h < 1) h = 1;
14606 			XMoveResizeWindow(display, window, x, y, w, h);
14607 			updateOpenglViewportIfNeeded(w, h);
14608 		}
14609 
14610 		void hideCursor () {
14611 			if (curHidden++ == 0) {
14612 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14613 					static const(char)[1] cmbmp = 0;
14614 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14615 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14616 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14617 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14618 					XFreePixmap(display, pm);
14619 				}
14620 				XDefineCursor(display, window, blankCurPtr);
14621 			}
14622 		}
14623 
14624 		void showCursor () {
14625 			if (--curHidden == 0) XUndefineCursor(display, window);
14626 		}
14627 
14628 		void warpMouse (int x, int y) {
14629 			// here i will send dummy "ignore next mouse motion" event,
14630 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14631 			// and we don't need to report it to the user (as warping is
14632 			// used when the user needs movement deltas).
14633 			//XClientMessageEvent xclient;
14634 			XEvent e;
14635 			e.xclient.type = EventType.ClientMessage;
14636 			e.xclient.window = window;
14637 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14638 			e.xclient.format = 32;
14639 			e.xclient.data.l[0] = 0;
14640 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14641 			//{ 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]); }
14642 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14643 			// now warp pointer...
14644 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14645 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14646 			// ...and flush
14647 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14648 			XFlush(display);
14649 		}
14650 
14651 		void sendDummyEvent () {
14652 			// here i will send dummy event to ping event queue
14653 			XEvent e;
14654 			e.xclient.type = EventType.ClientMessage;
14655 			e.xclient.window = window;
14656 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14657 			e.xclient.format = 32;
14658 			e.xclient.data.l[0] = 0;
14659 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14660 			XFlush(display);
14661 		}
14662 
14663 		void setTitle(string title) {
14664 			if (title.ptr is null) title = "";
14665 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14666 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14667 			XTextProperty windowName;
14668 			windowName.value = title.ptr;
14669 			windowName.encoding = XA_UTF8; //XA_STRING;
14670 			windowName.format = 8;
14671 			windowName.nitems = cast(uint)title.length;
14672 			XSetWMName(display, window, &windowName);
14673 			char[1024] namebuf = 0;
14674 			auto maxlen = namebuf.length-1;
14675 			if (maxlen > title.length) maxlen = title.length;
14676 			namebuf[0..maxlen] = title[0..maxlen];
14677 			XStoreName(display, window, namebuf.ptr);
14678 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14679 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14680 		}
14681 
14682 		string[] getTitles() {
14683 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14684 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14685 			XTextProperty textProp;
14686 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14687 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14688 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14689 				} else
14690 					return [];
14691 			} else
14692 				return null;
14693 		}
14694 
14695 		string getTitle() {
14696 			auto titles = getTitles();
14697 			return titles.length ? titles[0] : null;
14698 		}
14699 
14700 		void setMinSize (int minwidth, int minheight) {
14701 			import core.stdc.config : c_long;
14702 			if (minwidth < 1) minwidth = 1;
14703 			if (minheight < 1) minheight = 1;
14704 			XSizeHints sh;
14705 			c_long spr;
14706 			XGetWMNormalHints(display, window, &sh, &spr);
14707 			sh.min_width = minwidth;
14708 			sh.min_height = minheight;
14709 			sh.flags |= PMinSize;
14710 			XSetWMNormalHints(display, window, &sh);
14711 			flushGui();
14712 		}
14713 
14714 		void setMaxSize (int maxwidth, int maxheight) {
14715 			import core.stdc.config : c_long;
14716 			if (maxwidth < 1) maxwidth = 1;
14717 			if (maxheight < 1) maxheight = 1;
14718 			XSizeHints sh;
14719 			c_long spr;
14720 			XGetWMNormalHints(display, window, &sh, &spr);
14721 			sh.max_width = maxwidth;
14722 			sh.max_height = maxheight;
14723 			sh.flags |= PMaxSize;
14724 			XSetWMNormalHints(display, window, &sh);
14725 			flushGui();
14726 		}
14727 
14728 		void setResizeGranularity (int granx, int grany) {
14729 			import core.stdc.config : c_long;
14730 			if (granx < 1) granx = 1;
14731 			if (grany < 1) grany = 1;
14732 			XSizeHints sh;
14733 			c_long spr;
14734 			XGetWMNormalHints(display, window, &sh, &spr);
14735 			sh.width_inc = granx;
14736 			sh.height_inc = grany;
14737 			sh.flags |= PResizeInc;
14738 			XSetWMNormalHints(display, window, &sh);
14739 			flushGui();
14740 		}
14741 
14742 		void setOpacity (uint opacity) {
14743 			arch_ulong o = opacity;
14744 			if (opacity == uint.max)
14745 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14746 			else
14747 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14748 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14749 		}
14750 
14751 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14752 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14753 			display = XDisplayConnection.get();
14754 			auto screen = DefaultScreen(display);
14755 
14756 			bool overrideRedirect = false;
14757 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14758 				overrideRedirect = true;
14759 
14760 			version(without_opengl) {}
14761 			else {
14762 				if(opengl == OpenGlOptions.yes) {
14763 					GLXFBConfig fbconf = null;
14764 					XVisualInfo* vi = null;
14765 					bool useLegacy = false;
14766 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14767 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14768 						int[23] visualAttribs = [
14769 							GLX_X_RENDERABLE , 1/*True*/,
14770 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14771 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14772 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14773 							GLX_RED_SIZE     , 8,
14774 							GLX_GREEN_SIZE   , 8,
14775 							GLX_BLUE_SIZE    , 8,
14776 							GLX_ALPHA_SIZE   , 8,
14777 							GLX_DEPTH_SIZE   , 24,
14778 							GLX_STENCIL_SIZE , 8,
14779 							GLX_DOUBLEBUFFER , 1/*True*/,
14780 							0/*None*/,
14781 						];
14782 						int fbcount;
14783 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14784 						if (fbcount == 0) {
14785 							useLegacy = true; // try to do at least something
14786 						} else {
14787 							// pick the FB config/visual with the most samples per pixel
14788 							int bestidx = -1, bestns = -1;
14789 							foreach (int fbi; 0..fbcount) {
14790 								int sb, samples;
14791 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14792 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14793 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14794 							}
14795 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14796 							fbconf = fbc[bestidx];
14797 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14798 							XFree(fbc);
14799 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14800 						}
14801 					}
14802 					if (vi is null || useLegacy) {
14803 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14804 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14805 						useLegacy = true;
14806 					}
14807 					if (vi is null) throw new Exception("no open gl visual found");
14808 
14809 					XSetWindowAttributes swa;
14810 					auto root = RootWindow(display, screen);
14811 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14812 
14813 					swa.override_redirect = overrideRedirect;
14814 
14815 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14816 						0, 0, width, height,
14817 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14818 
14819 					// now try to use `glXCreateContextAttribsARB()` if it's here
14820 					if (!useLegacy) {
14821 						// request fairly advanced context, even with stencil buffer!
14822 						int[9] contextAttribs = [
14823 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14824 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14825 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14826 							// for modern context, set "forward compatibility" flag too
14827 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14828 							0/*None*/,
14829 						];
14830 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14831 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14832 							sdpyOpenGLContextVersion = 0;
14833 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14834 						}
14835 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14836 					} else {
14837 						// fallback to old GLX call
14838 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14839 							sdpyOpenGLContextVersion = 0;
14840 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14841 						}
14842 					}
14843 					// sync to ensure any errors generated are processed
14844 					XSync(display, 0/*False*/);
14845 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14846 					if(glc is null)
14847 						throw new Exception("glc");
14848 				}
14849 			}
14850 
14851 			if(opengl == OpenGlOptions.no) {
14852 
14853 				XSetWindowAttributes swa;
14854 				swa.background_pixel = WhitePixel(display, screen);
14855 				swa.border_pixel = BlackPixel(display, screen);
14856 				swa.override_redirect = overrideRedirect;
14857 				auto root = RootWindow(display, screen);
14858 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14859 
14860 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14861 					0, 0, width, height,
14862 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14863 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14864 
14865 
14866 
14867 				/*
14868 				window = XCreateSimpleWindow(
14869 					display,
14870 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14871 					0, 0, // x, y
14872 					width, height,
14873 					1, // border width
14874 					BlackPixel(display, screen), // border
14875 					WhitePixel(display, screen)); // background
14876 				*/
14877 
14878 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14879 				bufferw = width;
14880 				bufferh = height;
14881 
14882 				gc = DefaultGC(display, screen);
14883 
14884 				// clear out the buffer to get us started...
14885 				XSetForeground(display, gc, WhitePixel(display, screen));
14886 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14887 				XSetForeground(display, gc, BlackPixel(display, screen));
14888 			}
14889 
14890 			// input context
14891 			//TODO: create this only for top-level windows, and reuse that?
14892 			populateXic();
14893 
14894 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14895 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14896 			// window class
14897 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14898 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14899 				XClassHint klass;
14900 				XWMHints wh;
14901 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14902 					wh.input = true;
14903 					wh.flags |= InputHint;
14904 				}
14905 				XSizeHints size;
14906 				klass.res_name = sdpyWindowClassStr;
14907 				klass.res_class = sdpyWindowClassStr;
14908 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14909 			}
14910 
14911 			setTitle(title);
14912 			SimpleWindow.nativeMapping[window] = this;
14913 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14914 
14915 			// This gives our window a close button
14916 			if (windowType != WindowTypes.eventOnly) {
14917 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14918 				int useAtoms;
14919 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14920 					useAtoms = 2;
14921 				} else {
14922 					useAtoms = 1;
14923 				}
14924 				assert(useAtoms <= atoms.length);
14925 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14926 			}
14927 
14928 			// FIXME: windowType and customizationFlags
14929 			Atom[8] wsatoms; // here, due to goto
14930 			int wmsacount = 0; // here, due to goto
14931 
14932 			try
14933 			final switch(windowType) {
14934 				case WindowTypes.normal:
14935 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14936 				break;
14937 				case WindowTypes.undecorated:
14938 					motifHideDecorations();
14939 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14940 				break;
14941 				case WindowTypes.eventOnly:
14942 					_hidden = true;
14943 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14944 					goto hiddenWindow;
14945 				//break;
14946 				case WindowTypes.nestedChild:
14947 					// handled in XCreateWindow calls
14948 				break;
14949 
14950 				case WindowTypes.dropdownMenu:
14951 					motifHideDecorations();
14952 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14953 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14954 				break;
14955 				case WindowTypes.popupMenu:
14956 					motifHideDecorations();
14957 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14958 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14959 				break;
14960 				case WindowTypes.notification:
14961 					motifHideDecorations();
14962 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14963 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14964 				break;
14965 				case WindowTypes.minimallyWrapped:
14966 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14967 				/+
14968 				case WindowTypes.menu:
14969 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14970 					motifHideDecorations();
14971 				break;
14972 				case WindowTypes.desktop:
14973 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14974 				break;
14975 				case WindowTypes.dock:
14976 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14977 				break;
14978 				case WindowTypes.toolbar:
14979 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14980 				break;
14981 				case WindowTypes.menu:
14982 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14983 				break;
14984 				case WindowTypes.utility:
14985 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14986 				break;
14987 				case WindowTypes.splash:
14988 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14989 				break;
14990 				case WindowTypes.dialog:
14991 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14992 				break;
14993 				case WindowTypes.tooltip:
14994 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14995 				break;
14996 				case WindowTypes.notification:
14997 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14998 				break;
14999 				case WindowTypes.combo:
15000 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
15001 				break;
15002 				case WindowTypes.dnd:
15003 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
15004 				break;
15005 				+/
15006 			}
15007 			catch(Exception e) {
15008 				// XInternAtom failed, prolly a WM
15009 				// that doesn't support these things
15010 			}
15011 
15012 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
15013 			// the two following flags may be ignored by WM
15014 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
15015 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
15016 
15017 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
15018 
15019 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
15020 
15021 			// What would be ideal here is if they only were
15022 			// selected if there was actually an event handler
15023 			// for them...
15024 
15025 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15026 
15027 			hiddenWindow:
15028 
15029 			// set the pid property for lookup later by window managers
15030 			// a standard convenience
15031 			import core.sys.posix.unistd;
15032 			arch_ulong pid = getpid();
15033 
15034 			XChangeProperty(
15035 				display,
15036 				impl.window,
15037 				GetAtom!("_NET_WM_PID", true)(display),
15038 				XA_CARDINAL,
15039 				32 /* bits */,
15040 				0 /*PropModeReplace*/,
15041 				&pid,
15042 				1);
15043 
15044 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15045 				if(parent is null) assert(0);
15046 				XChangeProperty(
15047 					display,
15048 					impl.window,
15049 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15050 					XA_WINDOW,
15051 					32 /* bits */,
15052 					0 /*PropModeReplace*/,
15053 					&parent.impl.window,
15054 					1);
15055 
15056 			}
15057 
15058 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15059 				XMapWindow(display, window);
15060 			} else {
15061 				_hidden = true;
15062 			}
15063 		}
15064 
15065 		void populateXic() {
15066 			if (XDisplayConnection.xim !is null) {
15067 				xic = XCreateIC(XDisplayConnection.xim,
15068 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15069 						/*XNClientWindow*/"clientWindow".ptr, window,
15070 						/*XNFocusWindow*/"focusWindow".ptr, window,
15071 						null);
15072 				if (xic is null) {
15073 					import core.stdc.stdio : stderr, fprintf;
15074 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15075 				}
15076 			}
15077 		}
15078 
15079 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15080 			auto mask = EventMask.ExposureMask |
15081 				EventMask.KeyPressMask |
15082 				EventMask.KeyReleaseMask |
15083 				EventMask.PropertyChangeMask |
15084 				EventMask.FocusChangeMask |
15085 				EventMask.StructureNotifyMask |
15086 				EventMask.SubstructureNotifyMask |
15087 				EventMask.VisibilityChangeMask
15088 				| EventMask.ButtonPressMask
15089 				| EventMask.ButtonReleaseMask
15090 			;
15091 
15092 			// xshm is our shortcut for local connections
15093 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15094 				mask |= EventMask.PointerMotionMask;
15095 			else
15096 				mask |= EventMask.ButtonMotionMask;
15097 
15098 			XSelectInput(display, window, mask);
15099 		}
15100 
15101 
15102 		void setNetWMWindowType(Atom type) {
15103 			Atom[2] atoms;
15104 
15105 			atoms[0] = type;
15106 			// generic fallback
15107 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15108 
15109 			XChangeProperty(
15110 				display,
15111 				impl.window,
15112 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15113 				XA_ATOM,
15114 				32 /* bits */,
15115 				0 /*PropModeReplace*/,
15116 				atoms.ptr,
15117 				cast(int) atoms.length);
15118 		}
15119 
15120 		void motifHideDecorations(bool hide = true) {
15121 			MwmHints hints;
15122 			hints.flags = MWM_HINTS_DECORATIONS;
15123 			hints.decorations = hide ? 0 : 1;
15124 
15125 			XChangeProperty(
15126 				display,
15127 				impl.window,
15128 				GetAtom!"_MOTIF_WM_HINTS"(display),
15129 				GetAtom!"_MOTIF_WM_HINTS"(display),
15130 				32 /* bits */,
15131 				0 /*PropModeReplace*/,
15132 				&hints,
15133 				hints.sizeof / 4);
15134 		}
15135 
15136 		/*k8: unused
15137 		void createOpenGlContext() {
15138 
15139 		}
15140 		*/
15141 
15142 		void closeWindow() {
15143 			// I can't close this or a child window closing will
15144 			// break events for everyone. So I'm just leaking it right
15145 			// now and that is probably perfectly fine...
15146 			version(none)
15147 			if (customEventFDRead != -1) {
15148 				import core.sys.posix.unistd : close;
15149 				auto same = customEventFDRead == customEventFDWrite;
15150 
15151 				close(customEventFDRead);
15152 				if(!same)
15153 					close(customEventFDWrite);
15154 				customEventFDRead = -1;
15155 				customEventFDWrite = -1;
15156 			}
15157 
15158 			version(without_opengl) {} else
15159 			if(glc !is null) {
15160 				glXDestroyContext(display, glc);
15161 				glc = null;
15162 			}
15163 
15164 			if(buffer)
15165 				XFreePixmap(display, buffer);
15166 			bufferw = bufferh = 0;
15167 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15168 			XDestroyWindow(display, window);
15169 			XFlush(display);
15170 		}
15171 
15172 		void dispose() {
15173 		}
15174 
15175 		bool destroyed = false;
15176 	}
15177 
15178 	bool insideXEventLoop;
15179 }
15180 
15181 version(X11) {
15182 
15183 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15184 
15185 	private class ResizeEvent {
15186 		int width, height;
15187 	}
15188 
15189 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15190 		if(win.windowType == WindowTypes.minimallyWrapped)
15191 			return;
15192 
15193 		if(win.pendingResizeEvent is null) {
15194 			win.pendingResizeEvent = new ResizeEvent();
15195 			win.addEventListener((ResizeEvent re) {
15196 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15197 			});
15198 		}
15199 		win.pendingResizeEvent.width = width;
15200 		win.pendingResizeEvent.height = height;
15201 		if(!win.eventQueued!ResizeEvent) {
15202 			win.postEvent(win.pendingResizeEvent);
15203 		}
15204 	}
15205 
15206 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15207 		if(win.windowType == WindowTypes.minimallyWrapped)
15208 			return;
15209 		if(win.closed)
15210 			return;
15211 
15212 		if(width != win.width || height != win.height) {
15213 
15214 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15215 			win._width = width;
15216 			win._height = height;
15217 
15218 			if(win.openglMode == OpenGlOptions.no) {
15219 				// FIXME: could this be more efficient?
15220 
15221 				if (win.bufferw < width || win.bufferh < height) {
15222 					//{ 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); }
15223 					// grow the internal buffer to match the window...
15224 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15225 					{
15226 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15227 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15228 						scope(exit) XFreeGC(win.display, xgc);
15229 						XSetClipMask(win.display, xgc, None);
15230 						XSetForeground(win.display, xgc, 0);
15231 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15232 					}
15233 					XCopyArea(display,
15234 						cast(Drawable) win.buffer,
15235 						cast(Drawable) newPixmap,
15236 						win.gc, 0, 0,
15237 						win.bufferw < width ? win.bufferw : win.width,
15238 						win.bufferh < height ? win.bufferh : win.height,
15239 						0, 0);
15240 
15241 					XFreePixmap(display, win.buffer);
15242 					win.buffer = newPixmap;
15243 					win.bufferw = width;
15244 					win.bufferh = height;
15245 				}
15246 
15247 				// clear unused parts of the buffer
15248 				if (win.bufferw > width || win.bufferh > height) {
15249 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15250 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15251 					scope(exit) XFreeGC(win.display, xgc);
15252 					XSetClipMask(win.display, xgc, None);
15253 					XSetForeground(win.display, xgc, 0);
15254 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15255 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15256 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15257 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15258 				}
15259 
15260 			}
15261 
15262 			win.updateOpenglViewportIfNeeded(width, height);
15263 
15264 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15265 
15266 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15267 			if(win.windowResized !is null) {
15268 				XUnlockDisplay(display);
15269 				scope(exit) XLockDisplay(display);
15270 				win.windowResized(width, height);
15271 			}
15272 		}
15273 	}
15274 
15275 
15276 	/// Platform-specific, you might use it when doing a custom event loop.
15277 	bool doXNextEvent(Display* display) {
15278 		bool done;
15279 		XEvent e;
15280 		XNextEvent(display, &e);
15281 		version(sddddd) {
15282 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15283 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15284 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15285 			}
15286 		}
15287 
15288 		// filter out compose events
15289 		if (XFilterEvent(&e, None)) {
15290 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15291 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15292 			return false;
15293 		}
15294 		// process keyboard mapping changes
15295 		if (e.type == EventType.KeymapNotify) {
15296 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15297 			XRefreshKeyboardMapping(&e.xmapping);
15298 			return false;
15299 		}
15300 
15301 		version(with_eventloop)
15302 			import arsd.eventloop;
15303 
15304 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15305 			// see windows impl's comments
15306 			XUnlockDisplay(display);
15307 			scope(exit) XLockDisplay(display);
15308 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15309 			if(ret == 0)
15310 				return done;
15311 		}
15312 
15313 
15314 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15315 			if(win.getNativeEventHandler !is null) {
15316 				XUnlockDisplay(display);
15317 				scope(exit) XLockDisplay(display);
15318 				auto ret = win.getNativeEventHandler()(e);
15319 				if(ret == 0)
15320 					return done;
15321 			}
15322 		}
15323 
15324 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15325 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15326 				// we get this because of the RRScreenChangeNotifyMask
15327 
15328 				// this isn't actually an ideal way to do it since it wastes time
15329 				// but meh it is simple and it works.
15330 				win.actualDpiLoadAttempted = false;
15331 				SimpleWindow.xRandrInfoLoadAttemped = false;
15332 				win.updateActualDpi(); // trigger a reload
15333 			}
15334 		}
15335 
15336 		switch(e.type) {
15337 		  case EventType.SelectionClear:
15338 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15339 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15340 				// writeln("SelectionClear");
15341 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15342 			}
15343 		  break;
15344 		  case EventType.SelectionRequest:
15345 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15346 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15347 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15348 				XUnlockDisplay(display);
15349 				scope(exit) XLockDisplay(display);
15350 				(*ssh).handleRequest(e);
15351 			}
15352 		  break;
15353 		  case EventType.PropertyNotify:
15354 			// printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15355 
15356 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15357 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15358 					ssh.sendMoreIncr(&e.xproperty);
15359 			}
15360 
15361 
15362 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15363 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15364 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15365 					Atom target;
15366 					int format;
15367 					arch_ulong bytesafter, length;
15368 					void* value;
15369 
15370 					ubyte[] s;
15371 					Atom targetToKeep;
15372 
15373 					XGetWindowProperty(
15374 						e.xproperty.display,
15375 						e.xproperty.window,
15376 						e.xproperty.atom,
15377 						0,
15378 						100000 /* length */,
15379 						true, /* erase it to signal we got it and want more */
15380 						0 /*AnyPropertyType*/,
15381 						&target, &format, &length, &bytesafter, &value);
15382 
15383 					if(!targetToKeep)
15384 						targetToKeep = target;
15385 
15386 					auto id = (cast(ubyte*) value)[0 .. length];
15387 
15388 					handler.handleIncrData(targetToKeep, id);
15389 
15390 					XFree(value);
15391 				}
15392 			}
15393 		  break;
15394 		  case EventType.SelectionNotify:
15395 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15396 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15397 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15398 					XUnlockDisplay(display);
15399 					scope(exit) XLockDisplay(display);
15400 					handler.handleData(None, null);
15401 				} else {
15402 					Atom target;
15403 					int format;
15404 					arch_ulong bytesafter, length;
15405 					void* value;
15406 					XGetWindowProperty(
15407 						e.xselection.display,
15408 						e.xselection.requestor,
15409 						e.xselection.property,
15410 						0,
15411 						100000 /* length */,
15412 						//false, /* don't erase it */
15413 						true, /* do erase it lol */
15414 						0 /*AnyPropertyType*/,
15415 						&target, &format, &length, &bytesafter, &value);
15416 
15417 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15418 
15419 					{
15420 						XUnlockDisplay(display);
15421 						scope(exit) XLockDisplay(display);
15422 
15423 						if(target == XA_ATOM) {
15424 							// initial request, see what they are able to work with and request the best one
15425 							// we can handle, if available
15426 
15427 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15428 							Atom best = handler.findBestFormat(answer);
15429 
15430 							/+
15431 							writeln("got ", answer);
15432 							foreach(a; answer)
15433 								printf("%s\n", XGetAtomName(display, a));
15434 							writeln("best ", best);
15435 							+/
15436 
15437 							if(best != None) {
15438 								// actually request the best format
15439 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15440 							}
15441 						} else if(target == GetAtom!"INCR"(display)) {
15442 							// incremental
15443 
15444 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15445 
15446 							// signal the sending program that we see
15447 							// the incr and are ready to receive more.
15448 							XDeleteProperty(
15449 								e.xselection.display,
15450 								e.xselection.requestor,
15451 								e.xselection.property);
15452 						} else {
15453 							// unsupported type... maybe, forward
15454 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15455 						}
15456 					}
15457 					XFree(value);
15458 					/*
15459 					XDeleteProperty(
15460 						e.xselection.display,
15461 						e.xselection.requestor,
15462 						e.xselection.property);
15463 					*/
15464 				}
15465 			}
15466 		  break;
15467 		  case EventType.ConfigureNotify:
15468 			auto event = e.xconfigure;
15469 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15470 				if(win.windowType == WindowTypes.minimallyWrapped)
15471 					break;
15472 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
15473 
15474 				/+
15475 					The ICCCM says window managers must send a synthetic event when the window
15476 					is moved but NOT when it is resized. In the resize case, an event is sent
15477 					with position (0, 0) which can be wrong and break the dpi calculations.
15478 
15479 					So we only consider the synthetic events from the WM and otherwise
15480 					need to wait for some other event to get the position which... sucks.
15481 
15482 					I'd rather not have windows changing their layout on mouse motion after
15483 					switching monitors... might be forced to but for now just ignoring it.
15484 
15485 					Easiest way to switch monitors without sending a size position is by
15486 					maximize or fullscreen in a setup like mine, but on most setups those
15487 					work on the monitor it is already living on, so it should be ok most the
15488 					time.
15489 				+/
15490 				if(event.send_event) {
15491 					win.screenPositionKnown = true;
15492 					win.screenPositionX = event.x;
15493 					win.screenPositionY = event.y;
15494 					win.updateActualDpi();
15495 				}
15496 
15497 				win.updateIMEPopupLocation();
15498 				recordX11ResizeAsync(display, *win, event.width, event.height);
15499 			}
15500 		  break;
15501 		  case EventType.Expose:
15502 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15503 				if(win.windowType == WindowTypes.minimallyWrapped)
15504 					break;
15505 				// if it is closing from a popup menu, it can get
15506 				// an Expose event right by the end and trigger a
15507 				// BadDrawable error ... we'll just check
15508 				// closed to handle that.
15509 				if((*win).closed) break;
15510 				if((*win).openglMode == OpenGlOptions.no) {
15511 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15512 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15513 					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);
15514 				} else {
15515 					// need to redraw the scene somehow
15516 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15517 						XUnlockDisplay(display);
15518 						scope(exit) XLockDisplay(display);
15519 						version(without_opengl) {} else
15520 						win.redrawOpenGlSceneSoon();
15521 					}
15522 				}
15523 			}
15524 		  break;
15525 		  case EventType.FocusIn:
15526 		  case EventType.FocusOut:
15527 
15528 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15529 				/+
15530 
15531 				void info(string detail) {
15532 					string s;
15533 					// import std.conv;
15534 					// import std.datetime;
15535 					s ~= to!string(Clock.currTime);
15536 					s ~= " ";
15537 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15538 					s ~= " ";
15539 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15540 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15541 					s ~= detail;
15542 					s ~= " ";
15543 
15544 					sdpyPrintDebugString(s);
15545 
15546 				}
15547 
15548 				switch(e.xfocus.detail) {
15549 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15550 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15551 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15552 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15553 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15554 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15555 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15556 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15557 					default:
15558 
15559 				}
15560 				+/
15561 
15562 
15563 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15564 					break; // just ignore these they seem irrelevant
15565 
15566 				auto old = win._focused;
15567 				win._focused = e.type == EventType.FocusIn;
15568 
15569 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15570 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15571 					win._focused = true;
15572 
15573 				if(win.demandingAttention)
15574 					demandAttention(*win, false);
15575 
15576 				win.updateIMEFocused();
15577 
15578 				if(old != win._focused && win.onFocusChange) {
15579 					XUnlockDisplay(display);
15580 					scope(exit) XLockDisplay(display);
15581 					win.onFocusChange(win._focused);
15582 				}
15583 			}
15584 		  break;
15585 		  case EventType.VisibilityNotify:
15586 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15587 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15588 						if (win.visibilityChanged !is null) {
15589 								XUnlockDisplay(display);
15590 								scope(exit) XLockDisplay(display);
15591 								win.visibilityChanged(false);
15592 							}
15593 					} else {
15594 						if (win.visibilityChanged !is null) {
15595 							XUnlockDisplay(display);
15596 							scope(exit) XLockDisplay(display);
15597 							win.visibilityChanged(true);
15598 						}
15599 					}
15600 				}
15601 				break;
15602 		  case EventType.ClientMessage:
15603 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15604 					// "ignore next mouse motion" event, increment ignore counter for teh window
15605 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15606 						++(*win).warpEventCount;
15607 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15608 					} else {
15609 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15610 					}
15611 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15612 					// user clicked the close button on the window manager
15613 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15614 						XUnlockDisplay(display);
15615 						scope(exit) XLockDisplay(display);
15616 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15617 					}
15618 
15619 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15620 					// writeln("HAPPENED");
15621 					// user clicked the close button on the window manager
15622 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15623 						XUnlockDisplay(display);
15624 						scope(exit) XLockDisplay(display);
15625 
15626 						auto setTo = *win;
15627 
15628 						if(win.setRequestedInputFocus !is null) {
15629 							auto s = win.setRequestedInputFocus();
15630 							if(s !is null) {
15631 								setTo = s;
15632 							}
15633 						}
15634 
15635 						assert(setTo !is null);
15636 
15637 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15638 
15639 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15640 					}
15641 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15642 					foreach(nai; NotificationAreaIcon.activeIcons)
15643 						nai.newManager();
15644 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15645 
15646 					bool xDragWindow = true;
15647 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15648 						//XDefineCursor(display, xDragWindow.impl.window,
15649 							//writeln("XdndStatus ", e.xclient.data.l);
15650 					}
15651 					if(auto dh = win.dropHandler) {
15652 
15653 						static Atom[3] xFormatsBuffer;
15654 						static Atom[] xFormats;
15655 
15656 						void resetXFormats() {
15657 							xFormatsBuffer[] = 0;
15658 							xFormats = xFormatsBuffer[];
15659 						}
15660 
15661 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15662 							// on Windows it is supposed to return the effect you actually do FIXME
15663 
15664 							auto sourceWindow =  e.xclient.data.l[0];
15665 
15666 							xFormatsBuffer[0] = e.xclient.data.l[2];
15667 							xFormatsBuffer[1] = e.xclient.data.l[3];
15668 							xFormatsBuffer[2] = e.xclient.data.l[4];
15669 
15670 							if(e.xclient.data.l[1] & 1) {
15671 								// can just grab it all but like we don't necessarily need them...
15672 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15673 							} else {
15674 								int len;
15675 								foreach(fmt; xFormatsBuffer)
15676 									if(fmt) len++;
15677 								xFormats = xFormatsBuffer[0 .. len];
15678 							}
15679 
15680 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15681 
15682 							dh.dragEnter(&pkg);
15683 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15684 
15685 							auto pack = e.xclient.data.l[2];
15686 
15687 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15688 
15689 
15690 							XClientMessageEvent xclient;
15691 
15692 							xclient.type = EventType.ClientMessage;
15693 							xclient.window = e.xclient.data.l[0];
15694 							xclient.message_type = GetAtom!"XdndStatus"(display);
15695 							xclient.format = 32;
15696 							xclient.data.l[0] = win.impl.window;
15697 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15698 							auto r = result.consistentWithin;
15699 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15700 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15701 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15702 
15703 							XSendEvent(
15704 								display,
15705 								e.xclient.data.l[0],
15706 								false,
15707 								EventMask.NoEventMask,
15708 								cast(XEvent*) &xclient
15709 							);
15710 
15711 
15712 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15713 							//writeln("XdndLeave");
15714 							// drop cancelled.
15715 							// data.l[0] is the source window
15716 							dh.dragLeave();
15717 
15718 							resetXFormats();
15719 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15720 							// drop happening, should fetch data, then send finished
15721 							// writeln("XdndDrop");
15722 
15723 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15724 
15725 							dh.drop(&pkg);
15726 
15727 							resetXFormats();
15728 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15729 							// writeln("XdndFinished");
15730 
15731 							dh.finish();
15732 						}
15733 
15734 					}
15735 				}
15736 		  break;
15737 		  case EventType.MapNotify:
15738 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15739 					(*win)._visible = true;
15740 					if (!(*win)._visibleForTheFirstTimeCalled) {
15741 						(*win)._visibleForTheFirstTimeCalled = true;
15742 						if ((*win).visibleForTheFirstTime !is null) {
15743 							XUnlockDisplay(display);
15744 							scope(exit) XLockDisplay(display);
15745 							(*win).visibleForTheFirstTime();
15746 						}
15747 					}
15748 					if ((*win).visibilityChanged !is null) {
15749 						XUnlockDisplay(display);
15750 						scope(exit) XLockDisplay(display);
15751 						(*win).visibilityChanged(true);
15752 					}
15753 				}
15754 		  break;
15755 		  case EventType.UnmapNotify:
15756 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15757 					win._visible = false;
15758 					if (win.visibilityChanged !is null) {
15759 						XUnlockDisplay(display);
15760 						scope(exit) XLockDisplay(display);
15761 						win.visibilityChanged(false);
15762 					}
15763 			}
15764 		  break;
15765 		  case EventType.DestroyNotify:
15766 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15767 				if(win.destroyed)
15768 					break; // might get a notification both for itself and from its parent
15769 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15770 				win._closed = true; // just in case
15771 				win.destroyed = true;
15772 				if (win.xic !is null) {
15773 					XDestroyIC(win.xic);
15774 					win.xic = null; // just in case
15775 				}
15776 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15777 				bool anyImportant = false;
15778 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15779 					if(w.beingOpenKeepsAppOpen) {
15780 						anyImportant = true;
15781 						break;
15782 					}
15783 				if(!anyImportant) {
15784 					EventLoop.quitApplication();
15785 					done = true;
15786 				}
15787 			}
15788 			auto window = e.xdestroywindow.window;
15789 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15790 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15791 
15792 			version(with_eventloop) {
15793 				if(done) exit();
15794 			}
15795 		  break;
15796 
15797 		  case EventType.MotionNotify:
15798 			MouseEvent mouse;
15799 			auto event = e.xmotion;
15800 
15801 			mouse.type = MouseEventType.motion;
15802 			mouse.x = event.x;
15803 			mouse.y = event.y;
15804 			mouse.modifierState = event.state;
15805 
15806 			mouse.timestamp = event.time;
15807 
15808 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15809 				mouse.window = *win;
15810 				if (win.warpEventCount > 0) {
15811 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15812 					--(*win).warpEventCount;
15813 					(*win).mdx(mouse); // so deltas will be correctly updated
15814 				} else {
15815 					win.warpEventCount = 0; // just in case
15816 					(*win).mdx(mouse);
15817 					if((*win).handleMouseEvent) {
15818 						XUnlockDisplay(display);
15819 						scope(exit) XLockDisplay(display);
15820 						(*win).handleMouseEvent(mouse);
15821 					}
15822 				}
15823 			}
15824 
15825 		  	version(with_eventloop)
15826 				send(mouse);
15827 		  break;
15828 		  case EventType.ButtonPress:
15829 		  case EventType.ButtonRelease:
15830 			MouseEvent mouse;
15831 			auto event = e.xbutton;
15832 
15833 			mouse.timestamp = event.time;
15834 
15835 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15836 			mouse.x = event.x;
15837 			mouse.y = event.y;
15838 
15839 			static Time lastMouseDownTime = 0;
15840 			static int lastMouseDownButton = -1;
15841 
15842 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15843 			if(e.type == EventType.ButtonPress) {
15844 				lastMouseDownTime = event.time;
15845 				lastMouseDownButton = event.button;
15846 			}
15847 
15848 			switch(event.button) {
15849 				case 1: mouse.button = MouseButton.left; break; // left
15850 				case 2: mouse.button = MouseButton.middle; break; // middle
15851 				case 3: mouse.button = MouseButton.right; break; // right
15852 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15853 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15854 				case 6: break; // idk
15855 				case 7: break; // idk
15856 				case 8: mouse.button = MouseButton.backButton; break;
15857 				case 9: mouse.button = MouseButton.forwardButton; break;
15858 				default:
15859 			}
15860 
15861 			// FIXME: double check this
15862 			mouse.modifierState = event.state;
15863 
15864 			//mouse.modifierState = event.detail;
15865 
15866 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15867 				mouse.window = *win;
15868 				(*win).mdx(mouse);
15869 				if((*win).handleMouseEvent) {
15870 					XUnlockDisplay(display);
15871 					scope(exit) XLockDisplay(display);
15872 					(*win).handleMouseEvent(mouse);
15873 				}
15874 			}
15875 			version(with_eventloop)
15876 				send(mouse);
15877 		  break;
15878 
15879 		  case EventType.KeyPress:
15880 		  case EventType.KeyRelease:
15881 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15882 			KeyEvent ke;
15883 			ke.pressed = e.type == EventType.KeyPress;
15884 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15885 
15886 			auto sym = XKeycodeToKeysym(
15887 				XDisplayConnection.get(),
15888 				e.xkey.keycode,
15889 				0);
15890 
15891 			ke.key = cast(Key) sym;//e.xkey.keycode;
15892 
15893 			ke.modifierState = e.xkey.state;
15894 
15895 			// writefln("%x", sym);
15896 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15897 			int charbuflen = 0; // return value of XwcLookupString
15898 			if (ke.pressed) {
15899 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15900 				if (win !is null && win.xic !is null) {
15901 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15902 					Status status;
15903 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15904 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15905 				} else {
15906 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15907 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15908 					char[16] buffer;
15909 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15910 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15911 				}
15912 			}
15913 
15914 			// if there's no char, subst one
15915 			if (charbuflen == 0) {
15916 				switch (sym) {
15917 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15918 					case 0xff8d: // keypad enter
15919 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15920 					default : // ignore
15921 				}
15922 			}
15923 
15924 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15925 				ke.window = *win;
15926 
15927 
15928 				if(win.inputProxy)
15929 					win = &win.inputProxy;
15930 
15931 				// char events are separate since they are on Windows too
15932 				// also, xcompose can generate long char sequences
15933 				// don't send char events if Meta and/or Hyper is pressed
15934 				// TODO: ctrl+char should only send control chars; not yet
15935 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15936 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15937 				}
15938 
15939 				dchar[32] charsComingBuffer;
15940 				int charsComingPosition;
15941 				dchar[] charsComing = charsComingBuffer[];
15942 
15943 				if (ke.pressed && charbuflen > 0) {
15944 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15945 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15946 						if(charsComingPosition >= charsComing.length)
15947 							charsComing.length = charsComingPosition + 8;
15948 
15949 						charsComing[charsComingPosition++] = ch;
15950 					}
15951 
15952 					charsComing = charsComing[0 .. charsComingPosition];
15953 				} else {
15954 					charsComing = null;
15955 				}
15956 
15957 				ke.charsPossible = charsComing;
15958 
15959 				if (win.handleKeyEvent) {
15960 					XUnlockDisplay(display);
15961 					scope(exit) XLockDisplay(display);
15962 					win.handleKeyEvent(ke);
15963 				}
15964 
15965 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15966 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15967 					XUnlockDisplay(display);
15968 					scope(exit) XLockDisplay(display);
15969 					foreach(ch; charsComing)
15970 						win.handleCharEvent(ch);
15971 				}
15972 			}
15973 
15974 			version(with_eventloop)
15975 				send(ke);
15976 		  break;
15977 		  default:
15978 		}
15979 
15980 		return done;
15981 	}
15982 }
15983 
15984 /* *************************************** */
15985 /*      Done with simpledisplay stuff      */
15986 /* *************************************** */
15987 
15988 // Necessary C library bindings follow
15989 version(Windows) {} else
15990 version(X11) {
15991 
15992 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15993 
15994 // X11 bindings needed here
15995 /*
15996 	A little of this is from the bindings project on
15997 	D Source and some of it is copy/paste from the C
15998 	header.
15999 
16000 	The DSource listing consistently used D's long
16001 	where C used long. That's wrong - C long is 32 bit, so
16002 	it should be int in D. I changed that here.
16003 
16004 	Note:
16005 	This isn't complete, just took what I needed for myself.
16006 */
16007 
16008 import core.stdc.stddef : wchar_t;
16009 
16010 interface XLib {
16011 extern(C) nothrow @nogc {
16012 	char* XResourceManagerString(Display*);
16013 	void XrmInitialize();
16014 	XrmDatabase XrmGetStringDatabase(char* data);
16015 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
16016 
16017 	Cursor XCreateFontCursor(Display*, uint shape);
16018 	int XDefineCursor(Display* display, Window w, Cursor cursor);
16019 	int XUndefineCursor(Display* display, Window w);
16020 
16021 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
16022 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
16023 	int XFreeCursor(Display* display, Cursor cursor);
16024 
16025 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16026 
16027 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16028 
16029 	XVaNestedList XVaCreateNestedList(int unused, ...);
16030 
16031 	char *XKeysymToString(KeySym keysym);
16032 	KeySym XKeycodeToKeysym(
16033 		Display*		/* display */,
16034 		KeyCode		/* keycode */,
16035 		int			/* index */
16036 	);
16037 
16038 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16039 
16040 	int XFree(void*);
16041 	int XDeleteProperty(Display *display, Window w, Atom property);
16042 
16043 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16044 
16045 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16046 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16047 		*actual_type_return, int *actual_format_return, arch_ulong
16048 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16049 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16050 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16051 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16052 
16053 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16054 
16055 	Window XGetSelectionOwner(Display *display, Atom selection);
16056 
16057 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16058 
16059 	char** XListFonts(Display*, const char*, int, int*);
16060 	void XFreeFontNames(char**);
16061 
16062 	Display* XOpenDisplay(const char*);
16063 	int XCloseDisplay(Display*);
16064 
16065 	int function() XSynchronize(Display*, bool);
16066 	int function() XSetAfterFunction(Display*, int function() proc);
16067 
16068 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16069 
16070 	Bool XSupportsLocale();
16071 	char* XSetLocaleModifiers(const(char)* modifier_list);
16072 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16073 	Status XCloseOM(XOM om);
16074 
16075 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16076 	Status XCloseIM(XIM im);
16077 
16078 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16079 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16080 	Display* XDisplayOfIM(XIM im);
16081 	char* XLocaleOfIM(XIM im);
16082 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16083 	void XDestroyIC(XIC ic);
16084 	void XSetICFocus(XIC ic);
16085 	void XUnsetICFocus(XIC ic);
16086 	//wchar_t* XwcResetIC(XIC ic);
16087 	char* XmbResetIC(XIC ic);
16088 	char* Xutf8ResetIC(XIC ic);
16089 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16090 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16091 	XIM XIMOfIC(XIC ic);
16092 
16093 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16094 
16095 
16096 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16097 	int XFreeFont(Display *display, XFontStruct *font_struct);
16098 	int XSetFont(Display* display, GC gc, Font font);
16099 	int XTextWidth(XFontStruct*, scope const char*, int);
16100 
16101 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16102 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16103 
16104 	Window XCreateSimpleWindow(
16105 		Display*	/* display */,
16106 		Window		/* parent */,
16107 		int			/* x */,
16108 		int			/* y */,
16109 		uint		/* width */,
16110 		uint		/* height */,
16111 		uint		/* border_width */,
16112 		uint		/* border */,
16113 		uint		/* background */
16114 	);
16115 	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);
16116 
16117 	int XReparentWindow(Display*, Window, Window, int, int);
16118 	int XClearWindow(Display*, Window);
16119 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16120 	int XMoveWindow(Display*, Window, int, int);
16121 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16122 
16123 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16124 
16125 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16126 
16127 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16128 
16129 	XImage *XCreateImage(
16130 		Display*		/* display */,
16131 		Visual*		/* visual */,
16132 		uint	/* depth */,
16133 		int			/* format */,
16134 		int			/* offset */,
16135 		ubyte*		/* data */,
16136 		uint	/* width */,
16137 		uint	/* height */,
16138 		int			/* bitmap_pad */,
16139 		int			/* bytes_per_line */
16140 	);
16141 
16142 	Status XInitImage (XImage* image);
16143 
16144 	Atom XInternAtom(
16145 		Display*		/* display */,
16146 		const char*	/* atom_name */,
16147 		Bool		/* only_if_exists */
16148 	);
16149 
16150 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16151 	char* XGetAtomName(Display*, Atom);
16152 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16153 
16154 	int XPutImage(
16155 		Display*	/* display */,
16156 		Drawable	/* d */,
16157 		GC			/* gc */,
16158 		XImage*	/* image */,
16159 		int			/* src_x */,
16160 		int			/* src_y */,
16161 		int			/* dest_x */,
16162 		int			/* dest_y */,
16163 		uint		/* width */,
16164 		uint		/* height */
16165 	);
16166 
16167 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16168 
16169 
16170 	int XDestroyWindow(
16171 		Display*	/* display */,
16172 		Window		/* w */
16173 	);
16174 
16175 	int XDestroyImage(XImage*);
16176 
16177 	int XSelectInput(
16178 		Display*	/* display */,
16179 		Window		/* w */,
16180 		EventMask	/* event_mask */
16181 	);
16182 
16183 	int XMapWindow(
16184 		Display*	/* display */,
16185 		Window		/* w */
16186 	);
16187 
16188 	Status XIconifyWindow(Display*, Window, int);
16189 	int XMapRaised(Display*, Window);
16190 	int XMapSubwindows(Display*, Window);
16191 
16192 	int XNextEvent(
16193 		Display*	/* display */,
16194 		XEvent*		/* event_return */
16195 	);
16196 
16197 	int XMaskEvent(Display*, arch_long, XEvent*);
16198 
16199 	Bool XFilterEvent(XEvent *event, Window window);
16200 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16201 
16202 	Status XSetWMProtocols(
16203 		Display*	/* display */,
16204 		Window		/* w */,
16205 		Atom*		/* protocols */,
16206 		int			/* count */
16207 	);
16208 
16209 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16210 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16211 
16212 
16213 	Status XInitThreads();
16214 	void XLockDisplay (Display* display);
16215 	void XUnlockDisplay (Display* display);
16216 
16217 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16218 
16219 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16220 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16221 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16222 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16223 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16224 
16225 
16226 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16227 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16228 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16229 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16230 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16231 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16232 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16233 	int XDrawPoint(Display*, Drawable, GC, int, int);
16234 	int XSetForeground(Display*, GC, uint);
16235 	int XSetBackground(Display*, GC, uint);
16236 
16237 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16238 	void XFreeFontSet(Display*, XFontSet);
16239 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16240 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16241 
16242 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16243 
16244 
16245 //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);
16246 
16247 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16248 	int XSetFunction(Display*, GC, int);
16249 
16250 	GC XCreateGC(Display*, Drawable, uint, void*);
16251 	int XCopyGC(Display*, GC, uint, GC);
16252 	int XFreeGC(Display*, GC);
16253 
16254 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16255 	bool XCheckMaskEvent(Display*, int, XEvent*);
16256 
16257 	int XPending(Display*);
16258 	int XEventsQueued(Display* display, int mode);
16259 
16260 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16261 	int XFreePixmap(Display*, Pixmap);
16262 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16263 	int XFlush(Display*);
16264 	int XBell(Display*, int);
16265 	int XSync(Display*, bool);
16266 
16267 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16268 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16269 
16270 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16271 	int XUngrabKeyboard(Display*, Time);
16272 
16273 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16274 
16275 	KeySym XStringToKeysym(const char *string);
16276 
16277 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16278 
16279 	Window XDefaultRootWindow(Display*);
16280 
16281 	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);
16282 
16283 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16284 
16285 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16286 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16287 
16288 	Status XAllocColor(Display*, Colormap, XColor*);
16289 
16290 	int XWithdrawWindow(Display*, Window, int);
16291 	int XUnmapWindow(Display*, Window);
16292 	int XLowerWindow(Display*, Window);
16293 	int XRaiseWindow(Display*, Window);
16294 
16295 	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);
16296 	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);
16297 
16298 	int XGetInputFocus(Display*, Window*, int*);
16299 	int XSetInputFocus(Display*, Window, int, Time);
16300 
16301 	XErrorHandler XSetErrorHandler(XErrorHandler);
16302 
16303 	int XGetErrorText(Display*, int, char*, int);
16304 
16305 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16306 
16307 
16308 	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);
16309 	int XUngrabPointer(Display *display, Time time);
16310 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16311 
16312 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16313 
16314 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16315 	int XSetClipMask(Display*, GC, Pixmap);
16316 	int XSetClipOrigin(Display*, GC, int, int);
16317 
16318 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16319 
16320 	void XSetWMName(Display*, Window, XTextProperty*);
16321 	Status XGetWMName(Display*, Window, XTextProperty*);
16322 	int XStoreName(Display* display, Window w, const(char)* window_name);
16323 
16324 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16325 
16326 }
16327 }
16328 
16329 interface Xext {
16330 extern(C) nothrow @nogc {
16331 	Status XShmAttach(Display*, XShmSegmentInfo*);
16332 	Status XShmDetach(Display*, XShmSegmentInfo*);
16333 	Status XShmPutImage(
16334 		Display*            /* dpy */,
16335 		Drawable            /* d */,
16336 		GC                  /* gc */,
16337 		XImage*             /* image */,
16338 		int                 /* src_x */,
16339 		int                 /* src_y */,
16340 		int                 /* dst_x */,
16341 		int                 /* dst_y */,
16342 		uint        /* src_width */,
16343 		uint        /* src_height */,
16344 		Bool                /* send_event */
16345 	);
16346 
16347 	Status XShmQueryExtension(Display*);
16348 
16349 	XImage *XShmCreateImage(
16350 		Display*            /* dpy */,
16351 		Visual*             /* visual */,
16352 		uint        /* depth */,
16353 		int                 /* format */,
16354 		char*               /* data */,
16355 		XShmSegmentInfo*    /* shminfo */,
16356 		uint        /* width */,
16357 		uint        /* height */
16358 	);
16359 
16360 	Pixmap XShmCreatePixmap(
16361 		Display*            /* dpy */,
16362 		Drawable            /* d */,
16363 		char*               /* data */,
16364 		XShmSegmentInfo*    /* shminfo */,
16365 		uint        /* width */,
16366 		uint        /* height */,
16367 		uint        /* depth */
16368 	);
16369 
16370 }
16371 }
16372 
16373 	// this requires -lXpm
16374 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16375 
16376 
16377 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16378 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16379 shared static this() {
16380 	xlib.loadDynamicLibrary();
16381 	xext.loadDynamicLibrary();
16382 }
16383 
16384 
16385 extern(C) nothrow @nogc {
16386 
16387 alias XrmDatabase = void*;
16388 struct XrmValue {
16389 	uint size;
16390 	void* addr;
16391 }
16392 
16393 struct XVisualInfo {
16394 	Visual* visual;
16395 	VisualID visualid;
16396 	int screen;
16397 	uint depth;
16398 	int c_class;
16399 	c_ulong red_mask;
16400 	c_ulong green_mask;
16401 	c_ulong blue_mask;
16402 	int colormap_size;
16403 	int bits_per_rgb;
16404 }
16405 
16406 enum VisualNoMask=	0x0;
16407 enum VisualIDMask=	0x1;
16408 enum VisualScreenMask=0x2;
16409 enum VisualDepthMask=	0x4;
16410 enum VisualClassMask=	0x8;
16411 enum VisualRedMaskMask=0x10;
16412 enum VisualGreenMaskMask=0x20;
16413 enum VisualBlueMaskMask=0x40;
16414 enum VisualColormapSizeMask=0x80;
16415 enum VisualBitsPerRGBMask=0x100;
16416 enum VisualAllMask=	0x1FF;
16417 
16418 enum AnyKey = 0;
16419 enum AnyModifier = 1 << 15;
16420 
16421 // XIM and other crap
16422 struct _XOM {}
16423 struct _XIM {}
16424 struct _XIC {}
16425 alias XOM = _XOM*;
16426 alias XIM = _XIM*;
16427 alias XIC = _XIC*;
16428 
16429 alias XVaNestedList = void*;
16430 
16431 alias XIMStyle = arch_ulong;
16432 enum : arch_ulong {
16433 	XIMPreeditArea      = 0x0001,
16434 	XIMPreeditCallbacks = 0x0002,
16435 	XIMPreeditPosition  = 0x0004,
16436 	XIMPreeditNothing   = 0x0008,
16437 	XIMPreeditNone      = 0x0010,
16438 	XIMStatusArea       = 0x0100,
16439 	XIMStatusCallbacks  = 0x0200,
16440 	XIMStatusNothing    = 0x0400,
16441 	XIMStatusNone       = 0x0800,
16442 }
16443 
16444 
16445 /* X Shared Memory Extension functions */
16446 	//pragma(lib, "Xshm");
16447 	alias arch_ulong ShmSeg;
16448 	struct XShmSegmentInfo {
16449 		ShmSeg shmseg;
16450 		int shmid;
16451 		ubyte* shmaddr;
16452 		Bool readOnly;
16453 	}
16454 
16455 	// and the necessary OS functions
16456 	int shmget(int, size_t, int);
16457 	void* shmat(int, scope const void*, int);
16458 	int shmdt(scope const void*);
16459 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16460 
16461 	enum IPC_PRIVATE = 0;
16462 	enum IPC_CREAT = 512;
16463 	enum IPC_RMID = 0;
16464 
16465 /* MIT-SHM end */
16466 
16467 
16468 enum MappingType:int {
16469 	MappingModifier		=0,
16470 	MappingKeyboard		=1,
16471 	MappingPointer		=2
16472 }
16473 
16474 /* ImageFormat -- PutImage, GetImage */
16475 enum ImageFormat:int {
16476 	XYBitmap	=0,	/* depth 1, XYFormat */
16477 	XYPixmap	=1,	/* depth == drawable depth */
16478 	ZPixmap	=2	/* depth == drawable depth */
16479 }
16480 
16481 enum ModifierName:int {
16482 	ShiftMapIndex	=0,
16483 	LockMapIndex	=1,
16484 	ControlMapIndex	=2,
16485 	Mod1MapIndex	=3,
16486 	Mod2MapIndex	=4,
16487 	Mod3MapIndex	=5,
16488 	Mod4MapIndex	=6,
16489 	Mod5MapIndex	=7
16490 }
16491 
16492 enum ButtonMask:int {
16493 	Button1Mask	=1<<8,
16494 	Button2Mask	=1<<9,
16495 	Button3Mask	=1<<10,
16496 	Button4Mask	=1<<11,
16497 	Button5Mask	=1<<12,
16498 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16499 }
16500 
16501 enum KeyOrButtonMask:uint {
16502 	ShiftMask	=1<<0,
16503 	LockMask	=1<<1,
16504 	ControlMask	=1<<2,
16505 	Mod1Mask	=1<<3,
16506 	Mod2Mask	=1<<4,
16507 	Mod3Mask	=1<<5,
16508 	Mod4Mask	=1<<6,
16509 	Mod5Mask	=1<<7,
16510 	Button1Mask	=1<<8,
16511 	Button2Mask	=1<<9,
16512 	Button3Mask	=1<<10,
16513 	Button4Mask	=1<<11,
16514 	Button5Mask	=1<<12,
16515 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16516 }
16517 
16518 enum ButtonName:int {
16519 	Button1	=1,
16520 	Button2	=2,
16521 	Button3	=3,
16522 	Button4	=4,
16523 	Button5	=5
16524 }
16525 
16526 /* Notify modes */
16527 enum NotifyModes:int
16528 {
16529 	NotifyNormal		=0,
16530 	NotifyGrab			=1,
16531 	NotifyUngrab		=2,
16532 	NotifyWhileGrabbed	=3
16533 }
16534 enum NotifyHint = 1;	/* for MotionNotify events */
16535 
16536 /* Notify detail */
16537 enum NotifyDetail:int
16538 {
16539 	NotifyAncestor			=0,
16540 	NotifyVirtual			=1,
16541 	NotifyInferior			=2,
16542 	NotifyNonlinear			=3,
16543 	NotifyNonlinearVirtual	=4,
16544 	NotifyPointer			=5,
16545 	NotifyPointerRoot		=6,
16546 	NotifyDetailNone		=7
16547 }
16548 
16549 /* Visibility notify */
16550 
16551 enum VisibilityNotify:int
16552 {
16553 VisibilityUnobscured		=0,
16554 VisibilityPartiallyObscured	=1,
16555 VisibilityFullyObscured		=2
16556 }
16557 
16558 
16559 enum WindowStackingMethod:int
16560 {
16561 	Above		=0,
16562 	Below		=1,
16563 	TopIf		=2,
16564 	BottomIf	=3,
16565 	Opposite	=4
16566 }
16567 
16568 /* Circulation request */
16569 enum CirculationRequest:int
16570 {
16571 	PlaceOnTop		=0,
16572 	PlaceOnBottom	=1
16573 }
16574 
16575 enum PropertyNotification:int
16576 {
16577 	PropertyNewValue	=0,
16578 	PropertyDelete		=1
16579 }
16580 
16581 enum ColorMapNotification:int
16582 {
16583 	ColormapUninstalled	=0,
16584 	ColormapInstalled		=1
16585 }
16586 
16587 
16588 	struct _XPrivate {}
16589 	struct _XrmHashBucketRec {}
16590 
16591 	alias void* XPointer;
16592 	alias void* XExtData;
16593 
16594 	version( X86_64 ) {
16595 		alias ulong XID;
16596 		alias ulong arch_ulong;
16597 		alias long arch_long;
16598 	} else version (AArch64) {
16599 		alias ulong XID;
16600 		alias ulong arch_ulong;
16601 		alias long arch_long;
16602 	} else {
16603 		alias uint XID;
16604 		alias uint arch_ulong;
16605 		alias int arch_long;
16606 	}
16607 
16608 	alias XID Window;
16609 	alias XID Drawable;
16610 	alias XID Pixmap;
16611 
16612 	alias arch_ulong Atom;
16613 	alias int Bool;
16614 	alias Display XDisplay;
16615 
16616 	alias int ByteOrder;
16617 	alias arch_ulong Time;
16618 	alias void ScreenFormat;
16619 
16620 	struct XImage {
16621 		int width, height;			/* size of image */
16622 		int xoffset;				/* number of pixels offset in X direction */
16623 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16624 		void *data;					/* pointer to image data */
16625 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16626 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16627 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16628 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16629 		int depth;					/* depth of image */
16630 		int bytes_per_line;			/* accelarator to next line */
16631 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16632 		arch_ulong red_mask;	/* bits in z arrangment */
16633 		arch_ulong green_mask;
16634 		arch_ulong blue_mask;
16635 		XPointer obdata;			/* hook for the object routines to hang on */
16636 		static struct F {				/* image manipulation routines */
16637 			XImage* function(
16638 				XDisplay* 			/* display */,
16639 				Visual*				/* visual */,
16640 				uint				/* depth */,
16641 				int					/* format */,
16642 				int					/* offset */,
16643 				ubyte*				/* data */,
16644 				uint				/* width */,
16645 				uint				/* height */,
16646 				int					/* bitmap_pad */,
16647 				int					/* bytes_per_line */) create_image;
16648 			int function(XImage *) destroy_image;
16649 			arch_ulong function(XImage *, int, int) get_pixel;
16650 			int function(XImage *, int, int, arch_ulong) put_pixel;
16651 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16652 			int function(XImage *, arch_long) add_pixel;
16653 		}
16654 		F f;
16655 	}
16656 	version(X86_64) static assert(XImage.sizeof == 136);
16657 	else version(X86) static assert(XImage.sizeof == 88);
16658 
16659 struct XCharStruct {
16660 	short       lbearing;       /* origin to left edge of raster */
16661 	short       rbearing;       /* origin to right edge of raster */
16662 	short       width;          /* advance to next char's origin */
16663 	short       ascent;         /* baseline to top edge of raster */
16664 	short       descent;        /* baseline to bottom edge of raster */
16665 	ushort attributes;  /* per char flags (not predefined) */
16666 }
16667 
16668 /*
16669  * To allow arbitrary information with fonts, there are additional properties
16670  * returned.
16671  */
16672 struct XFontProp {
16673 	Atom name;
16674 	arch_ulong card32;
16675 }
16676 
16677 alias Atom Font;
16678 
16679 struct XFontStruct {
16680 	XExtData *ext_data;           /* Hook for extension to hang data */
16681 	Font fid;                     /* Font ID for this font */
16682 	uint direction;           /* Direction the font is painted */
16683 	uint min_char_or_byte2;   /* First character */
16684 	uint max_char_or_byte2;   /* Last character */
16685 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16686 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16687 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16688 	uint default_char;        /* Char to print for undefined character */
16689 	int n_properties;             /* How many properties there are */
16690 	XFontProp *properties;        /* Pointer to array of additional properties*/
16691 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16692 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16693 	XCharStruct *per_char;        /* first_char to last_char information */
16694 	int ascent;                   /* Max extent above baseline for spacing */
16695 	int descent;                  /* Max descent below baseline for spacing */
16696 }
16697 
16698 
16699 /*
16700  * Definitions of specific events.
16701  */
16702 struct XKeyEvent
16703 {
16704 	int type;			/* of event */
16705 	arch_ulong serial;		/* # of last request processed by server */
16706 	Bool send_event;	/* true if this came from a SendEvent request */
16707 	Display *display;	/* Display the event was read from */
16708 	Window window;	        /* "event" window it is reported relative to */
16709 	Window root;	        /* root window that the event occurred on */
16710 	Window subwindow;	/* child window */
16711 	Time time;		/* milliseconds */
16712 	int x, y;		/* pointer x, y coordinates in event window */
16713 	int x_root, y_root;	/* coordinates relative to root */
16714 	KeyOrButtonMask state;	/* key or button mask */
16715 	uint keycode;	/* detail */
16716 	Bool same_screen;	/* same screen flag */
16717 }
16718 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16719 alias XKeyEvent XKeyPressedEvent;
16720 alias XKeyEvent XKeyReleasedEvent;
16721 
16722 struct XButtonEvent
16723 {
16724 	int type;		/* of event */
16725 	arch_ulong serial;	/* # of last request processed by server */
16726 	Bool send_event;	/* true if this came from a SendEvent request */
16727 	Display *display;	/* Display the event was read from */
16728 	Window window;	        /* "event" window it is reported relative to */
16729 	Window root;	        /* root window that the event occurred on */
16730 	Window subwindow;	/* child window */
16731 	Time time;		/* milliseconds */
16732 	int x, y;		/* pointer x, y coordinates in event window */
16733 	int x_root, y_root;	/* coordinates relative to root */
16734 	KeyOrButtonMask state;	/* key or button mask */
16735 	uint button;	/* detail */
16736 	Bool same_screen;	/* same screen flag */
16737 }
16738 alias XButtonEvent XButtonPressedEvent;
16739 alias XButtonEvent XButtonReleasedEvent;
16740 
16741 struct XMotionEvent{
16742 	int type;		/* of event */
16743 	arch_ulong serial;	/* # of last request processed by server */
16744 	Bool send_event;	/* true if this came from a SendEvent request */
16745 	Display *display;	/* Display the event was read from */
16746 	Window window;	        /* "event" window reported relative to */
16747 	Window root;	        /* root window that the event occurred on */
16748 	Window subwindow;	/* child window */
16749 	Time time;		/* milliseconds */
16750 	int x, y;		/* pointer x, y coordinates in event window */
16751 	int x_root, y_root;	/* coordinates relative to root */
16752 	KeyOrButtonMask state;	/* key or button mask */
16753 	byte is_hint;		/* detail */
16754 	Bool same_screen;	/* same screen flag */
16755 }
16756 alias XMotionEvent XPointerMovedEvent;
16757 
16758 struct XCrossingEvent{
16759 	int type;		/* of event */
16760 	arch_ulong serial;	/* # of last request processed by server */
16761 	Bool send_event;	/* true if this came from a SendEvent request */
16762 	Display *display;	/* Display the event was read from */
16763 	Window window;	        /* "event" window reported relative to */
16764 	Window root;	        /* root window that the event occurred on */
16765 	Window subwindow;	/* child window */
16766 	Time time;		/* milliseconds */
16767 	int x, y;		/* pointer x, y coordinates in event window */
16768 	int x_root, y_root;	/* coordinates relative to root */
16769 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16770 	NotifyDetail detail;
16771 	/*
16772 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16773 	 * NotifyNonlinear,NotifyNonlinearVirtual
16774 	 */
16775 	Bool same_screen;	/* same screen flag */
16776 	Bool focus;		/* Boolean focus */
16777 	KeyOrButtonMask state;	/* key or button mask */
16778 }
16779 alias XCrossingEvent XEnterWindowEvent;
16780 alias XCrossingEvent XLeaveWindowEvent;
16781 
16782 struct XFocusChangeEvent{
16783 	int type;		/* FocusIn or FocusOut */
16784 	arch_ulong serial;	/* # of last request processed by server */
16785 	Bool send_event;	/* true if this came from a SendEvent request */
16786 	Display *display;	/* Display the event was read from */
16787 	Window window;		/* window of event */
16788 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16789 				   NotifyGrab, NotifyUngrab */
16790 	NotifyDetail detail;
16791 	/*
16792 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16793 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16794 	 * NotifyPointerRoot, NotifyDetailNone
16795 	 */
16796 }
16797 alias XFocusChangeEvent XFocusInEvent;
16798 alias XFocusChangeEvent XFocusOutEvent;
16799 
16800 enum CWBackPixmap              = (1L<<0);
16801 enum CWBackPixel               = (1L<<1);
16802 enum CWBorderPixmap            = (1L<<2);
16803 enum CWBorderPixel             = (1L<<3);
16804 enum CWBitGravity              = (1L<<4);
16805 enum CWWinGravity              = (1L<<5);
16806 enum CWBackingStore            = (1L<<6);
16807 enum CWBackingPlanes           = (1L<<7);
16808 enum CWBackingPixel            = (1L<<8);
16809 enum CWOverrideRedirect        = (1L<<9);
16810 enum CWSaveUnder               = (1L<<10);
16811 enum CWEventMask               = (1L<<11);
16812 enum CWDontPropagate           = (1L<<12);
16813 enum CWColormap                = (1L<<13);
16814 enum CWCursor                  = (1L<<14);
16815 
16816 struct XWindowAttributes {
16817 	int x, y;			/* location of window */
16818 	int width, height;		/* width and height of window */
16819 	int border_width;		/* border width of window */
16820 	int depth;			/* depth of window */
16821 	Visual *visual;			/* the associated visual structure */
16822 	Window root;			/* root of screen containing window */
16823 	int class_;			/* InputOutput, InputOnly*/
16824 	int bit_gravity;		/* one of the bit gravity values */
16825 	int win_gravity;		/* one of the window gravity values */
16826 	int backing_store;		/* NotUseful, WhenMapped, Always */
16827 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16828 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16829 	Bool save_under;		/* boolean, should bits under be saved? */
16830 	Colormap colormap;		/* color map to be associated with window */
16831 	Bool map_installed;		/* boolean, is color map currently installed*/
16832 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16833 	arch_long all_event_masks;		/* set of events all people have interest in*/
16834 	arch_long your_event_mask;		/* my event mask */
16835 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16836 	Bool override_redirect;		/* boolean value for override-redirect */
16837 	Screen *screen;			/* back pointer to correct screen */
16838 }
16839 
16840 enum IsUnmapped = 0;
16841 enum IsUnviewable = 1;
16842 enum IsViewable = 2;
16843 
16844 struct XSetWindowAttributes {
16845 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16846 	arch_ulong background_pixel;/* background pixel */
16847 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16848 	arch_ulong border_pixel;/* border pixel value */
16849 	int bit_gravity;         /* one of bit gravity values */
16850 	int win_gravity;         /* one of the window gravity values */
16851 	int backing_store;       /* NotUseful, WhenMapped, Always */
16852 	arch_ulong backing_planes;/* planes to be preserved if possible */
16853 	arch_ulong backing_pixel;/* value to use in restoring planes */
16854 	Bool save_under;         /* should bits under be saved? (popups) */
16855 	arch_long event_mask;         /* set of events that should be saved */
16856 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16857 	Bool override_redirect;  /* boolean value for override_redirect */
16858 	Colormap colormap;       /* color map to be associated with window */
16859 	Cursor cursor;           /* cursor to be displayed (or None) */
16860 }
16861 
16862 
16863 alias int Status;
16864 
16865 
16866 enum EventMask:int
16867 {
16868 	NoEventMask				=0,
16869 	KeyPressMask			=1<<0,
16870 	KeyReleaseMask			=1<<1,
16871 	ButtonPressMask			=1<<2,
16872 	ButtonReleaseMask		=1<<3,
16873 	EnterWindowMask			=1<<4,
16874 	LeaveWindowMask			=1<<5,
16875 	PointerMotionMask		=1<<6,
16876 	PointerMotionHintMask	=1<<7,
16877 	Button1MotionMask		=1<<8,
16878 	Button2MotionMask		=1<<9,
16879 	Button3MotionMask		=1<<10,
16880 	Button4MotionMask		=1<<11,
16881 	Button5MotionMask		=1<<12,
16882 	ButtonMotionMask		=1<<13,
16883 	KeymapStateMask		=1<<14,
16884 	ExposureMask			=1<<15,
16885 	VisibilityChangeMask	=1<<16,
16886 	StructureNotifyMask		=1<<17,
16887 	ResizeRedirectMask		=1<<18,
16888 	SubstructureNotifyMask	=1<<19,
16889 	SubstructureRedirectMask=1<<20,
16890 	FocusChangeMask			=1<<21,
16891 	PropertyChangeMask		=1<<22,
16892 	ColormapChangeMask		=1<<23,
16893 	OwnerGrabButtonMask		=1<<24
16894 }
16895 
16896 struct MwmHints {
16897 	c_ulong flags;
16898 	c_ulong functions;
16899 	c_ulong decorations;
16900 	c_long input_mode;
16901 	c_ulong status;
16902 }
16903 
16904 enum {
16905 	MWM_HINTS_FUNCTIONS = (1L << 0),
16906 	MWM_HINTS_DECORATIONS =  (1L << 1),
16907 
16908 	MWM_FUNC_ALL = (1L << 0),
16909 	MWM_FUNC_RESIZE = (1L << 1),
16910 	MWM_FUNC_MOVE = (1L << 2),
16911 	MWM_FUNC_MINIMIZE = (1L << 3),
16912 	MWM_FUNC_MAXIMIZE = (1L << 4),
16913 	MWM_FUNC_CLOSE = (1L << 5),
16914 
16915 	MWM_DECOR_ALL = (1L << 0),
16916 	MWM_DECOR_BORDER = (1L << 1),
16917 	MWM_DECOR_RESIZEH = (1L << 2),
16918 	MWM_DECOR_TITLE = (1L << 3),
16919 	MWM_DECOR_MENU = (1L << 4),
16920 	MWM_DECOR_MINIMIZE = (1L << 5),
16921 	MWM_DECOR_MAXIMIZE = (1L << 6),
16922 }
16923 
16924 import core.stdc.config : c_long, c_ulong;
16925 
16926 	/* Size hints mask bits */
16927 
16928 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16929 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16930 	enum   PPosition   = (1L << 2)          /* program specified position */;
16931 	enum   PSize       = (1L << 3)          /* program specified size */;
16932 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16933 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16934 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16935 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16936 	enum   PBaseSize   = (1L << 8);
16937 	enum   PWinGravity = (1L << 9);
16938 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16939 	struct XSizeHints {
16940 		arch_long flags;         /* marks which fields in this structure are defined */
16941 		int x, y;           /* Obsolete */
16942 		int width, height;  /* Obsolete */
16943 		int min_width, min_height;
16944 		int max_width, max_height;
16945 		int width_inc, height_inc;
16946 		struct Aspect {
16947 			int x;       /* numerator */
16948 			int y;       /* denominator */
16949 		}
16950 
16951 		Aspect min_aspect;
16952 		Aspect max_aspect;
16953 		int base_width, base_height;
16954 		int win_gravity;
16955 		/* this structure may be extended in the future */
16956 	}
16957 
16958 
16959 
16960 enum EventType:int
16961 {
16962 	KeyPress			=2,
16963 	KeyRelease			=3,
16964 	ButtonPress			=4,
16965 	ButtonRelease		=5,
16966 	MotionNotify		=6,
16967 	EnterNotify			=7,
16968 	LeaveNotify			=8,
16969 	FocusIn				=9,
16970 	FocusOut			=10,
16971 	KeymapNotify		=11,
16972 	Expose				=12,
16973 	GraphicsExpose		=13,
16974 	NoExpose			=14,
16975 	VisibilityNotify	=15,
16976 	CreateNotify		=16,
16977 	DestroyNotify		=17,
16978 	UnmapNotify		=18,
16979 	MapNotify			=19,
16980 	MapRequest			=20,
16981 	ReparentNotify		=21,
16982 	ConfigureNotify		=22,
16983 	ConfigureRequest	=23,
16984 	GravityNotify		=24,
16985 	ResizeRequest		=25,
16986 	CirculateNotify		=26,
16987 	CirculateRequest	=27,
16988 	PropertyNotify		=28,
16989 	SelectionClear		=29,
16990 	SelectionRequest	=30,
16991 	SelectionNotify		=31,
16992 	ColormapNotify		=32,
16993 	ClientMessage		=33,
16994 	MappingNotify		=34,
16995 	LASTEvent			=35	/* must be bigger than any event # */
16996 }
16997 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16998 struct XKeymapEvent
16999 {
17000 	int type;
17001 	arch_ulong serial;	/* # of last request processed by server */
17002 	Bool send_event;	/* true if this came from a SendEvent request */
17003 	Display *display;	/* Display the event was read from */
17004 	Window window;
17005 	byte[32] key_vector;
17006 }
17007 
17008 struct XExposeEvent
17009 {
17010 	int type;
17011 	arch_ulong serial;	/* # of last request processed by server */
17012 	Bool send_event;	/* true if this came from a SendEvent request */
17013 	Display *display;	/* Display the event was read from */
17014 	Window window;
17015 	int x, y;
17016 	int width, height;
17017 	int count;		/* if non-zero, at least this many more */
17018 }
17019 
17020 struct XGraphicsExposeEvent{
17021 	int type;
17022 	arch_ulong serial;	/* # of last request processed by server */
17023 	Bool send_event;	/* true if this came from a SendEvent request */
17024 	Display *display;	/* Display the event was read from */
17025 	Drawable drawable;
17026 	int x, y;
17027 	int width, height;
17028 	int count;		/* if non-zero, at least this many more */
17029 	int major_code;		/* core is CopyArea or CopyPlane */
17030 	int minor_code;		/* not defined in the core */
17031 }
17032 
17033 struct XNoExposeEvent{
17034 	int type;
17035 	arch_ulong serial;	/* # of last request processed by server */
17036 	Bool send_event;	/* true if this came from a SendEvent request */
17037 	Display *display;	/* Display the event was read from */
17038 	Drawable drawable;
17039 	int major_code;		/* core is CopyArea or CopyPlane */
17040 	int minor_code;		/* not defined in the core */
17041 }
17042 
17043 struct XVisibilityEvent{
17044 	int type;
17045 	arch_ulong serial;	/* # of last request processed by server */
17046 	Bool send_event;	/* true if this came from a SendEvent request */
17047 	Display *display;	/* Display the event was read from */
17048 	Window window;
17049 	VisibilityNotify state;		/* Visibility state */
17050 }
17051 
17052 struct XCreateWindowEvent{
17053 	int type;
17054 	arch_ulong serial;	/* # of last request processed by server */
17055 	Bool send_event;	/* true if this came from a SendEvent request */
17056 	Display *display;	/* Display the event was read from */
17057 	Window parent;		/* parent of the window */
17058 	Window window;		/* window id of window created */
17059 	int x, y;		/* window location */
17060 	int width, height;	/* size of window */
17061 	int border_width;	/* border width */
17062 	Bool override_redirect;	/* creation should be overridden */
17063 }
17064 
17065 struct XDestroyWindowEvent
17066 {
17067 	int type;
17068 	arch_ulong serial;		/* # of last request processed by server */
17069 	Bool send_event;	/* true if this came from a SendEvent request */
17070 	Display *display;	/* Display the event was read from */
17071 	Window event;
17072 	Window window;
17073 }
17074 
17075 struct XUnmapEvent
17076 {
17077 	int type;
17078 	arch_ulong serial;		/* # of last request processed by server */
17079 	Bool send_event;	/* true if this came from a SendEvent request */
17080 	Display *display;	/* Display the event was read from */
17081 	Window event;
17082 	Window window;
17083 	Bool from_configure;
17084 }
17085 
17086 struct XMapEvent
17087 {
17088 	int type;
17089 	arch_ulong serial;		/* # of last request processed by server */
17090 	Bool send_event;	/* true if this came from a SendEvent request */
17091 	Display *display;	/* Display the event was read from */
17092 	Window event;
17093 	Window window;
17094 	Bool override_redirect;	/* Boolean, is override set... */
17095 }
17096 
17097 struct XMapRequestEvent
17098 {
17099 	int type;
17100 	arch_ulong serial;	/* # of last request processed by server */
17101 	Bool send_event;	/* true if this came from a SendEvent request */
17102 	Display *display;	/* Display the event was read from */
17103 	Window parent;
17104 	Window window;
17105 }
17106 
17107 struct XReparentEvent
17108 {
17109 	int type;
17110 	arch_ulong serial;	/* # of last request processed by server */
17111 	Bool send_event;	/* true if this came from a SendEvent request */
17112 	Display *display;	/* Display the event was read from */
17113 	Window event;
17114 	Window window;
17115 	Window parent;
17116 	int x, y;
17117 	Bool override_redirect;
17118 }
17119 
17120 struct XConfigureEvent
17121 {
17122 	int type;
17123 	arch_ulong serial;	/* # of last request processed by server */
17124 	Bool send_event;	/* true if this came from a SendEvent request */
17125 	Display *display;	/* Display the event was read from */
17126 	Window event;
17127 	Window window;
17128 	int x, y;
17129 	int width, height;
17130 	int border_width;
17131 	Window above;
17132 	Bool override_redirect;
17133 }
17134 
17135 struct XGravityEvent
17136 {
17137 	int type;
17138 	arch_ulong serial;	/* # of last request processed by server */
17139 	Bool send_event;	/* true if this came from a SendEvent request */
17140 	Display *display;	/* Display the event was read from */
17141 	Window event;
17142 	Window window;
17143 	int x, y;
17144 }
17145 
17146 struct XResizeRequestEvent
17147 {
17148 	int type;
17149 	arch_ulong serial;	/* # of last request processed by server */
17150 	Bool send_event;	/* true if this came from a SendEvent request */
17151 	Display *display;	/* Display the event was read from */
17152 	Window window;
17153 	int width, height;
17154 }
17155 
17156 struct  XConfigureRequestEvent
17157 {
17158 	int type;
17159 	arch_ulong serial;	/* # of last request processed by server */
17160 	Bool send_event;	/* true if this came from a SendEvent request */
17161 	Display *display;	/* Display the event was read from */
17162 	Window parent;
17163 	Window window;
17164 	int x, y;
17165 	int width, height;
17166 	int border_width;
17167 	Window above;
17168 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17169 	arch_ulong value_mask;
17170 }
17171 
17172 struct XCirculateEvent
17173 {
17174 	int type;
17175 	arch_ulong serial;	/* # of last request processed by server */
17176 	Bool send_event;	/* true if this came from a SendEvent request */
17177 	Display *display;	/* Display the event was read from */
17178 	Window event;
17179 	Window window;
17180 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17181 }
17182 
17183 struct XCirculateRequestEvent
17184 {
17185 	int type;
17186 	arch_ulong serial;	/* # of last request processed by server */
17187 	Bool send_event;	/* true if this came from a SendEvent request */
17188 	Display *display;	/* Display the event was read from */
17189 	Window parent;
17190 	Window window;
17191 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17192 }
17193 
17194 struct XPropertyEvent
17195 {
17196 	int type;
17197 	arch_ulong serial;	/* # of last request processed by server */
17198 	Bool send_event;	/* true if this came from a SendEvent request */
17199 	Display *display;	/* Display the event was read from */
17200 	Window window;
17201 	Atom atom;
17202 	Time time;
17203 	PropertyNotification state;		/* NewValue, Deleted */
17204 }
17205 
17206 struct XSelectionClearEvent
17207 {
17208 	int type;
17209 	arch_ulong serial;	/* # of last request processed by server */
17210 	Bool send_event;	/* true if this came from a SendEvent request */
17211 	Display *display;	/* Display the event was read from */
17212 	Window window;
17213 	Atom selection;
17214 	Time time;
17215 }
17216 
17217 struct XSelectionRequestEvent
17218 {
17219 	int type;
17220 	arch_ulong serial;	/* # of last request processed by server */
17221 	Bool send_event;	/* true if this came from a SendEvent request */
17222 	Display *display;	/* Display the event was read from */
17223 	Window owner;
17224 	Window requestor;
17225 	Atom selection;
17226 	Atom target;
17227 	Atom property;
17228 	Time time;
17229 }
17230 
17231 struct XSelectionEvent
17232 {
17233 	int type;
17234 	arch_ulong serial;	/* # of last request processed by server */
17235 	Bool send_event;	/* true if this came from a SendEvent request */
17236 	Display *display;	/* Display the event was read from */
17237 	Window requestor;
17238 	Atom selection;
17239 	Atom target;
17240 	Atom property;		/* ATOM or None */
17241 	Time time;
17242 }
17243 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17244 
17245 struct XColormapEvent
17246 {
17247 	int type;
17248 	arch_ulong serial;	/* # of last request processed by server */
17249 	Bool send_event;	/* true if this came from a SendEvent request */
17250 	Display *display;	/* Display the event was read from */
17251 	Window window;
17252 	Colormap colormap;	/* COLORMAP or None */
17253 	Bool new_;		/* C++ */
17254 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17255 }
17256 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17257 
17258 struct XClientMessageEvent
17259 {
17260 	int type;
17261 	arch_ulong serial;	/* # of last request processed by server */
17262 	Bool send_event;	/* true if this came from a SendEvent request */
17263 	Display *display;	/* Display the event was read from */
17264 	Window window;
17265 	Atom message_type;
17266 	int format;
17267 	union Data{
17268 		byte[20] b;
17269 		short[10] s;
17270 		arch_ulong[5] l;
17271 	}
17272 	Data data;
17273 
17274 }
17275 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17276 
17277 struct XMappingEvent
17278 {
17279 	int type;
17280 	arch_ulong serial;	/* # of last request processed by server */
17281 	Bool send_event;	/* true if this came from a SendEvent request */
17282 	Display *display;	/* Display the event was read from */
17283 	Window window;		/* unused */
17284 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17285 				   MappingPointer */
17286 	int first_keycode;	/* first keycode */
17287 	int count;		/* defines range of change w. first_keycode*/
17288 }
17289 
17290 struct XErrorEvent
17291 {
17292 	int type;
17293 	Display *display;	/* Display the event was read from */
17294 	XID resourceid;		/* resource id */
17295 	arch_ulong serial;	/* serial number of failed request */
17296 	ubyte error_code;	/* error code of failed request */
17297 	ubyte request_code;	/* Major op-code of failed request */
17298 	ubyte minor_code;	/* Minor op-code of failed request */
17299 }
17300 
17301 struct XAnyEvent
17302 {
17303 	int type;
17304 	arch_ulong serial;	/* # of last request processed by server */
17305 	Bool send_event;	/* true if this came from a SendEvent request */
17306 	Display *display;/* Display the event was read from */
17307 	Window window;	/* window on which event was requested in event mask */
17308 }
17309 
17310 union XEvent{
17311 	int type;		/* must not be changed; first element */
17312 	XAnyEvent xany;
17313 	XKeyEvent xkey;
17314 	XButtonEvent xbutton;
17315 	XMotionEvent xmotion;
17316 	XCrossingEvent xcrossing;
17317 	XFocusChangeEvent xfocus;
17318 	XExposeEvent xexpose;
17319 	XGraphicsExposeEvent xgraphicsexpose;
17320 	XNoExposeEvent xnoexpose;
17321 	XVisibilityEvent xvisibility;
17322 	XCreateWindowEvent xcreatewindow;
17323 	XDestroyWindowEvent xdestroywindow;
17324 	XUnmapEvent xunmap;
17325 	XMapEvent xmap;
17326 	XMapRequestEvent xmaprequest;
17327 	XReparentEvent xreparent;
17328 	XConfigureEvent xconfigure;
17329 	XGravityEvent xgravity;
17330 	XResizeRequestEvent xresizerequest;
17331 	XConfigureRequestEvent xconfigurerequest;
17332 	XCirculateEvent xcirculate;
17333 	XCirculateRequestEvent xcirculaterequest;
17334 	XPropertyEvent xproperty;
17335 	XSelectionClearEvent xselectionclear;
17336 	XSelectionRequestEvent xselectionrequest;
17337 	XSelectionEvent xselection;
17338 	XColormapEvent xcolormap;
17339 	XClientMessageEvent xclient;
17340 	XMappingEvent xmapping;
17341 	XErrorEvent xerror;
17342 	XKeymapEvent xkeymap;
17343 	arch_ulong[24] pad;
17344 }
17345 
17346 
17347 	struct Display {
17348 		XExtData *ext_data;	/* hook for extension to hang data */
17349 		_XPrivate *private1;
17350 		int fd;			/* Network socket. */
17351 		int private2;
17352 		int proto_major_version;/* major version of server's X protocol */
17353 		int proto_minor_version;/* minor version of servers X protocol */
17354 		char *vendor;		/* vendor of the server hardware */
17355 	    	XID private3;
17356 		XID private4;
17357 		XID private5;
17358 		int private6;
17359 		XID function(Display*)resource_alloc;/* allocator function */
17360 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17361 		int bitmap_unit;	/* padding and data requirements */
17362 		int bitmap_pad;		/* padding requirements on bitmaps */
17363 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17364 		int nformats;		/* number of pixmap formats in list */
17365 		ScreenFormat *pixmap_format;	/* pixmap format list */
17366 		int private8;
17367 		int release;		/* release of the server */
17368 		_XPrivate *private9;
17369 		_XPrivate *private10;
17370 		int qlen;		/* Length of input event queue */
17371 		arch_ulong last_request_read; /* seq number of last event read */
17372 		arch_ulong request;	/* sequence number of last request. */
17373 		XPointer private11;
17374 		XPointer private12;
17375 		XPointer private13;
17376 		XPointer private14;
17377 		uint max_request_size; /* maximum number 32 bit words in request*/
17378 		_XrmHashBucketRec *db;
17379 		int function  (Display*)private15;
17380 		char *display_name;	/* "host:display" string used on this connect*/
17381 		int default_screen;	/* default screen for operations */
17382 		int nscreens;		/* number of screens on this server*/
17383 		Screen *screens;	/* pointer to list of screens */
17384 		arch_ulong motion_buffer;	/* size of motion buffer */
17385 		arch_ulong private16;
17386 		int min_keycode;	/* minimum defined keycode */
17387 		int max_keycode;	/* maximum defined keycode */
17388 		XPointer private17;
17389 		XPointer private18;
17390 		int private19;
17391 		byte *xdefaults;	/* contents of defaults from server */
17392 		/* there is more to this structure, but it is private to Xlib */
17393 	}
17394 
17395 	// I got these numbers from a C program as a sanity test
17396 	version(X86_64) {
17397 		static assert(Display.sizeof == 296);
17398 		static assert(XPointer.sizeof == 8);
17399 		static assert(XErrorEvent.sizeof == 40);
17400 		static assert(XAnyEvent.sizeof == 40);
17401 		static assert(XMappingEvent.sizeof == 56);
17402 		static assert(XEvent.sizeof == 192);
17403     	} else version (AArch64) {
17404         	// omit check for aarch64
17405 	} else {
17406 		static assert(Display.sizeof == 176);
17407 		static assert(XPointer.sizeof == 4);
17408 		static assert(XEvent.sizeof == 96);
17409 	}
17410 
17411 struct Depth
17412 {
17413 	int depth;		/* this depth (Z) of the depth */
17414 	int nvisuals;		/* number of Visual types at this depth */
17415 	Visual *visuals;	/* list of visuals possible at this depth */
17416 }
17417 
17418 alias void* GC;
17419 alias c_ulong VisualID;
17420 alias XID Colormap;
17421 alias XID Cursor;
17422 alias XID KeySym;
17423 alias uint KeyCode;
17424 enum None = 0;
17425 }
17426 
17427 version(without_opengl) {}
17428 else {
17429 extern(C) nothrow @nogc {
17430 
17431 
17432 static if(!SdpyIsUsingIVGLBinds) {
17433 enum GLX_USE_GL=            1;       /* support GLX rendering */
17434 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17435 enum GLX_LEVEL=             3;       /* level in plane stacking */
17436 enum GLX_RGBA=              4;       /* true if RGBA mode */
17437 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17438 enum GLX_STEREO=            6;       /* stereo buffering supported */
17439 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17440 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17441 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17442 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17443 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17444 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17445 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17446 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17447 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17448 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17449 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17450 
17451 
17452 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17453 
17454 
17455 
17456 enum GL_TRUE = 1;
17457 enum GL_FALSE = 0;
17458 }
17459 
17460 alias XID GLXContextID;
17461 alias XID GLXPixmap;
17462 alias XID GLXDrawable;
17463 alias XID GLXPbuffer;
17464 alias XID GLXWindow;
17465 alias XID GLXFBConfigID;
17466 alias void* GLXContext;
17467 
17468 }
17469 }
17470 
17471 enum AllocNone = 0;
17472 
17473 extern(C) {
17474 	/* WARNING, this type not in Xlib spec */
17475 	extern(C) alias XIOErrorHandler = int function (Display* display);
17476 }
17477 
17478 extern(C) nothrow
17479 alias XErrorHandler = int function(Display*, XErrorEvent*);
17480 
17481 extern(C) nothrow @nogc {
17482 struct Screen{
17483 	XExtData *ext_data;		/* hook for extension to hang data */
17484 	Display *display;		/* back pointer to display structure */
17485 	Window root;			/* Root window id. */
17486 	int width, height;		/* width and height of screen */
17487 	int mwidth, mheight;	/* width and height of  in millimeters */
17488 	int ndepths;			/* number of depths possible */
17489 	Depth *depths;			/* list of allowable depths on the screen */
17490 	int root_depth;			/* bits per pixel */
17491 	Visual *root_visual;	/* root visual */
17492 	GC default_gc;			/* GC for the root root visual */
17493 	Colormap cmap;			/* default color map */
17494 	uint white_pixel;
17495 	uint black_pixel;		/* White and Black pixel values */
17496 	int max_maps, min_maps;	/* max and min color maps */
17497 	int backing_store;		/* Never, WhenMapped, Always */
17498 	bool save_unders;
17499 	int root_input_mask;	/* initial root input mask */
17500 }
17501 
17502 struct Visual
17503 {
17504 	XExtData *ext_data;	/* hook for extension to hang data */
17505 	VisualID visualid;	/* visual id of this visual */
17506 	int class_;			/* class of screen (monochrome, etc.) */
17507 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17508 	int bits_per_rgb;	/* log base 2 of distinct color values */
17509 	int map_entries;	/* color map entries */
17510 }
17511 
17512 	alias Display* _XPrivDisplay;
17513 
17514 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17515 		assert(dpy !is null);
17516 		return &dpy.screens[scr];
17517 	}
17518 
17519 	extern(D) Window RootWindow(Display *dpy,int scr) {
17520 		return ScreenOfDisplay(dpy,scr).root;
17521 	}
17522 
17523 	struct XWMHints {
17524 		arch_long flags;
17525 		Bool input;
17526 		int initial_state;
17527 		Pixmap icon_pixmap;
17528 		Window icon_window;
17529 		int icon_x, icon_y;
17530 		Pixmap icon_mask;
17531 		XID window_group;
17532 	}
17533 
17534 	struct XClassHint {
17535 		char* res_name;
17536 		char* res_class;
17537 	}
17538 
17539 	extern(D) int DefaultScreen(Display *dpy) {
17540 		return dpy.default_screen;
17541 	}
17542 
17543 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17544 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17545 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17546 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17547 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17548 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17549 
17550 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17551 
17552 	enum int AnyPropertyType = 0;
17553 	enum int Success = 0;
17554 
17555 	enum int RevertToNone = None;
17556 	enum int PointerRoot = 1;
17557 	enum Time CurrentTime = 0;
17558 	enum int RevertToPointerRoot = PointerRoot;
17559 	enum int RevertToParent = 2;
17560 
17561 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17562 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17563 	}
17564 
17565 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17566 		return ScreenOfDisplay(dpy,scr).root_visual;
17567 	}
17568 
17569 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17570 		return ScreenOfDisplay(dpy,scr).default_gc;
17571 	}
17572 
17573 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17574 		return ScreenOfDisplay(dpy,scr).black_pixel;
17575 	}
17576 
17577 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17578 		return ScreenOfDisplay(dpy,scr).white_pixel;
17579 	}
17580 
17581 	alias void* XFontSet; // i think
17582 	struct XmbTextItem {
17583 		char* chars;
17584 		int nchars;
17585 		int delta;
17586 		XFontSet font_set;
17587 	}
17588 
17589 	struct XTextItem {
17590 		char* chars;
17591 		int nchars;
17592 		int delta;
17593 		Font font;
17594 	}
17595 
17596 	enum {
17597 		GXclear        = 0x0, /* 0 */
17598 		GXand          = 0x1, /* src AND dst */
17599 		GXandReverse   = 0x2, /* src AND NOT dst */
17600 		GXcopy         = 0x3, /* src */
17601 		GXandInverted  = 0x4, /* NOT src AND dst */
17602 		GXnoop         = 0x5, /* dst */
17603 		GXxor          = 0x6, /* src XOR dst */
17604 		GXor           = 0x7, /* src OR dst */
17605 		GXnor          = 0x8, /* NOT src AND NOT dst */
17606 		GXequiv        = 0x9, /* NOT src XOR dst */
17607 		GXinvert       = 0xa, /* NOT dst */
17608 		GXorReverse    = 0xb, /* src OR NOT dst */
17609 		GXcopyInverted = 0xc, /* NOT src */
17610 		GXorInverted   = 0xd, /* NOT src OR dst */
17611 		GXnand         = 0xe, /* NOT src OR NOT dst */
17612 		GXset          = 0xf, /* 1 */
17613 	}
17614 	enum QueueMode : int {
17615 		QueuedAlready,
17616 		QueuedAfterReading,
17617 		QueuedAfterFlush
17618 	}
17619 
17620 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17621 
17622 	struct XPoint {
17623 		short x;
17624 		short y;
17625 	}
17626 
17627 	enum CoordMode:int {
17628 		CoordModeOrigin = 0,
17629 		CoordModePrevious = 1
17630 	}
17631 
17632 	enum PolygonShape:int {
17633 		Complex = 0,
17634 		Nonconvex = 1,
17635 		Convex = 2
17636 	}
17637 
17638 	struct XTextProperty {
17639 		const(char)* value;		/* same as Property routines */
17640 		Atom encoding;			/* prop type */
17641 		int format;				/* prop data format: 8, 16, or 32 */
17642 		arch_ulong nitems;		/* number of data items in value */
17643 	}
17644 
17645 	version( X86_64 ) {
17646 		static assert(XTextProperty.sizeof == 32);
17647 	}
17648 
17649 
17650 	struct XGCValues {
17651 		int function_;           /* logical operation */
17652 		arch_ulong plane_mask;/* plane mask */
17653 		arch_ulong foreground;/* foreground pixel */
17654 		arch_ulong background;/* background pixel */
17655 		int line_width;         /* line width */
17656 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17657 		int cap_style;          /* CapNotLast, CapButt,
17658 					   CapRound, CapProjecting */
17659 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17660 		int fill_style;         /* FillSolid, FillTiled,
17661 					   FillStippled, FillOpaeueStippled */
17662 		int fill_rule;          /* EvenOddRule, WindingRule */
17663 		int arc_mode;           /* ArcChord, ArcPieSlice */
17664 		Pixmap tile;            /* tile pixmap for tiling operations */
17665 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17666 		int ts_x_origin;        /* offset for tile or stipple operations */
17667 		int ts_y_origin;
17668 		Font font;              /* default text font for text operations */
17669 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17670 		Bool graphics_exposures;/* boolean, should exposures be generated */
17671 		int clip_x_origin;      /* origin for clipping */
17672 		int clip_y_origin;
17673 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17674 		int dash_offset;        /* patterned/dashed line information */
17675 		char dashes;
17676 	}
17677 
17678 	struct XColor {
17679 		arch_ulong pixel;
17680 		ushort red, green, blue;
17681 		byte flags;
17682 		byte pad;
17683 	}
17684 
17685 	struct XRectangle {
17686 		short x;
17687 		short y;
17688 		ushort width;
17689 		ushort height;
17690 	}
17691 
17692 	enum ClipByChildren = 0;
17693 	enum IncludeInferiors = 1;
17694 
17695 	enum Atom XA_PRIMARY = 1;
17696 	enum Atom XA_SECONDARY = 2;
17697 	enum Atom XA_STRING = 31;
17698 	enum Atom XA_CARDINAL = 6;
17699 	enum Atom XA_WM_NAME = 39;
17700 	enum Atom XA_ATOM = 4;
17701 	enum Atom XA_WINDOW = 33;
17702 	enum Atom XA_WM_HINTS = 35;
17703 	enum int PropModeAppend = 2;
17704 	enum int PropModeReplace = 0;
17705 	enum int PropModePrepend = 1;
17706 
17707 	enum int CopyFromParent = 0;
17708 	enum int InputOutput = 1;
17709 
17710 	// XWMHints
17711 	enum InputHint = 1 << 0;
17712 	enum StateHint = 1 << 1;
17713 	enum IconPixmapHint = (1L << 2);
17714 	enum IconWindowHint = (1L << 3);
17715 	enum IconPositionHint = (1L << 4);
17716 	enum IconMaskHint = (1L << 5);
17717 	enum WindowGroupHint = (1L << 6);
17718 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17719 	enum XUrgencyHint = (1L << 8);
17720 
17721 	// GC Components
17722 	enum GCFunction           =   (1L<<0);
17723 	enum GCPlaneMask         =    (1L<<1);
17724 	enum GCForeground       =     (1L<<2);
17725 	enum GCBackground      =      (1L<<3);
17726 	enum GCLineWidth      =       (1L<<4);
17727 	enum GCLineStyle     =        (1L<<5);
17728 	enum GCCapStyle     =         (1L<<6);
17729 	enum GCJoinStyle   =          (1L<<7);
17730 	enum GCFillStyle  =           (1L<<8);
17731 	enum GCFillRule  =            (1L<<9);
17732 	enum GCTile     =             (1L<<10);
17733 	enum GCStipple           =    (1L<<11);
17734 	enum GCTileStipXOrigin  =     (1L<<12);
17735 	enum GCTileStipYOrigin =      (1L<<13);
17736 	enum GCFont               =   (1L<<14);
17737 	enum GCSubwindowMode     =    (1L<<15);
17738 	enum GCGraphicsExposures=     (1L<<16);
17739 	enum GCClipXOrigin     =      (1L<<17);
17740 	enum GCClipYOrigin    =       (1L<<18);
17741 	enum GCClipMask      =        (1L<<19);
17742 	enum GCDashOffset   =         (1L<<20);
17743 	enum GCDashList    =          (1L<<21);
17744 	enum GCArcMode    =           (1L<<22);
17745 	enum GCLastBit   =            22;
17746 
17747 
17748 	enum int WithdrawnState = 0;
17749 	enum int NormalState = 1;
17750 	enum int IconicState = 3;
17751 
17752 }
17753 } else version (OSXCocoa) {
17754 
17755 /+
17756 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
17757 +/
17758 
17759 	private __gshared AppDelegate globalAppDelegate;
17760 
17761 	extern(Objective-C)
17762 	class AppDelegate : NSObject, NSApplicationDelegate {
17763 		override static AppDelegate alloc() @selector("alloc");
17764 
17765 
17766 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
17767 			SimpleWindow.processAllCustomEvents();
17768 		}
17769 
17770 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
17771 			immutable style = NSWindowStyleMask.resizable |
17772 				NSWindowStyleMask.closable |
17773 				NSWindowStyleMask.miniaturizable |
17774 				NSWindowStyleMask.titled;
17775 
17776 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
17777 
17778 			{
17779 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
17780 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
17781 				mainMenu.setSubmenu(menu, item);
17782 
17783 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
17784 				newItem.target = NSApp;
17785 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
17786 				newItem2.target = NSApp;
17787 			}
17788 
17789 			{
17790 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
17791 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
17792 				mainMenu.setSubmenu(menu, item);
17793 
17794 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
17795 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
17796 			}
17797 
17798 
17799 			NSApp.menu = mainMenu;
17800 
17801 
17802 			// auto controller = ViewController.alloc.init;
17803 
17804 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
17805 
17806 			/+
17807 			this.window = window;
17808 			this.controller = controller;
17809 			+/
17810 		}
17811 
17812 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
17813 			NSApplication.shared_.activateIgnoringOtherApps(true);
17814 		}
17815 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
17816 			return true;
17817 		}
17818 	}
17819 
17820 	extern(Objective-C)
17821 	class SDWindowDelegate : NSObject, NSWindowDelegate {
17822 		override static SDWindowDelegate alloc() @selector("alloc");
17823 		override SDWindowDelegate init() @selector("init");
17824 
17825 		SimpleWindow simpleWindow;
17826 
17827 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
17828 			auto window = cast(void*) notification.object;
17829 
17830 			// FIXME: do i need to release it?
17831 			SimpleWindow.nativeMapping.remove(window);
17832 		}
17833 
17834 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
17835 			if(simpleWindow.windowResized) {
17836 				// FIXME: automaticallyScaleIfPossible behaviors
17837 
17838 				simpleWindow._width = cast(int) frameSize.width;
17839 				simpleWindow._height = cast(int) frameSize.height;
17840 
17841 				simpleWindow.view.setFrameSize(frameSize);
17842 
17843 				/+
17844 				auto size = simpleWindow.view.frame.size;
17845 				writeln(cast(int) size.width, "x", cast(int) size.height);
17846 				+/
17847 
17848 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
17849 
17850 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
17851 
17852 				// simpleWindow.view.setNeedsDisplay(true);
17853 			}
17854 
17855 			return frameSize;
17856 		}
17857 
17858 		/+
17859 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
17860 			if(simpleWindow.windowResized) {
17861 				auto window = simpleWindow.window;
17862 				auto rect = window.contentRectForFrameRect(window.frame);
17863 				import std.stdio; writeln(window.frame.size);
17864 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
17865 			}
17866 		}
17867 		+/
17868 	}
17869 
17870 	extern(Objective-C)
17871 	class SDGraphicsView : NSView {
17872 		SimpleWindow simpleWindow;
17873 
17874 		override static SDGraphicsView alloc() @selector("alloc");
17875 		override SDGraphicsView init() @selector("init") {
17876 			super.init();
17877 			return this;
17878 		}
17879 
17880 		override void drawRect(NSRect rect) @selector("drawRect:") {
17881 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
17882 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
17883 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
17884 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
17885 			CGImageRelease(cgImage);
17886 		}
17887 
17888 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
17889 			MouseEvent me;
17890 			me.type = type;
17891 
17892 			auto pos = event.locationInWindow;
17893 
17894 			me.x = cast(int) pos.x;
17895 			me.y = cast(int) (simpleWindow.height - pos.y);
17896 
17897 			me.dx = 0; // FIXME
17898 			me.dy = 0; // FIXME
17899 
17900 			me.button = button;
17901 			me.modifierState = cast(uint) event.modifierFlags;
17902 			me.window = simpleWindow;
17903 
17904 			me.doubleClick = false;
17905 
17906 			if(simpleWindow && simpleWindow.handleMouseEvent)
17907 				simpleWindow.handleMouseEvent(me);
17908 		}
17909 
17910 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
17911 			// writeln(event.pressedMouseButtons);
17912 
17913 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17914 		}
17915 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
17916 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
17917 		}
17918 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
17919 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
17920 		}
17921 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
17922 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
17923 		}
17924 		/+
17925 			// FIXME
17926 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
17927 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17928 		}
17929 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
17930 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17931 		}
17932 		+/
17933 
17934 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
17935 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
17936 		}
17937 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
17938 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
17939 		}
17940 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
17941 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
17942 		}
17943 
17944 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
17945 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
17946 		}
17947 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
17948 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
17949 		}
17950 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
17951 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
17952 		}
17953 
17954 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
17955 			import std.stdio;
17956 			writeln(event.deltaY);
17957 		}
17958 
17959 		override void keyDown(NSEvent event) @selector("keyDown:") {
17960 			// the event may have multiple characters, and we send them all at once.
17961 			if (simpleWindow.handleCharEvent) {
17962 				auto chars = DeifiedNSString(event.characters);
17963 				foreach (dchar dc; chars.str)
17964 					simpleWindow.handleCharEvent(dc);
17965 			}
17966 
17967 			keyHelper(event, true);
17968 		}
17969 
17970 		override void keyUp(NSEvent event) @selector("keyUp:") {
17971 			keyHelper(event, false);
17972 		}
17973 
17974 		private void keyHelper(NSEvent event, bool pressed) {
17975 			if(simpleWindow.handleKeyEvent) {
17976 				KeyEvent ev;
17977 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
17978 				ev.pressed = pressed;
17979 				ev.hardwareCode = cast(ubyte) event.keyCode;
17980 				ev.modifierState = cast(uint) event.modifierFlags;
17981 				ev.window = simpleWindow;
17982 
17983 				simpleWindow.handleKeyEvent(ev);
17984 			}
17985 		}
17986 
17987 		override bool isFlipped() @selector("isFlipped") {
17988 			return true;
17989 		}
17990 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
17991 			return true;
17992 		}
17993 
17994 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
17995 			if(simpleWindow && simpleWindow.handlePulse)
17996 				simpleWindow.handlePulse();
17997 			/+
17998 			setNeedsDisplay = true;
17999 			+/
18000 		}
18001 	}
18002 
18003 private:
18004 	alias const(void)* CFStringRef;
18005 	alias const(void)* CFAllocatorRef;
18006 	alias const(void)* CFTypeRef;
18007 	alias const(void)* CGColorSpaceRef;
18008 	alias const(void)* CGImageRef;
18009 	alias ulong CGBitmapInfo;
18010 	alias NSGraphicsContext CGContextRef;
18011 
18012 	alias NSPoint CGPoint;
18013 	alias NSSize CGSize;
18014 	alias NSRect CGRect;
18015 
18016 	struct CGAffineTransform {
18017 		double a, b, c, d, tx, ty;
18018 	}
18019 
18020 	enum NSApplicationActivationPolicyRegular = 0;
18021 	enum NSBackingStoreBuffered = 2;
18022 	enum kCFStringEncodingUTF8 = 0x08000100;
18023 
18024 	enum : size_t {
18025 		NSBorderlessWindowMask = 0,
18026 		NSTitledWindowMask = 1 << 0,
18027 		NSClosableWindowMask = 1 << 1,
18028 		NSMiniaturizableWindowMask = 1 << 2,
18029 		NSResizableWindowMask = 1 << 3,
18030 		NSTexturedBackgroundWindowMask = 1 << 8
18031 	}
18032 
18033 	enum : ulong {
18034 		kCGImageAlphaNone,
18035 		kCGImageAlphaPremultipliedLast,
18036 		kCGImageAlphaPremultipliedFirst,
18037 		kCGImageAlphaLast,
18038 		kCGImageAlphaFirst,
18039 		kCGImageAlphaNoneSkipLast,
18040 		kCGImageAlphaNoneSkipFirst
18041 	}
18042 	enum : ulong {
18043 		kCGBitmapAlphaInfoMask = 0x1F,
18044 		kCGBitmapFloatComponents = (1 << 8),
18045 		kCGBitmapByteOrderMask = 0x7000,
18046 		kCGBitmapByteOrderDefault = (0 << 12),
18047 		kCGBitmapByteOrder16Little = (1 << 12),
18048 		kCGBitmapByteOrder32Little = (2 << 12),
18049 		kCGBitmapByteOrder16Big = (3 << 12),
18050 		kCGBitmapByteOrder32Big = (4 << 12)
18051 	}
18052 	enum CGPathDrawingMode {
18053 		kCGPathFill,
18054 		kCGPathEOFill,
18055 		kCGPathStroke,
18056 		kCGPathFillStroke,
18057 		kCGPathEOFillStroke
18058 	}
18059 	enum objc_AssociationPolicy : size_t {
18060 		OBJC_ASSOCIATION_ASSIGN = 0,
18061 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18062 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18063 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18064 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18065 	}
18066 
18067 	extern(C) {
18068 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18069 		void CGContextRelease(CGContextRef c);
18070 		ubyte* CGBitmapContextGetData(CGContextRef c);
18071 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18072 		size_t CGBitmapContextGetWidth(CGContextRef c);
18073 		size_t CGBitmapContextGetHeight(CGContextRef c);
18074 
18075 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18076 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18077 
18078 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18079 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18080 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18081 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18082 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18083 
18084 		void CGContextBeginPath(CGContextRef c);
18085 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18086 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18087 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18088 		void CGContextAddRect(CGContextRef c, CGRect rect);
18089 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18090 		void CGContextSaveGState(CGContextRef c);
18091 		void CGContextRestoreGState(CGContextRef c);
18092 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18093 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18094 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18095 
18096 		void CGImageRelease(CGImageRef image);
18097 	}
18098 } else static assert(0, "Unsupported operating system");
18099 
18100 
18101 version(OSXCocoa) {
18102 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18103 	//
18104 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18105 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18106 	//
18107 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18108 	// Probably won't even fully compile right now
18109 
18110 	private enum double PI = 3.14159265358979323;
18111 
18112 	alias NSWindow NativeWindowHandle;
18113 	alias void delegate(NSid) NativeEventHandler;
18114 
18115 	enum KEY_ESCAPE = 27;
18116 
18117 	mixin template NativeImageImplementation() {
18118 		CGContextRef context;
18119 		ubyte* rawData;
18120 
18121 		final:
18122 
18123 		void convertToRgbaBytes(ubyte[] where) {
18124 			assert(where.length == this.width * this.height * 4);
18125 
18126 			// if rawData had a length....
18127 			//assert(rawData.length == where.length);
18128 			for(long idx = 0; idx < where.length; idx += 4) {
18129 				auto alpha = rawData[idx + 3];
18130 				if(alpha == 255) {
18131 					where[idx + 0] = rawData[idx + 0]; // r
18132 					where[idx + 1] = rawData[idx + 1]; // g
18133 					where[idx + 2] = rawData[idx + 2]; // b
18134 					where[idx + 3] = rawData[idx + 3]; // a
18135 				} else {
18136 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18137 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18138 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18139 					where[idx + 3] = rawData[idx + 3]; // a
18140 
18141 				}
18142 			}
18143 		}
18144 
18145 		void setFromRgbaBytes(in ubyte[] where) {
18146 			// FIXME: this is probably wrong
18147 			assert(where.length == this.width * this.height * 4);
18148 
18149 			// if rawData had a length....
18150 			//assert(rawData.length == where.length);
18151 			for(long idx = 0; idx < where.length; idx += 4) {
18152 				auto alpha = where[idx + 3];
18153 				if(alpha == 255) {
18154 					rawData[idx + 0] = where[idx + 0]; // r
18155 					rawData[idx + 1] = where[idx + 1]; // g
18156 					rawData[idx + 2] = where[idx + 2]; // b
18157 					rawData[idx + 3] = where[idx + 3]; // a
18158 				} else if(alpha == 0) {
18159 					rawData[idx + 0] = 0;
18160 					rawData[idx + 1] = 0;
18161 					rawData[idx + 2] = 0;
18162 					rawData[idx + 3] = 0;
18163 				} else {
18164 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18165 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18166 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18167 					rawData[idx + 3] = where[idx + 3]; // a
18168 				}
18169 			}
18170 		}
18171 
18172 
18173 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18174 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18175 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18176 			CGColorSpaceRelease(colorSpace);
18177 			rawData = CGBitmapContextGetData(context);
18178 		}
18179 		void dispose() {
18180 			CGContextRelease(context);
18181 		}
18182 
18183 		void setPixel(int x, int y, Color c) {
18184 			auto offset = (y * width + x) * 4;
18185 			if (c.a == 255) {
18186 				rawData[offset + 0] = c.r;
18187 				rawData[offset + 1] = c.g;
18188 				rawData[offset + 2] = c.b;
18189 				rawData[offset + 3] = c.a;
18190 			} else {
18191 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18192 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18193 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18194 				rawData[offset + 3] = c.a;
18195 			}
18196 		}
18197 	}
18198 
18199 	mixin template NativeScreenPainterImplementation() {
18200 		CGContextRef context;
18201 		ubyte[4] _outlineComponents;
18202 		NSView view;
18203 
18204 		void create(PaintingHandle window) {
18205 			// this.destiny = window;
18206 			if(auto sw = cast(SimpleWindow) this.window) {
18207 				context = sw.drawingContext;
18208 				view = sw.view;
18209 			} else {
18210 				throw new NotYetImplementedException();
18211 			}
18212 		}
18213 
18214 		void dispose() {
18215 			view.setNeedsDisplay(true);
18216 		}
18217 
18218 		bool manualInvalidations;
18219 		void invalidateRect(Rectangle invalidRect) { }
18220 
18221 		// NotYetImplementedException
18222 		Size textSize(in char[] txt) {
18223 			return Size(32, 16); /*throw new NotYetImplementedException();*/
18224 		}
18225 		void rasterOp(RasterOp op) {
18226 		}
18227 		Pen _activePen;
18228 		Color _fillColor;
18229 		Rectangle _clipRectangle;
18230 		void setClipRectangle(int, int, int, int) {
18231 		}
18232 		void setFont(OperatingSystemFont) {
18233 		}
18234 		int fontHeight() {
18235 			return 14;
18236 		}
18237 
18238 		// end
18239 
18240 		void pen(Pen pen) {
18241 			_activePen = pen;
18242 			auto color = pen.color; // FIXME
18243 			double alphaComponent = color.a/255.0f;
18244 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
18245 
18246 			if (color.a != 255) {
18247 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
18248 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
18249 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
18250 				_outlineComponents[3] = color.a;
18251 			} else {
18252 				_outlineComponents[0] = color.r;
18253 				_outlineComponents[1] = color.g;
18254 				_outlineComponents[2] = color.b;
18255 				_outlineComponents[3] = color.a;
18256 			}
18257 		}
18258 
18259 		@property void fillColor(Color color) {
18260 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
18261 		}
18262 
18263 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
18264 		// NotYetImplementedException for upper left/width/height
18265 			auto cgImage = CGBitmapContextCreateImage(image.context);
18266 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
18267 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18268 			CGImageRelease(cgImage);
18269 		}
18270 
18271 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
18272 		// FIXME: is this efficient?
18273 			auto cgImage = CGBitmapContextCreateImage(s.handle);
18274 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
18275 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18276 			CGImageRelease(cgImage);
18277 		}
18278 
18279 
18280 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
18281 		// FIXME: alignment
18282 			if (_outlineComponents[3] != 0) {
18283 				CGContextSaveGState(context);
18284 				auto invAlpha = 1.0f/_outlineComponents[3];
18285 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
18286 												  _outlineComponents[1]*invAlpha,
18287 												  _outlineComponents[2]*invAlpha,
18288 												  _outlineComponents[3]/255.0f);
18289 				CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
18290 // auto cfstr = cast(NSid)createCFString(text);
18291 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
18292 // NSPoint(x, y), null);
18293 // CFRelease(cfstr);
18294 				CGContextRestoreGState(context);
18295 			}
18296 		}
18297 
18298 		void drawPixel(int x, int y) {
18299 			auto rawData = CGBitmapContextGetData(context);
18300 			auto width = CGBitmapContextGetWidth(context);
18301 			auto height = CGBitmapContextGetHeight(context);
18302 			auto offset = ((height - y - 1) * width + x) * 4;
18303 			rawData[offset .. offset+4] = _outlineComponents;
18304 		}
18305 
18306 		void drawLine(int x1, int y1, int x2, int y2) {
18307 			CGPoint[2] linePoints;
18308 			linePoints[0] = CGPoint(x1, y1);
18309 			linePoints[1] = CGPoint(x2, y2);
18310 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
18311 		}
18312 
18313 		void drawRectangle(int x, int y, int width, int height) {
18314 			CGContextBeginPath(context);
18315 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18316 			CGContextAddRect(context, rect);
18317 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18318 		}
18319 
18320 		void drawEllipse(int x1, int y1, int x2, int y2) {
18321 			CGContextBeginPath(context);
18322 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18323 			CGContextAddEllipseInRect(context, rect);
18324 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18325 		}
18326 
18327 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
18328 			// @@@BUG@@@ Does not support elliptic arc (width != height).
18329 			CGContextBeginPath(context);
18330 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18331 							start*PI/(180*64), finish*PI/(180*64), 0);
18332 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18333 		}
18334 
18335 		void drawPolygon(Point[] intPoints) {
18336 			CGContextBeginPath(context);
18337 			CGPoint[16] pointsBuffer;
18338 			CGPoint[] points;
18339 			if(intPoints.length <= pointsBuffer.length)
18340 				points = pointsBuffer[0 .. intPoints.length];
18341 			else
18342 				points = new CGPoint[](intPoints.length);
18343 
18344 			foreach(idx, pt; intPoints)
18345 				points[idx] = CGPoint(pt.x, pt.y);
18346 
18347 			CGContextAddLines(context, points.ptr, points.length);
18348 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18349 		}
18350 	}
18351 
18352 	private bool appInitialized = false;
18353 	void initializeApp() {
18354 		if(appInitialized)
18355 			return;
18356 		synchronized {
18357 			if(appInitialized)
18358 				return;
18359 
18360 			auto app = NSApp(); // ensure the is initialized
18361 
18362 			auto dg = AppDelegate.alloc;
18363 			globalAppDelegate = dg;
18364 			NSApp.delegate_ = dg;
18365 
18366 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
18367 
18368 			appInitialized = true;
18369 		}
18370 	}
18371 
18372 	mixin template NativeSimpleWindowImplementation() {
18373 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18374 			initializeApp();
18375 
18376 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18377 
18378 			auto window = NSWindow.alloc.initWithContentRect(
18379 				contentRect,
18380 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
18381 				NSBackingStoreType.buffered,
18382 				true
18383 			);
18384 
18385 			SimpleWindow.nativeMapping[cast(void*) window] = this;
18386 
18387 			window.title = MacString(title).borrow;
18388 
18389 			auto dg = SDWindowDelegate.alloc.init;
18390 			dg.simpleWindow = this;
18391 			window.delegate_ = dg;
18392 
18393 			auto view = SDGraphicsView.alloc.init;
18394 			assert(view !is null);
18395 			window.contentView = view;
18396 			this.view = view;
18397 			view.simpleWindow = this;
18398 
18399 			window.center();
18400 
18401 			window.makeKeyAndOrderFront(null);
18402 
18403 			// no need to make a bitmap on mac since everything is double buffered already
18404 
18405 			// create area to draw on.
18406 			createNewDrawingContext(width, height);
18407 
18408 			window.setBackgroundColor(NSColor.whiteColor);
18409 		}
18410 
18411 		void createNewDrawingContext(int width, int height) {
18412 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
18413 			if(this.drawingContext)
18414 				CGContextRelease(this.drawingContext);
18415 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18416 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18417 			CGColorSpaceRelease(colorSpace);
18418 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18419 			auto matrix = CGContextGetTextMatrix(drawingContext);
18420 			matrix.c = -matrix.c;
18421 			matrix.d = -matrix.d;
18422 			CGContextSetTextMatrix(drawingContext, matrix);
18423 
18424 		}
18425 
18426 		void dispose() {
18427 			closeWindow();
18428 			// window.release(); // closing the window does this automatically i think
18429 		}
18430 		void closeWindow() {
18431 			if(timer)
18432 				timer.invalidate();
18433 			window.close();
18434 		}
18435 
18436 		ScreenPainter getPainter(bool manualInvalidations) {
18437 			return ScreenPainter(this, this.window, manualInvalidations);
18438 		}
18439 
18440 		NSWindow window;
18441 		NSTimer timer;
18442 		NSView view;
18443 		CGContextRef drawingContext;
18444 	}
18445 }
18446 
18447 version(without_opengl) {} else
18448 extern(System) nothrow @nogc {
18449 	//enum uint GL_VERSION = 0x1F02;
18450 	//const(char)* glGetString (/*GLenum*/uint);
18451 	version(X11) {
18452 	static if (!SdpyIsUsingIVGLBinds) {
18453 
18454 		enum GLX_X_RENDERABLE = 0x8012;
18455 		enum GLX_DRAWABLE_TYPE = 0x8010;
18456 		enum GLX_RENDER_TYPE = 0x8011;
18457 		enum GLX_X_VISUAL_TYPE = 0x22;
18458 		enum GLX_TRUE_COLOR = 0x8002;
18459 		enum GLX_WINDOW_BIT = 0x00000001;
18460 		enum GLX_RGBA_BIT = 0x00000001;
18461 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18462 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18463 		enum GLX_SAMPLES = 0x186a1;
18464 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18465 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18466 	}
18467 
18468 		// GLX_EXT_swap_control
18469 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18470 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18471 
18472 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18473 		extern(System) {
18474 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18475 		}
18476 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18477 
18478 		// this made public so we don't have to get it again and again
18479 		public bool glXCreateContextAttribsARB_present () {
18480 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18481 				// get it
18482 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18483 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18484 			}
18485 			return (glXCreateContextAttribsARBFn !is null);
18486 		}
18487 
18488 		// this made public so we don't have to get it again and again
18489 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18490 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18491 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18492 		}
18493 
18494 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18495 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18496 
18497 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18498 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18499 			if (_glx_swapInterval_fn is null) {
18500 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18501 				if (_glx_swapInterval_fn is null) {
18502 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18503 					return;
18504 				}
18505 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
18506 			}
18507 
18508 			if(glXSwapIntervalMESA is null) {
18509 				// it seems to require both to actually take effect on many computers
18510 				// idk why
18511 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18512 				if(glXSwapIntervalMESA is null)
18513 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18514 			}
18515 
18516 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18517 				glXSwapIntervalMESA(wait ? 1 : 0);
18518 
18519 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18520 		}
18521 	} else version(Windows) {
18522 	static if (!SdpyIsUsingIVGLBinds) {
18523 	enum GL_TRUE = 1;
18524 	enum GL_FALSE = 0;
18525 
18526 	public void* glbindGetProcAddress (const(char)* name) {
18527 		void* res = wglGetProcAddress(name);
18528 		if (res is null) {
18529 			/+
18530 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18531 			import core.sys.windows.windef, core.sys.windows.winbase;
18532 			__gshared HINSTANCE dll = null;
18533 			if (dll is null) {
18534 				dll = LoadLibraryA("opengl32.dll");
18535 				if (dll is null) return null; // <32, but idc
18536 			}
18537 			res = GetProcAddress(dll, name);
18538 			+/
18539 			res = GetProcAddress(gl.libHandle, name);
18540 		}
18541 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18542 		return res;
18543 	}
18544 	}
18545 
18546 
18547  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18548         void wglSetVSync(bool wait) {
18549 		if(wglSwapIntervalEXT is null) {
18550 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18551 			if(wglSwapIntervalEXT is null)
18552 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18553 		}
18554 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18555 			return;
18556 
18557 		wglSwapIntervalEXT(wait ? 1 : 0);
18558 	}
18559 
18560 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18561 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18562 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18563 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18564 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18565 
18566 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18567 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18568 
18569 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18570 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18571 
18572 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18573 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18574 
18575 		void wglInitOtherFunctions () {
18576 			if (wglCreateContextAttribsARB is null) {
18577 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18578 			}
18579 		}
18580 	}
18581 
18582 	static if (!SdpyIsUsingIVGLBinds) {
18583 
18584 	interface GL {
18585 		extern(System) @nogc nothrow:
18586 
18587 		void glGetIntegerv(int, void*);
18588 		void glMatrixMode(int);
18589 		void glPushMatrix();
18590 		void glLoadIdentity();
18591 		void glOrtho(double, double, double, double, double, double);
18592 		void glFrustum(double, double, double, double, double, double);
18593 
18594 		void glPopMatrix();
18595 		void glEnable(int);
18596 		void glDisable(int);
18597 		void glClear(int);
18598 		void glBegin(int);
18599 		void glVertex2f(float, float);
18600 		void glVertex3f(float, float, float);
18601 		void glEnd();
18602 		void glColor3b(byte, byte, byte);
18603 		void glColor3ub(ubyte, ubyte, ubyte);
18604 		void glColor4b(byte, byte, byte, byte);
18605 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18606 		void glColor3i(int, int, int);
18607 		void glColor3ui(uint, uint, uint);
18608 		void glColor4i(int, int, int, int);
18609 		void glColor4ui(uint, uint, uint, uint);
18610 		void glColor3f(float, float, float);
18611 		void glColor4f(float, float, float, float);
18612 		void glTranslatef(float, float, float);
18613 		void glScalef(float, float, float);
18614 		version(X11) {
18615 			void glSecondaryColor3b(byte, byte, byte);
18616 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18617 			void glSecondaryColor3i(int, int, int);
18618 			void glSecondaryColor3ui(uint, uint, uint);
18619 			void glSecondaryColor3f(float, float, float);
18620 		}
18621 
18622 		void glDrawElements(int, int, int, void*);
18623 
18624 		void glRotatef(float, float, float, float);
18625 
18626 		uint glGetError();
18627 
18628 		void glDeleteTextures(int, uint*);
18629 
18630 
18631 		void glRasterPos2i(int, int);
18632 		void glDrawPixels(int, int, uint, uint, void*);
18633 		void glClearColor(float, float, float, float);
18634 
18635 
18636 		void glPixelStorei(uint, int);
18637 
18638 		void glGenTextures(uint, uint*);
18639 		void glBindTexture(int, int);
18640 		void glTexParameteri(uint, uint, int);
18641 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18642 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
18643 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18644 			/*GLsizei*/int width, /*GLsizei*/int height,
18645 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18646 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18647 
18648 		void glLineWidth(int);
18649 
18650 
18651 		void glTexCoord2f(float, float);
18652 		void glVertex2i(int, int);
18653 		void glBlendFunc (int, int);
18654 		void glDepthFunc (int);
18655 		void glViewport(int, int, int, int);
18656 
18657 		void glClearDepth(double);
18658 
18659 		void glReadBuffer(uint);
18660 		void glReadPixels(int, int, int, int, int, int, void*);
18661 
18662 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
18663 
18664 		void glFlush();
18665 		void glFinish();
18666 
18667 		version(Windows) {
18668 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18669 			HGLRC wglCreateContext(HDC);
18670 			HGLRC wglCreateLayerContext(HDC, int);
18671 			BOOL wglDeleteContext(HGLRC);
18672 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18673 			HGLRC wglGetCurrentContext();
18674 			HDC wglGetCurrentDC();
18675 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18676 			PROC wglGetProcAddress(LPCSTR);
18677 			BOOL wglMakeCurrent(HDC, HGLRC);
18678 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18679 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18680 			BOOL wglShareLists(HGLRC, HGLRC);
18681 			BOOL wglSwapLayerBuffers(HDC, UINT);
18682 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18683 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18684 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18685 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18686 		}
18687 
18688 	}
18689 
18690 	interface GL3 {
18691 		extern(System) @nogc nothrow:
18692 
18693 		void glGenVertexArrays(GLsizei, GLuint*);
18694 		void glBindVertexArray(GLuint);
18695 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18696 		void glGenerateMipmap(GLenum);
18697 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18698 		void glStencilMask(GLuint);
18699 		void glStencilFunc(GLenum, GLint, GLuint);
18700 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18701 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18702 		GLuint glCreateProgram();
18703 		GLuint glCreateShader(GLenum);
18704 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18705 		void glCompileShader(GLuint);
18706 		void glGetShaderiv(GLuint, GLenum, GLint*);
18707 		void glAttachShader(GLuint, GLuint);
18708 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18709 		void glLinkProgram(GLuint);
18710 		void glGetProgramiv(GLuint, GLenum, GLint*);
18711 		void glDeleteProgram(GLuint);
18712 		void glDeleteShader(GLuint);
18713 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18714 		void glGenBuffers(GLsizei, GLuint*);
18715 
18716 		void glUniform1f(GLint location, GLfloat v0);
18717 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18718 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18719 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18720 		void glUniform1i(GLint location, GLint v0);
18721 		void glUniform2i(GLint location, GLint v0, GLint v1);
18722 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18723 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18724 		void glUniform1ui(GLint location, GLuint v0);
18725 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18726 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18727 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18728 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18729 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18730 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18731 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18732 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18733 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18734 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18735 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18736 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18737 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18738 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18739 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18740 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18741 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18742 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18743 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18744 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18745 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18746 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18747 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18748 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18749 
18750 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18751 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18752 		void glDrawArrays(GLenum, GLint, GLsizei);
18753 		void glStencilOp(GLenum, GLenum, GLenum);
18754 		void glUseProgram(GLuint);
18755 		void glCullFace(GLenum);
18756 		void glFrontFace(GLenum);
18757 		void glActiveTexture(GLenum);
18758 		void glBindBuffer(GLenum, GLuint);
18759 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18760 		void glEnableVertexAttribArray(GLuint);
18761 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18762 		void glUniform1i(GLint, GLint);
18763 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18764 		void glDisableVertexAttribArray(GLuint);
18765 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18766 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18767 		void glLogicOp (GLenum opcode);
18768 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18769 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18770 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18771 		GLenum glCheckFramebufferStatus (GLenum target);
18772 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18773 	}
18774 
18775 	interface GL4 {
18776 		extern(System) @nogc nothrow:
18777 
18778 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18779 			/*GLsizei*/int width, /*GLsizei*/int height,
18780 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18781 	}
18782 
18783 	interface GLU {
18784 		extern(System) @nogc nothrow:
18785 
18786 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18787 		void gluPerspective(double, double, double, double);
18788 
18789 		char* gluErrorString(uint);
18790 	}
18791 
18792 
18793 	enum GL_RED = 0x1903;
18794 	enum GL_ALPHA = 0x1906;
18795 
18796 	enum uint GL_FRONT = 0x0404;
18797 
18798 	enum uint GL_BLEND = 0x0be2;
18799 	enum uint GL_LEQUAL = 0x0203;
18800 
18801 
18802 	enum uint GL_RGB = 0x1907;
18803 	enum uint GL_BGRA = 0x80e1;
18804 	enum uint GL_RGBA = 0x1908;
18805 	enum uint GL_RGBA8 = 0x8058;
18806 	enum uint GL_TEXTURE_2D =   0x0DE1;
18807 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18808 	enum uint GL_NEAREST = 0x2600;
18809 	enum uint GL_LINEAR = 0x2601;
18810 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18811 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18812 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18813 	enum uint GL_REPEAT = 0x2901;
18814 	enum uint GL_CLAMP = 0x2900;
18815 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18816 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18817 	enum uint GL_DECAL = 0x2101;
18818 	enum uint GL_MODULATE = 0x2100;
18819 	enum uint GL_TEXTURE_ENV = 0x2300;
18820 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18821 	enum uint GL_REPLACE = 0x1E01;
18822 	enum uint GL_LIGHTING = 0x0B50;
18823 	enum uint GL_DITHER = 0x0BD0;
18824 
18825 	enum uint GL_NO_ERROR = 0;
18826 
18827 
18828 
18829 	enum int GL_VIEWPORT = 0x0BA2;
18830 	enum int GL_MODELVIEW = 0x1700;
18831 	enum int GL_TEXTURE = 0x1702;
18832 	enum int GL_PROJECTION = 0x1701;
18833 	enum int GL_DEPTH_TEST = 0x0B71;
18834 
18835 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18836 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18837 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18838 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18839 
18840 	enum int GL_POINTS = 0x0000;
18841 	enum int GL_LINES =  0x0001;
18842 	enum int GL_LINE_LOOP = 0x0002;
18843 	enum int GL_LINE_STRIP = 0x0003;
18844 	enum int GL_TRIANGLES = 0x0004;
18845 	enum int GL_TRIANGLE_STRIP = 5;
18846 	enum int GL_TRIANGLE_FAN = 6;
18847 	enum int GL_QUADS = 7;
18848 	enum int GL_QUAD_STRIP = 8;
18849 	enum int GL_POLYGON = 9;
18850 
18851 	alias GLvoid = void;
18852 	alias GLboolean = ubyte;
18853 	alias GLint = int;
18854 	alias GLuint = uint;
18855 	alias GLenum = uint;
18856 	alias GLchar = char;
18857 	alias GLsizei = int;
18858 	alias GLfloat = float;
18859 	alias GLintptr = size_t;
18860 	alias GLsizeiptr = ptrdiff_t;
18861 
18862 
18863 	enum uint GL_INVALID_ENUM = 0x0500;
18864 
18865 	enum uint GL_ZERO = 0;
18866 	enum uint GL_ONE = 1;
18867 
18868 	enum uint GL_BYTE = 0x1400;
18869 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18870 	enum uint GL_SHORT = 0x1402;
18871 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18872 	enum uint GL_INT = 0x1404;
18873 	enum uint GL_UNSIGNED_INT = 0x1405;
18874 	enum uint GL_FLOAT = 0x1406;
18875 	enum uint GL_2_BYTES = 0x1407;
18876 	enum uint GL_3_BYTES = 0x1408;
18877 	enum uint GL_4_BYTES = 0x1409;
18878 	enum uint GL_DOUBLE = 0x140A;
18879 
18880 	enum uint GL_STREAM_DRAW = 0x88E0;
18881 
18882 	enum uint GL_CCW = 0x0901;
18883 
18884 	enum uint GL_STENCIL_TEST = 0x0B90;
18885 	enum uint GL_SCISSOR_TEST = 0x0C11;
18886 
18887 	enum uint GL_EQUAL = 0x0202;
18888 	enum uint GL_NOTEQUAL = 0x0205;
18889 
18890 	enum uint GL_ALWAYS = 0x0207;
18891 	enum uint GL_KEEP = 0x1E00;
18892 
18893 	enum uint GL_INCR = 0x1E02;
18894 
18895 	enum uint GL_INCR_WRAP = 0x8507;
18896 	enum uint GL_DECR_WRAP = 0x8508;
18897 
18898 	enum uint GL_CULL_FACE = 0x0B44;
18899 	enum uint GL_BACK = 0x0405;
18900 
18901 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18902 	enum uint GL_VERTEX_SHADER = 0x8B31;
18903 
18904 	enum uint GL_COMPILE_STATUS = 0x8B81;
18905 	enum uint GL_LINK_STATUS = 0x8B82;
18906 
18907 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18908 
18909 	enum uint GL_STATIC_DRAW = 0x88E4;
18910 
18911 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18912 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18913 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18914 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18915 
18916 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18917 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18918 
18919 	enum uint GL_TEXTURE0 = 0x84C0U;
18920 	enum uint GL_TEXTURE1 = 0x84C1U;
18921 
18922 	enum uint GL_ARRAY_BUFFER = 0x8892;
18923 
18924 	enum uint GL_SRC_COLOR = 0x0300;
18925 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18926 	enum uint GL_SRC_ALPHA = 0x0302;
18927 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18928 	enum uint GL_DST_ALPHA = 0x0304;
18929 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18930 	enum uint GL_DST_COLOR = 0x0306;
18931 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18932 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18933 
18934 	enum uint GL_INVERT = 0x150AU;
18935 
18936 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18937 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18938 
18939 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18940 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18941 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18942 
18943 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18944 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18945 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18946 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18947 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18948 
18949 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18950 	enum uint GL_CLEAR = 0x1500U;
18951 	enum uint GL_COPY = 0x1503U;
18952 	enum uint GL_XOR = 0x1506U;
18953 
18954 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18955 
18956 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18957 
18958 	}
18959 }
18960 
18961 /++
18962 	History:
18963 		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.
18964 +/
18965 __gshared bool gluSuccessfullyLoaded = true;
18966 
18967 version(without_opengl) {} else {
18968 static if(!SdpyIsUsingIVGLBinds) {
18969 	version(Windows) {
18970 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18971 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18972 	} else {
18973 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18974 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18975 	}
18976 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18977 
18978 
18979 	shared static this() {
18980 		gl.loadDynamicLibrary();
18981 
18982 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18983 		// unless those functions are actually used
18984 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18985 		glu.loadDynamicLibrary();
18986 	}
18987 }
18988 }
18989 
18990 /++
18991 	Convenience method for converting D arrays to opengl buffer data
18992 
18993 	I would LOVE to overload it with the original glBufferData, but D won't
18994 	let me since glBufferData is a function pointer :(
18995 
18996 	Added: August 25, 2020 (version 8.5)
18997 +/
18998 version(without_opengl) {} else
18999 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
19000 	glBufferData(target, data.length, data.ptr, usage);
19001 }
19002 
19003 /+
19004 /++
19005 	A matrix for simple uses that easily integrates with [OpenGlShader].
19006 
19007 	Might not be useful to you since it only as some simple functions and
19008 	probably isn't that fast.
19009 
19010 	Note it uses an inline static array for its storage, so copying it
19011 	may be expensive.
19012 +/
19013 struct BasicMatrix(int columns, int rows, T = float) {
19014 	import core.stdc.math;
19015 
19016 	T[columns * rows] data = 0.0;
19017 
19018 	/++
19019 		Basic operations that operate *in place*.
19020 	+/
19021 	void translate() {
19022 
19023 	}
19024 
19025 	/// ditto
19026 	void scale() {
19027 
19028 	}
19029 
19030 	/// ditto
19031 	void rotate() {
19032 
19033 	}
19034 
19035 	/++
19036 
19037 	+/
19038 	static if(columns == rows)
19039 	static BasicMatrix identity() {
19040 		BasicMatrix m;
19041 		foreach(i; 0 .. columns)
19042 			data[0 + i + i * columns] = 1.0;
19043 		return m;
19044 	}
19045 
19046 	static BasicMatrix ortho() {
19047 		return BasicMatrix.init;
19048 	}
19049 }
19050 +/
19051 
19052 /++
19053 	Convenience class for using opengl shaders.
19054 
19055 	Ensure that you've loaded opengl 3+ and set your active
19056 	context before trying to use this.
19057 
19058 	Added: August 25, 2020 (version 8.5)
19059 +/
19060 version(without_opengl) {} else
19061 final class OpenGlShader {
19062 	private int shaderProgram_;
19063 	private @property void shaderProgram(int a) {
19064 		shaderProgram_ = a;
19065 	}
19066 	/// Get the program ID for use in OpenGL functions.
19067 	public @property int shaderProgram() {
19068 		return shaderProgram_;
19069 	}
19070 
19071 	/++
19072 
19073 	+/
19074 	static struct Source {
19075 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19076 		string code; ///
19077 	}
19078 
19079 	/++
19080 		Helper method to just compile some shader code and check for errors
19081 		while you do glCreateShader, etc. on the outside yourself.
19082 
19083 		This just does `glShaderSource` and `glCompileShader` for the given code.
19084 
19085 		If you the OpenGlShader class constructor, you never need to call this yourself.
19086 	+/
19087 	static void compile(int sid, Source code) {
19088 		const(char)*[1] buffer;
19089 		int[1] lengthBuffer;
19090 
19091 		buffer[0] = code.code.ptr;
19092 		lengthBuffer[0] = cast(int) code.code.length;
19093 
19094 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19095 		glCompileShader(sid);
19096 
19097 		int success;
19098 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19099 		if(!success) {
19100 			char[512] info;
19101 			int len;
19102 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19103 
19104 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19105 		}
19106 	}
19107 
19108 	/++
19109 		Calls `glLinkProgram` and throws if error a occurs.
19110 
19111 		If you the OpenGlShader class constructor, you never need to call this yourself.
19112 	+/
19113 	static void link(int shaderProgram) {
19114 		glLinkProgram(shaderProgram);
19115 		int success;
19116 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19117 		if(!success) {
19118 			char[512] info;
19119 			int len;
19120 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19121 
19122 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19123 		}
19124 	}
19125 
19126 	/++
19127 		Constructs the shader object by calling `glCreateProgram`, then
19128 		compiling each given [Source], and finally, linking them together.
19129 
19130 		Throws: on compile or link failure.
19131 	+/
19132 	this(Source[] codes...) {
19133 		shaderProgram = glCreateProgram();
19134 
19135 		int[16] shadersBufferStack;
19136 
19137 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19138 			shadersBufferStack[0 .. codes.length] :
19139 			new int[](codes.length);
19140 
19141 		foreach(idx, code; codes) {
19142 			shadersBuffer[idx] = glCreateShader(code.type);
19143 
19144 			compile(shadersBuffer[idx], code);
19145 
19146 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19147 		}
19148 
19149 		link(shaderProgram);
19150 
19151 		foreach(s; shadersBuffer)
19152 			glDeleteShader(s);
19153 	}
19154 
19155 	/// Calls `glUseProgram(this.shaderProgram)`
19156 	void use() {
19157 		glUseProgram(this.shaderProgram);
19158 	}
19159 
19160 	/// Deletes the program.
19161 	void delete_() {
19162 		glDeleteProgram(shaderProgram);
19163 		shaderProgram = 0;
19164 	}
19165 
19166 	/++
19167 		[OpenGlShader.uniforms].name gives you one of these.
19168 
19169 		You can get the id out of it or just assign
19170 	+/
19171 	static struct Uniform {
19172 		/// the id passed to glUniform*
19173 		int id;
19174 
19175 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
19176 		void opAssign(float x, float y, float z, float w) {
19177 			if(id != -1)
19178 			glUniform4f(id, x, y, z, w);
19179 		}
19180 
19181 		void opAssign(float x) {
19182 			if(id != -1)
19183 			glUniform1f(id, x);
19184 		}
19185 
19186 		void opAssign(float x, float y) {
19187 			if(id != -1)
19188 			glUniform2f(id, x, y);
19189 		}
19190 
19191 		void opAssign(T)(T t) {
19192 			t.glUniform(id);
19193 		}
19194 	}
19195 
19196 	static struct UniformsHelper {
19197 		OpenGlShader _shader;
19198 
19199 		@property Uniform opDispatch(string name)() {
19200 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
19201 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
19202 			//if(i == -1)
19203 				//throw new Exception("Could not find uniform " ~ name);
19204 			return Uniform(i);
19205 		}
19206 
19207 		@property void opDispatch(string name, T)(T t) {
19208 			Uniform f = this.opDispatch!name;
19209 			t.glUniform(f);
19210 		}
19211 	}
19212 
19213 	/++
19214 		Gives access to the uniforms through dot access.
19215 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
19216 	+/
19217 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
19218 }
19219 
19220 version(without_opengl) {} else {
19221 /++
19222 	A static container of experimental types and value constructors for opengl 3+ shaders.
19223 
19224 
19225 	You can declare variables like:
19226 
19227 	```
19228 	OGL.vec3f something;
19229 	```
19230 
19231 	But generally it would be used with [OpenGlShader]'s uniform helpers like
19232 
19233 	```
19234 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19235 	```
19236 
19237 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19238 
19239 
19240 	History:
19241 		Added December 7, 2021. Not yet stable.
19242 +/
19243 final class OGL {
19244 	static:
19245 
19246 	private template typeFromSpecifier(string specifier) {
19247 		static if(specifier == "f")
19248 			alias typeFromSpecifier = GLfloat;
19249 		else static if(specifier == "i")
19250 			alias typeFromSpecifier = GLint;
19251 		else static if(specifier == "ui")
19252 			alias typeFromSpecifier = GLuint;
19253 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19254 	}
19255 
19256 	private template CommonType(T...) {
19257 		static if(T.length == 1)
19258 			alias CommonType = T[0];
19259 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19260 			alias CommonType = CommonType!(C, T[2 .. $]);
19261 	}
19262 
19263 	private template typesToSpecifier(T...) {
19264 		static if(is(CommonType!T == float))
19265 			enum typesToSpecifier = "f";
19266 		else static if(is(CommonType!T == int))
19267 			enum typesToSpecifier = "i";
19268 		else static if(is(CommonType!T == uint))
19269 			enum typesToSpecifier = "ui";
19270 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19271 	}
19272 
19273 	private template genNames(size_t dim, size_t dim2 = 0) {
19274 		string helper() {
19275 			string s;
19276 			if(dim2) {
19277 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
19278 			} else {
19279 				if(dim > 0) s ~= "type x = 0;";
19280 				if(dim > 1) s ~= "type y = 0;";
19281 				if(dim > 2) s ~= "type z = 0;";
19282 				if(dim > 3) s ~= "type w = 0;";
19283 			}
19284 			return s;
19285 		}
19286 
19287 		enum genNames = helper();
19288 	}
19289 
19290 	// there's vec, arrays of vec, mat, and arrays of mat
19291 	template opDispatch(string name)
19292 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19293 	{
19294 		static if(name[4] == 'x') {
19295 			enum dimX = cast(int) (name[3] - '0');
19296 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19297 
19298 			enum dimY = cast(int) (name[5] - '0');
19299 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19300 
19301 			enum isArray = name[$ - 1] == 'v';
19302 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19303 			alias type = typeFromSpecifier!typeSpecifier;
19304 		} else {
19305 			enum dim = cast(int) (name[3] - '0');
19306 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19307 			enum isArray = name[$ - 1] == 'v';
19308 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19309 			alias type = typeFromSpecifier!typeSpecifier;
19310 		}
19311 
19312 		align(1)
19313 		struct opDispatch {
19314 			align(1):
19315 			static if(name[4] == 'x')
19316 				mixin(genNames!(dimX, dimY));
19317 			else
19318 				mixin(genNames!dim);
19319 
19320 			private void glUniform(OpenGlShader.Uniform assignTo) {
19321 				glUniform(assignTo.id);
19322 			}
19323 			private void glUniform(int assignTo) {
19324 				static if(name[4] == 'x') {
19325 					// FIXME
19326 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19327 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19328 				} else
19329 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19330 			}
19331 		}
19332 	}
19333 
19334 	auto vec(T...)(T members) {
19335 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19336 	}
19337 }
19338 }
19339 
19340 version(linux) {
19341 	version(with_eventloop) {} else {
19342 		private int epollFd = -1;
19343 		void prepareEventLoop() {
19344 			if(epollFd != -1)
19345 				return; // already initialized, no need to do it again
19346 			import ep = core.sys.linux.epoll;
19347 
19348 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19349 			if(epollFd == -1)
19350 				throw new Exception("epoll create failure");
19351 		}
19352 	}
19353 } else version(Posix) {
19354 	void prepareEventLoop() {}
19355 }
19356 
19357 version(X11) {
19358 	import core.stdc.locale : LC_ALL; // rdmd fix
19359 	__gshared bool sdx_isUTF8Locale;
19360 
19361 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19362 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19363 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19364 	// anal magic is here. I (Ketmar) hope you like it.
19365 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19366 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19367 	// later.
19368 
19369 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19370 	shared static this () {
19371 		if(!librariesSuccessfullyLoaded)
19372 			return;
19373 
19374 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19375 
19376 		// this doesn't hurt; it may add some locking, but the speed is still
19377 		// allows doing 60 FPS videogames; also, ignore the result, as most
19378 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19379 		// never seen this failing).
19380 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19381 
19382 		setlocale(LC_ALL, "");
19383 		// check if out locale is UTF-8
19384 		auto lct = setlocale(LC_CTYPE, null);
19385 		if (lct is null) {
19386 			sdx_isUTF8Locale = false;
19387 		} else {
19388 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19389 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19390 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19391 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19392 				{
19393 					sdx_isUTF8Locale = true;
19394 					break;
19395 				}
19396 			}
19397 		}
19398 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19399 	}
19400 }
19401 
19402 class ExperimentalTextComponent2 {
19403 	/+
19404 		Stage 1: get it working monospace
19405 		Stage 2: use proportional font
19406 		Stage 3: allow changes in inline style
19407 		Stage 4: allow new fonts and sizes in the middle
19408 		Stage 5: optimize gap buffer
19409 		Stage 6: optimize layout
19410 		Stage 7: word wrap
19411 		Stage 8: justification
19412 		Stage 9: editing, selection, etc.
19413 
19414 			Operations:
19415 				insert text
19416 				overstrike text
19417 				select
19418 				cut
19419 				modify
19420 	+/
19421 
19422 	/++
19423 		It asks for a window so it can translate abstract font sizes to actual on-screen values depending on the window's current dpi, scaling settings, etc.
19424 	+/
19425 	this(SimpleWindow window) {
19426 		this.window = window;
19427 	}
19428 
19429 	private SimpleWindow window;
19430 
19431 
19432 	/++
19433 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19434 		representing the internal parts. The first pass is focused on the x parameter, then the
19435 		renderer is responsible for going back to the parts in the current line and calling
19436 		adjustDownForAscent to change the y params.
19437 	+/
19438 	static interface ComponentRenderHelper {
19439 
19440 		/+
19441 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19442 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19443 			to move (adjust y to make room for new line) until you get back to the same position,
19444 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19445 
19446 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19447 			once you reach something that is unchanged, you can stop.
19448 		+/
19449 
19450 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19451 
19452 		int ascent() const;
19453 		int descent() const;
19454 
19455 		int advance() const;
19456 
19457 		bool endsWithExplititLineBreak() const;
19458 	}
19459 
19460 	static interface RenderResult {
19461 		/++
19462 			This is responsible for using what space is left (your object is responsible for keeping its own state after getting it updated from [repositionForNextLine]) and not going over if at all possible. If you can word wrap, you should when space is out. Otherwise, you can keep going if it means overflow hidden or scroll.
19463 		+/
19464 		void popFront();
19465 		@property bool empty() const;
19466 		@property ComponentRenderHelper front() const;
19467 
19468 		void repositionForNextLine(Point baseline, int availableWidth);
19469 	}
19470 
19471 	static interface ComponentInFlow {
19472 		void draw(ScreenPainter painter);
19473 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19474 
19475 		bool startsWithExplicitLineBreak() const;
19476 	}
19477 
19478 	static class TextFlowComponent : ComponentInFlow {
19479 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19480 
19481 		Color foreground;
19482 		Color background;
19483 
19484 		OperatingSystemFont font; // should NEVER be null
19485 
19486 		ubyte attributes; // underline, strike through, display on new block
19487 
19488 		version(Windows)
19489 			const(wchar)[] content;
19490 		else
19491 			const(char)[] content; // this should NEVER have a newline, except at the end
19492 
19493 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19494 
19495 		// could prolly put some spacing around it too like margin / padding
19496 
19497 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19498 			in { assert(font !is null);
19499 			     assert(!font.isNull); }
19500 			do
19501 		{
19502 			this.foreground = f;
19503 			this.background = b;
19504 			this.font = font;
19505 
19506 			this.attributes = attr;
19507 			version(Windows) {
19508 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19509 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19510 				auto buffer = new wchar[](sz);
19511 				this.content = makeWindowsString(c, buffer, conversionFlags);
19512 			} else {
19513 				this.content = c.dup;
19514 			}
19515 		}
19516 
19517 		void draw(ScreenPainter painter) {
19518 			painter.setFont(this.font);
19519 			painter.outlineColor = this.foreground;
19520 			painter.fillColor = Color.transparent;
19521 			foreach(rendered; this.rendered) {
19522 				// the component works in term of baseline,
19523 				// but the painter works in term of upper left bounding box
19524 				// so need to translate that
19525 
19526 				if(this.background.a) {
19527 					painter.fillColor = this.background;
19528 					painter.outlineColor = this.background;
19529 
19530 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19531 
19532 					painter.outlineColor = this.foreground;
19533 					painter.fillColor = Color.transparent;
19534 				}
19535 
19536 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19537 
19538 				// FIXME: strike through, underline, highlight selection, etc.
19539 			}
19540 		}
19541 	}
19542 
19543 	// I could split the parts into words on render
19544 	// for easier word-wrap, each one being an unbreakable "inline-block"
19545 	private TextFlowComponent[] parts;
19546 	private int needsRerenderFrom;
19547 
19548 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19549 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19550 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19551 	}
19552 
19553 	static struct RenderedComponent {
19554 		int startX;
19555 		int startY;
19556 		short width;
19557 		// height is always from the containing part's font. This saves some space and means recalculations need not continue past the current line, unless a new part is added with a different font!
19558 		// for individual chars in here you've gotta process on demand
19559 		version(Windows)
19560 			const(wchar)[] slice;
19561 		else
19562 			const(char)[] slice;
19563 	}
19564 
19565 
19566 	void rerender(Rectangle boundingBox) {
19567 		Point baseline = boundingBox.upperLeft;
19568 
19569 		this.boundingBox.left = boundingBox.left;
19570 		this.boundingBox.top = boundingBox.top;
19571 
19572 		auto remainingParts = parts;
19573 
19574 		int largestX;
19575 
19576 
19577 		foreach(part; parts)
19578 			part.font.prepareContext(window);
19579 		scope(exit)
19580 		foreach(part; parts)
19581 			part.font.releaseContext();
19582 
19583 		calculateNextLine:
19584 
19585 		int nextLineHeight = 0;
19586 		int nextBiggestDescent = 0;
19587 
19588 		foreach(part; remainingParts) {
19589 			auto height = part.font.ascent;
19590 			if(height > nextLineHeight)
19591 				nextLineHeight = height;
19592 			if(part.font.descent > nextBiggestDescent)
19593 				nextBiggestDescent = part.font.descent;
19594 			if(part.content.length && part.content[$-1] == '\n')
19595 				break;
19596 		}
19597 
19598 		baseline.y += nextLineHeight;
19599 		auto lineStart = baseline;
19600 
19601 		while(remainingParts.length) {
19602 			remainingParts[0].rendered = null;
19603 
19604 			bool eol;
19605 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19606 				eol = true;
19607 
19608 			// FIXME: word wrap
19609 			auto font = remainingParts[0].font;
19610 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19611 			auto width = font.stringWidth(slice, window);
19612 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19613 
19614 			remainingParts = remainingParts[1 .. $];
19615 			baseline.x += width;
19616 
19617 			if(eol) {
19618 				baseline.y += nextBiggestDescent;
19619 				if(baseline.x > largestX)
19620 					largestX = baseline.x;
19621 				baseline.x = lineStart.x;
19622 				goto calculateNextLine;
19623 			}
19624 		}
19625 
19626 		if(baseline.x > largestX)
19627 			largestX = baseline.x;
19628 
19629 		this.boundingBox.right = largestX;
19630 		this.boundingBox.bottom = baseline.y;
19631 	}
19632 
19633 	// you must call rerender first!
19634 	void draw(ScreenPainter painter) {
19635 		foreach(part; parts) {
19636 			part.draw(painter);
19637 		}
19638 	}
19639 
19640 	struct IdentifyResult {
19641 		TextFlowComponent part;
19642 		int charIndexInPart;
19643 		int totalCharIndex = -1; // if this is -1, it just means the end
19644 
19645 		Rectangle boundingBox;
19646 	}
19647 
19648 	IdentifyResult identify(Point pt, bool exact = false) {
19649 		if(parts.length == 0)
19650 			return IdentifyResult(null, 0);
19651 
19652 		if(pt.y < boundingBox.top) {
19653 			if(exact)
19654 				return IdentifyResult(null, 1);
19655 			return IdentifyResult(parts[0], 0);
19656 		}
19657 		if(pt.y > boundingBox.bottom) {
19658 			if(exact)
19659 				return IdentifyResult(null, 2);
19660 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19661 		}
19662 
19663 		int tci = 0;
19664 
19665 		// I should probably like binary search this or something...
19666 		foreach(ref part; parts) {
19667 			foreach(rendered; part.rendered) {
19668 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19669 				if(rect.contains(pt)) {
19670 					auto x = pt.x - rendered.startX;
19671 					auto estimatedIdx = x / part.font.averageWidth;
19672 
19673 					if(estimatedIdx < 0)
19674 						estimatedIdx = 0;
19675 
19676 					if(estimatedIdx > rendered.slice.length)
19677 						estimatedIdx = cast(int) rendered.slice.length;
19678 
19679 					int idx;
19680 					int x1, x2;
19681 					if(part.font.isMonospace) {
19682 						auto w = part.font.averageWidth;
19683 						if(!exact && x > (estimatedIdx + 1) * w)
19684 							return IdentifyResult(null, 4);
19685 						idx = estimatedIdx;
19686 						x1 = idx * w;
19687 						x2 = (idx + 1) * w;
19688 					} else {
19689 						idx = estimatedIdx;
19690 
19691 						part.font.prepareContext(window);
19692 						scope(exit) part.font.releaseContext();
19693 
19694 						// int iterations;
19695 
19696 						while(true) {
19697 							// iterations++;
19698 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19699 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19700 
19701 							x1 += rendered.startX;
19702 							x2 += rendered.startX;
19703 
19704 							if(pt.x < x1) {
19705 								if(idx == 0) {
19706 									if(exact)
19707 										return IdentifyResult(null, 6);
19708 									else
19709 										break;
19710 								}
19711 								idx--;
19712 							} else if(pt.x > x2) {
19713 								idx++;
19714 								if(idx > rendered.slice.length) {
19715 									if(exact)
19716 										return IdentifyResult(null, 5);
19717 									else
19718 										break;
19719 								}
19720 							} else if(pt.x >= x1 && pt.x <= x2) {
19721 								if(idx)
19722 									idx--; // point it at the original index
19723 								break; // we fit
19724 							}
19725 						}
19726 
19727 						// writeln(iterations)
19728 					}
19729 
19730 
19731 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19732 				}
19733 			}
19734 			tci += cast(int) part.content.length; // FIXME: utf-8?
19735 		}
19736 		return IdentifyResult(null, 3);
19737 	}
19738 
19739 	Rectangle boundingBox; // only set after [rerender]
19740 
19741 	// text will be positioned around the exclusion zone
19742 	static struct ExclusionZone {
19743 
19744 	}
19745 
19746 	ExclusionZone[] exclusionZones;
19747 }
19748 
19749 
19750 // Don't use this yet. When I'm happy with it, I will move it to the
19751 // regular module namespace.
19752 mixin template ExperimentalTextComponent() {
19753 
19754 static:
19755 
19756 	alias Rectangle = arsd.color.Rectangle;
19757 
19758 	struct ForegroundColor {
19759 		Color color;
19760 		alias color this;
19761 
19762 		this(Color c) {
19763 			color = c;
19764 		}
19765 
19766 		this(int r, int g, int b, int a = 255) {
19767 			color = Color(r, g, b, a);
19768 		}
19769 
19770 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19771 			return ForegroundColor(mixin("Color." ~ s));
19772 		}
19773 	}
19774 
19775 	struct BackgroundColor {
19776 		Color color;
19777 		alias color this;
19778 
19779 		this(Color c) {
19780 			color = c;
19781 		}
19782 
19783 		this(int r, int g, int b, int a = 255) {
19784 			color = Color(r, g, b, a);
19785 		}
19786 
19787 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19788 			return BackgroundColor(mixin("Color." ~ s));
19789 		}
19790 	}
19791 
19792 	static class InlineElement {
19793 		string text;
19794 
19795 		BlockElement containingBlock;
19796 
19797 		Color color = Color.black;
19798 		Color backgroundColor = Color.transparent;
19799 		ushort styles;
19800 
19801 		string font;
19802 		int fontSize;
19803 
19804 		int lineHeight;
19805 
19806 		void* identifier;
19807 
19808 		Rectangle boundingBox;
19809 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19810 
19811 		bool isMergeCompatible(InlineElement other) {
19812 			return
19813 				containingBlock is other.containingBlock &&
19814 				color == other.color &&
19815 				backgroundColor == other.backgroundColor &&
19816 				styles == other.styles &&
19817 				font == other.font &&
19818 				fontSize == other.fontSize &&
19819 				lineHeight == other.lineHeight &&
19820 				true;
19821 		}
19822 
19823 		int xOfIndex(size_t index) {
19824 			if(index < letterXs.length)
19825 				return letterXs[index];
19826 			else
19827 				return boundingBox.right;
19828 		}
19829 
19830 		InlineElement clone() {
19831 			auto ie = new InlineElement();
19832 			ie.tupleof = this.tupleof;
19833 			return ie;
19834 		}
19835 
19836 		InlineElement getPreviousInlineElement() {
19837 			InlineElement prev = null;
19838 			foreach(ie; this.containingBlock.parts) {
19839 				if(ie is this)
19840 					break;
19841 				prev = ie;
19842 			}
19843 			if(prev is null) {
19844 				BlockElement pb;
19845 				BlockElement cb = this.containingBlock;
19846 				moar:
19847 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19848 					if(ie is cb)
19849 						break;
19850 					pb = ie;
19851 				}
19852 				if(pb is null)
19853 					return null;
19854 				if(pb.parts.length == 0) {
19855 					cb = pb;
19856 					goto moar;
19857 				}
19858 
19859 				prev = pb.parts[$-1];
19860 
19861 			}
19862 			return prev;
19863 		}
19864 
19865 		InlineElement getNextInlineElement() {
19866 			InlineElement next = null;
19867 			foreach(idx, ie; this.containingBlock.parts) {
19868 				if(ie is this) {
19869 					if(idx + 1 < this.containingBlock.parts.length)
19870 						next = this.containingBlock.parts[idx + 1];
19871 					break;
19872 				}
19873 			}
19874 			if(next is null) {
19875 				BlockElement n;
19876 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19877 					if(ie is this.containingBlock) {
19878 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19879 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19880 						break;
19881 					}
19882 				}
19883 				if(n is null)
19884 					return null;
19885 
19886 				if(n.parts.length)
19887 					next = n.parts[0];
19888 				else {} // FIXME
19889 
19890 			}
19891 			return next;
19892 		}
19893 
19894 	}
19895 
19896 	// Block elements are used entirely for positioning inline elements,
19897 	// which are the things that are actually drawn.
19898 	class BlockElement {
19899 		InlineElement[] parts;
19900 		uint alignment;
19901 
19902 		int whiteSpace; // pre, pre-wrap, wrap
19903 
19904 		TextLayout containingLayout;
19905 
19906 		// inputs
19907 		Point where;
19908 		Size minimumSize;
19909 		Size maximumSize;
19910 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19911 		void* identifier;
19912 
19913 		Rectangle margin;
19914 		Rectangle padding;
19915 
19916 		// outputs
19917 		Rectangle[] boundingBoxes;
19918 	}
19919 
19920 	struct TextIdentifyResult {
19921 		InlineElement element;
19922 		int offset;
19923 
19924 		private TextIdentifyResult fixupNewline() {
19925 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19926 				offset--;
19927 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19928 				offset--;
19929 			}
19930 			return this;
19931 		}
19932 	}
19933 
19934 	class TextLayout {
19935 		BlockElement[] blocks;
19936 		Rectangle boundingBox_;
19937 		Rectangle boundingBox() { return boundingBox_; }
19938 		void boundingBox(Rectangle r) {
19939 			if(r != boundingBox_) {
19940 				boundingBox_ = r;
19941 				layoutInvalidated = true;
19942 			}
19943 		}
19944 
19945 		Rectangle contentBoundingBox() {
19946 			Rectangle r;
19947 			foreach(block; blocks)
19948 			foreach(ie; block.parts) {
19949 				if(ie.boundingBox.right > r.right)
19950 					r.right = ie.boundingBox.right;
19951 				if(ie.boundingBox.bottom > r.bottom)
19952 					r.bottom = ie.boundingBox.bottom;
19953 			}
19954 			return r;
19955 		}
19956 
19957 		BlockElement[] getBlocks() {
19958 			return blocks;
19959 		}
19960 
19961 		InlineElement[] getTexts() {
19962 			InlineElement[] elements;
19963 			foreach(block; blocks)
19964 				elements ~= block.parts;
19965 			return elements;
19966 		}
19967 
19968 		string getPlainText() {
19969 			string text;
19970 			foreach(block; blocks)
19971 				foreach(part; block.parts)
19972 					text ~= part.text;
19973 			return text;
19974 		}
19975 
19976 		string getHtml() {
19977 			return null; // FIXME
19978 		}
19979 
19980 		this(Rectangle boundingBox) {
19981 			this.boundingBox = boundingBox;
19982 		}
19983 
19984 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19985 			auto be = new BlockElement();
19986 			be.containingLayout = this;
19987 			if(after is null)
19988 				blocks ~= be;
19989 			else {
19990 				foreach(idx, b; blocks) {
19991 					if(b is after.containingBlock) {
19992 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19993 						break;
19994 					}
19995 				}
19996 			}
19997 			return be;
19998 		}
19999 
20000 		void clear() {
20001 			blocks = null;
20002 			selectionStart = selectionEnd = caret = Caret.init;
20003 		}
20004 
20005 		void addText(Args...)(Args args) {
20006 			if(blocks.length == 0)
20007 				addBlock();
20008 
20009 			InlineElement ie = new InlineElement();
20010 			foreach(idx, arg; args) {
20011 				static if(is(typeof(arg) == ForegroundColor))
20012 					ie.color = arg;
20013 				else static if(is(typeof(arg) == TextFormat)) {
20014 					if(arg & 0x8000) // ~TextFormat.something turns it off
20015 						ie.styles &= arg;
20016 					else
20017 						ie.styles |= arg;
20018 				} else static if(is(typeof(arg) == string)) {
20019 					static if(idx == 0 && args.length > 1)
20020 						static assert(0, "Put styles before the string.");
20021 					size_t lastLineIndex;
20022 					foreach(cidx, char a; arg) {
20023 						if(a == '\n') {
20024 							ie.text = arg[lastLineIndex .. cidx + 1];
20025 							lastLineIndex = cidx + 1;
20026 							ie.containingBlock = blocks[$-1];
20027 							blocks[$-1].parts ~= ie.clone;
20028 							ie.text = null;
20029 						} else {
20030 
20031 						}
20032 					}
20033 
20034 					ie.text = arg[lastLineIndex .. $];
20035 					ie.containingBlock = blocks[$-1];
20036 					blocks[$-1].parts ~= ie.clone;
20037 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
20038 				}
20039 			}
20040 
20041 			invalidateLayout();
20042 		}
20043 
20044 		void tryMerge(InlineElement into, InlineElement what) {
20045 			if(!into.isMergeCompatible(what)) {
20046 				return; // cannot merge, different configs
20047 			}
20048 
20049 			// cool, can merge, bring text together...
20050 			into.text ~= what.text;
20051 
20052 			// and remove what
20053 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
20054 				if(what.containingBlock.parts[a] is what) {
20055 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
20056 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
20057 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
20058 
20059 				}
20060 			}
20061 
20062 			// FIXME: ensure no other carets have a reference to it
20063 		}
20064 
20065 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
20066 		TextIdentifyResult identify(int x, int y, bool exact = false) {
20067 			TextIdentifyResult inexactMatch;
20068 			foreach(block; blocks) {
20069 				foreach(part; block.parts) {
20070 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
20071 
20072 						// FIXME binary search
20073 						int tidx;
20074 						int lastX;
20075 						foreach_reverse(idxo, lx; part.letterXs) {
20076 							int idx = cast(int) idxo;
20077 							if(lx <= x) {
20078 								if(lastX && lastX - x < x - lx)
20079 									tidx = idx + 1;
20080 								else
20081 									tidx = idx;
20082 								break;
20083 							}
20084 							lastX = lx;
20085 						}
20086 
20087 						return TextIdentifyResult(part, tidx).fixupNewline;
20088 					} else if(!exact) {
20089 						// we're not in the box, but are we on the same line?
20090 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
20091 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
20092 					}
20093 				}
20094 			}
20095 
20096 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
20097 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
20098 
20099 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
20100 		}
20101 
20102 		void moveCaretToPixelCoordinates(int x, int y) {
20103 			auto result = identify(x, y);
20104 			caret.inlineElement = result.element;
20105 			caret.offset = result.offset;
20106 		}
20107 
20108 		void selectToPixelCoordinates(int x, int y) {
20109 			auto result = identify(x, y);
20110 
20111 			if(y < caretLastDrawnY1) {
20112 				// on a previous line, carat is selectionEnd
20113 				selectionEnd = caret;
20114 
20115 				selectionStart = Caret(this, result.element, result.offset);
20116 			} else if(y > caretLastDrawnY2) {
20117 				// on a later line
20118 				selectionStart = caret;
20119 
20120 				selectionEnd = Caret(this, result.element, result.offset);
20121 			} else {
20122 				// on the same line...
20123 				if(x <= caretLastDrawnX) {
20124 					selectionEnd = caret;
20125 					selectionStart = Caret(this, result.element, result.offset);
20126 				} else {
20127 					selectionStart = caret;
20128 					selectionEnd = Caret(this, result.element, result.offset);
20129 				}
20130 
20131 			}
20132 		}
20133 
20134 
20135 		/// Call this if the inputs change. It will reflow everything
20136 		void redoLayout(ScreenPainter painter) {
20137 			//painter.setClipRectangle(boundingBox);
20138 			auto pos = Point(boundingBox.left, boundingBox.top);
20139 
20140 			int lastHeight;
20141 			void nl() {
20142 				pos.x = boundingBox.left;
20143 				pos.y += lastHeight;
20144 			}
20145 			foreach(block; blocks) {
20146 				nl();
20147 				foreach(part; block.parts) {
20148 					part.letterXs = null;
20149 
20150 					auto size = painter.textSize(part.text);
20151 					version(Windows)
20152 						if(part.text.length && part.text[$-1] == '\n')
20153 							size.height /= 2; // windows counts the new line at the end, but we don't want that
20154 
20155 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
20156 
20157 					foreach(idx, char c; part.text) {
20158 							// FIXME: unicode
20159 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
20160 					}
20161 
20162 					pos.x += size.width;
20163 					if(pos.x >= boundingBox.right) {
20164 						pos.y += size.height;
20165 						pos.x = boundingBox.left;
20166 						lastHeight = 0;
20167 					} else {
20168 						lastHeight = size.height;
20169 					}
20170 
20171 					if(part.text.length && part.text[$-1] == '\n')
20172 						nl();
20173 				}
20174 			}
20175 
20176 			layoutInvalidated = false;
20177 		}
20178 
20179 		bool layoutInvalidated = true;
20180 		void invalidateLayout() {
20181 			layoutInvalidated = true;
20182 		}
20183 
20184 // FIXME: caret can remain sometimes when inserting
20185 // FIXME: inserting at the beginning once you already have something can eff it up.
20186 		void drawInto(ScreenPainter painter, bool focused = false) {
20187 			if(layoutInvalidated)
20188 				redoLayout(painter);
20189 			foreach(block; blocks) {
20190 				foreach(part; block.parts) {
20191 					painter.outlineColor = part.color;
20192 					painter.fillColor = part.backgroundColor;
20193 
20194 					auto pos = part.boundingBox.upperLeft;
20195 					auto size = part.boundingBox.size;
20196 
20197 					painter.drawText(pos, part.text);
20198 					if(part.styles & TextFormat.underline)
20199 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
20200 					if(part.styles & TextFormat.strikethrough)
20201 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
20202 				}
20203 			}
20204 
20205 			// on every redraw, I will force the caret to be
20206 			// redrawn too, in order to eliminate perceived lag
20207 			// when moving around with the mouse.
20208 			eraseCaret(painter);
20209 
20210 			if(focused) {
20211 				highlightSelection(painter);
20212 				drawCaret(painter);
20213 			}
20214 		}
20215 
20216 		Color selectionXorColor = Color(255, 255, 127);
20217 
20218 		void highlightSelection(ScreenPainter painter) {
20219 			if(selectionStart is selectionEnd)
20220 				return; // no selection
20221 
20222 			if(selectionStart.inlineElement is null) return;
20223 			if(selectionEnd.inlineElement is null) return;
20224 
20225 			assert(selectionStart.inlineElement !is null);
20226 			assert(selectionEnd.inlineElement !is null);
20227 
20228 			painter.rasterOp = RasterOp.xor;
20229 			painter.outlineColor = Color.transparent;
20230 			painter.fillColor = selectionXorColor;
20231 
20232 			auto at = selectionStart.inlineElement;
20233 			auto atOffset = selectionStart.offset;
20234 			bool done;
20235 			while(at) {
20236 				auto box = at.boundingBox;
20237 				if(atOffset < at.letterXs.length)
20238 					box.left = at.letterXs[atOffset];
20239 
20240 				if(at is selectionEnd.inlineElement) {
20241 					if(selectionEnd.offset < at.letterXs.length)
20242 						box.right = at.letterXs[selectionEnd.offset];
20243 					done = true;
20244 				}
20245 
20246 				painter.drawRectangle(box.upperLeft, box.width, box.height);
20247 
20248 				if(done)
20249 					break;
20250 
20251 				at = at.getNextInlineElement();
20252 				atOffset = 0;
20253 			}
20254 		}
20255 
20256 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
20257 		bool caretShowingOnScreen = false;
20258 		void drawCaret(ScreenPainter painter) {
20259 			//painter.setClipRectangle(boundingBox);
20260 			int x, y1, y2;
20261 			if(caret.inlineElement is null) {
20262 				x = boundingBox.left;
20263 				y1 = boundingBox.top + 2;
20264 				y2 = boundingBox.top + painter.fontHeight;
20265 			} else {
20266 				x = caret.inlineElement.xOfIndex(caret.offset);
20267 				y1 = caret.inlineElement.boundingBox.top + 2;
20268 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20269 			}
20270 
20271 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
20272 				eraseCaret(painter);
20273 
20274 			painter.pen = Pen(Color.white, 1);
20275 			painter.rasterOp = RasterOp.xor;
20276 			painter.drawLine(
20277 				Point(x, y1),
20278 				Point(x, y2)
20279 			);
20280 			painter.rasterOp = RasterOp.normal;
20281 			caretShowingOnScreen = !caretShowingOnScreen;
20282 
20283 			if(caretShowingOnScreen) {
20284 				caretLastDrawnX = x;
20285 				caretLastDrawnY1 = y1;
20286 				caretLastDrawnY2 = y2;
20287 			}
20288 		}
20289 
20290 		Rectangle caretBoundingBox() {
20291 			int x, y1, y2;
20292 			if(caret.inlineElement is null) {
20293 				x = boundingBox.left;
20294 				y1 = boundingBox.top + 2;
20295 				y2 = boundingBox.top + 16;
20296 			} else {
20297 				x = caret.inlineElement.xOfIndex(caret.offset);
20298 				y1 = caret.inlineElement.boundingBox.top + 2;
20299 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20300 			}
20301 
20302 			return Rectangle(x, y1, x + 1, y2);
20303 		}
20304 
20305 		void eraseCaret(ScreenPainter painter) {
20306 			//painter.setClipRectangle(boundingBox);
20307 			if(!caretShowingOnScreen) return;
20308 			painter.pen = Pen(Color.white, 1);
20309 			painter.rasterOp = RasterOp.xor;
20310 			painter.drawLine(
20311 				Point(caretLastDrawnX, caretLastDrawnY1),
20312 				Point(caretLastDrawnX, caretLastDrawnY2)
20313 			);
20314 
20315 			caretShowingOnScreen = false;
20316 			painter.rasterOp = RasterOp.normal;
20317 		}
20318 
20319 		/// Caret movement api
20320 		/// These should give the user a logical result based on what they see on screen...
20321 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20322 		void moveUp() {
20323 			if(caret.inlineElement is null) return;
20324 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20325 			auto y = caret.inlineElement.boundingBox.top + 2;
20326 
20327 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20328 			if(y < 0)
20329 				return;
20330 
20331 			auto i = identify(x, y);
20332 
20333 			if(i.element) {
20334 				caret.inlineElement = i.element;
20335 				caret.offset = i.offset;
20336 			}
20337 		}
20338 		void moveDown() {
20339 			if(caret.inlineElement is null) return;
20340 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20341 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20342 
20343 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20344 
20345 			auto i = identify(x, y);
20346 			if(i.element) {
20347 				caret.inlineElement = i.element;
20348 				caret.offset = i.offset;
20349 			}
20350 		}
20351 		void moveLeft() {
20352 			if(caret.inlineElement is null) return;
20353 			if(caret.offset)
20354 				caret.offset--;
20355 			else {
20356 				auto p = caret.inlineElement.getPreviousInlineElement();
20357 				if(p) {
20358 					caret.inlineElement = p;
20359 					if(p.text.length && p.text[$-1] == '\n')
20360 						caret.offset = cast(int) p.text.length - 1;
20361 					else
20362 						caret.offset = cast(int) p.text.length;
20363 				}
20364 			}
20365 		}
20366 		void moveRight() {
20367 			if(caret.inlineElement is null) return;
20368 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20369 				caret.offset++;
20370 			} else {
20371 				auto p = caret.inlineElement.getNextInlineElement();
20372 				if(p) {
20373 					caret.inlineElement = p;
20374 					caret.offset = 0;
20375 				}
20376 			}
20377 		}
20378 		void moveHome() {
20379 			if(caret.inlineElement is null) return;
20380 			auto x = 0;
20381 			auto y = caret.inlineElement.boundingBox.top + 2;
20382 
20383 			auto i = identify(x, y);
20384 
20385 			if(i.element) {
20386 				caret.inlineElement = i.element;
20387 				caret.offset = i.offset;
20388 			}
20389 		}
20390 		void moveEnd() {
20391 			if(caret.inlineElement is null) return;
20392 			auto x = int.max;
20393 			auto y = caret.inlineElement.boundingBox.top + 2;
20394 
20395 			auto i = identify(x, y);
20396 
20397 			if(i.element) {
20398 				caret.inlineElement = i.element;
20399 				caret.offset = i.offset;
20400 			}
20401 
20402 		}
20403 		void movePageUp(ref Caret caret) {}
20404 		void movePageDown(ref Caret caret) {}
20405 
20406 		void moveDocumentStart(ref Caret caret) {
20407 			if(blocks.length && blocks[0].parts.length)
20408 				caret = Caret(this, blocks[0].parts[0], 0);
20409 			else
20410 				caret = Caret.init;
20411 		}
20412 
20413 		void moveDocumentEnd(ref Caret caret) {
20414 			if(blocks.length) {
20415 				auto parts = blocks[$-1].parts;
20416 				if(parts.length) {
20417 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20418 				} else {
20419 					caret = Caret.init;
20420 				}
20421 			} else
20422 				caret = Caret.init;
20423 		}
20424 
20425 		void deleteSelection() {
20426 			if(selectionStart is selectionEnd)
20427 				return;
20428 
20429 			if(selectionStart.inlineElement is null) return;
20430 			if(selectionEnd.inlineElement is null) return;
20431 
20432 			assert(selectionStart.inlineElement !is null);
20433 			assert(selectionEnd.inlineElement !is null);
20434 
20435 			auto at = selectionStart.inlineElement;
20436 
20437 			if(selectionEnd.inlineElement is at) {
20438 				// same element, need to chop out
20439 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20440 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20441 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20442 			} else {
20443 				// different elements, we can do it with slicing
20444 				at.text = at.text[0 .. selectionStart.offset];
20445 				if(selectionStart.offset < at.letterXs.length)
20446 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20447 
20448 				at = at.getNextInlineElement();
20449 
20450 				while(at) {
20451 					if(at is selectionEnd.inlineElement) {
20452 						at.text = at.text[selectionEnd.offset .. $];
20453 						if(selectionEnd.offset < at.letterXs.length)
20454 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20455 						selectionEnd.offset = 0;
20456 						break;
20457 					} else {
20458 						auto cfd = at;
20459 						cfd.text = null; // delete the whole thing
20460 
20461 						at = at.getNextInlineElement();
20462 
20463 						if(cfd.text.length == 0) {
20464 							// and remove cfd
20465 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20466 								if(cfd.containingBlock.parts[a] is cfd) {
20467 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20468 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20469 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20470 
20471 								}
20472 							}
20473 						}
20474 					}
20475 				}
20476 			}
20477 
20478 			caret = selectionEnd;
20479 			selectNone();
20480 
20481 			invalidateLayout();
20482 
20483 		}
20484 
20485 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20486 		void insert(in char[] text) {
20487 			foreach(dchar ch; text)
20488 				insert(ch);
20489 		}
20490 		/// ditto
20491 		void insert(dchar ch) {
20492 
20493 			bool selectionDeleted = false;
20494 			if(selectionStart !is selectionEnd) {
20495 				deleteSelection();
20496 				selectionDeleted = true;
20497 			}
20498 
20499 			if(ch == 127) {
20500 				delete_();
20501 				return;
20502 			}
20503 			if(ch == 8) {
20504 				if(!selectionDeleted)
20505 					backspace();
20506 				return;
20507 			}
20508 
20509 			invalidateLayout();
20510 
20511 			if(ch == 13) ch = 10;
20512 			auto e = caret.inlineElement;
20513 			if(e is null) {
20514 				addText("" ~ cast(char) ch) ; // FIXME
20515 				return;
20516 			}
20517 
20518 			if(caret.offset == e.text.length) {
20519 				e.text ~= cast(char) ch; // FIXME
20520 				caret.offset++;
20521 				if(ch == 10) {
20522 					auto c = caret.inlineElement.clone;
20523 					c.text = null;
20524 					c.letterXs = null;
20525 					insertPartAfter(c,e);
20526 					caret = Caret(this, c, 0);
20527 				}
20528 			} else {
20529 				// FIXME cast char sucks
20530 				if(ch == 10) {
20531 					auto c = caret.inlineElement.clone;
20532 					c.text = e.text[caret.offset .. $];
20533 					if(caret.offset < c.letterXs.length)
20534 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20535 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20536 					if(caret.offset <= e.letterXs.length) {
20537 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20538 					}
20539 					insertPartAfter(c,e);
20540 					caret = Caret(this, c, 0);
20541 				} else {
20542 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20543 					caret.offset++;
20544 				}
20545 			}
20546 		}
20547 
20548 		void insertPartAfter(InlineElement what, InlineElement where) {
20549 			foreach(idx, p; where.containingBlock.parts) {
20550 				if(p is where) {
20551 					if(idx + 1 == where.containingBlock.parts.length)
20552 						where.containingBlock.parts ~= what;
20553 					else
20554 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20555 					return;
20556 				}
20557 			}
20558 		}
20559 
20560 		void cleanupStructures() {
20561 			for(size_t i = 0; i < blocks.length; i++) {
20562 				auto block = blocks[i];
20563 				for(size_t a = 0; a < block.parts.length; a++) {
20564 					auto part = block.parts[a];
20565 					if(part.text.length == 0) {
20566 						for(size_t b = a; b < block.parts.length - 1; b++)
20567 							block.parts[b] = block.parts[b+1];
20568 						block.parts = block.parts[0 .. $-1];
20569 					}
20570 				}
20571 				if(block.parts.length == 0) {
20572 					for(size_t a = i; a < blocks.length - 1; a++)
20573 						blocks[a] = blocks[a+1];
20574 					blocks = blocks[0 .. $-1];
20575 				}
20576 			}
20577 		}
20578 
20579 		void backspace() {
20580 			try_again:
20581 			auto e = caret.inlineElement;
20582 			if(e is null)
20583 				return;
20584 			if(caret.offset == 0) {
20585 				auto prev = e.getPreviousInlineElement();
20586 				if(prev is null)
20587 					return;
20588 				auto newOffset = cast(int) prev.text.length;
20589 				tryMerge(prev, e);
20590 				caret.inlineElement = prev;
20591 				caret.offset = prev is null ? 0 : newOffset;
20592 
20593 				goto try_again;
20594 			} else if(caret.offset == e.text.length) {
20595 				e.text = e.text[0 .. $-1];
20596 				caret.offset--;
20597 			} else {
20598 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20599 				caret.offset--;
20600 			}
20601 			//cleanupStructures();
20602 
20603 			invalidateLayout();
20604 		}
20605 		void delete_() {
20606 			if(selectionStart !is selectionEnd)
20607 				deleteSelection();
20608 			else {
20609 				auto before = caret;
20610 				moveRight();
20611 				if(caret != before) {
20612 					backspace();
20613 				}
20614 			}
20615 
20616 			invalidateLayout();
20617 		}
20618 		void overstrike() {}
20619 
20620 		/// Selection API. See also: caret movement.
20621 		void selectAll() {
20622 			moveDocumentStart(selectionStart);
20623 			moveDocumentEnd(selectionEnd);
20624 		}
20625 		bool selectNone() {
20626 			if(selectionStart != selectionEnd) {
20627 				selectionStart = selectionEnd = Caret.init;
20628 				return true;
20629 			}
20630 			return false;
20631 		}
20632 
20633 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20634 		/// They will modify the current selection if there is one and will splice one in if needed.
20635 		void changeAttributes() {}
20636 
20637 
20638 		/// Text search api. They manipulate the selection and/or caret.
20639 		void findText(string text) {}
20640 		void findIndex(size_t textIndex) {}
20641 
20642 		// sample event handlers
20643 
20644 		void handleEvent(KeyEvent event) {
20645 			//if(event.type == KeyEvent.Type.KeyPressed) {
20646 
20647 			//}
20648 		}
20649 
20650 		void handleEvent(dchar ch) {
20651 
20652 		}
20653 
20654 		void handleEvent(MouseEvent event) {
20655 
20656 		}
20657 
20658 		bool contentEditable; // can it be edited?
20659 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20660 		bool contentSelectable; // selectable?
20661 
20662 		Caret caret;
20663 		Caret selectionStart;
20664 		Caret selectionEnd;
20665 
20666 		bool insertMode;
20667 	}
20668 
20669 	struct Caret {
20670 		TextLayout layout;
20671 		InlineElement inlineElement;
20672 		int offset;
20673 	}
20674 
20675 	enum TextFormat : ushort {
20676 		// decorations
20677 		underline = 1,
20678 		strikethrough = 2,
20679 
20680 		// font selectors
20681 
20682 		bold = 0x4000 | 1, // weight 700
20683 		light = 0x4000 | 2, // weight 300
20684 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20685 		// bold | light is really invalid but should give weight 500
20686 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20687 
20688 		italic = 0x4000 | 8,
20689 		smallcaps = 0x4000 | 16,
20690 	}
20691 
20692 	void* findFont(string family, int weight, TextFormat formats) {
20693 		return null;
20694 	}
20695 
20696 }
20697 
20698 /++
20699 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20700 
20701 	History:
20702 		Added February 19, 2021
20703 +/
20704 /// Group: drag_and_drop
20705 interface DropHandler {
20706 	/++
20707 		Called when the drag enters the handler's area.
20708 	+/
20709 	DragAndDropAction dragEnter(DropPackage*);
20710 	/++
20711 		Called when the drag leaves the handler's area or is
20712 		cancelled. You should free your resources when this is called.
20713 	+/
20714 	void dragLeave();
20715 	/++
20716 		Called continually as the drag moves over the handler's area.
20717 
20718 		Returns: feedback to the dragger
20719 	+/
20720 	DropParameters dragOver(Point pt);
20721 	/++
20722 		The user dropped the data and you should process it now. You can
20723 		access the data through the given [DropPackage].
20724 	+/
20725 	void drop(scope DropPackage*);
20726 	/++
20727 		Called when the drop is complete. You should free whatever temporary
20728 		resources you were using. It is often reasonable to simply forward
20729 		this call to [dragLeave].
20730 	+/
20731 	void finish();
20732 
20733 	/++
20734 		Parameters returned by [DropHandler.drop].
20735 	+/
20736 	static struct DropParameters {
20737 		/++
20738 			Acceptable action over this area.
20739 		+/
20740 		DragAndDropAction action;
20741 		/++
20742 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20743 
20744 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20745 		+/
20746 		Rectangle consistentWithin;
20747 	}
20748 }
20749 
20750 /++
20751 	History:
20752 		Added February 19, 2021
20753 +/
20754 /// Group: drag_and_drop
20755 enum DragAndDropAction {
20756 	none = 0,
20757 	copy,
20758 	move,
20759 	link,
20760 	ask,
20761 	custom
20762 }
20763 
20764 /++
20765 	An opaque structure representing dropped data. It contains
20766 	private, platform-specific data that your `drop` function
20767 	should simply forward.
20768 
20769 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20770 
20771 	History:
20772 		Added February 19, 2021
20773 +/
20774 /// Group: drag_and_drop
20775 struct DropPackage {
20776 	/++
20777 		Lists the available formats as magic numbers. You should compare these
20778 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20779 		understand the passed data.
20780 	+/
20781 	DraggableData.FormatId[] availableFormats() {
20782 		version(X11) {
20783 			return xFormats;
20784 		} else version(Windows) {
20785 			if(pDataObj is null)
20786 				return null;
20787 
20788 			typeof(return) ret;
20789 
20790 			IEnumFORMATETC ef;
20791 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20792 				FORMATETC fmt;
20793 				ULONG fetched;
20794 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20795 					if(fetched == 0)
20796 						break;
20797 
20798 					if(fmt.lindex != -1)
20799 						continue;
20800 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20801 						continue;
20802 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20803 						continue;
20804 
20805 					ret ~= fmt.cfFormat;
20806 				}
20807 			}
20808 
20809 			return ret;
20810 		} else throw new NotYetImplementedException();
20811 	}
20812 
20813 	/++
20814 		Gets data from the drop and optionally accepts it.
20815 
20816 		Returns:
20817 			void because the data is fed asynchronously through the `dg` parameter.
20818 
20819 		Params:
20820 			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.
20821 
20822 			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.
20823 
20824 			Calling `getData` again after accepting a drop is not permitted.
20825 
20826 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20827 
20828 			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.
20829 
20830 		Throws:
20831 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20832 
20833 		History:
20834 			Included in first release of [DropPackage].
20835 	+/
20836 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20837 		version(X11) {
20838 
20839 			auto display = XDisplayConnection.get();
20840 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20841 			auto best = format;
20842 
20843 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20844 
20845 				XDisplay* display;
20846 				Atom selectionAtom;
20847 				DraggableData.FormatId best;
20848 				DraggableData.FormatId format;
20849 				void delegate(scope ubyte[] data) dg;
20850 				DragAndDropAction acceptedAction;
20851 				Window sourceWindow;
20852 				SimpleWindow win;
20853 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20854 					this.display = display;
20855 					this.win = win;
20856 					this.sourceWindow = sourceWindow;
20857 					this.format = format;
20858 					this.selectionAtom = selectionAtom;
20859 					this.best = best;
20860 					this.dg = dg;
20861 					this.acceptedAction = acceptedAction;
20862 				}
20863 
20864 
20865 				mixin X11GetSelectionHandler_Basics;
20866 
20867 				void handleData(Atom target, in ubyte[] data) {
20868 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20869 
20870 					dg(cast(ubyte[]) data);
20871 
20872 					if(acceptedAction != DragAndDropAction.none) {
20873 						auto display = XDisplayConnection.get;
20874 
20875 						XClientMessageEvent xclient;
20876 
20877 						xclient.type = EventType.ClientMessage;
20878 						xclient.window = sourceWindow;
20879 						xclient.message_type = GetAtom!"XdndFinished"(display);
20880 						xclient.format = 32;
20881 						xclient.data.l[0] = win.impl.window;
20882 						xclient.data.l[1] = 1; // drop successful
20883 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20884 
20885 						XSendEvent(
20886 							display,
20887 							sourceWindow,
20888 							false,
20889 							EventMask.NoEventMask,
20890 							cast(XEvent*) &xclient
20891 						);
20892 
20893 						XFlush(display);
20894 					}
20895 				}
20896 
20897 				Atom findBestFormat(Atom[] answer) {
20898 					Atom best = None;
20899 					foreach(option; answer) {
20900 						if(option == format) {
20901 							best = option;
20902 							break;
20903 						}
20904 						/*
20905 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20906 							best = option;
20907 							break;
20908 						} else if(option == XA_STRING) {
20909 							best = option;
20910 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20911 							best = option;
20912 						}
20913 						*/
20914 					}
20915 					return best;
20916 				}
20917 			}
20918 
20919 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20920 
20921 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20922 
20923 		} else version(Windows) {
20924 
20925 			// clean up like DragLeave
20926 			// pass effect back up
20927 
20928 			FORMATETC t;
20929 			assert(format >= 0 && format <= ushort.max);
20930 			t.cfFormat = cast(ushort) format;
20931 			t.lindex = -1;
20932 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20933 			t.tymed = TYMED.TYMED_HGLOBAL;
20934 
20935 			STGMEDIUM m;
20936 
20937 			if(pDataObj.GetData(&t, &m) != S_OK) {
20938 				// fail
20939 			} else {
20940 				// succeed, take the data and clean up
20941 
20942 				// FIXME: ensure it is legit HGLOBAL
20943 				auto handle = m.hGlobal;
20944 
20945 				if(handle) {
20946 					auto sz = GlobalSize(handle);
20947 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20948 						scope(exit) GlobalUnlock(handle);
20949 						scope(exit) GlobalFree(handle);
20950 
20951 						auto data = ptr[0 .. sz];
20952 
20953 						dg(data);
20954 					}
20955 				}
20956 			}
20957 		}
20958 	}
20959 
20960 	private:
20961 
20962 	version(X11) {
20963 		SimpleWindow win;
20964 		Window sourceWindow;
20965 		Time dataTimestamp;
20966 
20967 		Atom[] xFormats;
20968 	}
20969 	version(Windows) {
20970 		IDataObject pDataObj;
20971 	}
20972 }
20973 
20974 /++
20975 	A generic helper base class for making a drop handler with a preference list of custom types.
20976 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20977 	droppers too.
20978 
20979 	It assumes the whole window it used, but you can subclass to change that.
20980 
20981 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20982 
20983 	History:
20984 		Added February 19, 2021
20985 +/
20986 /// Group: drag_and_drop
20987 class GenericDropHandlerBase : DropHandler {
20988 	// no fancy state here so no need to do anything here
20989 	void finish() { }
20990 	void dragLeave() { }
20991 
20992 	private DragAndDropAction acceptedAction;
20993 	private DraggableData.FormatId acceptedFormat;
20994 	private void delegate(scope ubyte[]) acceptedHandler;
20995 
20996 	struct FormatHandler {
20997 		DraggableData.FormatId format;
20998 		void delegate(scope ubyte[]) handler;
20999 	}
21000 
21001 	protected abstract FormatHandler[] formatHandlers();
21002 
21003 	DragAndDropAction dragEnter(DropPackage* pkg) {
21004 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
21005 		foreach(fmt; formatHandlers())
21006 		foreach(f; pkg.availableFormats())
21007 			if(f == fmt.format) {
21008 				acceptedFormat = f;
21009 				acceptedHandler = fmt.handler;
21010 				return acceptedAction = DragAndDropAction.copy;
21011 			}
21012 		return acceptedAction = DragAndDropAction.none;
21013 	}
21014 	DropParameters dragOver(Point pt) {
21015 		return DropParameters(acceptedAction);
21016 	}
21017 
21018 	void drop(scope DropPackage* dropPackage) {
21019 		if(!acceptedFormat || acceptedHandler is null) {
21020 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
21021 			return; // prolly shouldn't happen anyway...
21022 		}
21023 
21024 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
21025 	}
21026 }
21027 
21028 /++
21029 	A simple handler for making your window accept drops of plain text.
21030 
21031 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21032 
21033 	History:
21034 		Added February 22, 2021
21035 +/
21036 /// Group: drag_and_drop
21037 class TextDropHandler : GenericDropHandlerBase {
21038 	private void delegate(in char[] text) dg;
21039 
21040 	/++
21041 
21042 	+/
21043 	this(void delegate(in char[] text) dg) {
21044 		this.dg = dg;
21045 	}
21046 
21047 	protected override FormatHandler[] formatHandlers() {
21048 		version(X11)
21049 			return [
21050 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21051 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21052 			];
21053 		else version(Windows)
21054 			return [
21055 				FormatHandler(CF_UNICODETEXT, &translator),
21056 			];
21057 		else throw new NotYetImplementedException();
21058 	}
21059 
21060 	private void translator(scope ubyte[] data) {
21061 		version(X11)
21062 			dg(cast(char[]) data);
21063 		else version(Windows)
21064 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21065 	}
21066 }
21067 
21068 /++
21069 	A simple handler for making your window accept drops of files, issued to you as file names.
21070 
21071 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21072 
21073 	History:
21074 		Added February 22, 2021
21075 +/
21076 /// Group: drag_and_drop
21077 
21078 class FilesDropHandler : GenericDropHandlerBase {
21079 	private void delegate(in char[][]) dg;
21080 
21081 	/++
21082 
21083 	+/
21084 	this(void delegate(in char[][] fileNames) dg) {
21085 		this.dg = dg;
21086 	}
21087 
21088 	protected override FormatHandler[] formatHandlers() {
21089 		version(X11)
21090 			return [
21091 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
21092 			];
21093 		else version(Windows)
21094 			return [
21095 				FormatHandler(CF_HDROP, &translator),
21096 			];
21097 		else throw new NotYetImplementedException();
21098 	}
21099 
21100 	private void translator(scope ubyte[] data) {
21101 		version(X11) {
21102 			char[] listString = cast(char[]) data;
21103 			char[][16] buffer;
21104 			int count;
21105 			char[][] result = buffer[];
21106 
21107 			void commit(char[] s) {
21108 				if(count == result.length)
21109 					result.length += 16;
21110 				if(s.length > 7 && s[0 ..7] == "file://")
21111 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
21112 				result[count++] = s;
21113 			}
21114 
21115 			size_t last;
21116 			foreach(idx, char c; listString) {
21117 				if(c == '\n') {
21118 					commit(listString[last .. idx - 1]); // a \r
21119 					last = idx + 1; // a \n
21120 				}
21121 			}
21122 
21123 			if(last < listString.length) {
21124 				commit(listString[last .. $]);
21125 			}
21126 
21127 			// FIXME: they are uris now, should I translate it to local file names?
21128 			// of course the host name is supposed to be there cuz of X rokking...
21129 
21130 			dg(result[0 .. count]);
21131 		} else version(Windows) {
21132 
21133 			static struct DROPFILES {
21134 				DWORD pFiles;
21135 				POINT pt;
21136 				BOOL  fNC;
21137 				BOOL  fWide;
21138 			}
21139 
21140 
21141 			const(char)[][16] buffer;
21142 			int count;
21143 			const(char)[][] result = buffer[];
21144 			size_t last;
21145 
21146 			void commitA(in char[] stuff) {
21147 				if(count == result.length)
21148 					result.length += 16;
21149 				result[count++] = stuff;
21150 			}
21151 
21152 			void commitW(in wchar[] stuff) {
21153 				commitA(makeUtf8StringFromWindowsString(stuff));
21154 			}
21155 
21156 			void magic(T)(T chars) {
21157 				size_t idx;
21158 				while(chars[idx]) {
21159 					last = idx;
21160 					while(chars[idx]) {
21161 						idx++;
21162 					}
21163 					static if(is(T == char*))
21164 						commitA(chars[last .. idx]);
21165 					else
21166 						commitW(chars[last .. idx]);
21167 					idx++;
21168 				}
21169 			}
21170 
21171 			auto df = cast(DROPFILES*) data.ptr;
21172 			if(df.fWide) {
21173 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
21174 				magic(chars);
21175 			} else {
21176 				char* chars = cast(char*) (data.ptr + df.pFiles);
21177 				magic(chars);
21178 			}
21179 			dg(result[0 .. count]);
21180 		}
21181 		else throw new NotYetImplementedException();
21182 	}
21183 }
21184 
21185 /++
21186 	Interface to describe data being dragged. See also [draggable] helper function.
21187 
21188 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21189 
21190 	History:
21191 		Added February 19, 2021
21192 +/
21193 interface DraggableData {
21194 	version(X11)
21195 		alias FormatId = Atom;
21196 	else
21197 		alias FormatId = uint;
21198 	/++
21199 		Gets the platform-specific FormatId associated with the given named format.
21200 
21201 		This may be a MIME type, but may also be other various strings defined by the
21202 		programs you want to interoperate with.
21203 
21204 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
21205 		and convert it to some particular type for you.
21206 	+/
21207 	static FormatId getFormatId(string name)() {
21208 		version(X11)
21209 			return GetAtom!name(XDisplayConnection.get);
21210 		else version(Windows) {
21211 			static UINT cache;
21212 			if(!cache)
21213 				cache = RegisterClipboardFormatA(name);
21214 			return cache;
21215 		} else
21216 			throw new NotYetImplementedException();
21217 	}
21218 
21219 	/++
21220 		Looks up a string to represent the name for the given format, if there is one.
21221 
21222 		You should avoid using this function because it is slow. It is provided more for
21223 		debugging than for primary use.
21224 	+/
21225 	static string getFormatName(FormatId format) {
21226 		version(X11) {
21227 			if(format == 0)
21228 				return "None";
21229 			else
21230 				return getAtomName(format, XDisplayConnection.get);
21231 		} else version(Windows) {
21232 			switch(format) {
21233 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
21234 				case CF_DIBV5: return "CF_DIBV5";
21235 				case CF_RIFF: return "CF_RIFF";
21236 				case CF_WAVE: return "CF_WAVE";
21237 				case CF_HDROP: return "CF_HDROP";
21238 				default:
21239 					char[1024] name;
21240 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
21241 					return name[0 .. count].idup;
21242 			}
21243 		} else throw new NotYetImplementedException();
21244 	}
21245 
21246 	FormatId[] availableFormats();
21247 	// Return the slice of data you filled, empty slice if done.
21248 	// this is to support the incremental thing
21249 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21250 
21251 	size_t dataLength(FormatId format);
21252 }
21253 
21254 /++
21255 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21256 
21257 	History:
21258 		Added February 19, 2021
21259 +/
21260 DraggableData draggable(string s) {
21261 	version(X11)
21262 	return new class X11SetSelectionHandler_Text, DraggableData {
21263 		this() {
21264 			super(s);
21265 		}
21266 
21267 		override FormatId[] availableFormats() {
21268 			return X11SetSelectionHandler_Text.availableFormats();
21269 		}
21270 
21271 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21272 			return X11SetSelectionHandler_Text.getData(format, data);
21273 		}
21274 
21275 		size_t dataLength(FormatId format) {
21276 			return s.length;
21277 		}
21278 	};
21279 	else version(Windows)
21280 	return new class DraggableData {
21281 		FormatId[] availableFormats() {
21282 			return [CF_UNICODETEXT];
21283 		}
21284 
21285 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21286 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21287 		}
21288 
21289 		size_t dataLength(FormatId format) {
21290 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21291 		}
21292 	};
21293 	else
21294 	throw new NotYetImplementedException();
21295 }
21296 
21297 /++
21298 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21299 
21300 	History:
21301 		Added February 19, 2021
21302 +/
21303 /// Group: drag_and_drop
21304 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21305 in {
21306 	assert(window !is null);
21307 	assert(handler !is null);
21308 }
21309 do
21310 {
21311 	version(X11) {
21312 		auto sh = cast(X11SetSelectionHandler) handler;
21313 		if(sh is null) {
21314 			// gotta make my own adapter.
21315 			sh = new class X11SetSelectionHandler {
21316 				mixin X11SetSelectionHandler_Basics;
21317 
21318 				Atom[] availableFormats() { return handler.availableFormats(); }
21319 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21320 					return handler.getData(format, data);
21321 				}
21322 
21323 				// since the drop selection is only ever used once it isn't important
21324 				// to reset it.
21325 				void done() {}
21326 			};
21327 		}
21328 		return doDragDropX11(window, sh, action);
21329 	} else version(Windows) {
21330 		return doDragDropWindows(window, handler, action);
21331 	} else throw new NotYetImplementedException();
21332 }
21333 
21334 version(Windows)
21335 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21336 	IDataObject obj = new class IDataObject {
21337 		ULONG refCount;
21338 		ULONG AddRef() {
21339 			return ++refCount;
21340 		}
21341 		ULONG Release() {
21342 			return --refCount;
21343 		}
21344 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21345 			if (IID_IUnknown == *riid) {
21346 				*ppv = cast(void*) cast(IUnknown) this;
21347 			}
21348 			else if (IID_IDataObject == *riid) {
21349 				*ppv = cast(void*) cast(IDataObject) this;
21350 			}
21351 			else {
21352 				*ppv = null;
21353 				return E_NOINTERFACE;
21354 			}
21355 
21356 			AddRef();
21357 			return NOERROR;
21358 		}
21359 
21360 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21361 			//  writeln("Advise");
21362 			return E_NOTIMPL;
21363 		}
21364 		HRESULT DUnadvise(DWORD dwConnection) {
21365 			return E_NOTIMPL;
21366 		}
21367 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21368 			//  writeln("EnumDAdvise");
21369 			return OLE_E_ADVISENOTSUPPORTED;
21370 		}
21371 		// tell what formats it supports
21372 
21373 		FORMATETC[] types;
21374 		this() {
21375 			FORMATETC t;
21376 			foreach(ty; handler.availableFormats()) {
21377 				assert(ty <= ushort.max && ty >= 0);
21378 				t.cfFormat = cast(ushort) ty;
21379 				t.lindex = -1;
21380 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21381 				t.tymed = TYMED.TYMED_HGLOBAL;
21382 			}
21383 			types ~= t;
21384 		}
21385 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21386 			if(dwDirection == DATADIR.DATADIR_GET) {
21387 				*ppenumFormatEtc = new class IEnumFORMATETC {
21388 					ULONG refCount;
21389 					ULONG AddRef() {
21390 						return ++refCount;
21391 					}
21392 					ULONG Release() {
21393 						return --refCount;
21394 					}
21395 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21396 						if (IID_IUnknown == *riid) {
21397 							*ppv = cast(void*) cast(IUnknown) this;
21398 						}
21399 						else if (IID_IEnumFORMATETC == *riid) {
21400 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21401 						}
21402 						else {
21403 							*ppv = null;
21404 							return E_NOINTERFACE;
21405 						}
21406 
21407 						AddRef();
21408 						return NOERROR;
21409 					}
21410 
21411 
21412 					int pos;
21413 					this() {
21414 						pos = 0;
21415 					}
21416 
21417 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21418 						// writeln("clone");
21419 						return E_NOTIMPL; // FIXME
21420 					}
21421 
21422 					// Caller is responsible for freeing memory
21423 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21424 						// fetched may be null if celt is one
21425 						if(celt != 1)
21426 							return E_NOTIMPL; // FIXME
21427 
21428 						if(celt + pos > types.length)
21429 							return S_FALSE;
21430 
21431 						*rgelt = types[pos++];
21432 
21433 						if(pceltFetched !is null)
21434 							*pceltFetched = 1;
21435 
21436 						// writeln("ok celt ", celt);
21437 						return S_OK;
21438 					}
21439 
21440 					HRESULT Reset() {
21441 						pos = 0;
21442 						return S_OK;
21443 					}
21444 
21445 					HRESULT Skip(ULONG celt) {
21446 						if(celt + pos <= types.length) {
21447 							pos += celt;
21448 							return S_OK;
21449 						}
21450 						return S_FALSE;
21451 					}
21452 				};
21453 
21454 				return S_OK;
21455 			} else
21456 				return E_NOTIMPL;
21457 		}
21458 		// given a format, return the format you'd prefer to use cuz it is identical
21459 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21460 			// FIXME: prolly could be better but meh
21461 			// writeln("gcf: ", *pformatectIn);
21462 			*pformatetcOut = *pformatectIn;
21463 			return S_OK;
21464 		}
21465 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21466 			foreach(ty; types) {
21467 				if(ty == *pformatetcIn) {
21468 					auto format = ty.cfFormat;
21469 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
21470 					STGMEDIUM medium;
21471 					medium.tymed = TYMED.TYMED_HGLOBAL;
21472 
21473 					auto sz = handler.dataLength(format);
21474 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21475 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21476 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21477 						auto slice = data[0 .. sz];
21478 						scope(exit)
21479 							GlobalUnlock(handle);
21480 
21481 						handler.getData(format, cast(ubyte[]) slice[]);
21482 					}
21483 
21484 
21485 					medium.hGlobal = handle; // FIXME
21486 					*pmedium = medium;
21487 					return S_OK;
21488 				}
21489 			}
21490 			return DV_E_FORMATETC;
21491 		}
21492 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21493 			// writeln("GDH: ", *pformatetcIn);
21494 			return E_NOTIMPL; // FIXME
21495 		}
21496 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21497 			auto search = *pformatetc;
21498 			search.tymed &= TYMED.TYMED_HGLOBAL;
21499 			foreach(ty; types)
21500 				if(ty == search) {
21501 					// writeln("QueryGetData ", search, " ", types[0]);
21502 					return S_OK;
21503 				}
21504 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21505 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
21506 			}
21507 			return S_FALSE;
21508 		}
21509 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21510 			//  writeln("SetData: ");
21511 			return E_NOTIMPL;
21512 		}
21513 	};
21514 
21515 
21516 	IDropSource src = new class IDropSource {
21517 		ULONG refCount;
21518 		ULONG AddRef() {
21519 			return ++refCount;
21520 		}
21521 		ULONG Release() {
21522 			return --refCount;
21523 		}
21524 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21525 			if (IID_IUnknown == *riid) {
21526 				*ppv = cast(void*) cast(IUnknown) this;
21527 			}
21528 			else if (IID_IDropSource == *riid) {
21529 				*ppv = cast(void*) cast(IDropSource) this;
21530 			}
21531 			else {
21532 				*ppv = null;
21533 				return E_NOINTERFACE;
21534 			}
21535 
21536 			AddRef();
21537 			return NOERROR;
21538 		}
21539 
21540 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21541 			if(fEscapePressed)
21542 				return DRAGDROP_S_CANCEL;
21543 			if(!(grfKeyState & MK_LBUTTON))
21544 				return DRAGDROP_S_DROP;
21545 			return S_OK;
21546 		}
21547 
21548 		int GiveFeedback(uint dwEffect) {
21549 			return DRAGDROP_S_USEDEFAULTCURSORS;
21550 		}
21551 	};
21552 
21553 	DWORD effect;
21554 
21555 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21556 
21557 	DROPEFFECT de = win32DragAndDropAction(action);
21558 
21559 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21560 	// but still prolly a FIXME
21561 
21562 	auto ret = DoDragDrop(obj, src, de, &effect);
21563 	/+
21564 	if(ret == DRAGDROP_S_DROP)
21565 		writeln("drop ", effect);
21566 	else if(ret == DRAGDROP_S_CANCEL)
21567 		writeln("cancel");
21568 	else if(ret == S_OK)
21569 		writeln("ok");
21570 	else writeln(ret);
21571 	+/
21572 
21573 	return ret;
21574 }
21575 
21576 version(Windows)
21577 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21578 	DROPEFFECT de;
21579 
21580 	with(DragAndDropAction)
21581 	with(DROPEFFECT)
21582 	final switch(action) {
21583 		case none: de = DROPEFFECT_NONE; break;
21584 		case copy: de = DROPEFFECT_COPY; break;
21585 		case move: de = DROPEFFECT_MOVE; break;
21586 		case link: de = DROPEFFECT_LINK; break;
21587 		case ask: throw new Exception("ask not implemented yet");
21588 		case custom: throw new Exception("custom not implemented yet");
21589 	}
21590 
21591 	return de;
21592 }
21593 
21594 
21595 /++
21596 	History:
21597 		Added February 19, 2021
21598 +/
21599 /// Group: drag_and_drop
21600 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21601 	version(X11) {
21602 		auto display = XDisplayConnection.get;
21603 
21604 		Atom atom = 5; // right???
21605 
21606 		XChangeProperty(
21607 			display,
21608 			window.impl.window,
21609 			GetAtom!"XdndAware"(display),
21610 			XA_ATOM,
21611 			32 /* bits */,
21612 			PropModeReplace,
21613 			&atom,
21614 			1);
21615 
21616 		window.dropHandler = handler;
21617 	} else version(Windows) {
21618 
21619 		initDnd();
21620 
21621 		auto dropTarget = new class (handler) IDropTarget {
21622 			DropHandler handler;
21623 			this(DropHandler handler) {
21624 				this.handler = handler;
21625 			}
21626 			ULONG refCount;
21627 			ULONG AddRef() {
21628 				return ++refCount;
21629 			}
21630 			ULONG Release() {
21631 				return --refCount;
21632 			}
21633 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21634 				if (IID_IUnknown == *riid) {
21635 					*ppv = cast(void*) cast(IUnknown) this;
21636 				}
21637 				else if (IID_IDropTarget == *riid) {
21638 					*ppv = cast(void*) cast(IDropTarget) this;
21639 				}
21640 				else {
21641 					*ppv = null;
21642 					return E_NOINTERFACE;
21643 				}
21644 
21645 				AddRef();
21646 				return NOERROR;
21647 			}
21648 
21649 
21650 			// ///////////////////
21651 
21652 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21653 				DropPackage dropPackage = DropPackage(pDataObj);
21654 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21655 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21656 			}
21657 
21658 			HRESULT DragLeave() {
21659 				handler.dragLeave();
21660 				// release the IDataObject if needed
21661 				return S_OK;
21662 			}
21663 
21664 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21665 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21666 
21667 				*pdwEffect = win32DragAndDropAction(res.action);
21668 				// same as DragEnter basically
21669 				return S_OK;
21670 			}
21671 
21672 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21673 				DropPackage pkg = DropPackage(pDataObj);
21674 				handler.drop(&pkg);
21675 
21676 				return S_OK;
21677 			}
21678 		};
21679 		// Windows can hold on to the handler and try to call it
21680 		// during which time the GC can't see it. so important to
21681 		// manually manage this. At some point i'll FIXME and make
21682 		// all my com instances manually managed since they supposed
21683 		// to respect the refcount.
21684 		import core.memory;
21685 		GC.addRoot(cast(void*) dropTarget);
21686 
21687 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21688 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21689 
21690 		window.dropHandler = handler;
21691 	} else throw new NotYetImplementedException();
21692 }
21693 
21694 
21695 
21696 static if(UsingSimpledisplayX11) {
21697 
21698 enum _NET_WM_STATE_ADD = 1;
21699 enum _NET_WM_STATE_REMOVE = 0;
21700 enum _NET_WM_STATE_TOGGLE = 2;
21701 
21702 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21703 void demandAttention(SimpleWindow window, bool needs = true) {
21704 	demandAttention(window.impl.window, needs);
21705 }
21706 
21707 /// ditto
21708 void demandAttention(Window window, bool needs = true) {
21709 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21710 }
21711 
21712 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21713 	auto display = XDisplayConnection.get();
21714 	if(atom == None)
21715 		return; // non-failure error
21716 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21717 
21718 	XClientMessageEvent xclient;
21719 
21720 	xclient.type = EventType.ClientMessage;
21721 	xclient.window = window;
21722 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21723 	xclient.format = 32;
21724 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21725 	xclient.data.l[1] = atom;
21726 	xclient.data.l[2] = atom2;
21727 	xclient.data.l[3] = 1;
21728 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21729 
21730 	XSendEvent(
21731 		display,
21732 		RootWindow(display, DefaultScreen(display)),
21733 		false,
21734 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21735 		cast(XEvent*) &xclient
21736 	);
21737 
21738 	/+
21739 	XChangeProperty(
21740 		display,
21741 		window.impl.window,
21742 		GetAtom!"_NET_WM_STATE"(display),
21743 		XA_ATOM,
21744 		32 /* bits */,
21745 		PropModeAppend,
21746 		&atom,
21747 		1);
21748 	+/
21749 }
21750 
21751 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21752 	Atom actionAtom;
21753 	with(DragAndDropAction)
21754 	final switch(action) {
21755 		case none: actionAtom = None; break;
21756 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21757 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21758 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21759 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21760 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21761 	}
21762 
21763 	return actionAtom;
21764 }
21765 
21766 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21767 	// FIXME: I need to show user feedback somehow.
21768 	auto display = XDisplayConnection.get;
21769 
21770 	auto actionAtom = dndActionAtom(display, action);
21771 	assert(actionAtom, "Don't use action none to accept a drop");
21772 
21773 	setX11Selection!"XdndSelection"(window, handler, null);
21774 
21775 	auto oldKeyHandler = window.handleKeyEvent;
21776 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21777 
21778 	auto oldCharHandler = window.handleCharEvent;
21779 	scope(exit) window.handleCharEvent = oldCharHandler;
21780 
21781 	auto oldMouseHandler = window.handleMouseEvent;
21782 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21783 
21784 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21785 
21786 	import core.sys.posix.sys.time;
21787 	timeval tv;
21788 	gettimeofday(&tv, null);
21789 
21790 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21791 
21792 	Time lastMouseTimestamp;
21793 
21794 	bool dnding = true;
21795 	Window lastIn = None;
21796 
21797 	void leave() {
21798 		if(lastIn == None)
21799 			return;
21800 
21801 		XEvent ev;
21802 		ev.xclient.type = EventType.ClientMessage;
21803 		ev.xclient.window = lastIn;
21804 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21805 		ev.xclient.format = 32;
21806 		ev.xclient.data.l[0] = window.impl.window;
21807 
21808 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21809 		XFlush(display);
21810 
21811 		lastIn = None;
21812 	}
21813 
21814 	void enter(Window w) {
21815 		assert(lastIn == None);
21816 
21817 		lastIn = w;
21818 
21819 		XEvent ev;
21820 		ev.xclient.type = EventType.ClientMessage;
21821 		ev.xclient.window = lastIn;
21822 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21823 		ev.xclient.format = 32;
21824 		ev.xclient.data.l[0] = window.impl.window;
21825 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21826 
21827 		auto types = handler.availableFormats();
21828 		assert(types.length > 0);
21829 
21830 		ev.xclient.data.l[2] = types[0];
21831 		if(types.length > 1)
21832 			ev.xclient.data.l[3] = types[1];
21833 		if(types.length > 2)
21834 			ev.xclient.data.l[4] = types[2];
21835 
21836 		// FIXME: other types?!?!? and make sure we skip TARGETS
21837 
21838 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21839 		XFlush(display);
21840 	}
21841 
21842 	void position(int rootX, int rootY) {
21843 		assert(lastIn != None);
21844 
21845 		XEvent ev;
21846 		ev.xclient.type = EventType.ClientMessage;
21847 		ev.xclient.window = lastIn;
21848 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21849 		ev.xclient.format = 32;
21850 		ev.xclient.data.l[0] = window.impl.window;
21851 		ev.xclient.data.l[1] = 0; // reserved
21852 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21853 		ev.xclient.data.l[3] = dataTimestamp;
21854 		ev.xclient.data.l[4] = actionAtom;
21855 
21856 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21857 		XFlush(display);
21858 
21859 	}
21860 
21861 	void drop() {
21862 		XEvent ev;
21863 		ev.xclient.type = EventType.ClientMessage;
21864 		ev.xclient.window = lastIn;
21865 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21866 		ev.xclient.format = 32;
21867 		ev.xclient.data.l[0] = window.impl.window;
21868 		ev.xclient.data.l[1] = 0; // reserved
21869 		ev.xclient.data.l[2] = dataTimestamp;
21870 
21871 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21872 		XFlush(display);
21873 
21874 		lastIn = None;
21875 		dnding = false;
21876 	}
21877 
21878 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21879 	// but idk if i should...
21880 
21881 	window.setEventHandlers(
21882 		delegate(KeyEvent ev) {
21883 			if(ev.pressed == true && ev.key == Key.Escape) {
21884 				// cancel
21885 				dnding = false;
21886 			}
21887 		},
21888 		delegate(MouseEvent ev) {
21889 			if(ev.timestamp < lastMouseTimestamp)
21890 				return;
21891 
21892 			lastMouseTimestamp = ev.timestamp;
21893 
21894 			if(ev.type == MouseEventType.motion) {
21895 				auto display = XDisplayConnection.get;
21896 				auto root = RootWindow(display, DefaultScreen(display));
21897 
21898 				Window topWindow;
21899 				int rootX, rootY;
21900 
21901 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21902 
21903 				if(topWindow == None)
21904 					return;
21905 
21906 				top:
21907 				if(auto result = topWindow in eligibility) {
21908 					auto dropWindow = *result;
21909 					if(dropWindow == None) {
21910 						leave();
21911 						return;
21912 					}
21913 
21914 					if(dropWindow != lastIn) {
21915 						leave();
21916 						enter(dropWindow);
21917 						position(rootX, rootY);
21918 					} else {
21919 						position(rootX, rootY);
21920 					}
21921 				} else {
21922 					// determine eligibility
21923 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21924 					if(data.length == 1) {
21925 						// in case there is no WM or it isn't reparenting
21926 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21927 					} else {
21928 
21929 						Window tryScanChildren(Window search, int maxRecurse) {
21930 							// could be reparenting window manager, so gotta check the next few children too
21931 							Window child;
21932 							int x;
21933 							int y;
21934 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21935 
21936 							if(child == None)
21937 								return None;
21938 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21939 							if(data.length == 1) {
21940 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21941 							} else {
21942 								if(maxRecurse)
21943 									return tryScanChildren(child, maxRecurse - 1);
21944 								else
21945 									return None;
21946 							}
21947 
21948 						}
21949 
21950 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21951 						auto topResult = tryScanChildren(topWindow, 3);
21952 						// it is easy to have a false negative due to the mouse going over a WM
21953 						// child window like the close button if separate from the frame... so I
21954 						// can't really cache negatives, :(
21955 						if(topResult != None) {
21956 							eligibility[topWindow] = topResult;
21957 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21958 						}
21959 					}
21960 
21961 				}
21962 
21963 			} else if(ev.type == MouseEventType.buttonReleased) {
21964 				drop();
21965 				dnding = false;
21966 			}
21967 		}
21968 	);
21969 
21970 	window.grabInput();
21971 	scope(exit)
21972 		window.releaseInputGrab();
21973 
21974 
21975 	EventLoop.get.run(() => dnding);
21976 
21977 	return 0;
21978 }
21979 
21980 /// X-specific
21981 TrueColorImage getWindowNetWmIcon(Window window) {
21982 	try {
21983 		auto display = XDisplayConnection.get;
21984 
21985 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21986 
21987 		if (data.length > arch_ulong.sizeof * 2) {
21988 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21989 			// these are an array of rgba images that we have to convert into pixmaps ourself
21990 
21991 			int width = cast(int) meta[0];
21992 			int height = cast(int) meta[1];
21993 
21994 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21995 
21996 			static if(arch_ulong.sizeof == 4) {
21997 				bytes = bytes[0 .. width * height * 4];
21998 				alias imageData = bytes;
21999 			} else static if(arch_ulong.sizeof == 8) {
22000 				bytes = bytes[0 .. width * height * 8];
22001 				auto imageData = new ubyte[](4 * width * height);
22002 			} else static assert(0);
22003 
22004 
22005 
22006 			// this returns ARGB. Remember it is little-endian so
22007 			//                                         we have BGRA
22008 			// our thing uses RGBA, which in little endian, is ABGR
22009 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
22010 				auto r = bytes[idx + 2];
22011 				auto g = bytes[idx + 1];
22012 				auto b = bytes[idx + 0];
22013 				auto a = bytes[idx + 3];
22014 
22015 				imageData[idx2 + 0] = r;
22016 				imageData[idx2 + 1] = g;
22017 				imageData[idx2 + 2] = b;
22018 				imageData[idx2 + 3] = a;
22019 			}
22020 
22021 			return new TrueColorImage(width, height, imageData);
22022 		}
22023 
22024 		return null;
22025 	} catch(Exception e) {
22026 		return null;
22027 	}
22028 }
22029 
22030 } /* UsingSimpledisplayX11 */
22031 
22032 
22033 void loadBinNameToWindowClassName () {
22034 	import core.stdc.stdlib : realloc;
22035 	version(linux) {
22036 		// args[0] MAY be empty, so we'll just use this
22037 		import core.sys.posix.unistd : readlink;
22038 		char[1024] ebuf = void; // 1KB should be enough for everyone!
22039 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
22040 		if (len < 1) return;
22041 	} else /*version(Windows)*/ {
22042 		import core.runtime : Runtime;
22043 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
22044 		auto ebuf = Runtime.args[0];
22045 		auto len = ebuf.length;
22046 	}
22047 	auto pos = len;
22048 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22049 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22050 	if (sdpyWindowClassStr is null) return; // oops
22051 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22052 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22053 }
22054 
22055 /++
22056 	An interface representing a font that is drawn with custom facilities.
22057 
22058 	You might want [OperatingSystemFont] instead, which represents
22059 	a font loaded and drawn by functions native to the operating system.
22060 
22061 	WARNING: I might still change this.
22062 +/
22063 interface DrawableFont : MeasurableFont {
22064 	/++
22065 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22066 
22067 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22068 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22069 		fill color, but that's up to the implementation.
22070 	+/
22071 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22072 
22073 	/++
22074 		Requests that the given string is added to the image cache. You should only do this rarely, but
22075 		if you have a string that you know will be used over and over again, adding it to a cache can
22076 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22077 		to implement this as a do-nothing method).
22078 	+/
22079 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22080 }
22081 
22082 /++
22083 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
22084 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
22085 
22086 	You should also consider [OperatingSystemFont], which loads and draws a font with
22087 	facilities native to the user's operating system. You might also consider
22088 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
22089 	of game, as they have their own ways to draw text too.
22090 
22091 	Be warned: this can be slow, especially on remote connections to the X server, since
22092 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
22093 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
22094 	experiment in your specific case.
22095 
22096 	Please note that the return type of [DrawableFont] also includes an implementation of
22097 	[MeasurableFont].
22098 +/
22099 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
22100 	import arsd.ttf;
22101 	static class ArsdTtfFont : DrawableFont {
22102 		TtfFont font;
22103 		int size;
22104 		this(in ubyte[] data, int size) {
22105 			font = TtfFont(data);
22106 			this.size = size;
22107 
22108 
22109 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
22110 			int ascent_, descent_, line_gap;
22111 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
22112 
22113 			int advance, lsb;
22114 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
22115 			xWidth = cast(int) (advance * scale);
22116 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
22117 			MWidth = cast(int) (advance * scale);
22118 		}
22119 
22120 		private int ascent_;
22121 		private int descent_;
22122 		private int xWidth;
22123 		private int MWidth;
22124 
22125 		bool isMonospace() {
22126 			return xWidth == MWidth;
22127 		}
22128 		int averageWidth() {
22129 			return xWidth;
22130 		}
22131 		int height() {
22132 			return size;
22133 		}
22134 		int ascent() {
22135 			return ascent_;
22136 		}
22137 		int descent() {
22138 			return descent_;
22139 		}
22140 
22141 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
22142 			int width, height;
22143 			font.getStringSize(s, size, width, height);
22144 			return width;
22145 		}
22146 
22147 
22148 
22149 		Sprite[string] cache;
22150 
22151 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
22152 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
22153 			cache[text] = sprite;
22154 		}
22155 
22156 		Image stringToImage(Color fg, Color bg, in char[] text) {
22157 			int width, height;
22158 			auto data = font.renderString(text, size, width, height);
22159 			auto image = new TrueColorImage(width, height);
22160 			int pos = 0;
22161 			foreach(y; 0 .. height)
22162 			foreach(x; 0 .. width) {
22163 				fg.a = data[0];
22164 				bg.a = 255;
22165 				auto color = alphaBlend(fg, bg);
22166 				image.imageData.bytes[pos++] = color.r;
22167 				image.imageData.bytes[pos++] = color.g;
22168 				image.imageData.bytes[pos++] = color.b;
22169 				image.imageData.bytes[pos++] = data[0];
22170 				data = data[1 .. $];
22171 			}
22172 			assert(data.length == 0);
22173 
22174 			return Image.fromMemoryImage(image);
22175 		}
22176 
22177 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
22178 			Sprite sprite = (text in cache) ? *(text in cache) : null;
22179 
22180 			auto fg = painter.impl._outlineColor;
22181 			auto bg = painter.impl._fillColor;
22182 
22183 			if(sprite !is null) {
22184 				auto w = cast(SimpleWindow) painter.window;
22185 				assert(w !is null);
22186 
22187 				sprite.drawAt(painter, upperLeft);
22188 			} else {
22189 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
22190 			}
22191 		}
22192 	}
22193 
22194 	return new ArsdTtfFont(data, size);
22195 }
22196 
22197 class NotYetImplementedException : Exception {
22198 	this(string file = __FILE__, size_t line = __LINE__) {
22199 		super("Not yet implemented", file, line);
22200 	}
22201 }
22202 
22203 ///
22204 __gshared bool librariesSuccessfullyLoaded = true;
22205 ///
22206 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
22207 
22208 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
22209 	mixin(staticForeachReplacement!Iface);
22210 
22211 	void loadDynamicLibrary() @nogc {
22212 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22213 	}
22214 
22215         void loadDynamicLibraryForReal() {
22216                 foreach(name; __traits(derivedMembers, Iface)) {
22217                         mixin("alias tmp = " ~ name ~ ";");
22218                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
22219                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
22220                 }
22221         }
22222 }
22223 
22224 private const(char)[] staticForeachReplacement(Iface)() pure {
22225 /*
22226 	// just this for gdc 9....
22227 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
22228 
22229         static foreach(name; __traits(derivedMembers, Iface))
22230                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22231 */
22232 
22233 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
22234 	size_t pos;
22235 
22236 	void append(in char[] what) {
22237 		if(pos + what.length > code.length)
22238 			code.length = (code.length * 3) / 2;
22239 		code[pos .. pos + what.length] = what[];
22240 		pos += what.length;
22241 	}
22242 
22243         foreach(name; __traits(derivedMembers, Iface)) {
22244                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
22245 		append(name);
22246 		append(`")) `);
22247 		append(name);
22248 		append(";");
22249 	}
22250 
22251 	return code[0 .. pos];
22252 }
22253 
22254 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22255 	mixin(staticForeachReplacement!Iface);
22256 
22257 	private __gshared void* libHandle;
22258 	private __gshared bool attempted;
22259 
22260         void loadDynamicLibrary() @nogc {
22261 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22262 	}
22263 
22264 	bool loadAttempted() {
22265 		return attempted;
22266 	}
22267 	bool loadSuccessful() {
22268 		return libHandle !is null;
22269 	}
22270 
22271         void loadDynamicLibraryForReal() {
22272 		attempted = true;
22273                 version(Posix) {
22274                         import core.sys.posix.dlfcn;
22275 			version(OSX) {
22276 				version(X11)
22277                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22278 				else
22279                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22280 			} else {
22281                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22282 				if(libHandle is null)
22283                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22284 			}
22285 
22286 			static void* loadsym(void* l, const char* name) {
22287 				import core.stdc.stdlib;
22288 				if(l is null)
22289 					return &abort;
22290 				return dlsym(l, name);
22291 			}
22292                 } else version(Windows) {
22293                         import core.sys.windows.winbase;
22294                         libHandle = LoadLibrary(library ~ ".dll");
22295 			static void* loadsym(void* l, const char* name) {
22296 				import core.stdc.stdlib;
22297 				if(l is null)
22298 					return &abort;
22299 				return GetProcAddress(l, name);
22300 			}
22301                 }
22302                 if(libHandle is null) {
22303 			success = false;
22304                         //throw new Exception("load failure of library " ~ library);
22305 		}
22306                 foreach(name; __traits(derivedMembers, Iface)) {
22307                         mixin("alias tmp = " ~ name ~ ";");
22308                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22309                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22310                 }
22311         }
22312 
22313         void unloadDynamicLibrary() {
22314                 version(Posix) {
22315                         import core.sys.posix.dlfcn;
22316                         dlclose(libHandle);
22317                 } else version(Windows) {
22318                         import core.sys.windows.winbase;
22319                         FreeLibrary(libHandle);
22320                 }
22321                 foreach(name; __traits(derivedMembers, Iface))
22322                         mixin(name ~ " = null;");
22323         }
22324 }
22325 
22326 /+
22327 	The GC can be called from any thread, and a lot of cleanup must be done
22328 	on the gui thread. Since the GC can interrupt any locks - including being
22329 	triggered inside a critical section - it is vital to avoid deadlocks to get
22330 	these functions called from the right place.
22331 
22332 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22333 	right now.
22334 
22335 	The cleanup function is run when the event loop gets around to it, which is just
22336 	whenever there's something there after it has been woken up for other work. It does
22337 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22338 	(Well actually it might be ok but i don't wanna mess with it right now.)
22339 +/
22340 private struct CleanupQueue {
22341 	import core.stdc.stdlib;
22342 
22343 	void queue(alias func, T...)(T args) {
22344 		static struct Args {
22345 			T args;
22346 		}
22347 		static struct RealJob {
22348 			Job j;
22349 			Args a;
22350 		}
22351 		static void call(Job* data) {
22352 			auto rj = cast(RealJob*) data;
22353 			func(rj.a.args);
22354 		}
22355 
22356 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22357 		thing.j.call = &call;
22358 		thing.a.args = args;
22359 
22360 		buffer[tail++] = cast(Job*) thing;
22361 
22362 		// FIXME: set overflowed
22363 	}
22364 
22365 	void process() {
22366 		const tail = this.tail;
22367 
22368 		while(tail != head) {
22369 			Job* job = cast(Job*) buffer[head++];
22370 			job.call(job);
22371 			free(job);
22372 		}
22373 
22374 		if(overflowed)
22375 			throw new Exception("cleanup overflowed");
22376 	}
22377 
22378 	private:
22379 
22380 	ubyte tail; // must ONLY be written by queue
22381 	ubyte head; // must ONLY be written by process
22382 	bool overflowed;
22383 
22384 	static struct Job {
22385 		void function(Job*) call;
22386 	}
22387 
22388 	void*[256] buffer;
22389 }
22390 private __gshared CleanupQueue cleanupQueue;
22391 
22392 version(X11)
22393 /++
22394 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22395 
22396 	$(WARNING
22397 		This function is exempted from stability guarantees.
22398 	)
22399 +/
22400 float customScalingFactorForMonitor(int monitorNumber) {
22401 	import core.stdc.stdlib;
22402 	auto val = getenv("ARSD_SCALING_FACTOR");
22403 
22404 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
22405 	if(val is null)
22406 		return 1.0;
22407 
22408 	char[16] buffer = 0;
22409 	int pos;
22410 
22411 	const(char)* at = val;
22412 
22413 	foreach(item; 0 .. monitorNumber + 1) {
22414 		if(*at == 0)
22415 			break; // reuse the last number when we at the end of the string
22416 		pos = 0;
22417 		while(pos + 1 < buffer.length && *at && *at != ';') {
22418 			buffer[pos++] = *at;
22419 			at++;
22420 		}
22421 		if(*at)
22422 			at++; // skip the semicolon
22423 		buffer[pos] = 0;
22424 	}
22425 
22426 	//sdpyPrintDebugString(buffer[0 .. pos]);
22427 
22428 	import core.stdc.math;
22429 	auto f = atof(buffer.ptr);
22430 
22431 	if(f <= 0.0 || isnan(f) || isinf(f))
22432 		return 1.0;
22433 
22434 	return f;
22435 }
22436 
22437 void guiAbortProcess(string msg) {
22438 	import core.stdc.stdlib;
22439 	version(Windows) {
22440 		WCharzBuffer t = WCharzBuffer(msg);
22441 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22442 	} else {
22443 		import core.stdc.stdio;
22444 		fwrite(msg.ptr, 1, msg.length, stderr);
22445 		msg = "\n";
22446 		fwrite(msg.ptr, 1, msg.length, stderr);
22447 		fflush(stderr);
22448 	}
22449 
22450 	abort();
22451 }
22452 
22453 private int minInternal(int a, int b) {
22454 	return (a < b) ? a : b;
22455 }
22456 
22457 private alias scriptable = arsd_jsvar_compatible;