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/VulkanSdpyDemo
479 
480 	https://github.com/Cy-Tek/VulkanizeD/compare/main...adamdruppe:VulkanizeDSdpy:main
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 								// or i might add support for other FDs too
4418 								// but for now it is just timer
4419 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4420 							}
4421 						}
4422 						if(flags & ep.EPOLLHUP) {
4423 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4424 								(*pfr).hup(flags);
4425 							if(globalHupHandler)
4426 								globalHupHandler(fd, flags);
4427 						}
4428 						/+
4429 						} else {
4430 							// not interested in OUT, we are just reading here.
4431 							//
4432 							// error or hup might also be reported
4433 							// but it shouldn't here since we are only
4434 							// using a few types of FD and Xlib will report
4435 							// if it dies.
4436 							// so instead of thoughtfully handling it, I'll
4437 							// just throw. for now at least
4438 
4439 							throw new Exception("epoll did something else");
4440 						}
4441 						+/
4442 					}
4443 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4444 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4445 					xpending:
4446 					if (!done && forceXPending) {
4447 						this.mtLock();
4448 						scope(exit) this.mtUnlock();
4449 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4450 						while(!done && XPending(display)) {
4451 							done = doXNextEvent(this.display);
4452 						}
4453 					}
4454 				}
4455 			} else {
4456 				// Generic fallback: yes to simple pulse support,
4457 				// but NO timer support!
4458 
4459 				// FIXME: we could probably support the POSIX timer_create
4460 				// signal-based option, but I'm in no rush to write it since
4461 				// I prefer the fd-based functions.
4462 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4463 
4464 					import core.sys.posix.poll;
4465 
4466 					pollfd[] pfds;
4467 					pollfd[32] pfdsBuffer;
4468 					auto len = PosixFdReader.mapping.length + 2;
4469 					// FIXME: i should just reuse the buffer
4470 					if(len < pfdsBuffer.length)
4471 						pfds = pfdsBuffer[0 .. len];
4472 					else
4473 						pfds = new pollfd[](len);
4474 
4475 					pfds[0].fd = display.fd;
4476 					pfds[0].events = POLLIN;
4477 					pfds[0].revents = 0;
4478 
4479 					int slot = 1;
4480 
4481 					if(customEventFDRead != -1) {
4482 						pfds[slot].fd = customEventFDRead;
4483 						pfds[slot].events = POLLIN;
4484 						pfds[slot].revents = 0;
4485 
4486 						slot++;
4487 					}
4488 
4489 					foreach(fd, obj; PosixFdReader.mapping) {
4490 						if(!obj.enabled) continue;
4491 						pfds[slot].fd = fd;
4492 						pfds[slot].events = POLLIN;
4493 						pfds[slot].revents = 0;
4494 
4495 						slot++;
4496 					}
4497 
4498 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4499 					if(ret == -1) throw new Exception("poll");
4500 
4501 					if(ret == 0) {
4502 						// FIXME it may not necessarily time out if events keep coming
4503 						if(handlePulse !is null)
4504 							handlePulse();
4505 					} else {
4506 						foreach(s; 0 .. slot) {
4507 							if(pfds[s].revents == 0) continue;
4508 
4509 							if(pfds[s].fd == display.fd) {
4510 								while(!done && XPending(display)) {
4511 									this.mtLock();
4512 									scope(exit) this.mtUnlock();
4513 									done = doXNextEvent(this.display);
4514 								}
4515 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4516 
4517 								import core.sys.posix.unistd : read;
4518 								ulong n;
4519 								read(customEventFDRead, &n, n.sizeof);
4520 								SimpleWindow.processAllCustomEvents();
4521 							} else {
4522 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4523 								if(pfds[s].revents & POLLNVAL) {
4524 									obj.dispose();
4525 								} else {
4526 									obj.ready(pfds[s].revents);
4527 								}
4528 							}
4529 
4530 							ret--;
4531 							if(ret == 0) break;
4532 						}
4533 					}
4534 				}
4535 			}
4536 		}
4537 
4538 		version(Windows) {
4539 			int ret = -1;
4540 			MSG message;
4541 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4542 				eventLoopRound++;
4543 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4544 				auto waitResult = MsgWaitForMultipleObjectsEx(
4545 					cast(int) handles.length, handles.ptr,
4546 					(wto == 0 ? INFINITE : wto), /* timeout */
4547 					0x04FF, /* QS_ALLINPUT */
4548 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4549 
4550 				SimpleWindow.processAllCustomEvents(); // anyway
4551 				enum WAIT_OBJECT_0 = 0;
4552 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4553 					auto h = handles[waitResult - WAIT_OBJECT_0];
4554 					if(auto e = h in WindowsHandleReader.mapping) {
4555 						(*e).ready();
4556 					}
4557 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4558 					// message ready
4559 					int count;
4560 					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
4561 						ret = GetMessage(&message, null, 0, 0);
4562 						if(ret == -1)
4563 							throw new WindowsApiException("GetMessage", GetLastError());
4564 						TranslateMessage(&message);
4565 						DispatchMessage(&message);
4566 
4567 						count++;
4568 						if(count > 10)
4569 							break; // take the opportunity to catch up on other events
4570 
4571 						if(ret == 0) { // WM_QUIT
4572 							EventLoop.quitApplication();
4573 							break;
4574 						}
4575 					}
4576 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4577 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4578 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4579 					// timeout, should never happen since we aren't using it
4580 				} else if(waitResult == 0xFFFFFFFF) {
4581 						// failed
4582 						throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4583 				} else {
4584 					// idk....
4585 				}
4586 			}
4587 
4588 			// return message.wParam;
4589 			return 0;
4590 		} else {
4591 			return 0;
4592 		}
4593 	}
4594 
4595 	int run(bool delegate() whileCondition = null) {
4596 		if(disposed)
4597 			initialize(this.pulseTimeout);
4598 
4599 		version(X11) {
4600 			try {
4601 				return loopHelper(whileCondition);
4602 			} catch(XDisconnectException e) {
4603 				if(e.userRequested) {
4604 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4605 						item.discardConnectionState();
4606 					XCloseDisplay(XDisplayConnection.display);
4607 				}
4608 
4609 				XDisplayConnection.display = null;
4610 
4611 				this.dispose();
4612 
4613 				throw e;
4614 			}
4615 		} else {
4616 			return loopHelper(whileCondition);
4617 		}
4618 	}
4619 }
4620 
4621 
4622 /++
4623 	Provides an icon on the system notification area (also known as the system tray).
4624 
4625 
4626 	If a notification area is not available with the NotificationIcon object is created,
4627 	it will silently succeed and simply attempt to create one when an area becomes available.
4628 
4629 
4630 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
4631 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
4632 	use the older version.
4633 +/
4634 version(OSXCocoa) {} else // NotYetImplementedException
4635 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4636 
4637 	version(X11) {
4638 		void recreateAfterDisconnect() {
4639 			stateDiscarded = false;
4640 			clippixmap = None;
4641 			throw new Exception("NOT IMPLEMENTED");
4642 		}
4643 
4644 		bool stateDiscarded;
4645 		void discardConnectionState() {
4646 			stateDiscarded = true;
4647 		}
4648 	}
4649 
4650 
4651 	version(X11) {
4652 		Image img;
4653 
4654 		NativeEventHandler getNativeEventHandler() {
4655 			return delegate int(XEvent e) {
4656 				switch(e.type) {
4657 					case EventType.Expose:
4658 					//case EventType.VisibilityNotify:
4659 						redraw();
4660 					break;
4661 					case EventType.ClientMessage:
4662 						version(sddddd) {
4663 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4664 						writeln("\t", e.xclient.format);
4665 						writeln("\t", e.xclient.data.l);
4666 						}
4667 					break;
4668 					case EventType.ButtonPress:
4669 						auto event = e.xbutton;
4670 						if (onClick !is null || onClickEx !is null) {
4671 							MouseButton mb = cast(MouseButton)0;
4672 							switch (event.button) {
4673 								case 1: mb = MouseButton.left; break; // left
4674 								case 2: mb = MouseButton.middle; break; // middle
4675 								case 3: mb = MouseButton.right; break; // right
4676 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4677 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4678 								case 6: break; // scroll left...
4679 								case 7: break; // scroll right...
4680 								case 8: mb = MouseButton.backButton; break;
4681 								case 9: mb = MouseButton.forwardButton; break;
4682 								default:
4683 							}
4684 							if (mb) {
4685 								try { onClick()(mb); } catch (Exception) {}
4686 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4687 							}
4688 						}
4689 					break;
4690 					case EventType.EnterNotify:
4691 						if (onEnter !is null) {
4692 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4693 						}
4694 						break;
4695 					case EventType.LeaveNotify:
4696 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4697 						break;
4698 					case EventType.DestroyNotify:
4699 						active = false;
4700 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4701 					break;
4702 					case EventType.ConfigureNotify:
4703 						auto event = e.xconfigure;
4704 						this.width = event.width;
4705 						this.height = event.height;
4706 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4707 						redraw();
4708 					break;
4709 					default: return 1;
4710 				}
4711 				return 1;
4712 			};
4713 		}
4714 
4715 		/* private */ void hideBalloon() {
4716 			balloon.close();
4717 			version(with_timer)
4718 				timer.destroy();
4719 			balloon = null;
4720 			version(with_timer)
4721 				timer = null;
4722 		}
4723 
4724 		void redraw() {
4725 			if (!active) return;
4726 
4727 			auto display = XDisplayConnection.get;
4728 			GC gc;
4729 
4730 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
4731 
4732 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
4733 				Visual *visual;
4734 				XVisualInfo vis_info;
4735 				XSetWindowAttributes win_attr;
4736 				c_ulong win_mask;
4737 
4738 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
4739 					assert(0);
4740 					// return 1;
4741 				}
4742 
4743 				visual = vis_info.visual;
4744 
4745 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
4746 				win_attr.background_pixel = 0;
4747 				win_attr.border_pixel = 0;
4748 
4749 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
4750 
4751 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
4752 
4753 				return 0;
4754 			}
4755 
4756 			if(useAlpha)
4757 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
4758 			else
4759 				gc = DefaultGC(display, DefaultScreen(display));
4760 
4761 			XClearWindow(display, nativeHandle);
4762 
4763 			if(!useAlpha && img !is null)
4764 				XSetClipMask(display, gc, clippixmap);
4765 
4766 			/+
4767 			XSetForeground(display, gc,
4768 				cast(uint) 0 << 16 |
4769 				cast(uint) 0 << 8 |
4770 				cast(uint) 0);
4771 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4772 			+/
4773 
4774 			if (img is null) {
4775 				XSetForeground(display, gc,
4776 					cast(uint) 0 << 16 |
4777 					cast(uint) 127 << 8 |
4778 					cast(uint) 0);
4779 				XFillArc(display, nativeHandle,
4780 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4781 			} else {
4782 				int dx = 0;
4783 				int dy = 0;
4784 				if(width > img.width)
4785 					dx = (width - img.width) / 2;
4786 				if(height > img.height)
4787 					dy = (height - img.height) / 2;
4788 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
4789 				XSetClipOrigin(display, gc, dx, dy);
4790 
4791 				int max(int a, int b) {
4792 					if(a > b) return a; else return b;
4793 				}
4794 
4795 				if (img.usingXshm)
4796 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
4797 				else
4798 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
4799 			}
4800 			XSetClipMask(display, gc, None);
4801 			flushGui();
4802 		}
4803 
4804 		static Window getTrayOwner() {
4805 			auto display = XDisplayConnection.get;
4806 			auto i = cast(int) DefaultScreen(display);
4807 			if(i < 10 && i >= 0) {
4808 				static Atom atom;
4809 				if(atom == None)
4810 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4811 				return XGetSelectionOwner(display, atom);
4812 			}
4813 			return None;
4814 		}
4815 
4816 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4817 			auto to = getTrayOwner();
4818 			auto display = XDisplayConnection.get;
4819 			XEvent ev;
4820 			ev.xclient.type = EventType.ClientMessage;
4821 			ev.xclient.window = to;
4822 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4823 			ev.xclient.format = 32;
4824 			ev.xclient.data.l[0] = CurrentTime;
4825 			ev.xclient.data.l[1] = message;
4826 			ev.xclient.data.l[2] = d1;
4827 			ev.xclient.data.l[3] = d2;
4828 			ev.xclient.data.l[4] = d3;
4829 
4830 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4831 		}
4832 
4833 		private static NotificationAreaIcon[] activeIcons;
4834 
4835 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4836 		private void newManager() {
4837 			close();
4838 			createXWin();
4839 
4840 			if(this.clippixmap)
4841 				XFreePixmap(XDisplayConnection.get, clippixmap);
4842 			if(this.originalMemoryImage)
4843 				this.icon = this.originalMemoryImage;
4844 			else if(this.img)
4845 				this.icon = this.img;
4846 		}
4847 
4848 		private bool useAlpha = false;
4849 
4850 		private void createXWin () {
4851 			// create window
4852 			auto display = XDisplayConnection.get;
4853 
4854 			// to check for MANAGER on root window to catch new/changed tray owners
4855 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4856 			// so if a thing does appear, we can handle it
4857 			foreach(ai; activeIcons)
4858 				if(ai is this)
4859 					goto alreadythere;
4860 			activeIcons ~= this;
4861 			alreadythere:
4862 
4863 			// and check for an existing tray
4864 			auto trayOwner = getTrayOwner();
4865 			if(trayOwner == None)
4866 				return;
4867 				//throw new Exception("No notification area found");
4868 
4869 			Visual* v = cast(Visual*) CopyFromParent;
4870 
4871 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
4872 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
4873 			// a resize event later.
4874 			width = 22;
4875 			height = 22;
4876 
4877 			// if they system gave us a 32 bit visual we need to switch to it too
4878 			int depth = 24;
4879 
4880 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4881 			if(visualProp !is null) {
4882 				c_ulong[] info = cast(c_ulong[]) visualProp;
4883 				if(info.length == 1) {
4884 					auto vid = info[0];
4885 					int returned;
4886 					XVisualInfo t;
4887 					t.visualid = vid;
4888 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4889 					if(got !is null) {
4890 						if(returned == 1) {
4891 							v = got.visual;
4892 							depth = got.depth;
4893 							// writeln("using special visual ", got.depth);
4894 							// writeln(depth);
4895 						}
4896 						XFree(got);
4897 					}
4898 				}
4899 			}
4900 
4901 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
4902 			XSetWindowAttributes attr;
4903 			attr.background_pixel = 0;
4904 			attr.border_pixel = 0;
4905 			attr.override_redirect = 0;
4906 			if(v !is cast(Visual*) CopyFromParent) {
4907 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
4908 				CWFlags |= CWColormap;
4909 				if(depth == 32)
4910 					useAlpha = true;
4911 				else
4912 					goto plain;
4913 			} else {
4914 				plain:
4915 				attr.background_pixmap = 1 /* ParentRelative */;
4916 				CWFlags |= CWBackPixmap;
4917 			}
4918 
4919 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
4920 
4921 			assert(nativeWindow);
4922 
4923 			if(!useAlpha)
4924 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4925 
4926 			nativeHandle = nativeWindow;
4927 
4928 			///+
4929 			arch_ulong[2] info;
4930 			info[0] = 0;
4931 			info[1] = 1;
4932 
4933 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4934 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4935 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4936 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4937 
4938 			XChangeProperty(
4939 				display,
4940 				nativeWindow,
4941 				GetAtom!("_XEMBED_INFO", true)(display),
4942 				GetAtom!("_XEMBED_INFO", true)(display),
4943 				32 /* bits */,
4944 				0 /*PropModeReplace*/,
4945 				info.ptr,
4946 				2);
4947 
4948 			import core.sys.posix.unistd;
4949 			arch_ulong pid = getpid();
4950 
4951 			XChangeProperty(
4952 				display,
4953 				nativeWindow,
4954 				GetAtom!("_NET_WM_PID", true)(display),
4955 				XA_CARDINAL,
4956 				32 /* bits */,
4957 				0 /*PropModeReplace*/,
4958 				&pid,
4959 				1);
4960 
4961 			updateNetWmIcon();
4962 
4963 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4964 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4965 				XClassHint klass;
4966 				XWMHints wh;
4967 				XSizeHints size;
4968 				klass.res_name = sdpyWindowClassStr;
4969 				klass.res_class = sdpyWindowClassStr;
4970 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4971 			}
4972 
4973 				// believe it or not, THIS is what xfce needed for the 9999 issue
4974 				XSizeHints sh;
4975 				c_long spr;
4976 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4977 				sh.flags |= PMaxSize | PMinSize;
4978 				// FIXME maybe nicer resizing
4979 				sh.min_width = 16;
4980 				sh.min_height = 16;
4981 				sh.max_width = 22;
4982 				sh.max_height = 22;
4983 				XSetWMNormalHints(display, nativeWindow, &sh);
4984 
4985 
4986 			//+/
4987 
4988 
4989 			XSelectInput(display, nativeWindow,
4990 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4991 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4992 
4993 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4994 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
4995 
4996 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
4997 			active = true;
4998 		}
4999 
5000 		void updateNetWmIcon() {
5001 			if(img is null) return;
5002 			auto display = XDisplayConnection.get;
5003 			// FIXME: ensure this is correct
5004 			arch_ulong[] buffer;
5005 			auto imgMi = img.toTrueColorImage;
5006 			buffer ~= imgMi.width;
5007 			buffer ~= imgMi.height;
5008 			foreach(c; imgMi.imageData.colors) {
5009 				arch_ulong b;
5010 				b |= c.a << 24;
5011 				b |= c.r << 16;
5012 				b |= c.g << 8;
5013 				b |= c.b;
5014 				buffer ~= b;
5015 			}
5016 
5017 			XChangeProperty(
5018 				display,
5019 				nativeHandle,
5020 				GetAtom!"_NET_WM_ICON"(display),
5021 				GetAtom!"CARDINAL"(display),
5022 				32 /* bits */,
5023 				0 /*PropModeReplace*/,
5024 				buffer.ptr,
5025 				cast(int) buffer.length);
5026 		}
5027 
5028 
5029 
5030 		private SimpleWindow balloon;
5031 		version(with_timer)
5032 		private Timer timer;
5033 
5034 		private Window nativeHandle;
5035 		private Pixmap clippixmap = None;
5036 		private int width = 16;
5037 		private int height = 16;
5038 		private bool active = false;
5039 
5040 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5041 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5042 		void delegate () onLeave; /// X11 only.
5043 
5044 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5045 
5046 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5047 		void getWindowRect (out int x, out int y, out int width, out int height) {
5048 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5049 			Window dummyw;
5050 			auto dpy = XDisplayConnection.get;
5051 			//XWindowAttributes xwa;
5052 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5053 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5054 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5055 			width = this.width;
5056 			height = this.height;
5057 		}
5058 	}
5059 
5060 	/+
5061 		What I actually want from this:
5062 
5063 		* set / change: icon, tooltip
5064 		* handle: mouse click, right click
5065 		* show: notification bubble.
5066 	+/
5067 
5068 	version(Windows) {
5069 		WindowsIcon win32Icon;
5070 		HWND hwnd;
5071 
5072 		NOTIFYICONDATAW data;
5073 
5074 		NativeEventHandler getNativeEventHandler() {
5075 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5076 				if(msg == WM_USER) {
5077 					auto event = LOWORD(lParam);
5078 					auto iconId = HIWORD(lParam);
5079 					//auto x = GET_X_LPARAM(wParam);
5080 					//auto y = GET_Y_LPARAM(wParam);
5081 					switch(event) {
5082 						case WM_LBUTTONDOWN:
5083 							onClick()(MouseButton.left);
5084 						break;
5085 						case WM_RBUTTONDOWN:
5086 							onClick()(MouseButton.right);
5087 						break;
5088 						case WM_MBUTTONDOWN:
5089 							onClick()(MouseButton.middle);
5090 						break;
5091 						case WM_MOUSEMOVE:
5092 							// sent, we could use it.
5093 						break;
5094 						case WM_MOUSEWHEEL:
5095 							// NOT SENT
5096 						break;
5097 						//case NIN_KEYSELECT:
5098 						//case NIN_SELECT:
5099 						//break;
5100 						default: {}
5101 					}
5102 				}
5103 				return 0;
5104 			};
5105 		}
5106 
5107 		enum NIF_SHOWTIP = 0x00000080;
5108 
5109 		private static struct NOTIFYICONDATAW {
5110 			DWORD cbSize;
5111 			HWND  hWnd;
5112 			UINT  uID;
5113 			UINT  uFlags;
5114 			UINT  uCallbackMessage;
5115 			HICON hIcon;
5116 			WCHAR[128] szTip;
5117 			DWORD dwState;
5118 			DWORD dwStateMask;
5119 			WCHAR[256] szInfo;
5120 			union {
5121 				UINT uTimeout;
5122 				UINT uVersion;
5123 			}
5124 			WCHAR[64] szInfoTitle;
5125 			DWORD dwInfoFlags;
5126 			GUID  guidItem;
5127 			HICON hBalloonIcon;
5128 		}
5129 
5130 	}
5131 
5132 	/++
5133 		Note that on Windows, only left, right, and middle buttons are sent.
5134 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5135 		program is meant to be used on Windows too.
5136 	+/
5137 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5138 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5139 		// but on X, we need an Image, so its canonical ctor is there. They should
5140 		// forward to each other though.
5141 		version(X11) {
5142 			this.name = name;
5143 			this.onClick = onClick;
5144 			createXWin();
5145 			this.icon = icon;
5146 		} else version(Windows) {
5147 			this.onClick = onClick;
5148 			this.win32Icon = new WindowsIcon(icon);
5149 
5150 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5151 
5152 			static bool registered = false;
5153 			if(!registered) {
5154 				WNDCLASSEX wc;
5155 				wc.cbSize = wc.sizeof;
5156 				wc.hInstance = hInstance;
5157 				wc.lpfnWndProc = &WndProc;
5158 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5159 				if(!RegisterClassExW(&wc))
5160 					throw new WindowsApiException("RegisterClass", GetLastError());
5161 				registered = true;
5162 			}
5163 
5164 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5165 			if(hwnd is null)
5166 				throw new WindowsApiException("CreateWindow", GetLastError());
5167 
5168 			data.cbSize = data.sizeof;
5169 			data.hWnd = hwnd;
5170 			data.uID = cast(uint) cast(void*) this;
5171 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5172 				// NIF_INFO means show balloon
5173 			data.uCallbackMessage = WM_USER;
5174 			data.hIcon = this.win32Icon.hIcon;
5175 			data.szTip = ""; // FIXME
5176 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5177 			data.dwStateMask = NIS_HIDDEN; // windows vista
5178 
5179 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5180 
5181 
5182 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5183 
5184 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5185 		} else version(OSXCocoa) {
5186 			throw new NotYetImplementedException();
5187 		} else static assert(0);
5188 	}
5189 
5190 	/// ditto
5191 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5192 		version(X11) {
5193 			this.onClick = onClick;
5194 			this.name = name;
5195 			createXWin();
5196 			this.icon = icon;
5197 		} else version(Windows) {
5198 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5199 		} else version(OSXCocoa) {
5200 			throw new NotYetImplementedException();
5201 		} else static assert(0);
5202 	}
5203 
5204 	version(X11) {
5205 		/++
5206 			X-specific extension (for now at least)
5207 		+/
5208 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5209 			this.onClickEx = onClickEx;
5210 			createXWin();
5211 			if (icon !is null) this.icon = icon;
5212 		}
5213 
5214 		/// ditto
5215 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5216 			this.onClickEx = onClickEx;
5217 			createXWin();
5218 			this.icon = icon;
5219 		}
5220 	}
5221 
5222 	private void delegate (MouseButton button) onClick_;
5223 
5224 	///
5225 	@property final void delegate(MouseButton) onClick() {
5226 		if(onClick_ is null)
5227 			onClick_ = delegate void(MouseButton) {};
5228 		return onClick_;
5229 	}
5230 
5231 	/// ditto
5232 	@property final void onClick(void delegate(MouseButton) handler) {
5233 		// I made this a property setter so we can wrap smaller arg
5234 		// delegates and just forward all to onClickEx or something.
5235 		onClick_ = handler;
5236 	}
5237 
5238 
5239 	string name_;
5240 	@property void name(string n) {
5241 		name_ = n;
5242 	}
5243 
5244 	@property string name() {
5245 		return name_;
5246 	}
5247 
5248 	private MemoryImage originalMemoryImage;
5249 
5250 	///
5251 	@property void icon(MemoryImage i) {
5252 		version(X11) {
5253 			this.originalMemoryImage = i;
5254 			if (!active) return;
5255 			if (i !is null) {
5256 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5257 				if(!useAlpha)
5258 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5259 				// writeln("using pixmap ", clippixmap);
5260 				updateNetWmIcon();
5261 				redraw();
5262 			} else {
5263 				if (this.img !is null) {
5264 					this.img = null;
5265 					redraw();
5266 				}
5267 			}
5268 		} else version(Windows) {
5269 			this.win32Icon = new WindowsIcon(i);
5270 
5271 			data.uFlags = NIF_ICON;
5272 			data.hIcon = this.win32Icon.hIcon;
5273 
5274 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5275 		} else version(OSXCocoa) {
5276 			throw new NotYetImplementedException();
5277 		} else static assert(0);
5278 	}
5279 
5280 	/// ditto
5281 	@property void icon (Image i) {
5282 		version(X11) {
5283 			if (!active) return;
5284 			if (i !is img) {
5285 				originalMemoryImage = null;
5286 				img = i;
5287 				redraw();
5288 			}
5289 		} else version(Windows) {
5290 			this.icon(i is null ? null : i.toTrueColorImage());
5291 		} else version(OSXCocoa) {
5292 			throw new NotYetImplementedException();
5293 		} else static assert(0);
5294 	}
5295 
5296 	/++
5297 		Shows a balloon notification. You can only show one balloon at a time, if you call
5298 		it twice while one is already up, the first balloon will be replaced.
5299 
5300 
5301 		The user is free to block notifications and they will automatically disappear after
5302 		a timeout period.
5303 
5304 		Params:
5305 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5306 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5307 			icon = the icon to display with the notification. If null, it uses your existing icon.
5308 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5309 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5310 	+/
5311 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5312 		bool useCustom = true;
5313 		version(libnotify) {
5314 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5315 			try {
5316 				if(!active) return;
5317 
5318 				if(libnotify is null) {
5319 					libnotify = new C_DynamicLibrary("libnotify.so");
5320 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5321 				}
5322 
5323 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5324 
5325 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5326 
5327 				if(onclick) {
5328 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5329 					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);
5330 					libnotify_action_delegates_count++;
5331 				}
5332 
5333 				// FIXME icon
5334 
5335 				// set hint image-data
5336 				// set default action for onclick
5337 
5338 				void* error;
5339 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5340 
5341 				useCustom = false;
5342 			} catch(Exception e) {
5343 
5344 			}
5345 		}
5346 
5347 		version(X11) {
5348 		if(useCustom) {
5349 			if(!active) return;
5350 			if(balloon) {
5351 				hideBalloon();
5352 			}
5353 			// I know there are two specs for this, but one is never
5354 			// implemented by any window manager I have ever seen, and
5355 			// the other is a bloated mess and too complicated for simpledisplay...
5356 			// so doing my own little window instead.
5357 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5358 
5359 			int x, y, width, height;
5360 			getWindowRect(x, y, width, height);
5361 
5362 			int bx = x - balloon.width;
5363 			int by = y - balloon.height;
5364 			if(bx < 0)
5365 				bx = x + width + balloon.width;
5366 			if(by < 0)
5367 				by = y + height;
5368 
5369 			// just in case, make sure it is actually on scren
5370 			if(bx < 0)
5371 				bx = 0;
5372 			if(by < 0)
5373 				by = 0;
5374 
5375 			balloon.move(bx, by);
5376 			auto painter = balloon.draw();
5377 			painter.fillColor = Color(220, 220, 220);
5378 			painter.outlineColor = Color.black;
5379 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5380 			auto iconWidth = icon is null ? 0 : icon.width;
5381 			if(icon)
5382 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5383 			iconWidth += 6; // margin around the icon
5384 
5385 			// draw a close button
5386 			painter.outlineColor = Color(44, 44, 44);
5387 			painter.fillColor = Color(255, 255, 255);
5388 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5389 			painter.pen = Pen(Color.black, 3);
5390 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5391 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5392 			painter.pen = Pen(Color.black, 1);
5393 			painter.fillColor = Color(220, 220, 220);
5394 
5395 			// Draw the title and message
5396 			painter.drawText(Point(4 + iconWidth, 4), title);
5397 			painter.drawLine(
5398 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5399 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5400 			);
5401 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5402 
5403 			balloon.setEventHandlers(
5404 				(MouseEvent ev) {
5405 					if(ev.type == MouseEventType.buttonPressed) {
5406 						if(ev.x > balloon.width - 16 && ev.y < 16)
5407 							hideBalloon();
5408 						else if(onclick)
5409 							onclick();
5410 					}
5411 				}
5412 			);
5413 			balloon.show();
5414 
5415 			version(with_timer)
5416 			timer = new Timer(timeout, &hideBalloon);
5417 			else {} // FIXME
5418 		}
5419 		} else version(Windows) {
5420 			enum NIF_INFO = 0x00000010;
5421 
5422 			data.uFlags = NIF_INFO;
5423 
5424 			// FIXME: go back to the last valid unicode code point
5425 			if(title.length > 40)
5426 				title = title[0 .. 40];
5427 			if(message.length > 220)
5428 				message = message[0 .. 220];
5429 
5430 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5431 			enum NIIF_LARGE_ICON  = 0x00000020;
5432 			enum NIIF_NOSOUND = 0x00000010;
5433 			enum NIIF_USER = 0x00000004;
5434 			enum NIIF_ERROR = 0x00000003;
5435 			enum NIIF_WARNING = 0x00000002;
5436 			enum NIIF_INFO = 0x00000001;
5437 			enum NIIF_NONE = 0;
5438 
5439 			WCharzBuffer t = WCharzBuffer(title);
5440 			WCharzBuffer m = WCharzBuffer(message);
5441 
5442 			t.copyInto(data.szInfoTitle);
5443 			m.copyInto(data.szInfo);
5444 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5445 
5446 			if(icon !is null) {
5447 				auto i = new WindowsIcon(icon);
5448 				data.hBalloonIcon = i.hIcon;
5449 				data.dwInfoFlags |= NIIF_USER;
5450 			}
5451 
5452 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5453 		} else version(OSXCocoa) {
5454 			throw new NotYetImplementedException();
5455 		} else static assert(0);
5456 	}
5457 
5458 	///
5459 	//version(Windows)
5460 	void show() {
5461 		version(X11) {
5462 			if(!hidden)
5463 				return;
5464 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5465 			hidden = false;
5466 		} else version(Windows) {
5467 			data.uFlags = NIF_STATE;
5468 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5469 			data.dwStateMask = NIS_HIDDEN; // windows vista
5470 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5471 		} else version(OSXCocoa) {
5472 			throw new NotYetImplementedException();
5473 		} else static assert(0);
5474 	}
5475 
5476 	version(X11)
5477 		bool hidden = false;
5478 
5479 	///
5480 	//version(Windows)
5481 	void hide() {
5482 		version(X11) {
5483 			if(hidden)
5484 				return;
5485 			hidden = true;
5486 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5487 		} else version(Windows) {
5488 			data.uFlags = NIF_STATE;
5489 			data.dwState = NIS_HIDDEN; // windows vista
5490 			data.dwStateMask = NIS_HIDDEN; // windows vista
5491 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5492 		} else version(OSXCocoa) {
5493 			throw new NotYetImplementedException();
5494 		} else static assert(0);
5495 	}
5496 
5497 	///
5498 	void close () {
5499 		version(X11) {
5500 			if (active) {
5501 				active = false; // event handler will set this too, but meh
5502 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5503 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5504 				flushGui();
5505 			}
5506 		} else version(Windows) {
5507 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5508 		} else version(OSXCocoa) {
5509 			throw new NotYetImplementedException();
5510 		} else static assert(0);
5511 	}
5512 
5513 	~this() {
5514 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5515 		version(X11)
5516 			if(clippixmap != None)
5517 				XFreePixmap(XDisplayConnection.get, clippixmap);
5518 		close();
5519 	}
5520 }
5521 
5522 version(X11)
5523 /// Call `XFreePixmap` on the return value.
5524 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5525 	char[] data = new char[](i.width * i.height / 8 + 2);
5526 	data[] = 0;
5527 
5528 	int bitOffset = 0;
5529 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5530 		ubyte v = c.a > 128 ? 1 : 0;
5531 		data[bitOffset / 8] |= v << (bitOffset%8);
5532 		bitOffset++;
5533 	}
5534 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5535 	return handle;
5536 }
5537 
5538 
5539 // basic functions to make timers
5540 /**
5541 	A timer that will trigger your function on a given interval.
5542 
5543 
5544 	You create a timer with an interval and a callback. It will continue
5545 	to fire on the interval until it is destroyed.
5546 
5547 	There are currently no one-off timers (instead, just create one and
5548 	destroy it when it is triggered) nor are there pause/resume functions -
5549 	the timer must again be destroyed and recreated if you want to pause it.
5550 
5551 	---
5552 	auto timer = new Timer(50, { it happened!; });
5553 	timer.destroy();
5554 	---
5555 
5556 	Timers can only be expected to fire when the event loop is running and only
5557 	once per iteration through the event loop.
5558 
5559 	History:
5560 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5561 		slow could lock up the event loop. It now guarantees other things will
5562 		get a chance to run between timer calls, even if that means not keeping up
5563 		with the requested interval.
5564 */
5565 version(with_timer) {
5566 class Timer {
5567 // FIXME: needs pause and unpause
5568 	// FIXME: I might add overloads for ones that take a count of
5569 	// how many elapsed since last time (on Windows, it will divide
5570 	// the ticks thing given, on Linux it is just available) and
5571 	// maybe one that takes an instance of the Timer itself too
5572 	/// Create a timer with a callback when it triggers.
5573 	this(int intervalInMilliseconds, void delegate() onPulse) {
5574 		assert(onPulse !is null);
5575 
5576 		this.intervalInMilliseconds = intervalInMilliseconds;
5577 		this.onPulse = onPulse;
5578 
5579 		version(Windows) {
5580 			/*
5581 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5582 			if(handle == 0)
5583 				throw new WindowsApiException("SetTimer", GetLastError());
5584 			*/
5585 
5586 			// thanks to Archival 998 for the WaitableTimer blocks
5587 			handle = CreateWaitableTimer(null, false, null);
5588 			long initialTime = -intervalInMilliseconds;
5589 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5590 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5591 
5592 			mapping[handle] = this;
5593 
5594 		} else version(linux) {
5595 			static import ep = core.sys.linux.epoll;
5596 
5597 			import core.sys.linux.timerfd;
5598 
5599 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5600 			if(fd == -1)
5601 				throw new Exception("timer create failed");
5602 
5603 			mapping[fd] = this;
5604 
5605 			itimerspec value;
5606 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5607 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5608 
5609 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5610 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5611 
5612 			if(timerfd_settime(fd, 0, &value, null) == -1)
5613 				throw new Exception("couldn't make pulse timer");
5614 
5615 			version(with_eventloop) {
5616 				import arsd.eventloop;
5617 				addFileEventListeners(fd, &trigger, null, null);
5618 			} else {
5619 				prepareEventLoop();
5620 
5621 				ep.epoll_event ev = void;
5622 				ev.events = ep.EPOLLIN;
5623 				ev.data.fd = fd;
5624 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5625 			}
5626 		} else featureNotImplemented();
5627 	}
5628 
5629 	private int intervalInMilliseconds;
5630 
5631 	// just cuz I sometimes call it this.
5632 	alias dispose = destroy;
5633 
5634 	/// Stop and destroy the timer object.
5635 	void destroy() {
5636 		version(Windows) {
5637 			staticDestroy(handle);
5638 			handle = null;
5639 		} else version(linux) {
5640 			staticDestroy(fd);
5641 			fd = -1;
5642 		} else featureNotImplemented();
5643 	}
5644 
5645 	version(Windows)
5646 	static void staticDestroy(HANDLE handle) {
5647 		if(handle) {
5648 			// KillTimer(null, handle);
5649 			CancelWaitableTimer(cast(void*)handle);
5650 			mapping.remove(handle);
5651 			CloseHandle(handle);
5652 		}
5653 	}
5654 	else version(linux)
5655 	static void staticDestroy(int fd) {
5656 		if(fd != -1) {
5657 			import unix = core.sys.posix.unistd;
5658 			static import ep = core.sys.linux.epoll;
5659 
5660 			version(with_eventloop) {
5661 				import arsd.eventloop;
5662 				removeFileEventListeners(fd);
5663 			} else {
5664 				ep.epoll_event ev = void;
5665 				ev.events = ep.EPOLLIN;
5666 				ev.data.fd = fd;
5667 
5668 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5669 			}
5670 			unix.close(fd);
5671 			mapping.remove(fd);
5672 		}
5673 	}
5674 
5675 	~this() {
5676 		version(Windows) { if(handle)
5677 			cleanupQueue.queue!staticDestroy(handle);
5678 		} else version(linux) { if(fd != -1)
5679 			cleanupQueue.queue!staticDestroy(fd);
5680 		}
5681 	}
5682 
5683 
5684 	void changeTime(int intervalInMilliseconds)
5685 	{
5686 		this.intervalInMilliseconds = intervalInMilliseconds;
5687 		version(Windows)
5688 		{
5689 			if(handle)
5690 			{
5691 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5692 				long initialTime = -intervalInMilliseconds;
5693 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5694 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
5695 			}
5696 		}
5697 	}
5698 
5699 
5700 	private:
5701 
5702 	void delegate() onPulse;
5703 
5704 	int lastEventLoopRoundTriggered;
5705 
5706 	void trigger() {
5707 		version(linux) {
5708 			import unix = core.sys.posix.unistd;
5709 			long val;
5710 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5711 		} else version(Windows) {
5712 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5713 				return; // never try to actually run faster than the event loop
5714 			lastEventLoopRoundTriggered = eventLoopRound;
5715 		} else featureNotImplemented();
5716 
5717 		onPulse();
5718 	}
5719 
5720 	version(Windows)
5721 	void rearm() {
5722 
5723 	}
5724 
5725 	version(Windows)
5726 		extern(Windows)
5727 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5728 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5729 			if(Timer* t = timer in mapping) {
5730 				try
5731 				(*t).trigger();
5732 				catch(Exception e) { sdpy_abort(e); assert(0); }
5733 			}
5734 		}
5735 
5736 	version(Windows) {
5737 		//UINT_PTR handle;
5738 		//static Timer[UINT_PTR] mapping;
5739 		HANDLE handle;
5740 		__gshared Timer[HANDLE] mapping;
5741 	} else version(linux) {
5742 		int fd = -1;
5743 		__gshared Timer[int] mapping;
5744 	} else version(OSXCocoa) {
5745 	} else static assert(0, "timer not supported");
5746 }
5747 }
5748 
5749 version(Windows)
5750 private int eventLoopRound;
5751 
5752 version(Windows)
5753 /// 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
5754 class WindowsHandleReader {
5755 	///
5756 	this(void delegate() onReady, HANDLE handle) {
5757 		this.onReady = onReady;
5758 		this.handle = handle;
5759 
5760 		mapping[handle] = this;
5761 
5762 		enable();
5763 	}
5764 
5765 	///
5766 	void enable() {
5767 		auto el = EventLoop.get().impl;
5768 		el.handles ~= handle;
5769 	}
5770 
5771 	///
5772 	void disable() {
5773 		auto el = EventLoop.get().impl;
5774 		for(int i = 0; i < el.handles.length; i++) {
5775 			if(el.handles[i] is handle) {
5776 				el.handles[i] = el.handles[$-1];
5777 				el.handles = el.handles[0 .. $-1];
5778 				return;
5779 			}
5780 		}
5781 	}
5782 
5783 	void dispose() {
5784 		disable();
5785 		if(handle)
5786 			mapping.remove(handle);
5787 		handle = null;
5788 	}
5789 
5790 	void ready() {
5791 		if(onReady)
5792 			onReady();
5793 	}
5794 
5795 	HANDLE handle;
5796 	void delegate() onReady;
5797 
5798 	__gshared WindowsHandleReader[HANDLE] mapping;
5799 }
5800 
5801 version(Posix)
5802 /// Lets you add files to the event loop for reading. Use at your own risk.
5803 class PosixFdReader {
5804 	///
5805 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5806 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5807 	}
5808 
5809 	///
5810 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5811 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5812 	}
5813 
5814 	///
5815 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5816 		this.onReady = onReady;
5817 		this.fd = fd;
5818 		this.captureWrites = captureWrites;
5819 		this.captureReads = captureReads;
5820 
5821 		mapping[fd] = this;
5822 
5823 		version(with_eventloop) {
5824 			import arsd.eventloop;
5825 			addFileEventListeners(fd, &readyel);
5826 		} else {
5827 			enable();
5828 		}
5829 	}
5830 
5831 	bool captureReads;
5832 	bool captureWrites;
5833 
5834 	version(with_eventloop) {} else
5835 	///
5836 	void enable() {
5837 		prepareEventLoop();
5838 
5839 		enabled = true;
5840 
5841 		version(linux) {
5842 			static import ep = core.sys.linux.epoll;
5843 			ep.epoll_event ev = void;
5844 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5845 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5846 			ev.data.fd = fd;
5847 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5848 		} else {
5849 
5850 		}
5851 	}
5852 
5853 	version(with_eventloop) {} else
5854 	///
5855 	void disable() {
5856 		prepareEventLoop();
5857 
5858 		enabled = false;
5859 
5860 		version(linux) {
5861 			static import ep = core.sys.linux.epoll;
5862 			ep.epoll_event ev = void;
5863 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5864 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5865 			ev.data.fd = fd;
5866 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5867 		}
5868 	}
5869 
5870 	version(with_eventloop) {} else
5871 	///
5872 	void dispose() {
5873 		if(enabled)
5874 			disable();
5875 		if(fd != -1)
5876 			mapping.remove(fd);
5877 		fd = -1;
5878 	}
5879 
5880 	void delegate(int, bool, bool) onReady;
5881 
5882 	version(with_eventloop)
5883 	void readyel() {
5884 		onReady(fd, true, true);
5885 	}
5886 
5887 	void ready(uint flags) {
5888 		version(linux) {
5889 			static import ep = core.sys.linux.epoll;
5890 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5891 		} else {
5892 			import core.sys.posix.poll;
5893 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5894 		}
5895 	}
5896 
5897 	void hup(uint flags) {
5898 		if(onHup)
5899 			onHup();
5900 	}
5901 
5902 	void delegate() onHup;
5903 
5904 	int fd = -1;
5905 	private bool enabled;
5906 	__gshared PosixFdReader[int] mapping;
5907 }
5908 
5909 // basic functions to access the clipboard
5910 /+
5911 
5912 
5913 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5914 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5915 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5916 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5917 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5918 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5919 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5920 
5921 +/
5922 
5923 /++
5924 	this does a delegate because it is actually an async call on X...
5925 	the receiver may never be called if the clipboard is empty or unavailable
5926 	gets plain text from the clipboard.
5927 +/
5928 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5929 	version(Windows) {
5930 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5931 		if(OpenClipboard(hwndOwner) == 0)
5932 			throw new WindowsApiException("OpenClipboard", GetLastError());
5933 		scope(exit)
5934 			CloseClipboard();
5935 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5936 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5937 
5938 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5939 				scope(exit)
5940 					GlobalUnlock(dataHandle);
5941 
5942 				// FIXME: CR/LF conversions
5943 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5944 				int len = 0;
5945 				auto d = data;
5946 				while(*d) {
5947 					d++;
5948 					len++;
5949 				}
5950 				string s;
5951 				s.reserve(len);
5952 				foreach(dchar ch; data[0 .. len]) {
5953 					s ~= ch;
5954 				}
5955 				receiver(s);
5956 			}
5957 		}
5958 	} else version(X11) {
5959 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5960 	} else version(OSXCocoa) {
5961 		throw new NotYetImplementedException();
5962 	} else static assert(0);
5963 }
5964 
5965 // FIXME: a clipboard listener might be cool btw
5966 
5967 /++
5968 	this does a delegate because it is actually an async call on X...
5969 	the receiver may never be called if the clipboard is empty or unavailable
5970 	gets image from the clipboard.
5971 
5972 	templated because it introduces an optional dependency on arsd.bmp
5973 +/
5974 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5975 	version(Windows) {
5976 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5977 		if(OpenClipboard(hwndOwner) == 0)
5978 			throw new WindowsApiException("OpenClipboard", GetLastError());
5979 		scope(exit)
5980 			CloseClipboard();
5981 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5982 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5983 				scope(exit)
5984 					GlobalUnlock(dataHandle);
5985 
5986 				auto len = GlobalSize(dataHandle);
5987 
5988 				import arsd.bmp;
5989 				auto img = readBmp(data[0 .. len], false);
5990 				receiver(img);
5991 			}
5992 		}
5993 	} else version(X11) {
5994 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5995 	} else version(OSXCocoa) {
5996 		throw new NotYetImplementedException();
5997 	} else static assert(0);
5998 }
5999 
6000 /// Copies some text to the clipboard.
6001 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6002 	assert(clipboardOwner !is null);
6003 	version(Windows) {
6004 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6005 			throw new WindowsApiException("OpenClipboard", GetLastError());
6006 		scope(exit)
6007 			CloseClipboard();
6008 		EmptyClipboard();
6009 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6010 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6011 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6012 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6013 			auto slice = data[0 .. sz];
6014 			scope(failure)
6015 				GlobalUnlock(handle);
6016 
6017 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6018 
6019 			GlobalUnlock(handle);
6020 			SetClipboardData(CF_UNICODETEXT, handle);
6021 		}
6022 	} else version(X11) {
6023 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6024 	} else version(OSXCocoa) {
6025 		throw new NotYetImplementedException();
6026 	} else static assert(0);
6027 }
6028 
6029 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6030 	assert(clipboardOwner !is null);
6031 	version(Windows) {
6032 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6033 			throw new WindowsApiException("OpenClipboard", GetLastError());
6034 		scope(exit)
6035 			CloseClipboard();
6036 		EmptyClipboard();
6037 
6038 
6039 		import arsd.bmp;
6040 		ubyte[] mdata;
6041 		mdata.reserve(img.width * img.height);
6042 		void sink(ubyte b) {
6043 			mdata ~= b;
6044 		}
6045 		writeBmpIndirect(img, &sink, false);
6046 
6047 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6048 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6049 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6050 			auto slice = data[0 .. mdata.length];
6051 			scope(failure)
6052 				GlobalUnlock(handle);
6053 
6054 			slice[] = mdata[];
6055 
6056 			GlobalUnlock(handle);
6057 			SetClipboardData(CF_DIB, handle);
6058 		}
6059 	} else version(X11) {
6060 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6061 			mixin X11SetSelectionHandler_Basics;
6062 			private const(ubyte)[] mdata;
6063 			private const(ubyte)[] mdata_original;
6064 			this(MemoryImage img) {
6065 				import arsd.bmp;
6066 
6067 				mdata.reserve(img.width * img.height);
6068 				void sink(ubyte b) {
6069 					mdata ~= b;
6070 				}
6071 				writeBmpIndirect(img, &sink, true);
6072 
6073 				mdata_original = mdata;
6074 			}
6075 
6076 			Atom[] availableFormats() {
6077 				auto display = XDisplayConnection.get;
6078 				return [
6079 					GetAtom!"image/bmp"(display),
6080 					GetAtom!"TARGETS"(display)
6081 				];
6082 			}
6083 
6084 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6085 				if(mdata.length < data.length) {
6086 					data[0 .. mdata.length] = mdata[];
6087 					auto ret = data[0 .. mdata.length];
6088 					mdata = mdata[$..$];
6089 					return ret;
6090 				} else {
6091 					data[] = mdata[0 .. data.length];
6092 					mdata = mdata[data.length .. $];
6093 					return data[];
6094 				}
6095 			}
6096 
6097 			void done() {
6098 				mdata = mdata_original;
6099 			}
6100 		}
6101 
6102 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6103 	} else version(OSXCocoa) {
6104 		throw new NotYetImplementedException();
6105 	} else static assert(0);
6106 }
6107 
6108 
6109 version(X11) {
6110 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6111 
6112 	private Atom*[] interredAtoms; // for discardAndRecreate
6113 
6114 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6115 	/// Platform-specific for X11.
6116 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6117 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6118 		static Atom a;
6119 		if(!a) {
6120 			a = XInternAtom(display, name, !create);
6121 			interredAtoms ~= &a;
6122 		}
6123 		if(a == None)
6124 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6125 		return a;
6126 	}
6127 
6128 	/// Platform-specific for X11 - gets atom names as a string.
6129 	string getAtomName(Atom atom, Display* display) {
6130 		auto got = XGetAtomName(display, atom);
6131 		scope(exit) XFree(got);
6132 		import core.stdc.string;
6133 		string s = got[0 .. strlen(got)].idup;
6134 		return s;
6135 	}
6136 
6137 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6138 	void setPrimarySelection(SimpleWindow window, string text) {
6139 		setX11Selection!"PRIMARY"(window, text);
6140 	}
6141 
6142 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6143 	void setSecondarySelection(SimpleWindow window, string text) {
6144 		setX11Selection!"SECONDARY"(window, text);
6145 	}
6146 
6147 	interface X11SetSelectionHandler {
6148 		// should include TARGETS right now
6149 		Atom[] availableFormats();
6150 		// Return the slice of data you filled, empty slice if done.
6151 		// this is to support the incremental thing
6152 		ubyte[] getData(Atom format, return scope ubyte[] data);
6153 
6154 		void done();
6155 
6156 		void handleRequest(XEvent);
6157 
6158 		bool matchesIncr(Window, Atom);
6159 		void sendMoreIncr(XPropertyEvent*);
6160 	}
6161 
6162 	mixin template X11SetSelectionHandler_Basics() {
6163 		Window incrWindow;
6164 		Atom incrAtom;
6165 		Atom selectionAtom;
6166 		Atom formatAtom;
6167 		ubyte[] toSend;
6168 		bool matchesIncr(Window w, Atom a) {
6169 			return incrAtom && incrAtom == a && w == incrWindow;
6170 		}
6171 		void sendMoreIncr(XPropertyEvent* event) {
6172 			auto display = XDisplayConnection.get;
6173 
6174 			XChangeProperty (display,
6175 				incrWindow,
6176 				incrAtom,
6177 				formatAtom,
6178 				8 /* bits */, PropModeReplace,
6179 				toSend.ptr, cast(int) toSend.length);
6180 
6181 			if(toSend.length != 0) {
6182 				toSend = this.getData(formatAtom, toSend[]);
6183 			} else {
6184 				this.done();
6185 				incrWindow = None;
6186 				incrAtom = None;
6187 				selectionAtom = None;
6188 				formatAtom = None;
6189 				toSend = null;
6190 			}
6191 		}
6192 		void handleRequest(XEvent ev) {
6193 
6194 			auto display = XDisplayConnection.get;
6195 
6196 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6197 			XSelectionEvent selectionEvent;
6198 			selectionEvent.type = EventType.SelectionNotify;
6199 			selectionEvent.display = event.display;
6200 			selectionEvent.requestor = event.requestor;
6201 			selectionEvent.selection = event.selection;
6202 			selectionEvent.time = event.time;
6203 			selectionEvent.target = event.target;
6204 
6205 			bool supportedType() {
6206 				foreach(t; this.availableFormats())
6207 					if(t == event.target)
6208 						return true;
6209 				return false;
6210 			}
6211 
6212 			if(event.property == None) {
6213 				selectionEvent.property = event.target;
6214 
6215 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6216 				XFlush(display);
6217 			} if(event.target == GetAtom!"TARGETS"(display)) {
6218 				/* respond with the supported types */
6219 				auto tlist = this.availableFormats();
6220 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6221 				selectionEvent.property = event.property;
6222 
6223 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6224 				XFlush(display);
6225 			} else if(supportedType()) {
6226 				auto buffer = new ubyte[](1024 * 64);
6227 				auto toSend = this.getData(event.target, buffer[]);
6228 
6229 				if(toSend.length < 32 * 1024) {
6230 					// small enough to send directly...
6231 					selectionEvent.property = event.property;
6232 					XChangeProperty (display,
6233 						selectionEvent.requestor,
6234 						selectionEvent.property,
6235 						event.target,
6236 						8 /* bits */, 0 /* PropModeReplace */,
6237 						toSend.ptr, cast(int) toSend.length);
6238 
6239 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6240 					XFlush(display);
6241 				} else {
6242 					// large, let's send incrementally
6243 					arch_ulong l = toSend.length;
6244 
6245 					// if I wanted other events from this window don't want to clear that out....
6246 					XWindowAttributes xwa;
6247 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6248 
6249 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6250 
6251 					incrWindow = event.requestor;
6252 					incrAtom = event.property;
6253 					formatAtom = event.target;
6254 					selectionAtom = event.selection;
6255 					this.toSend = toSend;
6256 
6257 					selectionEvent.property = event.property;
6258 					XChangeProperty (display,
6259 						selectionEvent.requestor,
6260 						selectionEvent.property,
6261 						GetAtom!"INCR"(display),
6262 						32 /* bits */, PropModeReplace,
6263 						&l, 1);
6264 
6265 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6266 					XFlush(display);
6267 				}
6268 				//if(after)
6269 					//after();
6270 			} else {
6271 				debug(sdpy_clip) {
6272 					writeln("Unsupported data ", getAtomName(event.target, display));
6273 				}
6274 				selectionEvent.property = None; // I don't know how to handle this type...
6275 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6276 				XFlush(display);
6277 			}
6278 		}
6279 	}
6280 
6281 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6282 		mixin X11SetSelectionHandler_Basics;
6283 		private const(ubyte)[] text;
6284 		private const(ubyte)[] text_original;
6285 		this(string text) {
6286 			this.text = cast(const ubyte[]) text;
6287 			this.text_original = this.text;
6288 		}
6289 		Atom[] availableFormats() {
6290 			auto display = XDisplayConnection.get;
6291 			return [
6292 				GetAtom!"UTF8_STRING"(display),
6293 				GetAtom!"text/plain"(display),
6294 				XA_STRING,
6295 				GetAtom!"TARGETS"(display)
6296 			];
6297 		}
6298 
6299 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6300 			if(text.length < data.length) {
6301 				data[0 .. text.length] = text[];
6302 				return data[0 .. text.length];
6303 			} else {
6304 				data[] = text[0 .. data.length];
6305 				text = text[data.length .. $];
6306 				return data[];
6307 			}
6308 		}
6309 
6310 		void done() {
6311 			text = text_original;
6312 		}
6313 	}
6314 
6315 	/// 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?!)
6316 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6317 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6318 	}
6319 
6320 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6321 		assert(window !is null);
6322 
6323 		auto display = XDisplayConnection.get();
6324 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6325 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6326 		else Atom a = GetAtom!atomName(display);
6327 
6328 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6329 
6330 		window.impl.setSelectionHandlers[a] = data;
6331 	}
6332 
6333 	///
6334 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6335 		getX11Selection!"PRIMARY"(window, handler);
6336 	}
6337 
6338 	// added July 28, 2020
6339 	// undocumented as experimental tho
6340 	interface X11GetSelectionHandler {
6341 		void handleData(Atom target, in ubyte[] data);
6342 		Atom findBestFormat(Atom[] answer);
6343 
6344 		void prepareIncremental(Window, Atom);
6345 		bool matchesIncr(Window, Atom);
6346 		void handleIncrData(Atom, in ubyte[] data);
6347 	}
6348 
6349 	mixin template X11GetSelectionHandler_Basics() {
6350 		Window incrWindow;
6351 		Atom incrAtom;
6352 
6353 		void prepareIncremental(Window w, Atom a) {
6354 			incrWindow = w;
6355 			incrAtom = a;
6356 		}
6357 		bool matchesIncr(Window w, Atom a) {
6358 			return incrWindow == w && incrAtom == a;
6359 		}
6360 
6361 		Atom incrFormatAtom;
6362 		ubyte[] incrData;
6363 		void handleIncrData(Atom format, in ubyte[] data) {
6364 			incrFormatAtom = format;
6365 
6366 			if(data.length)
6367 				incrData ~= data;
6368 			else
6369 				handleData(incrFormatAtom, incrData);
6370 
6371 		}
6372 	}
6373 
6374 	///
6375 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6376 		assert(window !is null);
6377 
6378 		auto display = XDisplayConnection.get();
6379 		auto atom = GetAtom!atomName(display);
6380 
6381 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6382 			this(void delegate(in char[]) handler) {
6383 				this.handler = handler;
6384 			}
6385 
6386 			mixin X11GetSelectionHandler_Basics;
6387 
6388 			void delegate(in char[]) handler;
6389 
6390 			void handleData(Atom target, in ubyte[] data) {
6391 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6392 					handler(cast(const char[]) data);
6393 			}
6394 
6395 			Atom findBestFormat(Atom[] answer) {
6396 				Atom best = None;
6397 				foreach(option; answer) {
6398 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6399 						best = option;
6400 						break;
6401 					} else if(option == XA_STRING) {
6402 						best = option;
6403 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6404 						best = option;
6405 					}
6406 				}
6407 				return best;
6408 			}
6409 		}
6410 
6411 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6412 
6413 		auto target = GetAtom!"TARGETS"(display);
6414 
6415 		// SDD_DATA is "simpledisplay.d data"
6416 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6417 	}
6418 
6419 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6420 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6421 		assert(window !is null);
6422 
6423 		auto display = XDisplayConnection.get();
6424 		auto atom = GetAtom!atomName(display);
6425 
6426 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6427 			this(void delegate(MemoryImage) handler) {
6428 				this.handler = handler;
6429 			}
6430 
6431 			mixin X11GetSelectionHandler_Basics;
6432 
6433 			void delegate(MemoryImage) handler;
6434 
6435 			void handleData(Atom target, in ubyte[] data) {
6436 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6437 					import arsd.bmp;
6438 					handler(readBmp(data));
6439 				}
6440 			}
6441 
6442 			Atom findBestFormat(Atom[] answer) {
6443 				Atom best = None;
6444 				foreach(option; answer) {
6445 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6446 						best = option;
6447 					}
6448 				}
6449 				return best;
6450 			}
6451 
6452 		}
6453 
6454 
6455 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6456 
6457 		auto target = GetAtom!"TARGETS"(display);
6458 
6459 		// SDD_DATA is "simpledisplay.d data"
6460 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6461 	}
6462 
6463 
6464 	///
6465 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6466 		Atom actualType;
6467 		int actualFormat;
6468 		arch_ulong actualItems;
6469 		arch_ulong bytesRemaining;
6470 		void* data;
6471 
6472 		auto display = XDisplayConnection.get();
6473 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6474 			if(actualFormat == 0)
6475 				return null;
6476 			else {
6477 				int byteLength;
6478 				if(actualFormat == 32) {
6479 					// 32 means it is a C long... which is variable length
6480 					actualFormat = cast(int) arch_long.sizeof * 8;
6481 				}
6482 
6483 				// then it is just a bit count
6484 				byteLength = cast(int) (actualItems * actualFormat / 8);
6485 
6486 				auto d = new ubyte[](byteLength);
6487 				d[] = cast(ubyte[]) data[0 .. byteLength];
6488 				XFree(data);
6489 				return d;
6490 			}
6491 		}
6492 		return null;
6493 	}
6494 
6495 	/* defined in the systray spec */
6496 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6497 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6498 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6499 
6500 
6501 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6502 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6503 	public class GlobalHotkey {
6504 		KeyEvent key;
6505 		void delegate () handler;
6506 
6507 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6508 
6509 		/// Create from initialzed KeyEvent object
6510 		this (KeyEvent akey, void delegate () ahandler=null) {
6511 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6512 			key = akey;
6513 			handler = ahandler;
6514 		}
6515 
6516 		/// Create from emacs-like key name ("C-M-Y", etc.)
6517 		this (const(char)[] akey, void delegate () ahandler=null) {
6518 			key = KeyEvent.parse(akey);
6519 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6520 			handler = ahandler;
6521 		}
6522 
6523 	}
6524 
6525 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6526 		//conwriteln("failed to grab key");
6527 		GlobalHotkeyManager.ghfailed = true;
6528 		return 0;
6529 	}
6530 
6531 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6532 		Image.impl.xshmfailed = true;
6533 		return 0;
6534 	}
6535 
6536 	private __gshared int errorHappened;
6537 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6538 		import core.stdc.stdio;
6539 		char[265] buffer;
6540 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6541 		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);
6542 		errorHappened = true;
6543 		return 0;
6544 	}
6545 
6546 	/++
6547 		Global hotkey manager. It contains static methods to manage global hotkeys.
6548 
6549 		---
6550 		 try {
6551 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6552 		} catch (Exception e) {
6553 			conwriteln("ERROR registering hotkey!");
6554 		}
6555 		EventLoop.get.run();
6556 		---
6557 
6558 		The key strings are based on Emacs. In practical terms,
6559 		`M` means `alt` and `H` means the Windows logo key. `C`
6560 		is `ctrl`.
6561 
6562 		$(WARNING
6563 			This is X-specific right now. If you are on
6564 			Windows, try [registerHotKey] instead.
6565 
6566 			We will probably merge these into a single
6567 			interface later.
6568 		)
6569 	+/
6570 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6571 		version(X11) {
6572 			void recreateAfterDisconnect() {
6573 				throw new Exception("NOT IMPLEMENTED");
6574 			}
6575 			void discardConnectionState() {
6576 				throw new Exception("NOT IMPLEMENTED");
6577 			}
6578 		}
6579 
6580 		private static immutable uint[8] masklist = [ 0,
6581 			KeyOrButtonMask.LockMask,
6582 			KeyOrButtonMask.Mod2Mask,
6583 			KeyOrButtonMask.Mod3Mask,
6584 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6585 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6586 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6587 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6588 		];
6589 		private __gshared GlobalHotkeyManager ghmanager;
6590 		private __gshared bool ghfailed = false;
6591 
6592 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6593 			if (modmask == 0) return false;
6594 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6595 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6596 			return true;
6597 		}
6598 
6599 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6600 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6601 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6602 			return modmask;
6603 		}
6604 
6605 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6606 			uint keycode = cast(uint)ke.key;
6607 			auto dpy = XDisplayConnection.get;
6608 			return XKeysymToKeycode(dpy, keycode);
6609 		}
6610 
6611 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6612 
6613 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6614 
6615 		NativeEventHandler getNativeEventHandler () {
6616 			return delegate int (XEvent e) {
6617 				if (e.type != EventType.KeyPress) return 1;
6618 				auto kev = cast(const(XKeyEvent)*)&e;
6619 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6620 				if (auto ghkp = hash in globalHotkeyList) {
6621 					try {
6622 						ghkp.doHandle();
6623 					} catch (Exception e) {
6624 						import core.stdc.stdio : stderr, fprintf;
6625 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6626 					}
6627 				}
6628 				return 1;
6629 			};
6630 		}
6631 
6632 		private this () {
6633 			auto dpy = XDisplayConnection.get;
6634 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6635 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6636 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6637 		}
6638 
6639 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6640 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6641 		static void register (GlobalHotkey gh) {
6642 			if (gh is null) return;
6643 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6644 
6645 			auto dpy = XDisplayConnection.get;
6646 			immutable keycode = keyEvent2KeyCode(gh.key);
6647 
6648 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6649 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6650 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6651 			XSync(dpy, 0/*False*/);
6652 
6653 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6654 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6655 			ghfailed = false;
6656 			foreach (immutable uint ormask; masklist[]) {
6657 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6658 			}
6659 			XSync(dpy, 0/*False*/);
6660 			XSetErrorHandler(savedErrorHandler);
6661 
6662 			if (ghfailed) {
6663 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6664 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6665 				XSync(dpy, 0/*False*/);
6666 				XSetErrorHandler(savedErrorHandler);
6667 				throw new Exception("cannot register global hotkey");
6668 			}
6669 
6670 			globalHotkeyList[hash] = gh;
6671 		}
6672 
6673 		/// Ditto
6674 		static void register (const(char)[] akey, void delegate () ahandler) {
6675 			register(new GlobalHotkey(akey, ahandler));
6676 		}
6677 
6678 		private static void removeByHash (ulong hash) {
6679 			if (auto ghp = hash in globalHotkeyList) {
6680 				auto dpy = XDisplayConnection.get;
6681 				immutable keycode = keyEvent2KeyCode(ghp.key);
6682 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6683 				XSync(dpy, 0/*False*/);
6684 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6685 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6686 				XSync(dpy, 0/*False*/);
6687 				XSetErrorHandler(savedErrorHandler);
6688 				globalHotkeyList.remove(hash);
6689 			}
6690 		}
6691 
6692 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6693 		/// It is safe to unregister unknown or invalid hotkey.
6694 		static void unregister (GlobalHotkey gh) {
6695 			//TODO: add second AA for faster search? prolly doesn't worth it.
6696 			if (gh is null) return;
6697 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6698 				if (kv.value is gh) {
6699 					removeByHash(kv.key);
6700 					return;
6701 				}
6702 			}
6703 		}
6704 
6705 		/// Ditto.
6706 		static void unregister (const(char)[] key) {
6707 			auto kev = KeyEvent.parse(key);
6708 			immutable keycode = keyEvent2KeyCode(kev);
6709 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6710 		}
6711 	}
6712 }
6713 
6714 version(Windows) {
6715 	/++
6716 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6717 
6718 		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).
6719 	+/
6720 	void sendSyntheticInput(wstring s) {
6721 			INPUT[] inputs;
6722 			inputs.reserve(s.length * 2);
6723 
6724 			foreach(wchar c; s) {
6725 				INPUT input;
6726 				input.type = INPUT_KEYBOARD;
6727 				input.ki.wScan = c;
6728 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6729 				inputs ~= input;
6730 
6731 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6732 				inputs ~= input;
6733 			}
6734 
6735 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6736 				throw new WindowsApiException("SendInput", GetLastError());
6737 			}
6738 
6739 	}
6740 
6741 
6742 	// global hotkey helper function
6743 
6744 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6745 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6746 		__gshared int hotkeyId = 0;
6747 		int id = ++hotkeyId;
6748 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6749 			throw new Exception("RegisterHotKey");
6750 
6751 		__gshared void delegate()[WPARAM][HWND] handlers;
6752 
6753 		handlers[window.impl.hwnd][id] = handler;
6754 
6755 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6756 
6757 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6758 			switch(msg) {
6759 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6760 				case WM_HOTKEY:
6761 					if(auto list = hwnd in handlers) {
6762 						if(auto h = wParam in *list) {
6763 							(*h)();
6764 							return 0;
6765 						}
6766 					}
6767 				goto default;
6768 				default:
6769 			}
6770 			if(oldHandler)
6771 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6772 			return 1; // pass it on
6773 		};
6774 
6775 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6776 			oldHandler = window.handleNativeEvent;
6777 			window.handleNativeEvent = nativeEventHandler;
6778 		}
6779 
6780 		return id;
6781 	}
6782 
6783 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6784 	void unregisterHotKey(SimpleWindow window, int id) {
6785 		if(!UnregisterHotKey(window.impl.hwnd, id))
6786 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
6787 	}
6788 }
6789 
6790 version (X11) {
6791 	pragma(lib, "dl");
6792 	import core.sys.posix.dlfcn;
6793 }
6794 
6795 /++
6796 	Allows for sending synthetic input to the X server via the Xtst
6797 	extension or on Windows using SendInput.
6798 
6799 	Please remember user input is meant to be user - don't use this
6800 	if you have some other alternative!
6801 
6802 	History:
6803 		Added May 17, 2020 with the X implementation.
6804 
6805 		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.)
6806 	Bugs:
6807 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6808 +/
6809 struct SyntheticInput {
6810 	@disable this();
6811 
6812 	private int* refcount;
6813 
6814 	version(X11) {
6815 		private void* lib;
6816 
6817 		private extern(C) {
6818 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6819 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6820 		}
6821 	}
6822 
6823 	/// The dummy param must be 0.
6824 	this(int dummy) {
6825 		version(X11) {
6826 			lib = dlopen("libXtst.so", RTLD_NOW);
6827 			if(lib is null)
6828 				throw new Exception("cannot load xtest lib extension");
6829 			scope(failure)
6830 				dlclose(lib);
6831 
6832 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6833 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6834 
6835 			if(XTestFakeKeyEvent is null)
6836 				throw new Exception("No XTestFakeKeyEvent");
6837 			if(XTestFakeButtonEvent is null)
6838 				throw new Exception("No XTestFakeButtonEvent");
6839 		}
6840 
6841 		refcount = new int;
6842 		*refcount = 1;
6843 	}
6844 
6845 	this(this) {
6846 		if(refcount)
6847 			*refcount += 1;
6848 	}
6849 
6850 	~this() {
6851 		if(refcount) {
6852 			*refcount -= 1;
6853 			if(*refcount == 0)
6854 				// I commented this because if I close the lib before
6855 				// XCloseDisplay, it is liable to segfault... so just
6856 				// gonna keep it loaded if it is loaded, no big deal
6857 				// anyway.
6858 				{} // dlclose(lib);
6859 		}
6860 	}
6861 
6862 	/++
6863 		Simulates typing a string into the keyboard.
6864 
6865 		Bugs:
6866 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6867 
6868 			Not implemented except on Windows and X11.
6869 	+/
6870 	void sendSyntheticInput(string s) {
6871 		version(Windows) {
6872 			INPUT[] inputs;
6873 			inputs.reserve(s.length * 2);
6874 
6875 			auto ei = GetMessageExtraInfo();
6876 
6877 			foreach(wchar c; s) {
6878 				INPUT input;
6879 				input.type = INPUT_KEYBOARD;
6880 				input.ki.wScan = c;
6881 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6882 				input.ki.dwExtraInfo = ei;
6883 				inputs ~= input;
6884 
6885 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6886 				inputs ~= input;
6887 			}
6888 
6889 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6890 				throw new WindowsApiException("SendInput", GetLastError());
6891 			}
6892 		} else version(X11) {
6893 			int delay = 0;
6894 			foreach(ch; s) {
6895 				pressKey(cast(Key) ch, true, delay);
6896 				pressKey(cast(Key) ch, false, delay);
6897 				delay += 5;
6898 			}
6899 		} else throw new NotYetImplementedException();
6900 	}
6901 
6902 	/++
6903 		Sends a fake press or release key event.
6904 
6905 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6906 
6907 		Bugs:
6908 			The `delay` parameter is not implemented yet on Windows.
6909 
6910 			Not implemented except on Windows and X11.
6911 	+/
6912 	void pressKey(Key key, bool pressed, int delay = 0) {
6913 		version(Windows) {
6914 			INPUT input;
6915 			input.type = INPUT_KEYBOARD;
6916 			input.ki.wVk = cast(ushort) key;
6917 
6918 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6919 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6920 
6921 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6922 				throw new WindowsApiException("SendInput", GetLastError());
6923 			}
6924 		} else version(X11) {
6925 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6926 		} else throw new NotYetImplementedException();
6927 	}
6928 
6929 	/++
6930 		Sends a fake mouse button press or release event.
6931 
6932 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6933 
6934 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6935 
6936 		Bugs:
6937 			The `delay` parameter is not implemented yet on Windows.
6938 
6939 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6940 
6941 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6942 	+/
6943 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6944 		version(Windows) {
6945 			INPUT input;
6946 			input.type = INPUT_MOUSE;
6947 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6948 
6949 			// input.mi.mouseData for a wheel event
6950 
6951 			switch(button) {
6952 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6953 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6954 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6955 				case MouseButton.wheelUp:
6956 				case MouseButton.wheelDown:
6957 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6958 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6959 				break;
6960 				case MouseButton.backButton: throw new NotYetImplementedException();
6961 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6962 				default:
6963 			}
6964 
6965 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6966 				throw new WindowsApiException("SendInput", GetLastError());
6967 			}
6968 		} else version(X11) {
6969 			int btn;
6970 
6971 			switch(button) {
6972 				case MouseButton.left: btn = 1; break;
6973 				case MouseButton.middle: btn = 2; break;
6974 				case MouseButton.right: btn = 3; break;
6975 				case MouseButton.wheelUp: btn = 4; break;
6976 				case MouseButton.wheelDown: btn = 5; break;
6977 				case MouseButton.backButton: btn = 8; break;
6978 				case MouseButton.forwardButton: btn = 9; break;
6979 				default:
6980 			}
6981 
6982 			assert(btn);
6983 
6984 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6985 		} else throw new NotYetImplementedException();
6986 	}
6987 
6988 	///
6989 	static void moveMouseArrowBy(int dx, int dy) {
6990 		version(Windows) {
6991 			INPUT input;
6992 			input.type = INPUT_MOUSE;
6993 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6994 			input.mi.dx = dx;
6995 			input.mi.dy = dy;
6996 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
6997 
6998 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6999 				throw new WindowsApiException("SendInput", GetLastError());
7000 			}
7001 		} else version(X11) {
7002 			auto disp = XDisplayConnection.get();
7003 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7004 			XFlush(disp);
7005 		} else throw new NotYetImplementedException();
7006 	}
7007 
7008 	///
7009 	static void moveMouseArrowTo(int x, int y) {
7010 		version(Windows) {
7011 			INPUT input;
7012 			input.type = INPUT_MOUSE;
7013 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7014 			input.mi.dx = x;
7015 			input.mi.dy = y;
7016 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7017 
7018 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7019 				throw new WindowsApiException("SendInput", GetLastError());
7020 			}
7021 		} else version(X11) {
7022 			auto disp = XDisplayConnection.get();
7023 			auto root = RootWindow(disp, DefaultScreen(disp));
7024 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7025 			XFlush(disp);
7026 		} else throw new NotYetImplementedException();
7027 	}
7028 }
7029 
7030 
7031 
7032 /++
7033 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7034 
7035 	See_Also:
7036 	$(LIST
7037 		*[ScreenPainter]
7038 		*[ScreenPainter.rasterOp]
7039 	)
7040 +/
7041 enum RasterOp {
7042 	normal, /// Replaces the pixel.
7043 	xor, /// Uses bitwise xor to draw.
7044 }
7045 
7046 // being phobos-free keeps the size WAY down
7047 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7048 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7049 package(arsd) const(wchar)* toWStringz(string s) {
7050 	wstring r;
7051 	foreach(dchar c; s)
7052 		r ~= c;
7053 	r ~= '\0';
7054 	return r.ptr;
7055 }
7056 private string[] split(in void[] a, char c) {
7057 		string[] ret;
7058 		size_t previous = 0;
7059 		foreach(i, char ch; cast(ubyte[]) a) {
7060 			if(ch == c) {
7061 				ret ~= cast(string) a[previous .. i];
7062 				previous = i + 1;
7063 			}
7064 		}
7065 		if(previous != a.length)
7066 			ret ~= cast(string) a[previous .. $];
7067 		return ret;
7068 	}
7069 
7070 version(without_opengl) {
7071 	enum OpenGlOptions {
7072 		no,
7073 	}
7074 } else {
7075 	/++
7076 		Determines if you want an OpenGL context created on the new window.
7077 
7078 
7079 		See more: [#topics-3d|in the 3d topic].
7080 
7081 		---
7082 		import arsd.simpledisplay;
7083 		void main() {
7084 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7085 
7086 			// Set up the matrix
7087 			window.setAsCurrentOpenGlContext(); // make this window active
7088 
7089 			// This is called on each frame, we will draw our scene
7090 			window.redrawOpenGlScene = delegate() {
7091 
7092 			};
7093 
7094 			window.eventLoop(0);
7095 		}
7096 		---
7097 	+/
7098 	enum OpenGlOptions {
7099 		no, /// No OpenGL context is created
7100 		yes, /// Yes, create an OpenGL context
7101 	}
7102 
7103 	version(X11) {
7104 		static if (!SdpyIsUsingIVGLBinds) {
7105 
7106 
7107 			struct __GLXFBConfigRec {}
7108 			alias GLXFBConfig = __GLXFBConfigRec*;
7109 
7110 			//pragma(lib, "GL");
7111 			//pragma(lib, "GLU");
7112 			interface GLX {
7113 			extern(C) nothrow @nogc {
7114 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7115 						const int *attrib_list);
7116 
7117 				 void glXCopyContext(Display *dpy, GLXContext src,
7118 						GLXContext dst, arch_ulong mask);
7119 
7120 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7121 						GLXContext share_list, Bool direct);
7122 
7123 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7124 						Pixmap pixmap);
7125 
7126 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7127 
7128 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7129 
7130 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7131 						int attrib, int *value);
7132 
7133 				 GLXContext glXGetCurrentContext();
7134 
7135 				 GLXDrawable glXGetCurrentDrawable();
7136 
7137 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7138 
7139 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7140 						GLXContext ctx);
7141 
7142 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7143 
7144 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7145 
7146 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7147 
7148 				 void glXUseXFont(Font font, int first, int count, int list_base);
7149 
7150 				 void glXWaitGL();
7151 
7152 				 void glXWaitX();
7153 
7154 
7155 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7156 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7157 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7158 
7159 				char* glXQueryExtensionsString (Display*, int);
7160 				void* glXGetProcAddress (const(char)*);
7161 
7162 			}
7163 			}
7164 
7165 			version(OSX)
7166 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7167 			else
7168 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7169 			shared static this() {
7170 				glx.loadDynamicLibrary();
7171 			}
7172 
7173 			alias glbindGetProcAddress = glXGetProcAddress;
7174 		}
7175 	} else version(Windows) {
7176 		/* it is done below by interface GL */
7177 	} else
7178 		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.");
7179 }
7180 
7181 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7182 alias Resizablity = Resizability;
7183 
7184 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7185 enum Resizability {
7186 	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.
7187 	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.
7188 	/++
7189 		$(PITFALL
7190 			Planned for the future but not implemented.
7191 		)
7192 
7193 		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.
7194 
7195 		History:
7196 			Added November 11, 2022, but not yet implemented and may not be for some time.
7197 	+/
7198 	/*@__future*/ allowResizingMaintainingAspectRatio,
7199 	/++
7200 		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.
7201 
7202 		History:
7203 			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.
7204 
7205 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7206 	+/
7207 	automaticallyScaleIfPossible,
7208 }
7209 /// ditto
7210 alias Resizeability = Resizability;
7211 
7212 
7213 /++
7214 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7215 +/
7216 enum TextAlignment : uint {
7217 	Left = 0, ///
7218 	Center = 1, ///
7219 	Right = 2, ///
7220 
7221 	VerticalTop = 0, ///
7222 	VerticalCenter = 4, ///
7223 	VerticalBottom = 8, ///
7224 }
7225 
7226 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7227 alias Rectangle = arsd.color.Rectangle;
7228 
7229 
7230 /++
7231 	Keyboard press and release events.
7232 +/
7233 struct KeyEvent {
7234 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7235 	Key key;
7236 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7237 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7238 
7239 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7240 
7241 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7242 
7243 	SimpleWindow window; /// associated Window
7244 
7245 	/++
7246 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7247 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7248 		to predict if char events are actually coming..
7249 
7250 		Only available on X systems since this information is not given ahead of time elsewhere.
7251 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7252 
7253 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7254 		and potential quirks I'd recommend avoiding it.
7255 
7256 		History:
7257 			Added April 26, 2021 (dub v9.5)
7258 	+/
7259 	version(X11)
7260 		dchar[] charsPossible;
7261 
7262 	// convert key event to simplified string representation a-la emacs
7263 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7264 		uint dpos = 0;
7265 		void put (const(char)[] s...) nothrow @trusted {
7266 			static if (growdest) {
7267 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7268 			} else {
7269 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7270 			}
7271 		}
7272 
7273 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7274 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7275 		}
7276 
7277 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7278 
7279 		// put modifiers
7280 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7281 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7282 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7283 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7284 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7285 
7286 		if (this.key) {
7287 			foreach (string kn; __traits(allMembers, Key)) {
7288 				if (this.key == __traits(getMember, Key, kn)) {
7289 					// HACK!
7290 					static if (kn == "N0") put("0");
7291 					else static if (kn == "N1") put("1");
7292 					else static if (kn == "N2") put("2");
7293 					else static if (kn == "N3") put("3");
7294 					else static if (kn == "N4") put("4");
7295 					else static if (kn == "N5") put("5");
7296 					else static if (kn == "N6") put("6");
7297 					else static if (kn == "N7") put("7");
7298 					else static if (kn == "N8") put("8");
7299 					else static if (kn == "N9") put("9");
7300 					else put(kn);
7301 					return dest[0..dpos];
7302 				}
7303 			}
7304 			put("Unknown");
7305 		} else {
7306 			if (dpos && dest[dpos-1] == '+') --dpos;
7307 		}
7308 		return dest[0..dpos];
7309 	}
7310 
7311 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7312 
7313 	/** Parse string into key name with modifiers. It accepts things like:
7314 	 *
7315 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7316 	 *
7317 	 * Ctrl+Win+1 -- windows style
7318 	 *
7319 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7320 	 *
7321 	 * Ctrl Win 1 -- and space
7322 	 *
7323 	 * and even "Win + 1 + Ctrl".
7324 	 */
7325 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7326 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7327 
7328 		// remove trailing spaces
7329 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7330 
7331 		// tokens delimited by blank, '+', or '-'
7332 		// null on eol
7333 		const(char)[] getToken () nothrow @trusted @nogc {
7334 			// remove leading spaces and delimiters
7335 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7336 			if (name.length == 0) return null; // oops, no more tokens
7337 			// get token
7338 			size_t epos = 0;
7339 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7340 			assert(epos > 0 && epos <= name.length);
7341 			auto res = name[0..epos];
7342 			name = name[epos..$];
7343 			return res;
7344 		}
7345 
7346 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7347 			if (s0.length != s1.length) return false;
7348 			foreach (immutable ci, char c0; s0) {
7349 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7350 				char c1 = s1[ci];
7351 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7352 				if (c0 != c1) return false;
7353 			}
7354 			return true;
7355 		}
7356 
7357 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7358 		if (updown !is null) *updown = -1;
7359 		KeyEvent res;
7360 		res.key = cast(Key)0; // just in case
7361 		const(char)[] tk, tkn; // last token
7362 		bool allowEmascStyle = true;
7363 		bool ignoreModifiers = false;
7364 		tokenloop: for (;;) {
7365 			tk = tkn;
7366 			tkn = getToken();
7367 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7368 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7369 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7370 			if (allowEmascStyle && tkn.length != 0) {
7371 				if (tk.length == 1) {
7372 					char mdc = tk[0];
7373 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7374 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7375 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7376 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7377 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7378 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7379 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7380 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7381 				}
7382 			}
7383 			allowEmascStyle = false;
7384 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7385 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7386 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7387 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7388 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7389 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7390 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7391 			if (tk.length == 0) continue;
7392 			// try key name
7393 			if (res.key == 0) {
7394 				// little hack
7395 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7396 					final switch (tk[0]) {
7397 						case '0': tk = "N0"; break;
7398 						case '1': tk = "N1"; break;
7399 						case '2': tk = "N2"; break;
7400 						case '3': tk = "N3"; break;
7401 						case '4': tk = "N4"; break;
7402 						case '5': tk = "N5"; break;
7403 						case '6': tk = "N6"; break;
7404 						case '7': tk = "N7"; break;
7405 						case '8': tk = "N8"; break;
7406 						case '9': tk = "N9"; break;
7407 					}
7408 				}
7409 				foreach (string kn; __traits(allMembers, Key)) {
7410 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7411 				}
7412 			}
7413 			// unknown or duplicate key name, get out of here
7414 			break;
7415 		}
7416 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7417 		return res; // something
7418 	}
7419 
7420 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7421 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7422 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7423 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7424 		}
7425 		bool ignoreMods;
7426 		int updown;
7427 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7428 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7429 		if (this.key != ke.key) {
7430 			// things like "ctrl+alt" are complicated
7431 			uint tkm = this.modifierState&modmask;
7432 			uint kkm = ke.modifierState&modmask;
7433 			Key tk = this.key;
7434 			// ke
7435 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7436 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7437 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7438 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7439 			// this
7440 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7441 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7442 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7443 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7444 			return (tk == ke.key && tkm == kkm);
7445 		}
7446 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7447 	}
7448 }
7449 
7450 /// Sets the application name.
7451 @property string ApplicationName(string name) {
7452 	return _applicationName = name;
7453 }
7454 
7455 string _applicationName;
7456 
7457 /// ditto
7458 @property string ApplicationName() {
7459 	if(_applicationName is null) {
7460 		import core.runtime;
7461 		return Runtime.args[0];
7462 	}
7463 	return _applicationName;
7464 }
7465 
7466 
7467 /// Type of a [MouseEvent].
7468 enum MouseEventType : int {
7469 	motion = 0, /// The mouse moved inside the window
7470 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7471 	buttonReleased = 2, /// A mouse button was released
7472 }
7473 
7474 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7475 /++
7476 	Listen for this on your event listeners if you are interested in mouse action.
7477 
7478 	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.
7479 
7480 	Examples:
7481 
7482 	This will draw boxes on the window with the mouse as you hold the left button.
7483 	---
7484 	import arsd.simpledisplay;
7485 
7486 	void main() {
7487 		auto window = new SimpleWindow();
7488 
7489 		window.eventLoop(0,
7490 			(MouseEvent ev) {
7491 				if(ev.modifierState & ModifierState.leftButtonDown) {
7492 					auto painter = window.draw();
7493 					painter.fillColor = Color.red;
7494 					painter.outlineColor = Color.black;
7495 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7496 				}
7497 			}
7498 		);
7499 	}
7500 	---
7501 +/
7502 struct MouseEvent {
7503 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7504 
7505 	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.
7506 	int y; /// Current Y position of the cursor when the event fired.
7507 
7508 	int dx; /// Change in X position since last report
7509 	int dy; /// Change in Y position since last report
7510 
7511 	MouseButton button; /// See [MouseButton]
7512 	int modifierState; /// See [ModifierState]
7513 
7514 	version(X11)
7515 		private Time timestamp;
7516 
7517 	/// Returns a linear representation of mouse button,
7518 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7519 	///
7520 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7521 	@property ubyte buttonLinear() const {
7522 		import core.bitop;
7523 		if(button == 0)
7524 			return 0;
7525 		return (bsf(button) + 1) & 0b1111;
7526 	}
7527 
7528 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7529 
7530 	SimpleWindow window; /// The window in which the event happened.
7531 
7532 	Point globalCoordinates() {
7533 		Point p;
7534 		if(window is null)
7535 			throw new Exception("wtf");
7536 		static if(UsingSimpledisplayX11) {
7537 			Window child;
7538 			XTranslateCoordinates(
7539 				XDisplayConnection.get,
7540 				window.impl.window,
7541 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7542 				x, y, &p.x, &p.y, &child);
7543 			return p;
7544 		} else version(Windows) {
7545 			POINT[1] points;
7546 			points[0].x = x;
7547 			points[0].y = y;
7548 			MapWindowPoints(
7549 				window.impl.hwnd,
7550 				null,
7551 				points.ptr,
7552 				points.length
7553 			);
7554 			p.x = points[0].x;
7555 			p.y = points[0].y;
7556 
7557 			return p;
7558 		} else version(OSXCocoa) {
7559 			throw new NotYetImplementedException();
7560 		} else static assert(0);
7561 	}
7562 
7563 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7564 
7565 	/**
7566 	can contain emacs-like modifier prefix
7567 	case-insensitive names:
7568 		lmbX/leftX
7569 		rmbX/rightX
7570 		mmbX/middleX
7571 		wheelX
7572 		motion (no prefix allowed)
7573 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7574 	*/
7575 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7576 		if (str.length == 0) return false; // just in case
7577 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7578 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7579 		auto anchor = str;
7580 		uint mods = 0; // uint.max == any
7581 		// interesting bits in kmod
7582 		uint kmodmask =
7583 			ModifierState.shift|
7584 			ModifierState.ctrl|
7585 			ModifierState.alt|
7586 			ModifierState.windows|
7587 			ModifierState.leftButtonDown|
7588 			ModifierState.middleButtonDown|
7589 			ModifierState.rightButtonDown|
7590 			0;
7591 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7592 		bool wasButtons = false;
7593 		while (str.length) {
7594 			if (str.ptr[0] <= ' ') {
7595 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7596 				continue;
7597 			}
7598 			// one-letter modifier?
7599 			if (str.length >= 2 && str.ptr[1] == '-') {
7600 				switch (str.ptr[0]) {
7601 					case '*': // "any" modifier (cannot be undone)
7602 						mods = mods.max;
7603 						break;
7604 					case 'C': case 'c': // emacs "ctrl"
7605 						if (mods != mods.max) mods |= ModifierState.ctrl;
7606 						break;
7607 					case 'M': case 'm': // emacs "meta"
7608 						if (mods != mods.max) mods |= ModifierState.alt;
7609 						break;
7610 					case 'S': case 's': // emacs "shift"
7611 						if (mods != mods.max) mods |= ModifierState.shift;
7612 						break;
7613 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7614 						if (mods != mods.max) mods |= ModifierState.windows;
7615 						break;
7616 					default:
7617 						return false; // unknown modifier
7618 				}
7619 				str = str[2..$];
7620 				continue;
7621 			}
7622 			// word
7623 			char[16] buf = void; // locased
7624 			auto wep = 0;
7625 			while (str.length) {
7626 				immutable char ch = str.ptr[0];
7627 				if (ch <= ' ' || ch == '-') break;
7628 				str = str[1..$];
7629 				if (wep > buf.length) return false; // too long
7630 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7631 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7632 				else return false; // invalid char
7633 			}
7634 			if (wep == 0) return false; // just in case
7635 			uint bnum;
7636 			enum UpDown { None = -1, Up, Down, Any }
7637 			auto updown = UpDown.None; // 0: up; 1: down
7638 			switch (buf[0..wep]) {
7639 				// left button
7640 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7641 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7642 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7643 				case "lmb": case "left": bnum = 0; break;
7644 				// middle button
7645 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7646 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7647 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7648 				case "mmb": case "middle": bnum = 1; break;
7649 				// right button
7650 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7651 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7652 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7653 				case "rmb": case "right": bnum = 2; break;
7654 				// wheel
7655 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7656 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7657 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7658 				case "wheel": bnum = 3; break;
7659 				// motion
7660 				case "motion": bnum = 7; break;
7661 				// unknown
7662 				default: return false;
7663 			}
7664 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7665 			// parse possible "-up" or "-down"
7666 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7667 				wep = 0;
7668 				foreach (immutable idx, immutable char ch; str[1..$]) {
7669 					if (ch <= ' ' || ch == '-') break;
7670 					assert(idx == wep); // for now; trick
7671 					if (wep > buf.length) { wep = 0; break; } // too long
7672 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7673 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7674 					else { wep = 0; break; } // invalid char
7675 				}
7676 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7677 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7678 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7679 				// remove parsed part
7680 				if (updown != UpDown.None) str = str[wep+1..$];
7681 			}
7682 			if (updown == UpDown.None) {
7683 				updown = UpDown.Down;
7684 			}
7685 			wasButtons = wasButtons || (bnum <= 2);
7686 			//assert(updown != UpDown.None);
7687 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7688 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7689 			if (lastButt != lastButt.max) {
7690 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7691 				if (mods != mods.max) {
7692 					uint butbit = 0;
7693 					final switch (lastButt&0x03) {
7694 						case 0: butbit = ModifierState.leftButtonDown; break;
7695 						case 1: butbit = ModifierState.middleButtonDown; break;
7696 						case 2: butbit = ModifierState.rightButtonDown; break;
7697 					}
7698 					     if (lastButt&Flag.Down) mods |= butbit;
7699 					else if (lastButt&Flag.Up) mods &= ~butbit;
7700 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7701 				}
7702 			}
7703 			// remember last button
7704 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7705 		}
7706 		// no button -- nothing to do
7707 		if (lastButt == lastButt.max) return false;
7708 		// done parsing, check if something's left
7709 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7710 		// remove action button from mask
7711 		if ((lastButt&0xff) < 3) {
7712 			final switch (lastButt&0x03) {
7713 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7714 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7715 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7716 			}
7717 		}
7718 		// special case: "Motion" means "ignore buttons"
7719 		if ((lastButt&0xff) == 7 && !wasButtons) {
7720 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7721 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7722 		}
7723 		uint kmod = event.modifierState&kmodmask;
7724 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7725 		// check modifier state
7726 		if (mods != mods.max) {
7727 			if (kmod != mods) return false;
7728 		}
7729 		// now check type
7730 		if ((lastButt&0xff) == 7) {
7731 			// motion
7732 			if (event.type != MouseEventType.motion) return false;
7733 		} else if ((lastButt&0xff) == 3) {
7734 			// wheel
7735 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7736 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7737 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7738 			return false;
7739 		} else {
7740 			// buttons
7741 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7742 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7743 			{
7744 				return false;
7745 			}
7746 			// button number
7747 			switch (lastButt&0x03) {
7748 				case 0: if (event.button != MouseButton.left) return false; break;
7749 				case 1: if (event.button != MouseButton.middle) return false; break;
7750 				case 2: if (event.button != MouseButton.right) return false; break;
7751 				default: return false;
7752 			}
7753 		}
7754 		return true;
7755 	}
7756 }
7757 
7758 version(arsd_mevent_strcmp_test) unittest {
7759 	MouseEvent event;
7760 	event.type = MouseEventType.buttonPressed;
7761 	event.button = MouseButton.left;
7762 	event.modifierState = ModifierState.ctrl;
7763 	assert(event == "C-LMB");
7764 	assert(event != "C-LMBUP");
7765 	assert(event != "C-LMB-UP");
7766 	assert(event != "C-S-LMB");
7767 	assert(event == "*-LMB");
7768 	assert(event != "*-LMB-UP");
7769 
7770 	event.type = MouseEventType.buttonReleased;
7771 	assert(event != "C-LMB");
7772 	assert(event == "C-LMBUP");
7773 	assert(event == "C-LMB-UP");
7774 	assert(event != "C-S-LMB");
7775 	assert(event != "*-LMB");
7776 	assert(event == "*-LMB-UP");
7777 
7778 	event.button = MouseButton.right;
7779 	event.modifierState |= ModifierState.shift;
7780 	event.type = MouseEventType.buttonPressed;
7781 	assert(event != "C-LMB");
7782 	assert(event != "C-LMBUP");
7783 	assert(event != "C-LMB-UP");
7784 	assert(event != "C-S-LMB");
7785 	assert(event != "*-LMB");
7786 	assert(event != "*-LMB-UP");
7787 
7788 	assert(event != "C-RMB");
7789 	assert(event != "C-RMBUP");
7790 	assert(event != "C-RMB-UP");
7791 	assert(event == "C-S-RMB");
7792 	assert(event == "*-RMB");
7793 	assert(event != "*-RMB-UP");
7794 }
7795 
7796 /// This gives a few more options to drawing lines and such
7797 struct Pen {
7798 	Color color; /// the foreground color
7799 	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.
7800 	Style style; /// See [Style]
7801 /+
7802 // From X.h
7803 
7804 #define LineSolid		0
7805 #define LineOnOffDash		1
7806 #define LineDoubleDash		2
7807        LineDou-        The full path of the line is drawn, but the
7808        bleDash         even dashes are filled differently from the
7809                        odd dashes (see fill-style) with CapButt
7810                        style used where even and odd dashes meet.
7811 
7812 
7813 
7814 /* capStyle */
7815 
7816 #define CapNotLast		0
7817 #define CapButt			1
7818 #define CapRound		2
7819 #define CapProjecting		3
7820 
7821 /* joinStyle */
7822 
7823 #define JoinMiter		0
7824 #define JoinRound		1
7825 #define JoinBevel		2
7826 
7827 /* fillStyle */
7828 
7829 #define FillSolid		0
7830 #define FillTiled		1
7831 #define FillStippled		2
7832 #define FillOpaqueStippled	3
7833 
7834 
7835 +/
7836 	/// Style of lines drawn
7837 	enum Style {
7838 		Solid, /// a solid line
7839 		Dashed, /// a dashed line
7840 		Dotted, /// a dotted line
7841 	}
7842 }
7843 
7844 
7845 /++
7846 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7847 
7848 
7849 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7850 
7851 	$(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.)
7852 
7853 	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.
7854 
7855 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7856 
7857 	$(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.
7858 
7859 	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!
7860 
7861 	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!)
7862 
7863 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7864 
7865 	---
7866 		auto image = new Image(256, 256);
7867 		scope(exit) destroy(image);
7868 	---
7869 
7870 	As long as you don't hold on to it outside the scope.
7871 
7872 	I might change it to be an owned pointer at some point in the future.
7873 
7874 	)
7875 
7876 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7877 	you can also often get a fair amount of speedup by getting the raw data format and
7878 	writing some custom code.
7879 
7880 	FIXME INSERT EXAMPLES HERE
7881 
7882 
7883 +/
7884 final class Image {
7885 	///
7886 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7887 		this.width = width;
7888 		this.height = height;
7889 		this.enableAlpha = enableAlpha;
7890 
7891 		impl.createImage(width, height, forcexshm, enableAlpha);
7892 	}
7893 
7894 	///
7895 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7896 		this(size.width, size.height, forcexshm, enableAlpha);
7897 	}
7898 
7899 	private bool suppressDestruction;
7900 
7901 	version(X11)
7902 	this(XImage* handle) {
7903 		this.handle = handle;
7904 		this.rawData = cast(ubyte*) handle.data;
7905 		this.width = handle.width;
7906 		this.height = handle.height;
7907 		this.enableAlpha = handle.depth == 32;
7908 		suppressDestruction = true;
7909 	}
7910 
7911 	~this() {
7912 		if(suppressDestruction) return;
7913 		impl.dispose();
7914 	}
7915 
7916 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7917 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7918 	pure const @system nothrow {
7919 		/*
7920 			To use these to draw a blue rectangle with size WxH at position X,Y...
7921 
7922 			// make certain that it will fit before we proceed
7923 			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!
7924 
7925 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7926 			// (though calculating them isn't really that expensive).
7927 			auto nextLineAdjustment = img.adjustmentForNextLine();
7928 			auto offR = img.redByteOffset();
7929 			auto offB = img.blueByteOffset();
7930 			auto offG = img.greenByteOffset();
7931 			auto bpp = img.bytesPerPixel();
7932 
7933 			auto data = img.getDataPointer();
7934 
7935 			// figure out the starting byte offset
7936 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7937 
7938 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7939 
7940 			// and now our drawing loop for the rectangle
7941 			foreach(y; 0 .. H) {
7942 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7943 				foreach(x; 0 .. W) {
7944 					// write our color
7945 					data[offR] = 0;
7946 					data[offG] = 0;
7947 					data[offB] = 255;
7948 
7949 					data += bpp; // moving to the next pixel is just an addition...
7950 				}
7951 				startOfLine += nextLineAdjustment;
7952 			}
7953 
7954 
7955 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7956 
7957 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7958 			can be made into a bitmask or something so we can write them as *uint...
7959 		*/
7960 
7961 		///
7962 		int offsetForTopLeftPixel() {
7963 			version(X11) {
7964 				return 0;
7965 			} else version(Windows) {
7966 				if(enableAlpha) {
7967 					return (width * 4) * (height - 1);
7968 				} else {
7969 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7970 				}
7971 			} else version(OSXCocoa) {
7972 				return 0 ; //throw new NotYetImplementedException();
7973 			} else static assert(0, "fill in this info for other OSes");
7974 		}
7975 
7976 		///
7977 		int offsetForPixel(int x, int y) {
7978 			version(X11) {
7979 				auto offset = (y * width + x) * 4;
7980 				return offset;
7981 			} else version(Windows) {
7982 				if(enableAlpha) {
7983 					auto itemsPerLine = width * 4;
7984 					// remember, bmps are upside down
7985 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7986 					return offset;
7987 				} else {
7988 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7989 					// remember, bmps are upside down
7990 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7991 					return offset;
7992 				}
7993 			} else version(OSXCocoa) {
7994 				return 0 ; //throw new NotYetImplementedException();
7995 			} else static assert(0, "fill in this info for other OSes");
7996 		}
7997 
7998 		///
7999 		int adjustmentForNextLine() {
8000 			version(X11) {
8001 				return width * 4;
8002 			} else version(Windows) {
8003 				// windows bmps are upside down, so the adjustment is actually negative
8004 				if(enableAlpha)
8005 					return - (cast(int) width * 4);
8006 				else
8007 					return -((cast(int) width * 3 + 3) / 4) * 4;
8008 			} else version(OSXCocoa) {
8009 				return 0 ; //throw new NotYetImplementedException();
8010 			} else static assert(0, "fill in this info for other OSes");
8011 		}
8012 
8013 		/// once you have the position of a pixel, use these to get to the proper color
8014 		int redByteOffset() {
8015 			version(X11) {
8016 				return 2;
8017 			} else version(Windows) {
8018 				return 2;
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 greenByteOffset() {
8026 			version(X11) {
8027 				return 1;
8028 			} else version(Windows) {
8029 				return 1;
8030 			} else version(OSXCocoa) {
8031 				return 0 ; //throw new NotYetImplementedException();
8032 			} else static assert(0, "fill in this info for other OSes");
8033 		}
8034 
8035 		///
8036 		int blueByteOffset() {
8037 			version(X11) {
8038 				return 0;
8039 			} else version(Windows) {
8040 				return 0;
8041 			} else version(OSXCocoa) {
8042 				return 0 ; //throw new NotYetImplementedException();
8043 			} else static assert(0, "fill in this info for other OSes");
8044 		}
8045 
8046 		/// Only valid if [enableAlpha] is true
8047 		int alphaByteOffset() {
8048 			version(X11) {
8049 				return 3;
8050 			} else version(Windows) {
8051 				return 3;
8052 			} else version(OSXCocoa) {
8053 				return 3; //throw new NotYetImplementedException();
8054 			} else static assert(0, "fill in this info for other OSes");
8055 		}
8056 	}
8057 
8058 	///
8059 	final void putPixel(int x, int y, Color c) {
8060 		if(x < 0 || x >= width)
8061 			return;
8062 		if(y < 0 || y >= height)
8063 			return;
8064 
8065 		impl.setPixel(x, y, c);
8066 	}
8067 
8068 	///
8069 	final Color getPixel(int x, int y) {
8070 		if(x < 0 || x >= width)
8071 			return Color.transparent;
8072 		if(y < 0 || y >= height)
8073 			return Color.transparent;
8074 
8075 		version(OSXCocoa) throw new NotYetImplementedException(); else
8076 		return impl.getPixel(x, y);
8077 	}
8078 
8079 	///
8080 	final void opIndexAssign(Color c, int x, int y) {
8081 		putPixel(x, y, c);
8082 	}
8083 
8084 	///
8085 	TrueColorImage toTrueColorImage() {
8086 		auto tci = new TrueColorImage(width, height);
8087 		convertToRgbaBytes(tci.imageData.bytes);
8088 		return tci;
8089 	}
8090 
8091 	///
8092 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8093 		auto tci = i.getAsTrueColorImage();
8094 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8095 		static if(UsingSimpledisplayX11)
8096 			img.premultiply = premultiply;
8097 		img.setRgbaBytes(tci.imageData.bytes);
8098 		return img;
8099 	}
8100 
8101 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8102 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8103 	/// if you pass null, it will allocate a new one.
8104 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8105 		if(where is null)
8106 			where = new ubyte[this.width*this.height*4];
8107 		convertToRgbaBytes(where);
8108 		return where;
8109 	}
8110 
8111 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8112 	void setRgbaBytes(in ubyte[] from ) {
8113 		assert(from.length == this.width * this.height * 4);
8114 		setFromRgbaBytes(from);
8115 	}
8116 
8117 	// FIXME: make properly cross platform by getting rgba right
8118 
8119 	/// warning: this is not portable across platforms because the data format can change
8120 	ubyte* getDataPointer() {
8121 		return impl.rawData;
8122 	}
8123 
8124 	/// for use with getDataPointer
8125 	final int bytesPerLine() const pure @safe nothrow {
8126 		version(Windows)
8127 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8128 		else version(X11)
8129 			return 4 * width;
8130 		else version(OSXCocoa)
8131 			return 4 * width;
8132 		else static assert(0);
8133 	}
8134 
8135 	/// for use with getDataPointer
8136 	final int bytesPerPixel() const pure @safe nothrow {
8137 		version(Windows)
8138 			return enableAlpha ? 4 : 3;
8139 		else version(X11)
8140 			return 4;
8141 		else version(OSXCocoa)
8142 			return 4;
8143 		else static assert(0);
8144 	}
8145 
8146 	///
8147 	immutable int width;
8148 
8149 	///
8150 	immutable int height;
8151 
8152 	///
8153 	immutable bool enableAlpha;
8154     //private:
8155 	mixin NativeImageImplementation!() impl;
8156 }
8157 
8158 /++
8159 	A convenience function to pop up a window displaying the image.
8160 	If you pass a win, it will draw the image in it. Otherwise, it will
8161 	create a window with the size of the image and run its event loop, closing
8162 	when a key is pressed.
8163 
8164 	History:
8165 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8166 		always block until the application quit which could cause bizarre behavior
8167 		inside a more complex application. Now, the default is to block until
8168 		this window closes if it is the only event loop running, and otherwise,
8169 		not to block at all and just pop up the display window asynchronously.
8170 +/
8171 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8172 	if(win is null) {
8173 		win = new SimpleWindow(image);
8174 		{
8175 			auto p = win.draw;
8176 			p.drawImage(Point(0, 0), image);
8177 		}
8178 		win.eventLoopWithBlockingMode(
8179 			bm, 0,
8180 			(KeyEvent ev) {
8181 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8182 			} );
8183 	} else {
8184 		win.image = image;
8185 	}
8186 }
8187 
8188 enum FontWeight : int {
8189 	dontcare = 0,
8190 	thin = 100,
8191 	extralight = 200,
8192 	light = 300,
8193 	regular = 400,
8194 	medium = 500,
8195 	semibold = 600,
8196 	bold = 700,
8197 	extrabold = 800,
8198 	heavy = 900
8199 }
8200 
8201 /++
8202 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8203 
8204 	History:
8205 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8206 +/
8207 interface MeasurableFont {
8208 	/++
8209 		Returns true if it is a monospace font, meaning each of the
8210 		glyphs (at least the ascii characters) have matching width
8211 		and no kerning, so you can determine the display width of some
8212 		strings by simply multiplying the string width by [averageWidth].
8213 
8214 		(Please note that multiply doesn't $(I actually) work in general,
8215 		consider characters like tab and newline, but it does sometimes.)
8216 	+/
8217 	bool isMonospace();
8218 
8219 	/++
8220 		The average width of glyphs in the font, traditionally equal to the
8221 		width of the lowercase x. Can be used to estimate bounding boxes,
8222 		especially if the font [isMonospace].
8223 
8224 		Given in pixels.
8225 	+/
8226 	int averageWidth();
8227 	/++
8228 		The height of the bounding box of a line.
8229 	+/
8230 	int height();
8231 	/++
8232 		The maximum ascent of a glyph above the baseline.
8233 
8234 		Given in pixels.
8235 	+/
8236 	int ascent();
8237 	/++
8238 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8239 
8240 		Given in pixels.
8241 	+/
8242 	int descent();
8243 	/++
8244 		The display width of the given string, and if you provide a window, it will use it to
8245 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8246 
8247 		Given in pixels.
8248 	+/
8249 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8250 
8251 }
8252 
8253 // FIXME: i need a font cache and it needs to handle disconnects.
8254 
8255 /++
8256 	Represents a font loaded off the operating system or the X server.
8257 
8258 
8259 	While the api here is unified cross platform, the fonts are not necessarily
8260 	available, even across machines of the same platform, so be sure to always check
8261 	for null (using [isNull]) and have a fallback plan.
8262 
8263 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8264 
8265 	Worst case, a null font will automatically fall back to the default font loaded
8266 	for your system.
8267 +/
8268 class OperatingSystemFont : MeasurableFont {
8269 	// FIXME: when the X Connection is lost, these need to be invalidated!
8270 	// that means I need to store the original stuff again to reconstruct it too.
8271 
8272 	version(X11) {
8273 		XFontStruct* font;
8274 		XFontSet fontset;
8275 
8276 		version(with_xft) {
8277 			XftFont* xftFont;
8278 			bool isXft;
8279 		}
8280 	} else version(Windows) {
8281 		HFONT font;
8282 		int width_;
8283 		int height_;
8284 	} else version(OSXCocoa) {
8285 		NSFont font;
8286 	} else static assert(0);
8287 
8288 	/++
8289 		Constructs the class and immediately calls [load].
8290 	+/
8291 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8292 		load(name, size, weight, italic);
8293 	}
8294 
8295 	/++
8296 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8297 
8298 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8299 
8300 		History:
8301 			Added January 24, 2021.
8302 	+/
8303 	this() {
8304 		// this space intentionally left blank
8305 	}
8306 
8307 	/++
8308 		Constructs a copy of the given font object.
8309 
8310 		History:
8311 			Added January 7, 2023.
8312 	+/
8313 	this(OperatingSystemFont font) {
8314 		if(font is null || font.loadedInfo is LoadedInfo.init)
8315 			loadDefault();
8316 		else
8317 			load(font.loadedInfo.tupleof);
8318 	}
8319 
8320 	/++
8321 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8322 
8323 		History:
8324 			Added November 13, 2020.
8325 	+/
8326 	version(with_xft)
8327 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8328 		unload();
8329 
8330 		if(!XftLibrary.attempted) {
8331 			XftLibrary.loadDynamicLibrary();
8332 		}
8333 
8334 		if(!XftLibrary.loadSuccessful)
8335 			return false;
8336 
8337 		auto display = XDisplayConnection.get;
8338 
8339 		char[256] nameBuffer = void;
8340 		int nbp = 0;
8341 
8342 		void add(in char[] a) {
8343 			nameBuffer[nbp .. nbp + a.length] = a[];
8344 			nbp += a.length;
8345 		}
8346 		add(name);
8347 
8348 		if(size) {
8349 			add(":size=");
8350 			add(toInternal!string(size));
8351 		}
8352 		if(weight != FontWeight.dontcare) {
8353 			add(":weight=");
8354 			add(weightToString(weight));
8355 		}
8356 		if(italic)
8357 			add(":slant=100");
8358 
8359 		nameBuffer[nbp] = 0;
8360 
8361 		this.xftFont = XftFontOpenName(
8362 			display,
8363 			DefaultScreen(display),
8364 			nameBuffer.ptr
8365 		);
8366 
8367 		this.isXft = true;
8368 
8369 		if(xftFont !is null) {
8370 			isMonospace_ = stringWidth("x") == stringWidth("M");
8371 			ascent_ = xftFont.ascent;
8372 			descent_ = xftFont.descent;
8373 		}
8374 
8375 		return !isNull();
8376 	}
8377 
8378 	/++
8379 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8380 
8381 
8382 		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.
8383 
8384 		If `pattern` is null, it returns all available font families.
8385 
8386 		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.
8387 
8388 		The format of the pattern is platform-specific.
8389 
8390 		History:
8391 			Added May 1, 2021 (dub v9.5)
8392 	+/
8393 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8394 		version(Windows) {
8395 			auto hdc = GetDC(null);
8396 			scope(exit) ReleaseDC(null, hdc);
8397 			LOGFONT logfont;
8398 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8399 				auto localHandler = *(cast(typeof(handler)*) p);
8400 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8401 			}
8402 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8403 		} else version(X11) {
8404 			//import core.stdc.stdio;
8405 			bool done = false;
8406 			version(with_xft) {
8407 				if(!XftLibrary.attempted) {
8408 					XftLibrary.loadDynamicLibrary();
8409 				}
8410 
8411 				if(!XftLibrary.loadSuccessful)
8412 					goto skipXft;
8413 
8414 				if(!FontConfigLibrary.attempted)
8415 					FontConfigLibrary.loadDynamicLibrary();
8416 				if(!FontConfigLibrary.loadSuccessful)
8417 					goto skipXft;
8418 
8419 				{
8420 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8421 					if(got is null)
8422 						goto skipXft;
8423 					scope(exit) FcFontSetDestroy(got);
8424 
8425 					auto fontPatterns = got.fonts[0 .. got.nfont];
8426 					foreach(candidate; fontPatterns) {
8427 						char* where, whereStyle;
8428 
8429 						char* pmg = FcNameUnparse(candidate);
8430 
8431 						//FcPatternGetString(candidate, "family", 0, &where);
8432 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8433 						//if(where && whereStyle) {
8434 						if(pmg) {
8435 							if(!handler(pmg.sliceCString))
8436 								return;
8437 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8438 						}
8439 					}
8440 				}
8441 			}
8442 
8443 			skipXft:
8444 
8445 			if(pattern is null)
8446 				pattern = "*";
8447 
8448 			int count;
8449 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8450 			scope(exit) XFreeFontNames(coreFontsRaw);
8451 
8452 			auto coreFonts = coreFontsRaw[0 .. count];
8453 
8454 			foreach(font; coreFonts) {
8455 				char[128] tmp;
8456 				tmp[0 ..5] = "core:";
8457 				auto cf = font.sliceCString;
8458 				if(5 + cf.length > tmp.length)
8459 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8460 				tmp[5 .. 5 + cf.length] = cf;
8461 				if(!handler(tmp[0 .. 5 + cf.length]))
8462 					return;
8463 			}
8464 		}
8465 	}
8466 
8467 	/++
8468 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8469 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8470 
8471 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8472 		underlying system doesn't support returning the raw bytes.
8473 
8474 		History:
8475 			Added September 10, 2021 (dub v10.3)
8476 	+/
8477 	ubyte[] getTtfBytes() {
8478 		if(isNull)
8479 			return null;
8480 
8481 		version(Windows) {
8482 			auto dc = GetDC(null);
8483 			auto orig = SelectObject(dc, font);
8484 
8485 			scope(exit) {
8486 				SelectObject(dc, orig);
8487 				ReleaseDC(null, dc);
8488 			}
8489 
8490 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8491 			if(res == GDI_ERROR)
8492 				return null;
8493 
8494 			ubyte[] buffer = new ubyte[](res);
8495 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8496 			if(res == GDI_ERROR)
8497 				return null; // wtf really tbh
8498 
8499 			return buffer;
8500 		} else version(with_xft) {
8501 			if(isXft && xftFont) {
8502 				if(!FontConfigLibrary.attempted)
8503 					FontConfigLibrary.loadDynamicLibrary();
8504 				if(!FontConfigLibrary.loadSuccessful)
8505 					return null;
8506 
8507 				char* file;
8508 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8509 					if (file !is null && file[0]) {
8510 						import core.stdc.stdio;
8511 						auto fp = fopen(file, "rb");
8512 						if(fp is null)
8513 							return null;
8514 						scope(exit)
8515 							fclose(fp);
8516 						fseek(fp, 0, SEEK_END);
8517 						ubyte[] buffer = new ubyte[](ftell(fp));
8518 						fseek(fp, 0, SEEK_SET);
8519 
8520 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8521 						if(got != buffer.length)
8522 							return null;
8523 
8524 						return buffer;
8525 					}
8526 				}
8527 			}
8528 			return null;
8529 		} else throw new NotYetImplementedException();
8530 	}
8531 
8532 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8533 
8534 	private string weightToString(FontWeight weight) {
8535 		with(FontWeight)
8536 		final switch(weight) {
8537 			case dontcare: return "*";
8538 			case thin: return "extralight";
8539 			case extralight: return "extralight";
8540 			case light: return "light";
8541 			case regular: return "regular";
8542 			case medium: return "medium";
8543 			case semibold: return "demibold";
8544 			case bold: return "bold";
8545 			case extrabold: return "demibold";
8546 			case heavy: return "black";
8547 		}
8548 	}
8549 
8550 	/++
8551 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8552 
8553 		History:
8554 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8555 	+/
8556 	version(X11)
8557 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8558 		unload();
8559 
8560 		string xfontstr;
8561 
8562 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8563 			// this is kinda a disgusting hack but if the user sends an exact
8564 			// string I'd like to honor it...
8565 			xfontstr = name;
8566 		} else {
8567 			string weightstr = weightToString(weight);
8568 			string sizestr;
8569 			if(size == 0)
8570 				sizestr = "*";
8571 			else
8572 				sizestr = toInternal!string(size);
8573 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8574 		}
8575 
8576 		// writeln(xfontstr);
8577 
8578 		auto display = XDisplayConnection.get;
8579 
8580 		font = XLoadQueryFont(display, xfontstr.ptr);
8581 		if(font is null)
8582 			return false;
8583 
8584 		char** lol;
8585 		int lol2;
8586 		char* lol3;
8587 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8588 
8589 		prepareFontInfo();
8590 
8591 		return !isNull();
8592 	}
8593 
8594 	version(X11)
8595 	private void prepareFontInfo() {
8596 		if(font !is null) {
8597 			isMonospace_ = stringWidth("l") == stringWidth("M");
8598 			ascent_ = font.max_bounds.ascent;
8599 			descent_ = font.max_bounds.descent;
8600 		}
8601 	}
8602 
8603 	version(OSXCocoa)
8604 	private void prepareFontInfo() {
8605 		if(font !is null) {
8606 			isMonospace_ = font.isFixedPitch;
8607 			ascent_ = cast(int) font.ascender;
8608 			descent_ = cast(int) - font.descender;
8609 		}
8610 	}
8611 
8612 
8613 	/++
8614 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8615 
8616 		History:
8617 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8618 	+/
8619 	version(Windows)
8620 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8621 		unload();
8622 
8623 		WCharzBuffer buffer = WCharzBuffer(name);
8624 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8625 
8626 		prepareFontInfo(hdc);
8627 
8628 		return !isNull();
8629 	}
8630 
8631 	version(Windows)
8632 	void prepareFontInfo(HDC hdc = null) {
8633 		if(font is null)
8634 			return;
8635 
8636 		TEXTMETRIC tm;
8637 		auto dc = hdc ? hdc : GetDC(null);
8638 		auto orig = SelectObject(dc, font);
8639 		GetTextMetrics(dc, &tm);
8640 		SelectObject(dc, orig);
8641 		if(hdc is null)
8642 			ReleaseDC(null, dc);
8643 
8644 		width_ = tm.tmAveCharWidth;
8645 		height_ = tm.tmHeight;
8646 		ascent_ = tm.tmAscent;
8647 		descent_ = tm.tmDescent;
8648 		// 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.
8649 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8650 	}
8651 
8652 
8653 	/++
8654 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8655 
8656 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8657 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8658 
8659 		On Windows, it forwards directly to [loadWin32].
8660 
8661 		Params:
8662 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8663 			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.
8664 			weight = approximate boldness, results may vary.
8665 			italic = try to get a slanted version of the given font.
8666 
8667 		History:
8668 			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.
8669 	+/
8670 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8671 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
8672 		version(X11) {
8673 			version(with_xft) {
8674 				if(name.length > 5 && name[0 .. 5] == "core:") {
8675 					goto core;
8676 				}
8677 
8678 				if(loadXft(name, size, weight, italic))
8679 					return true;
8680 				// if xft fails, fallback to core to avoid breaking
8681 				// code that already depended on this.
8682 			}
8683 
8684 			core:
8685 
8686 			if(name.length > 5 && name[0 .. 5] == "core:") {
8687 				name = name[5 .. $];
8688 			}
8689 
8690 			return loadCoreX(name, size, weight, italic);
8691 		} else version(Windows) {
8692 			return loadWin32(name, size, weight, italic);
8693 		} else version(OSXCocoa) {
8694 			return loadCocoa(name, size, weight, italic);
8695 		} else static assert(0);
8696 	}
8697 
8698 	version(OSXCocoa)
8699 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
8700 		unload();
8701 
8702 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
8703 		prepareFontInfo();
8704 
8705 		return !isNull();
8706 	}
8707 
8708 	private struct LoadedInfo {
8709 		string name;
8710 		int size;
8711 		FontWeight weight;
8712 		bool italic;
8713 	}
8714 	private LoadedInfo loadedInfo;
8715 
8716 	///
8717 	void unload() {
8718 		if(isNull())
8719 			return;
8720 
8721 		version(X11) {
8722 			auto display = XDisplayConnection.display;
8723 
8724 			if(display is null)
8725 				return;
8726 
8727 			version(with_xft) {
8728 				if(isXft) {
8729 					if(xftFont)
8730 						XftFontClose(display, xftFont);
8731 					isXft = false;
8732 					xftFont = null;
8733 					return;
8734 				}
8735 			}
8736 
8737 			if(font && font !is ScreenPainterImplementation.defaultfont)
8738 				XFreeFont(display, font);
8739 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8740 				XFreeFontSet(display, fontset);
8741 
8742 			font = null;
8743 			fontset = null;
8744 		} else version(Windows) {
8745 			DeleteObject(font);
8746 			font = null;
8747 		} else version(OSXCocoa) {
8748 			font.release();
8749 			font = null;
8750 		} else static assert(0);
8751 	}
8752 
8753 	private bool isMonospace_;
8754 
8755 	/++
8756 		History:
8757 			Added January 16, 2021
8758 	+/
8759 	bool isMonospace() {
8760 		return isMonospace_;
8761 	}
8762 
8763 	/++
8764 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8765 
8766 		History:
8767 			Added March 26, 2020
8768 			Documented January 16, 2021
8769 	+/
8770 	int averageWidth() {
8771 		version(X11) {
8772 			return stringWidth("x");
8773 		} version(OSXCocoa) {
8774 			return stringWidth("x");
8775 		} else version(Windows)
8776 			return width_;
8777 		else assert(0);
8778 	}
8779 
8780 	/++
8781 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8782 
8783 		History:
8784 			Added January 16, 2021
8785 	+/
8786 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8787 	// FIXME: what about tab?
8788 		if(isNull)
8789 			return 0;
8790 
8791 		version(X11) {
8792 			version(with_xft)
8793 				if(isXft && xftFont !is null) {
8794 					//return xftFont.max_advance_width;
8795 					XGlyphInfo extents;
8796 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8797 					// writeln(extents);
8798 					return extents.xOff;
8799 				}
8800 			if(font is null)
8801 				return 0;
8802 			else if(fontset) {
8803 				XRectangle rect;
8804 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8805 
8806 				return rect.width;
8807 			} else {
8808 				return XTextWidth(font, s.ptr, cast(int) s.length);
8809 			}
8810 		} else version(Windows) {
8811 			WCharzBuffer buffer = WCharzBuffer(s);
8812 
8813 			return stringWidth(buffer.slice, window);
8814 		} else version(OSXCocoa) {
8815 			/+
8816 			int charCount = [string length];
8817 			CGGlyph glyphs[charCount];
8818 			CGRect rects[charCount];
8819 
8820 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
8821 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
8822 
8823 			int totalwidth = 0, maxheight = 0;
8824 			for (int i=0; i < charCount; i++)
8825 			{
8826 				totalwidth += rects[i].size.width;
8827 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
8828 			}
8829 
8830 			dim = CGSizeMake(totalwidth, maxheight);
8831 			+/
8832 
8833 			return 16; // FIXME
8834 		}
8835 		else assert(0);
8836 	}
8837 
8838 	version(Windows)
8839 	/// ditto
8840 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8841 		if(isNull)
8842 			return 0;
8843 		version(Windows) {
8844 			SIZE size;
8845 
8846 			prepareContext(window);
8847 			scope(exit) releaseContext();
8848 
8849 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8850 
8851 			return size.cx;
8852 		} else {
8853 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8854 			static assert(0, "not implemented yet");
8855 			//return stringWidth(s, window);
8856 		}
8857 	}
8858 
8859 	private {
8860 		int prepRefcount;
8861 
8862 		version(Windows) {
8863 			HDC dc;
8864 			HANDLE orig;
8865 			HWND hwnd;
8866 		}
8867 	}
8868 	/++
8869 		[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.
8870 
8871 		History:
8872 			Added January 23, 2021
8873 	+/
8874 	void prepareContext(SimpleWindow window = null) {
8875 		prepRefcount++;
8876 		if(prepRefcount == 1) {
8877 			version(Windows) {
8878 				hwnd = window is null ? null : window.impl.hwnd;
8879 				dc = GetDC(hwnd);
8880 				orig = SelectObject(dc, font);
8881 			}
8882 		}
8883 	}
8884 	/// ditto
8885 	void releaseContext() {
8886 		prepRefcount--;
8887 		if(prepRefcount == 0) {
8888 			version(Windows) {
8889 				SelectObject(dc, orig);
8890 				ReleaseDC(hwnd, dc);
8891 				hwnd = null;
8892 				dc = null;
8893 				orig = null;
8894 			}
8895 		}
8896 	}
8897 
8898 	/+
8899 		FIXME: I think I need advance and kerning pair
8900 
8901 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8902 	+/
8903 
8904 	/++
8905 		Returns the height of the font.
8906 
8907 		History:
8908 			Added March 26, 2020
8909 			Documented January 16, 2021
8910 	+/
8911 	int height() {
8912 		version(X11) {
8913 			version(with_xft)
8914 				if(isXft && xftFont !is null) {
8915 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8916 				}
8917 			if(font is null)
8918 				return 0;
8919 			return font.max_bounds.ascent + font.max_bounds.descent;
8920 		} else version(Windows) {
8921 			return height_;
8922 		} else version(OSXCocoa) {
8923 			if(font is null)
8924 				return 0;
8925 			return cast(int) font.capHeight;
8926 		}
8927 		else assert(0);
8928 	}
8929 
8930 	private int ascent_;
8931 	private int descent_;
8932 
8933 	/++
8934 		Max ascent above the baseline.
8935 
8936 		History:
8937 			Added January 22, 2021
8938 	+/
8939 	int ascent() {
8940 		return ascent_;
8941 	}
8942 
8943 	/++
8944 		Max descent below the baseline.
8945 
8946 		History:
8947 			Added January 22, 2021
8948 	+/
8949 	int descent() {
8950 		return descent_;
8951 	}
8952 
8953 	/++
8954 		Loads the default font used by [ScreenPainter] if none others are loaded.
8955 
8956 		Returns:
8957 			This method mutates the `this` object, but then returns `this` for
8958 			easy chaining like:
8959 
8960 			---
8961 			auto font = foo.isNull ? foo : foo.loadDefault
8962 			---
8963 
8964 		History:
8965 			Added previously, but left unimplemented until January 24, 2021.
8966 	+/
8967 	OperatingSystemFont loadDefault() {
8968 		unload();
8969 
8970 		loadedInfo = LoadedInfo.init;
8971 
8972 		version(X11) {
8973 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8974 			// but meh since sdpy does its own thing, this should be ok too
8975 
8976 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8977 			this.font = ScreenPainterImplementation.defaultfont;
8978 			this.fontset = ScreenPainterImplementation.defaultfontset;
8979 
8980 			prepareFontInfo();
8981 			return this;
8982 		} else version(Windows) {
8983 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8984 			this.font = ScreenPainterImplementation.defaultGuiFont;
8985 
8986 			prepareFontInfo();
8987 			return this;
8988 		} else version(OSXCocoa) {
8989 			this.font = NSFont.systemFontOfSize(12);
8990 
8991 			prepareFontInfo();
8992 			return this;
8993 		} else throw new NotYetImplementedException();
8994 	}
8995 
8996 	///
8997 	bool isNull() {
8998 		version(with_xft)
8999 			if(isXft)
9000 				return xftFont is null;
9001 		return font is null;
9002 	}
9003 
9004 	/* Metrics */
9005 	/+
9006 		GetABCWidth
9007 		GetKerningPairs
9008 
9009 		if I do it right, I can size it all here, and match
9010 		what happens when I draw the full string with the OS functions.
9011 
9012 		subclasses might do the same thing while getting the glyphs on images
9013 	struct GlyphInfo {
9014 		int glyph;
9015 
9016 		size_t stringIdxStart;
9017 		size_t stringIdxEnd;
9018 
9019 		Rectangle boundingBox;
9020 	}
9021 	GlyphInfo[] getCharBoxes() {
9022 		// XftTextExtentsUtf8
9023 		return null;
9024 
9025 	}
9026 	+/
9027 
9028 	~this() {
9029 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9030 		unload();
9031 	}
9032 }
9033 
9034 version(Windows)
9035 private string sliceCString(const(wchar)[] w) {
9036 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9037 }
9038 
9039 private inout(char)[] sliceCString(inout(char)* s) {
9040 	import core.stdc.string;
9041 	auto len = strlen(s);
9042 	return s[0 .. len];
9043 }
9044 
9045 version(OSXCocoa)
9046 	alias PaintingHandle = NSObject;
9047 else
9048 	alias PaintingHandle = NativeWindowHandle;
9049 
9050 /**
9051 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9052 	than constructing it directly. Then, it is reference counted so you can pass it
9053 	at around and when the last ref goes out of scope, the buffered drawing activities
9054 	are all carried out.
9055 
9056 
9057 	Most functions use the outlineColor instead of taking a color themselves.
9058 	ScreenPainter is reference counted and draws its buffer to the screen when its
9059 	final reference goes out of scope.
9060 */
9061 struct ScreenPainter {
9062 	CapableOfBeingDrawnUpon window;
9063 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9064 		this.window = window;
9065 		if(window.closed)
9066 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9067 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9068 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9069 		if(window.activeScreenPainter !is null) {
9070 			impl = window.activeScreenPainter;
9071 			if(impl.referenceCount == 0) {
9072 				impl.window = window;
9073 				impl.create(handle);
9074 			}
9075 			impl.manualInvalidations = manualInvalidations;
9076 			impl.referenceCount++;
9077 		//	writeln("refcount ++ ", impl.referenceCount);
9078 		} else {
9079 			impl = new ScreenPainterImplementation;
9080 			impl.window = window;
9081 			impl.create(handle);
9082 			impl.referenceCount = 1;
9083 			impl.manualInvalidations = manualInvalidations;
9084 			window.activeScreenPainter = impl;
9085 			// writeln("constructed");
9086 		}
9087 
9088 		copyActiveOriginals();
9089 	}
9090 
9091 	/++
9092 		EXPERIMENTAL. subject to change.
9093 
9094 		When you draw a cursor, you can draw this to notify your window of where it is,
9095 		for IME systems to use.
9096 	+/
9097 	void notifyCursorPosition(int x, int y, int width, int height) {
9098 		if(auto w = cast(SimpleWindow) window) {
9099 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9100 		}
9101 	}
9102 
9103 	/++
9104 		If you are using manual invalidations, this informs the
9105 		window system that a section needs to be redrawn.
9106 
9107 		If you didn't opt into manual invalidation, you don't
9108 		have to call this.
9109 
9110 		History:
9111 			Added December 30, 2021 (dub v10.5)
9112 	+/
9113 	void invalidateRect(Rectangle rect) {
9114 		if(impl is null) return;
9115 
9116 		// transform(rect)
9117 		rect.left += _originX;
9118 		rect.right += _originX;
9119 		rect.top += _originY;
9120 		rect.bottom += _originY;
9121 
9122 		impl.invalidateRect(rect);
9123 	}
9124 
9125 	private Pen originalPen;
9126 	private Color originalFillColor;
9127 	private arsd.color.Rectangle originalClipRectangle;
9128 	private OperatingSystemFont originalFont;
9129 	void copyActiveOriginals() {
9130 		if(impl is null) return;
9131 		originalPen = impl._activePen;
9132 		originalFillColor = impl._fillColor;
9133 		originalClipRectangle = impl._clipRectangle;
9134 		version(OSXCocoa) {} else
9135 		originalFont = impl._activeFont;
9136 	}
9137 
9138 	~this() {
9139 		if(impl is null) return;
9140 		impl.referenceCount--;
9141 		//writeln("refcount -- ", impl.referenceCount);
9142 		if(impl.referenceCount == 0) {
9143 			// writeln("destructed");
9144 			impl.dispose();
9145 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9146 			// writeln("paint finished");
9147 		} else {
9148 			// there is still an active reference, reset stuff so the
9149 			// next user doesn't get weirdness via the reference
9150 			this.rasterOp = RasterOp.normal;
9151 			pen = originalPen;
9152 			fillColor = originalFillColor;
9153 			if(originalFont)
9154 				setFont(originalFont);
9155 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9156 		}
9157 	}
9158 
9159 	this(this) {
9160 		if(impl is null) return;
9161 		impl.referenceCount++;
9162 		//writeln("refcount ++ ", impl.referenceCount);
9163 
9164 		copyActiveOriginals();
9165 	}
9166 
9167 	private int _originX;
9168 	private int _originY;
9169 	@property int originX() { return _originX; }
9170 	@property int originY() { return _originY; }
9171 	@property int originX(int a) {
9172 		_originX = a;
9173 		return _originX;
9174 	}
9175 	@property int originY(int a) {
9176 		_originY = a;
9177 		return _originY;
9178 	}
9179 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9180 	private void transform(ref Point p) {
9181 		if(impl is null) return;
9182 		p.x += _originX;
9183 		p.y += _originY;
9184 	}
9185 
9186 	// this needs to be checked BEFORE the originX/Y transformation
9187 	private bool isClipped(Point p) {
9188 		return !currentClipRectangle.contains(p);
9189 	}
9190 	private bool isClipped(Point p, int width, int height) {
9191 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9192 	}
9193 	private bool isClipped(Point p, Size s) {
9194 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9195 	}
9196 	private bool isClipped(Point p, Point p2) {
9197 		// need to ensure the end points are actually included inside, so the +1 does that
9198 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9199 	}
9200 
9201 
9202 	/++
9203 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9204 
9205 		Returns:
9206 			The old clip rectangle.
9207 
9208 		History:
9209 			Return value was `void` prior to May 10, 2021.
9210 
9211 	+/
9212 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9213 		if(impl is null) return currentClipRectangle;
9214 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9215 			return currentClipRectangle; // no need to do anything
9216 		auto old = currentClipRectangle;
9217 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9218 		transform(pt);
9219 
9220 		impl.setClipRectangle(pt.x, pt.y, width, height);
9221 
9222 		return old;
9223 	}
9224 
9225 	/// ditto
9226 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9227 		if(impl is null) return currentClipRectangle;
9228 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9229 	}
9230 
9231 	///
9232 	void setFont(OperatingSystemFont font) {
9233 		if(impl is null) return;
9234 		impl.setFont(font);
9235 	}
9236 
9237 	///
9238 	int fontHeight() {
9239 		if(impl is null) return 0;
9240 		return impl.fontHeight();
9241 	}
9242 
9243 	private Pen activePen;
9244 
9245 	///
9246 	@property void pen(Pen p) {
9247 		if(impl is null) return;
9248 		activePen = p;
9249 		impl.pen(p);
9250 	}
9251 
9252 	///
9253 	@scriptable
9254 	@property void outlineColor(Color c) {
9255 		if(impl is null) return;
9256 		if(activePen.color == c)
9257 			return;
9258 		activePen.color = c;
9259 		impl.pen(activePen);
9260 	}
9261 
9262 	///
9263 	@scriptable
9264 	@property void fillColor(Color c) {
9265 		if(impl is null) return;
9266 		impl.fillColor(c);
9267 	}
9268 
9269 	///
9270 	@property void rasterOp(RasterOp op) {
9271 		if(impl is null) return;
9272 		impl.rasterOp(op);
9273 	}
9274 
9275 
9276 	void updateDisplay() {
9277 		// FIXME this should do what the dtor does
9278 	}
9279 
9280 	/// 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)
9281 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9282 		if(impl is null) return;
9283 		if(isClipped(upperLeft, width, height)) return;
9284 		transform(upperLeft);
9285 		version(Windows) {
9286 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9287 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9288 			RECT clip = scroll;
9289 			RECT uncovered;
9290 			HRGN hrgn;
9291 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9292 				throw new WindowsApiException("ScrollDC", GetLastError());
9293 
9294 		} else version(X11) {
9295 			// FIXME: clip stuff outside this rectangle
9296 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9297 		} else version(OSXCocoa) {
9298 			throw new NotYetImplementedException();
9299 		} else static assert(0);
9300 	}
9301 
9302 	///
9303 	void clear(Color color = Color.white()) {
9304 		if(impl is null) return;
9305 		fillColor = color;
9306 		outlineColor = color;
9307 		drawRectangle(Point(0, 0), window.width, window.height);
9308 	}
9309 
9310 	/++
9311 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9312 
9313 		Params:
9314 			upperLeft = point on the window where the upper left corner of the image will be drawn
9315 			imageUpperLeft = point on the image to start the slice to draw
9316 			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.
9317 		History:
9318 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9319 	+/
9320 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9321 		if(impl is null) return;
9322 		if(isClipped(upperLeft, s.width, s.height)) return;
9323 		transform(upperLeft);
9324 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9325 	}
9326 
9327 	///
9328 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9329 		if(impl is null) return;
9330 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9331 		transform(upperLeft);
9332 		if(w == 0 || w > i.width)
9333 			w = i.width;
9334 		if(h == 0 || h > i.height)
9335 			h = i.height;
9336 		if(upperLeftOfImage.x < 0)
9337 			upperLeftOfImage.x = 0;
9338 		if(upperLeftOfImage.y < 0)
9339 			upperLeftOfImage.y = 0;
9340 
9341 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9342 	}
9343 
9344 	///
9345 	Size textSize(in char[] text) {
9346 		if(impl is null) return Size(0, 0);
9347 		return impl.textSize(text);
9348 	}
9349 
9350 	/++
9351 		Draws a string in the window with the set font (see [setFont] to change it).
9352 
9353 		Params:
9354 			upperLeft = the upper left point of the bounding box of the text
9355 			text = the string to draw
9356 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9357 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9358 	+/
9359 	@scriptable
9360 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9361 		if(impl is null) return;
9362 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9363 			if(isClipped(upperLeft, lowerRight)) return;
9364 			transform(lowerRight);
9365 		} else {
9366 			if(isClipped(upperLeft, textSize(text))) return;
9367 		}
9368 		transform(upperLeft);
9369 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9370 	}
9371 
9372 	/++
9373 		Draws text using a custom font.
9374 
9375 		This is still MAJOR work in progress.
9376 
9377 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9378 	+/
9379 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9380 		if(impl is null) return;
9381 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9382 		transform(upperLeft);
9383 		font.drawString(this, upperLeft, text);
9384 	}
9385 
9386 	version(Windows)
9387 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9388 		if(impl is null) return;
9389 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9390 		transform(upperLeft);
9391 
9392 		if(text.length && text[$-1] == '\n')
9393 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9394 
9395 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9396 	}
9397 
9398 	static struct TextDrawingContext {
9399 		Point boundingBoxUpperLeft;
9400 		Point boundingBoxLowerRight;
9401 
9402 		Point currentLocation;
9403 
9404 		Point lastDrewUpperLeft;
9405 		Point lastDrewLowerRight;
9406 
9407 		// how do i do right aligned rich text?
9408 		// i kinda want to do a pre-made drawing then right align
9409 		// draw the whole block.
9410 		//
9411 		// That's exactly the diff: inline vs block stuff.
9412 
9413 		// I need to get coordinates of an inline section out too,
9414 		// not just a bounding box, but a series of bounding boxes
9415 		// should be ok. Consider what's needed to detect a click
9416 		// on a link in the middle of a paragraph breaking a line.
9417 		//
9418 		// Generally, we should be able to get the rectangles of
9419 		// any portion we draw.
9420 		//
9421 		// It also needs to tell what text is left if it overflows
9422 		// out of the box, so we can do stuff like float images around
9423 		// it. It should not attempt to draw a letter that would be
9424 		// clipped.
9425 		//
9426 		// I might also turn off word wrap stuff.
9427 	}
9428 
9429 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9430 		if(impl is null) return;
9431 		// FIXME
9432 	}
9433 
9434 	/// Drawing an individual pixel is slow. Avoid it if possible.
9435 	void drawPixel(Point where) {
9436 		if(impl is null) return;
9437 		if(isClipped(where)) return;
9438 		transform(where);
9439 		impl.drawPixel(where.x, where.y);
9440 	}
9441 
9442 
9443 	/// Draws a pen using the current pen / outlineColor
9444 	@scriptable
9445 	void drawLine(Point starting, Point ending) {
9446 		if(impl is null) return;
9447 		if(isClipped(starting, ending)) return;
9448 		transform(starting);
9449 		transform(ending);
9450 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9451 	}
9452 
9453 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9454 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9455 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9456 	@scriptable
9457 	void drawRectangle(Point upperLeft, int width, int height) {
9458 		if(impl is null) return;
9459 		if(isClipped(upperLeft, width, height)) return;
9460 		transform(upperLeft);
9461 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9462 	}
9463 
9464 	/// ditto
9465 	void drawRectangle(Point upperLeft, Size size) {
9466 		if(impl is null) return;
9467 		if(isClipped(upperLeft, size.width, size.height)) return;
9468 		transform(upperLeft);
9469 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9470 	}
9471 
9472 	/// ditto
9473 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9474 		if(impl is null) return;
9475 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9476 		transform(upperLeft);
9477 		transform(lowerRightInclusive);
9478 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9479 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9480 	}
9481 
9482 	// overload added on May 12, 2021
9483 	/// ditto
9484 	void drawRectangle(Rectangle rect) {
9485 		drawRectangle(rect.upperLeft, rect.size);
9486 	}
9487 
9488 	/// Arguments are the points of the bounding rectangle
9489 	void drawEllipse(Point upperLeft, Point lowerRight) {
9490 		if(impl is null) return;
9491 		if(isClipped(upperLeft, lowerRight)) return;
9492 		transform(upperLeft);
9493 		transform(lowerRight);
9494 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9495 	}
9496 
9497 	/++
9498 		start and finish are units of degrees * 64
9499 
9500 		History:
9501 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9502 
9503 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9504 	+/
9505 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9506 		if(impl is null) return;
9507 		// FIXME: not actually implemented
9508 		if(isClipped(upperLeft, width, height)) return;
9509 		transform(upperLeft);
9510 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9511 	}
9512 
9513 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9514 	void drawCircle(Point upperLeft, int diameter) {
9515 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9516 	}
9517 
9518 	/// .
9519 	void drawPolygon(Point[] vertexes) {
9520 		if(impl is null) return;
9521 		assert(vertexes.length);
9522 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9523 		foreach(ref vertex; vertexes) {
9524 			if(vertex.x < minX)
9525 				minX = vertex.x;
9526 			if(vertex.y < minY)
9527 				minY = vertex.y;
9528 			if(vertex.x > maxX)
9529 				maxX = vertex.x;
9530 			if(vertex.y > maxY)
9531 				maxY = vertex.y;
9532 			transform(vertex);
9533 		}
9534 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9535 		impl.drawPolygon(vertexes);
9536 	}
9537 
9538 	/// ditto
9539 	void drawPolygon(Point[] vertexes...) {
9540 		if(impl is null) return;
9541 		drawPolygon(vertexes);
9542 	}
9543 
9544 
9545 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9546 
9547 	//mixin NativeScreenPainterImplementation!() impl;
9548 
9549 
9550 	// HACK: if I mixin the impl directly, it won't let me override the copy
9551 	// constructor! The linker complains about there being multiple definitions.
9552 	// I'll make the best of it and reference count it though.
9553 	ScreenPainterImplementation* impl;
9554 }
9555 
9556 	// HACK: I need a pointer to the implementation so it's separate
9557 	struct ScreenPainterImplementation {
9558 		CapableOfBeingDrawnUpon window;
9559 		int referenceCount;
9560 		mixin NativeScreenPainterImplementation!();
9561 	}
9562 
9563 // FIXME: i haven't actually tested the sprite class on MS Windows
9564 
9565 /**
9566 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9567 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9568 
9569 
9570 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9571 	though I'm not sure that's ideal and the implementation might change.
9572 
9573 	You create one by giving a window and an image. It optimizes for that window,
9574 	and copies the image into it to use as the initial picture. Creating a sprite
9575 	can be quite slow (especially over a network connection) so you should do it
9576 	as little as possible and just hold on to your sprite handles after making them.
9577 	simpledisplay does try to do its best though, using the XSHM extension if available,
9578 	but you should still write your code as if it will always be slow.
9579 
9580 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9581 	a fast operation - much faster than drawing the Image itself every time.
9582 
9583 	`Sprite` represents a scarce resource which should be freed when you
9584 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9585 	after it has been disposed. If you are unsure about this, don't take chances,
9586 	just let the garbage collector do it for you. But ideally, you can manage its
9587 	lifetime more efficiently.
9588 
9589 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9590 	support alpha blending in its drawing at this time. That might change in the
9591 	future, but if you need alpha blending right now, use OpenGL instead. See
9592 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9593 
9594 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9595 	in by setting the enableAlpha = true in the constructor.
9596 */
9597 class Sprite : CapableOfBeingDrawnUpon {
9598 
9599 	///
9600 	ScreenPainter draw() {
9601 		return ScreenPainter(this, handle, false);
9602 	}
9603 
9604 	/++
9605 		Copies the sprite's current state into a [TrueColorImage].
9606 
9607 		Be warned: this can be a very slow operation
9608 
9609 		History:
9610 			Actually implemented on March 14, 2021
9611 	+/
9612 	TrueColorImage takeScreenshot() {
9613 		return trueColorImageFromNativeHandle(handle, width, height);
9614 	}
9615 
9616 	void delegate() paintingFinishedDg() { return null; }
9617 	bool closed() { return false; }
9618 	ScreenPainterImplementation* activeScreenPainter_;
9619 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9620 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9621 
9622 	version(Windows)
9623 		private ubyte* rawData;
9624 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9625 	// ditto on the XPicture stuff
9626 
9627 	version(X11) {
9628 		private static XRenderPictFormat* RGB24;
9629 		private static XRenderPictFormat* ARGB32;
9630 
9631 		private Picture xrenderPicture;
9632 	}
9633 
9634 	version(X11)
9635 	private static void requireXRender() {
9636 		if(!XRenderLibrary.loadAttempted) {
9637 			XRenderLibrary.loadDynamicLibrary();
9638 		}
9639 
9640 		if(!XRenderLibrary.loadSuccessful)
9641 			throw new Exception("XRender library load failure");
9642 
9643 		auto display = XDisplayConnection.get;
9644 
9645 		// FIXME: if we migrate X displays, these need to be changed
9646 		if(RGB24 is null)
9647 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9648 		if(ARGB32 is null)
9649 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9650 	}
9651 
9652 	protected this() {}
9653 
9654 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9655 		this._width = width;
9656 		this._height = height;
9657 		this.enableAlpha = enableAlpha;
9658 
9659 		version(X11) {
9660 			auto display = XDisplayConnection.get();
9661 
9662 			if(enableAlpha) {
9663 				requireXRender();
9664 			}
9665 
9666 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9667 
9668 			if(enableAlpha) {
9669 				XRenderPictureAttributes attrs;
9670 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9671 			}
9672 		} else version(Windows) {
9673 			version(CRuntime_DigitalMars) {
9674 				//if(enableAlpha)
9675 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9676 			}
9677 
9678 			BITMAPINFO infoheader;
9679 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9680 			infoheader.bmiHeader.biWidth = width;
9681 			infoheader.bmiHeader.biHeight = height;
9682 			infoheader.bmiHeader.biPlanes = 1;
9683 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9684 			infoheader.bmiHeader.biCompression = BI_RGB;
9685 
9686 			// FIXME: this should prolly be a device dependent bitmap...
9687 			handle = CreateDIBSection(
9688 				null,
9689 				&infoheader,
9690 				DIB_RGB_COLORS,
9691 				cast(void**) &rawData,
9692 				null,
9693 				0);
9694 
9695 			if(handle is null)
9696 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
9697 		}
9698 	}
9699 
9700 	/// Makes a sprite based on the image with the initial contents from the Image
9701 	this(SimpleWindow win, Image i) {
9702 		this(win, i.width, i.height, i.enableAlpha);
9703 
9704 		version(X11) {
9705 			auto display = XDisplayConnection.get();
9706 			auto gc = XCreateGC(display, this.handle, 0, null);
9707 			scope(exit) XFreeGC(display, gc);
9708 			if(i.usingXshm)
9709 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9710 			else
9711 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9712 		} else version(Windows) {
9713 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9714 			auto arrLength = itemsPerLine * height;
9715 			rawData[0..arrLength] = i.rawData[0..arrLength];
9716 		} else version(OSXCocoa) {
9717 			// FIXME: I have no idea if this is even any good
9718 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9719 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
9720 				colorSpace,
9721 				kCGImageAlphaPremultipliedLast
9722 				|kCGBitmapByteOrder32Big);
9723 			CGColorSpaceRelease(colorSpace);
9724 			auto rawData = CGBitmapContextGetData(handle);
9725 
9726 			auto rdl = (width * height * 4);
9727 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9728 		} else static assert(0);
9729 	}
9730 
9731 	/++
9732 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9733 
9734 		Params:
9735 			where = point on the window where the upper left corner of the image will be drawn
9736 			imageUpperLeft = point on the image to start the slice to draw
9737 			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.
9738 		History:
9739 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9740 	+/
9741 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9742 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9743 	}
9744 
9745 	/// Call this when you're ready to get rid of it
9746 	void dispose() {
9747 		version(X11) {
9748 			staticDispose(xrenderPicture, handle);
9749 			xrenderPicture = None;
9750 			handle = None;
9751 		} else version(Windows) {
9752 			staticDispose(handle);
9753 			handle = null;
9754 		} else version(OSXCocoa) {
9755 			staticDispose(handle);
9756 			handle = null;
9757 		} else static assert(0);
9758 
9759 	}
9760 
9761 	version(X11)
9762 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9763 		if(xrenderPicture)
9764 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9765 		if(handle)
9766 			XFreePixmap(XDisplayConnection.get(), handle);
9767 	}
9768 	else version(Windows)
9769 	static void staticDispose(HBITMAP handle) {
9770 		if(handle)
9771 			DeleteObject(handle);
9772 	}
9773 	else version(OSXCocoa)
9774 	static void staticDispose(CGContextRef context) {
9775 		if(context)
9776 			CGContextRelease(context);
9777 	}
9778 
9779 	~this() {
9780 		version(X11) { if(xrenderPicture || handle)
9781 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9782 		} else version(Windows) { if(handle)
9783 			cleanupQueue.queue!staticDispose(handle);
9784 		} else version(OSXCocoa) { if(handle)
9785 			cleanupQueue.queue!staticDispose(handle);
9786 		} else static assert(0);
9787 	}
9788 
9789 	///
9790 	final @property int width() { return _width; }
9791 
9792 	///
9793 	final @property int height() { return _height; }
9794 
9795 	///
9796 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9797 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9798 	}
9799 
9800 	auto nativeHandle() {
9801 		return handle;
9802 	}
9803 
9804 	private:
9805 
9806 	int _width;
9807 	int _height;
9808 	bool enableAlpha;
9809 	version(X11)
9810 		Pixmap handle;
9811 	else version(Windows)
9812 		HBITMAP handle;
9813 	else version(OSXCocoa)
9814 		CGContextRef handle;
9815 	else static assert(0);
9816 }
9817 
9818 /++
9819 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9820 
9821 	History:
9822 		Added November 20, 2021 (dub v10.4)
9823 +/
9824 version(OSXCocoa) {} else // NotYetImplementedException
9825 abstract class Gradient : Sprite {
9826 	protected this(int w, int h) {
9827 		version(X11) {
9828 			Sprite.requireXRender();
9829 
9830 			super();
9831 			enableAlpha = true;
9832 			_width = w;
9833 			_height = h;
9834 		} else version(Windows) {
9835 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9836 		}
9837 	}
9838 
9839 	version(Windows)
9840 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9841 		auto ptr = rawData;
9842 		foreach(j; 0 .. _height)
9843 		foreach(i; 0 .. _width) {
9844 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9845 			*rawData = (color.a * color.b) / 255; rawData++;
9846 			*rawData = (color.a * color.g) / 255; rawData++;
9847 			*rawData = (color.a * color.r) / 255; rawData++;
9848 			*rawData = color.a; rawData++;
9849 		}
9850 	}
9851 
9852 	version(X11)
9853 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9854 		assert(stops.length > 0);
9855 		assert(stops.length <= 16, "I got lazy with buffers");
9856 
9857 		XFixed[16] stopsPositions = void;
9858 		XRenderColor[16] colors = void;
9859 
9860 		foreach(idx, stop; stops) {
9861 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9862 			auto c = stop.c;
9863 			colors[idx] = XRenderColor(
9864 				cast(ushort)(c.r * ushort.max / 255),
9865 				cast(ushort)(c.g * ushort.max / 255),
9866 				cast(ushort)(c.b * ushort.max / 255),
9867 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9868 			);
9869 		}
9870 
9871 		xrenderPicture = dg(stopsPositions, colors);
9872 	}
9873 
9874 	///
9875 	static struct Stop {
9876 		float percentage; /// between 0 and 1.0
9877 		Color c;
9878 	}
9879 }
9880 
9881 /++
9882 	Creates a linear gradient between p1 and p2.
9883 
9884 	X ONLY RIGHT NOW
9885 
9886 	History:
9887 		Added November 20, 2021 (dub v10.4)
9888 
9889 	Bugs:
9890 		Not yet implemented on Windows.
9891 +/
9892 version(OSXCocoa) {} else // NotYetImplementedException
9893 class LinearGradient : Gradient {
9894 	/++
9895 
9896 	+/
9897 	this(Point p1, Point p2, Stop[] stops...) {
9898 		super(p2.x, p2.y);
9899 
9900 		version(X11) {
9901 			XLinearGradient gradient;
9902 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9903 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9904 
9905 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9906 				return XRenderCreateLinearGradient(
9907 					XDisplayConnection.get,
9908 					&gradient,
9909 					stopsPositions.ptr,
9910 					colors.ptr,
9911 					cast(int) stops.length);
9912 			});
9913 		} else version(Windows) {
9914 			// FIXME
9915 			forEachPixel((int x, int y) {
9916 				import core.stdc.math;
9917 
9918 				//sqrtf(
9919 
9920 				return Color.transparent;
9921 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9922 			});
9923 		}
9924 	}
9925 }
9926 
9927 /++
9928 	A conical gradient goes from color to color around a circumference from a center point.
9929 
9930 	X ONLY RIGHT NOW
9931 
9932 	History:
9933 		Added November 20, 2021 (dub v10.4)
9934 
9935 	Bugs:
9936 		Not yet implemented on Windows.
9937 +/
9938 version(OSXCocoa) {} else // NotYetImplementedException
9939 class ConicalGradient : Gradient {
9940 	/++
9941 
9942 	+/
9943 	this(Point center, float angleInDegrees, Stop[] stops...) {
9944 		super(center.x * 2, center.y * 2);
9945 
9946 		version(X11) {
9947 			XConicalGradient gradient;
9948 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9949 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9950 
9951 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9952 				return XRenderCreateConicalGradient(
9953 					XDisplayConnection.get,
9954 					&gradient,
9955 					stopsPositions.ptr,
9956 					colors.ptr,
9957 					cast(int) stops.length);
9958 			});
9959 		} else version(Windows) {
9960 			// FIXME
9961 			forEachPixel((int x, int y) {
9962 				import core.stdc.math;
9963 
9964 				//sqrtf(
9965 
9966 				return Color.transparent;
9967 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9968 			});
9969 
9970 		}
9971 	}
9972 }
9973 
9974 /++
9975 	A radial gradient goes from color to color based on distance from the center.
9976 	It is like rings of color.
9977 
9978 	X ONLY RIGHT NOW
9979 
9980 
9981 	More specifically, you create two circles: an inner circle and an outer circle.
9982 	The gradient is only drawn in the area outside the inner circle but inside the outer
9983 	circle. The closest line between those two circles forms the line for the gradient
9984 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9985 
9986 	History:
9987 		Added November 20, 2021 (dub v10.4)
9988 
9989 	Bugs:
9990 		Not yet implemented on Windows.
9991 +/
9992 version(OSXCocoa) {} else // NotYetImplementedException
9993 class RadialGradient : Gradient {
9994 	/++
9995 
9996 	+/
9997 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
9998 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
9999 
10000 		version(X11) {
10001 			XRadialGradient gradient;
10002 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10003 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10004 
10005 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10006 				return XRenderCreateRadialGradient(
10007 					XDisplayConnection.get,
10008 					&gradient,
10009 					stopsPositions.ptr,
10010 					colors.ptr,
10011 					cast(int) stops.length);
10012 			});
10013 		} else version(Windows) {
10014 			// FIXME
10015 			forEachPixel((int x, int y) {
10016 				import core.stdc.math;
10017 
10018 				//sqrtf(
10019 
10020 				return Color.transparent;
10021 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10022 			});
10023 		}
10024 	}
10025 }
10026 
10027 
10028 
10029 /+
10030 	NOT IMPLEMENTED
10031 
10032 	A display-stored image optimized for relatively quick drawing, like
10033 	[Sprite], but this one supports alpha channel blending and does NOT
10034 	support direct drawing upon it with a [ScreenPainter].
10035 
10036 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10037 	plain [ScreenPainter]... sort of.
10038 
10039 	On X11, it requires the Xrender extension and library. This is available
10040 	almost everywhere though.
10041 
10042 	History:
10043 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10044 +/
10045 version(none)
10046 class AlphaSprite {
10047 	/++
10048 		Copies the given image into it.
10049 	+/
10050 	this(MemoryImage img) {
10051 
10052 		if(!XRenderLibrary.loadAttempted) {
10053 			XRenderLibrary.loadDynamicLibrary();
10054 
10055 			// FIXME: this needs to be reconstructed when the X server changes
10056 			repopulateX();
10057 		}
10058 		if(!XRenderLibrary.loadSuccessful)
10059 			throw new Exception("XRender library load failure");
10060 
10061 		// I probably need to put the alpha mask in a separate Picture
10062 		// ugh
10063 		// maybe the Sprite itself can have an alpha bitmask anyway
10064 
10065 
10066 		auto display = XDisplayConnection.get();
10067 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10068 
10069 
10070 		XRenderPictureAttributes attrs;
10071 
10072 		handle = XRenderCreatePicture(
10073 			XDisplayConnection.get,
10074 			pixmap,
10075 			RGBA,
10076 			0,
10077 			&attrs
10078 		);
10079 
10080 	}
10081 
10082 	// maybe i'll use the create gradient functions too with static factories..
10083 
10084 	void drawAt(ScreenPainter painter, Point where) {
10085 		//painter.drawPixmap(this, where);
10086 
10087 		XRenderPictureAttributes attrs;
10088 
10089 		auto pic = XRenderCreatePicture(
10090 			XDisplayConnection.get,
10091 			painter.impl.d,
10092 			RGB,
10093 			0,
10094 			&attrs
10095 		);
10096 
10097 		XRenderComposite(
10098 			XDisplayConnection.get,
10099 			3, // PictOpOver
10100 			handle,
10101 			None,
10102 			pic,
10103 			0, // src
10104 			0,
10105 			0, // mask
10106 			0,
10107 			10, // dest
10108 			10,
10109 			100, // width
10110 			100
10111 		);
10112 
10113 		/+
10114 		XRenderFreePicture(
10115 			XDisplayConnection.get,
10116 			pic
10117 		);
10118 
10119 		XRenderFreePicture(
10120 			XDisplayConnection.get,
10121 			fill
10122 		);
10123 		+/
10124 		// on Windows you can stretch but Xrender still can't :(
10125 	}
10126 
10127 	static XRenderPictFormat* RGB;
10128 	static XRenderPictFormat* RGBA;
10129 	static void repopulateX() {
10130 		auto display = XDisplayConnection.get;
10131 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10132 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10133 	}
10134 
10135 	XPixmap pixmap;
10136 	Picture handle;
10137 }
10138 
10139 ///
10140 interface CapableOfBeingDrawnUpon {
10141 	///
10142 	ScreenPainter draw();
10143 	///
10144 	int width();
10145 	///
10146 	int height();
10147 	protected ScreenPainterImplementation* activeScreenPainter();
10148 	protected void activeScreenPainter(ScreenPainterImplementation*);
10149 	bool closed();
10150 
10151 	void delegate() paintingFinishedDg();
10152 
10153 	/// Be warned: this can be a very slow operation
10154 	TrueColorImage takeScreenshot();
10155 }
10156 
10157 /// 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].
10158 void flushGui() {
10159 	version(X11) {
10160 		auto dpy = XDisplayConnection.get();
10161 		XLockDisplay(dpy);
10162 		scope(exit) XUnlockDisplay(dpy);
10163 		XFlush(dpy);
10164 	}
10165 }
10166 
10167 /++
10168 	Runs the given code in the GUI thread when its event loop
10169 	is available, blocking until it completes. This allows you
10170 	to create and manipulate windows from another thread without
10171 	invoking undefined behavior.
10172 
10173 	If this is the gui thread, it runs the code immediately.
10174 
10175 	If no gui thread exists yet, the current thread is assumed
10176 	to be it. Attempting to create windows or run the event loop
10177 	in any other thread will cause an assertion failure.
10178 
10179 
10180 	$(TIP
10181 		Did you know you can use UFCS on delegate literals?
10182 
10183 		() {
10184 			// code here
10185 		}.runInGuiThread;
10186 	)
10187 
10188 	Returns:
10189 		`true` if the function was called, `false` if it was not.
10190 		The function may not be called because the gui thread had
10191 		already terminated by the time you called this.
10192 
10193 	History:
10194 		Added April 10, 2020 (v7.2.0)
10195 
10196 		Return value added and implementation tweaked to avoid locking
10197 		at program termination on February 24, 2021 (v9.2.1).
10198 +/
10199 bool runInGuiThread(scope void delegate() dg) @trusted {
10200 	claimGuiThread();
10201 
10202 	if(thisIsGuiThread) {
10203 		dg();
10204 		return true;
10205 	}
10206 
10207 	if(guiThreadTerminating)
10208 		return false;
10209 
10210 	import core.sync.semaphore;
10211 	static Semaphore sc;
10212 	if(sc is null)
10213 		sc = new Semaphore();
10214 
10215 	static RunQueueMember* rqm;
10216 	if(rqm is null)
10217 		rqm = new RunQueueMember;
10218 	rqm.dg = cast(typeof(rqm.dg)) dg;
10219 	rqm.signal = sc;
10220 	rqm.thrown = null;
10221 
10222 	synchronized(runInGuiThreadLock) {
10223 		runInGuiThreadQueue ~= rqm;
10224 	}
10225 
10226 	if(!SimpleWindow.eventWakeUp())
10227 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10228 
10229 	rqm.signal.wait();
10230 	auto t = rqm.thrown;
10231 
10232 	if(t)
10233 		throw t;
10234 
10235 	return true;
10236 }
10237 
10238 // note it runs sync if this is the gui thread....
10239 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10240 	claimGuiThread();
10241 
10242 	try {
10243 
10244 		if(thisIsGuiThread) {
10245 			dg();
10246 			return;
10247 		}
10248 
10249 		if(guiThreadTerminating)
10250 			return;
10251 
10252 		RunQueueMember* rqm = new RunQueueMember;
10253 		rqm.dg = cast(typeof(rqm.dg)) dg;
10254 		rqm.signal = null;
10255 		rqm.thrown = null;
10256 
10257 		synchronized(runInGuiThreadLock) {
10258 			runInGuiThreadQueue ~= rqm;
10259 		}
10260 
10261 		if(!SimpleWindow.eventWakeUp())
10262 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10263 	} catch(Exception e) {
10264 		if(handleError)
10265 			handleError(e);
10266 	}
10267 }
10268 
10269 private void runPendingRunInGuiThreadDelegates() {
10270 	more:
10271 	RunQueueMember* next;
10272 	synchronized(runInGuiThreadLock) {
10273 		if(runInGuiThreadQueue.length) {
10274 			next = runInGuiThreadQueue[0];
10275 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10276 		} else {
10277 			next = null;
10278 		}
10279 	}
10280 
10281 	if(next) {
10282 		try {
10283 			next.dg();
10284 			next.thrown = null;
10285 		} catch(Throwable t) {
10286 			next.thrown = t;
10287 		}
10288 
10289 		if(next.signal)
10290 			next.signal.notify();
10291 
10292 		goto more;
10293 	}
10294 }
10295 
10296 private void claimGuiThread() nothrow {
10297 	import core.atomic;
10298 	if(cas(&guiThreadExists_, false, true))
10299 		thisIsGuiThread = true;
10300 }
10301 
10302 private struct RunQueueMember {
10303 	void delegate() dg;
10304 	import core.sync.semaphore;
10305 	Semaphore signal;
10306 	Throwable thrown;
10307 }
10308 
10309 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10310 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10311 private bool thisIsGuiThread = false;
10312 private shared bool guiThreadExists_ = false;
10313 private shared bool guiThreadTerminating = false;
10314 
10315 /++
10316 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10317 	event loop. All windows must be exclusively created and managed by a single thread.
10318 
10319 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10320 	when you call one of its constructors.
10321 
10322 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10323 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10324 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10325 
10326 	The reason this function is available is in case you want to message pass between a gui
10327 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10328 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10329 
10330 	History:
10331 		Added December 3, 2021 (dub v10.5)
10332 +/
10333 public bool guiThreadExists() {
10334 	return guiThreadExists_;
10335 }
10336 
10337 /++
10338 	Returns `true` if this thread is either running or set to be running the
10339 	simpledisplay.d gui core event loop because it owns windows.
10340 
10341 	It is important to keep gui-related functionality in the right thread, so you will
10342 	want to `runInGuiThread` when you call them (with some specific exceptions called
10343 	out in those specific functions' documentation). Notably, all windows must be
10344 	created and managed only from the gui thread.
10345 
10346 	Will return false if simpledisplay's other functions haven't been called
10347 	yet; check [guiThreadExists] in addition to this.
10348 
10349 	History:
10350 		Added December 3, 2021 (dub v10.5)
10351 +/
10352 public bool thisThreadRunningGui() {
10353 	return thisIsGuiThread;
10354 }
10355 
10356 /++
10357 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10358 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10359 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10360 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10361 	file instead if you are in one of those situations).
10362 
10363 	It does not support outputting very many types; just strings and ints are likely to actually work.
10364 
10365 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10366 	is unspecified meaning I can change it at any time. The only point of this function is to help
10367 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10368 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10369 	in those contexts.
10370 
10371 	$(WARNING
10372 		I reserve the right to change this function at any time. You can use it if it helps you
10373 		but do not rely on it for anything permanent.
10374 	)
10375 
10376 	History:
10377 		Added December 3, 2021. Not formally supported under any stable tag.
10378 +/
10379 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10380 	try {
10381 		version(Windows) {
10382 			import core.sys.windows.wincon;
10383 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10384 				AllocConsole();
10385 			const(char)* fn = "CONOUT$";
10386 		} else version(Posix) {
10387 			const(char)* fn = "/dev/tty";
10388 		} else static assert(0, "Function not implemented for your system");
10389 
10390 		if(fileOverride.length)
10391 			fn = fileOverride.ptr;
10392 
10393 		import core.stdc.stdio;
10394 		auto fp = fopen(fn, "wt");
10395 		if(fp is null) return;
10396 		scope(exit) fclose(fp);
10397 
10398 		string str;
10399 		foreach(item; t) {
10400 			static if(is(typeof(item) : const(char)[]))
10401 				str ~= item;
10402 			else
10403 				str ~= toInternal!string(item);
10404 			str ~= " ";
10405 		}
10406 		str ~= "\n";
10407 
10408 		fwrite(str.ptr, 1, str.length, fp);
10409 		fflush(fp);
10410 	} catch(Exception e) {
10411 		// sorry no hope
10412 	}
10413 }
10414 
10415 private void guiThreadFinalize() {
10416 	assert(thisIsGuiThread);
10417 
10418 	guiThreadTerminating = true; // don't add any more from this point on
10419 	runPendingRunInGuiThreadDelegates();
10420 }
10421 
10422 /+
10423 interface IPromise {
10424 	void reportProgress(int current, int max, string message);
10425 
10426 	/+ // not formally in cuz of templates but still
10427 	IPromise Then();
10428 	IPromise Catch();
10429 	IPromise Finally();
10430 	+/
10431 }
10432 
10433 /+
10434 	auto promise = async({ ... });
10435 	promise.Then(whatever).
10436 		Then(whateverelse).
10437 		Catch((exception) { });
10438 
10439 
10440 	A promise is run inside a fiber and it looks something like:
10441 
10442 	try {
10443 		auto res = whatever();
10444 		auto res2 = whateverelse(res);
10445 	} catch(Exception e) {
10446 		{ }(e);
10447 	}
10448 
10449 	When a thing succeeds, it is passed as an arg to the next
10450 +/
10451 class Promise(T) : IPromise {
10452 	auto Then() { return null; }
10453 	auto Catch() { return null; }
10454 	auto Finally() { return null; }
10455 
10456 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10457 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10458 	T await();
10459 }
10460 
10461 interface Task {
10462 }
10463 
10464 interface Resolvable(T) : Task {
10465 	void run();
10466 
10467 	void resolve(T);
10468 
10469 	Resolvable!T then(void delegate(T)); // returns a new promise
10470 	Resolvable!T error(Throwable); // js catch
10471 	Resolvable!T completed(); // js finally
10472 
10473 }
10474 
10475 /++
10476 	Runs `work` in a helper thread and sends its return value back to the main gui
10477 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10478 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10479 	kill the program.
10480 
10481 	You can call reportProgress(position, max, message) to update your parent window
10482 	on your progress.
10483 
10484 	I should also use `shared` methods. FIXME
10485 
10486 	History:
10487 		Added March 6, 2021 (dub version 9.3).
10488 +/
10489 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10490 	uponCompletion(work(null));
10491 }
10492 
10493 +/
10494 
10495 /// Used internal to dispatch events to various classes.
10496 interface CapableOfHandlingNativeEvent {
10497 	NativeEventHandler getNativeEventHandler();
10498 
10499 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10500 
10501 	version(X11) {
10502 		// if this is impossible, you are allowed to just throw from it
10503 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10504 		void recreateAfterDisconnect();
10505 		// discard any *connection specific* state, but keep enough that you
10506 		// can be recreated if possible. discardConnectionState() is always called immediately
10507 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10508 		// you need initialization order
10509 		void discardConnectionState();
10510 	}
10511 }
10512 
10513 version(X11)
10514 /++
10515 	State of keys on mouse events, especially motion.
10516 
10517 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10518 +/
10519 enum ModifierState : uint {
10520 	shift = 1, ///
10521 	capsLock = 2, ///
10522 	ctrl = 4, ///
10523 	alt = 8, /// Not always available on Windows
10524 	windows = 64, /// ditto
10525 	numLock = 16, ///
10526 
10527 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10528 	middleButtonDown = 512, /// ditto
10529 	rightButtonDown = 1024, /// ditto
10530 }
10531 else version(Windows)
10532 /// ditto
10533 enum ModifierState : uint {
10534 	shift = 4, ///
10535 	ctrl = 8, ///
10536 
10537 	// i'm not sure if the next two are available
10538 	alt = 256, /// not always available on Windows
10539 	windows = 512, /// ditto
10540 
10541 	capsLock = 1024, ///
10542 	numLock = 2048, ///
10543 
10544 	leftButtonDown = 1, /// not available on key events
10545 	middleButtonDown = 16, /// ditto
10546 	rightButtonDown = 2, /// ditto
10547 
10548 	backButtonDown = 0x20, /// not available on X
10549 	forwardButtonDown = 0x40, /// ditto
10550 }
10551 else version(OSXCocoa)
10552 // FIXME FIXME NotYetImplementedException
10553 enum ModifierState : uint {
10554 	shift = 1, ///
10555 	capsLock = 2, ///
10556 	ctrl = 4, ///
10557 	alt = 8, /// Not always available on Windows
10558 	windows = 64, /// ditto
10559 	numLock = 16, ///
10560 
10561 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10562 	middleButtonDown = 512, /// ditto
10563 	rightButtonDown = 1024, /// ditto
10564 }
10565 
10566 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10567 enum MouseButton : int {
10568 	none = 0,
10569 	left = 1, ///
10570 	right = 2, ///
10571 	middle = 4, ///
10572 	wheelUp = 8, ///
10573 	wheelDown = 16, ///
10574 	backButton = 32, /// often found on the thumb and used for back in browsers
10575 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10576 }
10577 
10578 version(X11) {
10579 	// FIXME: match ASCII whenever we can. Most of it is already there,
10580 	// but there's a few exceptions and mismatches with Windows
10581 
10582 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10583 	enum Key {
10584 		Escape = 0xff1b, ///
10585 		F1 = 0xffbe, ///
10586 		F2 = 0xffbf, ///
10587 		F3 = 0xffc0, ///
10588 		F4 = 0xffc1, ///
10589 		F5 = 0xffc2, ///
10590 		F6 = 0xffc3, ///
10591 		F7 = 0xffc4, ///
10592 		F8 = 0xffc5, ///
10593 		F9 = 0xffc6, ///
10594 		F10 = 0xffc7, ///
10595 		F11 = 0xffc8, ///
10596 		F12 = 0xffc9, ///
10597 		PrintScreen = 0xff61, ///
10598 		ScrollLock = 0xff14, ///
10599 		Pause = 0xff13, ///
10600 		Grave = 0x60, /// The $(BACKTICK) ~ key
10601 		// number keys across the top of the keyboard
10602 		N1 = 0x31, /// Number key atop the keyboard
10603 		N2 = 0x32, ///
10604 		N3 = 0x33, ///
10605 		N4 = 0x34, ///
10606 		N5 = 0x35, ///
10607 		N6 = 0x36, ///
10608 		N7 = 0x37, ///
10609 		N8 = 0x38, ///
10610 		N9 = 0x39, ///
10611 		N0 = 0x30, ///
10612 		Dash = 0x2d, ///
10613 		Equals = 0x3d, ///
10614 		Backslash = 0x5c, /// The \ | key
10615 		Backspace = 0xff08, ///
10616 		Insert = 0xff63, ///
10617 		Home = 0xff50, ///
10618 		PageUp = 0xff55, ///
10619 		Delete = 0xffff, ///
10620 		End = 0xff57, ///
10621 		PageDown = 0xff56, ///
10622 		Up = 0xff52, ///
10623 		Down = 0xff54, ///
10624 		Left = 0xff51, ///
10625 		Right = 0xff53, ///
10626 
10627 		Tab = 0xff09, ///
10628 		Q = 0x71, ///
10629 		W = 0x77, ///
10630 		E = 0x65, ///
10631 		R = 0x72, ///
10632 		T = 0x74, ///
10633 		Y = 0x79, ///
10634 		U = 0x75, ///
10635 		I = 0x69, ///
10636 		O = 0x6f, ///
10637 		P = 0x70, ///
10638 		LeftBracket = 0x5b, /// the [ { key
10639 		RightBracket = 0x5d, /// the ] } key
10640 		CapsLock = 0xffe5, ///
10641 		A = 0x61, ///
10642 		S = 0x73, ///
10643 		D = 0x64, ///
10644 		F = 0x66, ///
10645 		G = 0x67, ///
10646 		H = 0x68, ///
10647 		J = 0x6a, ///
10648 		K = 0x6b, ///
10649 		L = 0x6c, ///
10650 		Semicolon = 0x3b, ///
10651 		Apostrophe = 0x27, ///
10652 		Enter = 0xff0d, ///
10653 		Shift = 0xffe1, ///
10654 		Z = 0x7a, ///
10655 		X = 0x78, ///
10656 		C = 0x63, ///
10657 		V = 0x76, ///
10658 		B = 0x62, ///
10659 		N = 0x6e, ///
10660 		M = 0x6d, ///
10661 		Comma = 0x2c, ///
10662 		Period = 0x2e, ///
10663 		Slash = 0x2f, /// the / ? key
10664 		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
10665 		Ctrl = 0xffe3, ///
10666 		Windows = 0xffeb, ///
10667 		Alt = 0xffe9, ///
10668 		Space = 0x20, ///
10669 		Alt_r = 0xffea, /// ditto of shift_r
10670 		Windows_r = 0xffec, ///
10671 		Menu = 0xff67, ///
10672 		Ctrl_r = 0xffe4, ///
10673 
10674 		NumLock = 0xff7f, ///
10675 		Divide = 0xffaf, /// The / key on the number pad
10676 		Multiply = 0xffaa, /// The * key on the number pad
10677 		Minus = 0xffad, /// The - key on the number pad
10678 		Plus = 0xffab, /// The + key on the number pad
10679 		PadEnter = 0xff8d, /// Numberpad enter key
10680 		Pad1 = 0xff9c, /// Numberpad keys
10681 		Pad2 = 0xff99, ///
10682 		Pad3 = 0xff9b, ///
10683 		Pad4 = 0xff96, ///
10684 		Pad5 = 0xff9d, ///
10685 		Pad6 = 0xff98, ///
10686 		Pad7 = 0xff95, ///
10687 		Pad8 = 0xff97, ///
10688 		Pad9 = 0xff9a, ///
10689 		Pad0 = 0xff9e, ///
10690 		PadDot = 0xff9f, ///
10691 	}
10692 } else version(Windows) {
10693 	// the character here is for en-us layouts and for illustration only
10694 	// if you actually want to get characters, wait for character events
10695 	// (the argument to your event handler is simply a dchar)
10696 	// those will be converted by the OS for the right locale.
10697 
10698 	enum Key {
10699 		Escape = 0x1b,
10700 		F1 = 0x70,
10701 		F2 = 0x71,
10702 		F3 = 0x72,
10703 		F4 = 0x73,
10704 		F5 = 0x74,
10705 		F6 = 0x75,
10706 		F7 = 0x76,
10707 		F8 = 0x77,
10708 		F9 = 0x78,
10709 		F10 = 0x79,
10710 		F11 = 0x7a,
10711 		F12 = 0x7b,
10712 		PrintScreen = 0x2c,
10713 		ScrollLock = 0x91,
10714 		Pause = 0x13,
10715 		Grave = 0xc0,
10716 		// number keys across the top of the keyboard
10717 		N1 = 0x31,
10718 		N2 = 0x32,
10719 		N3 = 0x33,
10720 		N4 = 0x34,
10721 		N5 = 0x35,
10722 		N6 = 0x36,
10723 		N7 = 0x37,
10724 		N8 = 0x38,
10725 		N9 = 0x39,
10726 		N0 = 0x30,
10727 		Dash = 0xbd,
10728 		Equals = 0xbb,
10729 		Backslash = 0xdc,
10730 		Backspace = 0x08,
10731 		Insert = 0x2d,
10732 		Home = 0x24,
10733 		PageUp = 0x21,
10734 		Delete = 0x2e,
10735 		End = 0x23,
10736 		PageDown = 0x22,
10737 		Up = 0x26,
10738 		Down = 0x28,
10739 		Left = 0x25,
10740 		Right = 0x27,
10741 
10742 		Tab = 0x09,
10743 		Q = 0x51,
10744 		W = 0x57,
10745 		E = 0x45,
10746 		R = 0x52,
10747 		T = 0x54,
10748 		Y = 0x59,
10749 		U = 0x55,
10750 		I = 0x49,
10751 		O = 0x4f,
10752 		P = 0x50,
10753 		LeftBracket = 0xdb,
10754 		RightBracket = 0xdd,
10755 		CapsLock = 0x14,
10756 		A = 0x41,
10757 		S = 0x53,
10758 		D = 0x44,
10759 		F = 0x46,
10760 		G = 0x47,
10761 		H = 0x48,
10762 		J = 0x4a,
10763 		K = 0x4b,
10764 		L = 0x4c,
10765 		Semicolon = 0xba,
10766 		Apostrophe = 0xde,
10767 		Enter = 0x0d,
10768 		Shift = 0x10,
10769 		Z = 0x5a,
10770 		X = 0x58,
10771 		C = 0x43,
10772 		V = 0x56,
10773 		B = 0x42,
10774 		N = 0x4e,
10775 		M = 0x4d,
10776 		Comma = 0xbc,
10777 		Period = 0xbe,
10778 		Slash = 0xbf,
10779 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10780 		Ctrl = 0x11,
10781 		Windows = 0x5b,
10782 		Alt = -5, // FIXME
10783 		Space = 0x20,
10784 		Alt_r = 0xffea, // ditto of shift_r
10785 		Windows_r = 0x5c, // ditto of shift_r
10786 		Menu = 0x5d,
10787 		Ctrl_r = 0xa3, // ditto of shift_r
10788 
10789 		NumLock = 0x90,
10790 		Divide = 0x6f,
10791 		Multiply = 0x6a,
10792 		Minus = 0x6d,
10793 		Plus = 0x6b,
10794 		PadEnter = -8, // FIXME
10795 		Pad1 = 0x61,
10796 		Pad2 = 0x62,
10797 		Pad3 = 0x63,
10798 		Pad4 = 0x64,
10799 		Pad5 = 0x65,
10800 		Pad6 = 0x66,
10801 		Pad7 = 0x67,
10802 		Pad8 = 0x68,
10803 		Pad9 = 0x69,
10804 		Pad0 = 0x60,
10805 		PadDot = 0x6e,
10806 	}
10807 
10808 	// I'm keeping this around for reference purposes
10809 	// ideally all these buttons will be listed for all platforms,
10810 	// but now now I'm just focusing on my US keyboard
10811 	version(none)
10812 	enum Key {
10813 		LBUTTON = 0x01,
10814 		RBUTTON = 0x02,
10815 		CANCEL = 0x03,
10816 		MBUTTON = 0x04,
10817 		//static if (_WIN32_WINNT > =  0x500) {
10818 		XBUTTON1 = 0x05,
10819 		XBUTTON2 = 0x06,
10820 		//}
10821 		BACK = 0x08,
10822 		TAB = 0x09,
10823 		CLEAR = 0x0C,
10824 		RETURN = 0x0D,
10825 		SHIFT = 0x10,
10826 		CONTROL = 0x11,
10827 		MENU = 0x12,
10828 		PAUSE = 0x13,
10829 		CAPITAL = 0x14,
10830 		KANA = 0x15,
10831 		HANGEUL = 0x15,
10832 		HANGUL = 0x15,
10833 		JUNJA = 0x17,
10834 		FINAL = 0x18,
10835 		HANJA = 0x19,
10836 		KANJI = 0x19,
10837 		ESCAPE = 0x1B,
10838 		CONVERT = 0x1C,
10839 		NONCONVERT = 0x1D,
10840 		ACCEPT = 0x1E,
10841 		MODECHANGE = 0x1F,
10842 		SPACE = 0x20,
10843 		PRIOR = 0x21,
10844 		NEXT = 0x22,
10845 		END = 0x23,
10846 		HOME = 0x24,
10847 		LEFT = 0x25,
10848 		UP = 0x26,
10849 		RIGHT = 0x27,
10850 		DOWN = 0x28,
10851 		SELECT = 0x29,
10852 		PRINT = 0x2A,
10853 		EXECUTE = 0x2B,
10854 		SNAPSHOT = 0x2C,
10855 		INSERT = 0x2D,
10856 		DELETE = 0x2E,
10857 		HELP = 0x2F,
10858 		LWIN = 0x5B,
10859 		RWIN = 0x5C,
10860 		APPS = 0x5D,
10861 		SLEEP = 0x5F,
10862 		NUMPAD0 = 0x60,
10863 		NUMPAD1 = 0x61,
10864 		NUMPAD2 = 0x62,
10865 		NUMPAD3 = 0x63,
10866 		NUMPAD4 = 0x64,
10867 		NUMPAD5 = 0x65,
10868 		NUMPAD6 = 0x66,
10869 		NUMPAD7 = 0x67,
10870 		NUMPAD8 = 0x68,
10871 		NUMPAD9 = 0x69,
10872 		MULTIPLY = 0x6A,
10873 		ADD = 0x6B,
10874 		SEPARATOR = 0x6C,
10875 		SUBTRACT = 0x6D,
10876 		DECIMAL = 0x6E,
10877 		DIVIDE = 0x6F,
10878 		F1 = 0x70,
10879 		F2 = 0x71,
10880 		F3 = 0x72,
10881 		F4 = 0x73,
10882 		F5 = 0x74,
10883 		F6 = 0x75,
10884 		F7 = 0x76,
10885 		F8 = 0x77,
10886 		F9 = 0x78,
10887 		F10 = 0x79,
10888 		F11 = 0x7A,
10889 		F12 = 0x7B,
10890 		F13 = 0x7C,
10891 		F14 = 0x7D,
10892 		F15 = 0x7E,
10893 		F16 = 0x7F,
10894 		F17 = 0x80,
10895 		F18 = 0x81,
10896 		F19 = 0x82,
10897 		F20 = 0x83,
10898 		F21 = 0x84,
10899 		F22 = 0x85,
10900 		F23 = 0x86,
10901 		F24 = 0x87,
10902 		NUMLOCK = 0x90,
10903 		SCROLL = 0x91,
10904 		LSHIFT = 0xA0,
10905 		RSHIFT = 0xA1,
10906 		LCONTROL = 0xA2,
10907 		RCONTROL = 0xA3,
10908 		LMENU = 0xA4,
10909 		RMENU = 0xA5,
10910 		//static if (_WIN32_WINNT > =  0x500) {
10911 		BROWSER_BACK = 0xA6,
10912 		BROWSER_FORWARD = 0xA7,
10913 		BROWSER_REFRESH = 0xA8,
10914 		BROWSER_STOP = 0xA9,
10915 		BROWSER_SEARCH = 0xAA,
10916 		BROWSER_FAVORITES = 0xAB,
10917 		BROWSER_HOME = 0xAC,
10918 		VOLUME_MUTE = 0xAD,
10919 		VOLUME_DOWN = 0xAE,
10920 		VOLUME_UP = 0xAF,
10921 		MEDIA_NEXT_TRACK = 0xB0,
10922 		MEDIA_PREV_TRACK = 0xB1,
10923 		MEDIA_STOP = 0xB2,
10924 		MEDIA_PLAY_PAUSE = 0xB3,
10925 		LAUNCH_MAIL = 0xB4,
10926 		LAUNCH_MEDIA_SELECT = 0xB5,
10927 		LAUNCH_APP1 = 0xB6,
10928 		LAUNCH_APP2 = 0xB7,
10929 		//}
10930 		OEM_1 = 0xBA,
10931 		//static if (_WIN32_WINNT > =  0x500) {
10932 		OEM_PLUS = 0xBB,
10933 		OEM_COMMA = 0xBC,
10934 		OEM_MINUS = 0xBD,
10935 		OEM_PERIOD = 0xBE,
10936 		//}
10937 		OEM_2 = 0xBF,
10938 		OEM_3 = 0xC0,
10939 		OEM_4 = 0xDB,
10940 		OEM_5 = 0xDC,
10941 		OEM_6 = 0xDD,
10942 		OEM_7 = 0xDE,
10943 		OEM_8 = 0xDF,
10944 		//static if (_WIN32_WINNT > =  0x500) {
10945 		OEM_102 = 0xE2,
10946 		//}
10947 		PROCESSKEY = 0xE5,
10948 		//static if (_WIN32_WINNT > =  0x500) {
10949 		PACKET = 0xE7,
10950 		//}
10951 		ATTN = 0xF6,
10952 		CRSEL = 0xF7,
10953 		EXSEL = 0xF8,
10954 		EREOF = 0xF9,
10955 		PLAY = 0xFA,
10956 		ZOOM = 0xFB,
10957 		NONAME = 0xFC,
10958 		PA1 = 0xFD,
10959 		OEM_CLEAR = 0xFE,
10960 	}
10961 
10962 } else version(OSXCocoa) {
10963 	enum Key {
10964 		Escape = 53,
10965 		F1 = 122,
10966 		F2 = 120,
10967 		F3 = 99,
10968 		F4 = 118,
10969 		F5 = 96,
10970 		F6 = 97,
10971 		F7 = 98,
10972 		F8 = 100,
10973 		F9 = 101,
10974 		F10 = 109,
10975 		F11 = 103,
10976 		F12 = 111,
10977 		PrintScreen = 105,
10978 		ScrollLock = 107,
10979 		Pause = 113,
10980 		Grave = 50,
10981 		// number keys across the top of the keyboard
10982 		N1 = 18,
10983 		N2 = 19,
10984 		N3 = 20,
10985 		N4 = 21,
10986 		N5 = 23,
10987 		N6 = 22,
10988 		N7 = 26,
10989 		N8 = 28,
10990 		N9 = 25,
10991 		N0 = 29,
10992 		Dash = 27,
10993 		Equals = 24,
10994 		Backslash = 42,
10995 		Backspace = 51,
10996 		Insert = 114,
10997 		Home = 115,
10998 		PageUp = 116,
10999 		Delete = 117,
11000 		End = 119,
11001 		PageDown = 121,
11002 		Up = 126,
11003 		Down = 125,
11004 		Left = 123,
11005 		Right = 124,
11006 
11007 		Tab = 48,
11008 		Q = 12,
11009 		W = 13,
11010 		E = 14,
11011 		R = 15,
11012 		T = 17,
11013 		Y = 16,
11014 		U = 32,
11015 		I = 34,
11016 		O = 31,
11017 		P = 35,
11018 		LeftBracket = 33,
11019 		RightBracket = 30,
11020 		CapsLock = 57,
11021 		A = 0,
11022 		S = 1,
11023 		D = 2,
11024 		F = 3,
11025 		G = 5,
11026 		H = 4,
11027 		J = 38,
11028 		K = 40,
11029 		L = 37,
11030 		Semicolon = 41,
11031 		Apostrophe = 39,
11032 		Enter = 36,
11033 		Shift = 56,
11034 		Z = 6,
11035 		X = 7,
11036 		C = 8,
11037 		V = 9,
11038 		B = 11,
11039 		N = 45,
11040 		M = 46,
11041 		Comma = 43,
11042 		Period = 47,
11043 		Slash = 44,
11044 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11045 		Ctrl = 59,
11046 		Windows = 55,
11047 		Alt = 58,
11048 		Space = 49,
11049 		Alt_r = -3, // ditto of shift_r
11050 		Windows_r = -2,
11051 		Menu = 110,
11052 		Ctrl_r = -1,
11053 
11054 		NumLock = 1,
11055 		Divide = 75,
11056 		Multiply = 67,
11057 		Minus = 78,
11058 		Plus = 69,
11059 		PadEnter = 76,
11060 		Pad1 = 83,
11061 		Pad2 = 84,
11062 		Pad3 = 85,
11063 		Pad4 = 86,
11064 		Pad5 = 87,
11065 		Pad6 = 88,
11066 		Pad7 = 89,
11067 		Pad8 = 91,
11068 		Pad9 = 92,
11069 		Pad0 = 82,
11070 		PadDot = 65,
11071 	}
11072 
11073 }
11074 
11075 /* Additional utilities */
11076 
11077 
11078 Color fromHsl(real h, real s, real l) {
11079 	return arsd.color.fromHsl([h,s,l]);
11080 }
11081 
11082 
11083 
11084 /* ********** What follows is the system-specific implementations *********/
11085 version(Windows) {
11086 
11087 
11088 	// helpers for making HICONs from MemoryImages
11089 	class WindowsIcon {
11090 		struct Win32Icon(int colorCount) {
11091 		align(1):
11092 			uint biSize;
11093 			int biWidth;
11094 			int biHeight;
11095 			ushort biPlanes;
11096 			ushort biBitCount;
11097 			uint biCompression;
11098 			uint biSizeImage;
11099 			int biXPelsPerMeter;
11100 			int biYPelsPerMeter;
11101 			uint biClrUsed;
11102 			uint biClrImportant;
11103 			RGBQUAD[colorCount] biColors;
11104 			/* Pixels:
11105 			Uint8 pixels[]
11106 			*/
11107 			/* Mask:
11108 			Uint8 mask[]
11109 			*/
11110 
11111 			// FIXME: this sux, needs to be dynamic
11112 			ubyte[/*4096*/ 256*256*4 + 256*256/8] data;
11113 
11114 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11115 				width = mi.width;
11116 				height = mi.height;
11117 
11118 				auto indexedImage = cast(IndexedImage) mi;
11119 				if(indexedImage is null)
11120 					indexedImage = quantize(mi.getAsTrueColorImage());
11121 
11122 				assert(width <= 256, "image too wide");
11123 				assert(height <= 256, "image too tall");
11124 				assert(width %8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy
11125 				assert(height %4 == 0, "image not multiple of 4 height");
11126 
11127 				int icon_plen = height*((width+3)&~3);
11128 				int icon_mlen = height*((((width+7)/8)+3)&~3);
11129 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11130 
11131 				biSize = 40;
11132 				biWidth = width;
11133 				biHeight = height*2;
11134 				biPlanes = 1;
11135 				biBitCount = 8;
11136 				biSizeImage = icon_plen+icon_mlen;
11137 
11138 				int offset = 0;
11139 				int andOff = icon_plen * 8; // the and offset is in bits
11140 				for(int y = height - 1; y >= 0; y--) {
11141 					int off2 = y * width;
11142 					foreach(x; 0 .. width) {
11143 						const b = indexedImage.data[off2 + x];
11144 						data[offset] = b;
11145 						offset++;
11146 
11147 						const andBit = andOff % 8;
11148 						const andIdx = andOff / 8;
11149 						assert(b < indexedImage.palette.length);
11150 						// this is anded to the destination, since and 0 means erase,
11151 						// we want that to  be opaque, and 1 for transparent
11152 						auto transparent = (indexedImage.palette[b].a <= 127);
11153 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
11154 
11155 						andOff++;
11156 					}
11157 
11158 					andOff += andOff % 32;
11159 				}
11160 
11161 				foreach(idx, entry; indexedImage.palette) {
11162 					if(entry.a > 127) {
11163 						biColors[idx].rgbBlue = entry.b;
11164 						biColors[idx].rgbGreen = entry.g;
11165 						biColors[idx].rgbRed = entry.r;
11166 					} else {
11167 						biColors[idx].rgbBlue = 255;
11168 						biColors[idx].rgbGreen = 255;
11169 						biColors[idx].rgbRed = 255;
11170 					}
11171 				}
11172 
11173 				/*
11174 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
11175 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
11176 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
11177 				auto pngMap = fetchPaletteWin32(png);
11178 				biColors[0..pngMap.length] = pngMap[];
11179 				*/
11180 			}
11181 		}
11182 
11183 
11184 		Win32Icon!(256) icon_win32;
11185 
11186 
11187 		this(MemoryImage mi) {
11188 			int icon_len, width, height;
11189 
11190 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
11191 
11192 			/*
11193 			PNG* png = readPnpngData);
11194 			PNGHeader pngh = getHeader(png);
11195 			void* icon_win32;
11196 			if(pngh.depth == 4) {
11197 				auto i = new Win32Icon!(16);
11198 				i.fromPNG(png, pngh, icon_len, width, height);
11199 				icon_win32 = i;
11200 			}
11201 			else if(pngh.depth == 8) {
11202 				auto i = new Win32Icon!(256);
11203 				i.fromPNG(png, pngh, icon_len, width, height);
11204 				icon_win32 = i;
11205 			} else assert(0);
11206 			*/
11207 
11208 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
11209 
11210 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11211 		}
11212 
11213 		~this() {
11214 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11215 			DestroyIcon(hIcon);
11216 		}
11217 
11218 		HICON hIcon;
11219 	}
11220 
11221 
11222 
11223 
11224 
11225 
11226 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11227 	alias HWND NativeWindowHandle;
11228 
11229 	extern(Windows)
11230 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11231 		try {
11232 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11233 				// it returns zero if the message is handled, so we won't do anything more there
11234 				// do I like that though?
11235 				int mustReturn;
11236 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11237 				if(mustReturn)
11238 					return ret;
11239 			}
11240 
11241 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11242 				if(window.getNativeEventHandler !is null) {
11243 					int mustReturn;
11244 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11245 					if(mustReturn)
11246 						return ret;
11247 				}
11248 				if(auto w = cast(SimpleWindow) (*window))
11249 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11250 				else
11251 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11252 			} else {
11253 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11254 			}
11255 		} catch (Exception e) {
11256 			try {
11257 				sdpy_abort(e);
11258 				return 0;
11259 			} catch(Exception e) { assert(0); }
11260 		}
11261 	}
11262 
11263 	void sdpy_abort(Throwable e) nothrow {
11264 		try
11265 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11266 		catch(Exception e)
11267 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11268 		ExitProcess(1);
11269 	}
11270 
11271 	mixin template NativeScreenPainterImplementation() {
11272 		HDC hdc;
11273 		HWND hwnd;
11274 		//HDC windowHdc;
11275 		HBITMAP oldBmp;
11276 
11277 		void create(PaintingHandle window) {
11278 			hwnd = window;
11279 
11280 			if(auto sw = cast(SimpleWindow) this.window) {
11281 				// drawing on a window, double buffer
11282 				auto windowHdc = GetDC(hwnd);
11283 
11284 				auto buffer = sw.impl.buffer;
11285 				if(buffer is null) {
11286 					hdc = windowHdc;
11287 					windowDc = true;
11288 				} else {
11289 					hdc = CreateCompatibleDC(windowHdc);
11290 
11291 					ReleaseDC(hwnd, windowHdc);
11292 
11293 					oldBmp = SelectObject(hdc, buffer);
11294 				}
11295 			} else {
11296 				// drawing on something else, draw directly
11297 				hdc = CreateCompatibleDC(null);
11298 				SelectObject(hdc, window);
11299 			}
11300 
11301 			// X doesn't draw a text background, so neither should we
11302 			SetBkMode(hdc, TRANSPARENT);
11303 
11304 			ensureDefaultFontLoaded();
11305 
11306 			if(defaultGuiFont) {
11307 				SelectObject(hdc, defaultGuiFont);
11308 				// DeleteObject(defaultGuiFont);
11309 			}
11310 		}
11311 
11312 		static HFONT defaultGuiFont;
11313 		static void ensureDefaultFontLoaded() {
11314 			static bool triedDefaultGuiFont = false;
11315 			if(!triedDefaultGuiFont) {
11316 				NONCLIENTMETRICS params;
11317 				params.cbSize = params.sizeof;
11318 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11319 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11320 				}
11321 				triedDefaultGuiFont = true;
11322 			}
11323 		}
11324 
11325 		private OperatingSystemFont _activeFont;
11326 
11327 		void setFont(OperatingSystemFont font) {
11328 			_activeFont = font;
11329 			if(font && font.font) {
11330 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11331 					// error... how to handle tho?
11332 				} else {
11333 
11334 				}
11335 			}
11336 			else if(defaultGuiFont)
11337 				SelectObject(hdc, defaultGuiFont);
11338 		}
11339 
11340 		arsd.color.Rectangle _clipRectangle;
11341 
11342 		void setClipRectangle(int x, int y, int width, int height) {
11343 			auto old = _clipRectangle;
11344 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11345 			if(old == _clipRectangle)
11346 				return;
11347 
11348 			if(width == 0 || height == 0) {
11349 				SelectClipRgn(hdc, null);
11350 			} else {
11351 				auto region = CreateRectRgn(x, y, x + width, y + height);
11352 				SelectClipRgn(hdc, region);
11353 				DeleteObject(region);
11354 			}
11355 		}
11356 
11357 
11358 		// just because we can on Windows...
11359 		//void create(Image image);
11360 
11361 		void invalidateRect(Rectangle invalidRect) {
11362 			RECT rect;
11363 			rect.left = invalidRect.left;
11364 			rect.right = invalidRect.right;
11365 			rect.top = invalidRect.top;
11366 			rect.bottom = invalidRect.bottom;
11367 			InvalidateRect(hwnd, &rect, false);
11368 		}
11369 		bool manualInvalidations;
11370 
11371 		void dispose() {
11372 			// FIXME: this.window.width/height is probably wrong
11373 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11374 			// ReleaseDC(hwnd, windowHdc);
11375 
11376 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11377 			if(cast(SimpleWindow) this.window) {
11378 				if(!manualInvalidations)
11379 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11380 			}
11381 
11382 			if(originalPen !is null)
11383 				SelectObject(hdc, originalPen);
11384 			if(currentPen !is null)
11385 				DeleteObject(currentPen);
11386 			if(originalBrush !is null)
11387 				SelectObject(hdc, originalBrush);
11388 			if(currentBrush !is null)
11389 				DeleteObject(currentBrush);
11390 
11391 			SelectObject(hdc, oldBmp);
11392 
11393 			if(windowDc)
11394 				ReleaseDC(hwnd, hdc);
11395 			else
11396 				DeleteDC(hdc);
11397 
11398 			if(window.paintingFinishedDg !is null)
11399 				window.paintingFinishedDg()();
11400 		}
11401 
11402 		bool windowDc;
11403 		HPEN originalPen;
11404 		HPEN currentPen;
11405 
11406 		Pen _activePen;
11407 
11408 		Color _outlineColor;
11409 
11410 		@property void pen(Pen p) {
11411 			_activePen = p;
11412 			_outlineColor = p.color;
11413 
11414 			HPEN pen;
11415 			if(p.color.a == 0) {
11416 				pen = GetStockObject(NULL_PEN);
11417 			} else {
11418 				int style = PS_SOLID;
11419 				final switch(p.style) {
11420 					case Pen.Style.Solid:
11421 						style = PS_SOLID;
11422 					break;
11423 					case Pen.Style.Dashed:
11424 						style = PS_DASH;
11425 					break;
11426 					case Pen.Style.Dotted:
11427 						style = PS_DOT;
11428 					break;
11429 				}
11430 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11431 			}
11432 			auto orig = SelectObject(hdc, pen);
11433 			if(originalPen is null)
11434 				originalPen = orig;
11435 
11436 			if(currentPen !is null)
11437 				DeleteObject(currentPen);
11438 
11439 			currentPen = pen;
11440 
11441 			// the outline is like a foreground since it's done that way on X
11442 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11443 
11444 		}
11445 
11446 		@property void rasterOp(RasterOp op) {
11447 			int mode;
11448 			final switch(op) {
11449 				case RasterOp.normal:
11450 					mode = R2_COPYPEN;
11451 				break;
11452 				case RasterOp.xor:
11453 					mode = R2_XORPEN;
11454 				break;
11455 			}
11456 			SetROP2(hdc, mode);
11457 		}
11458 
11459 		HBRUSH originalBrush;
11460 		HBRUSH currentBrush;
11461 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11462 		@property void fillColor(Color c) {
11463 			if(c == _fillColor)
11464 				return;
11465 			_fillColor = c;
11466 			HBRUSH brush;
11467 			if(c.a == 0) {
11468 				brush = GetStockObject(HOLLOW_BRUSH);
11469 			} else {
11470 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11471 			}
11472 			auto orig = SelectObject(hdc, brush);
11473 			if(originalBrush is null)
11474 				originalBrush = orig;
11475 
11476 			if(currentBrush !is null)
11477 				DeleteObject(currentBrush);
11478 
11479 			currentBrush = brush;
11480 
11481 			// background color is NOT set because X doesn't draw text backgrounds
11482 			//   SetBkColor(hdc, RGB(255, 255, 255));
11483 		}
11484 
11485 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11486 			BITMAP bm;
11487 
11488 			HDC hdcMem = CreateCompatibleDC(hdc);
11489 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11490 
11491 			GetObject(i.handle, bm.sizeof, &bm);
11492 
11493 			// or should I AlphaBlend!??!?!
11494 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11495 
11496 			SelectObject(hdcMem, hbmOld);
11497 			DeleteDC(hdcMem);
11498 		}
11499 
11500 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11501 			BITMAP bm;
11502 
11503 			HDC hdcMem = CreateCompatibleDC(hdc);
11504 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11505 
11506 			GetObject(s.handle, bm.sizeof, &bm);
11507 
11508 			version(CRuntime_DigitalMars) goto noalpha;
11509 
11510 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11511 			if(s.enableAlpha) {
11512 				auto dw = w ? w : bm.bmWidth;
11513 				auto dh = h ? h : bm.bmHeight;
11514 				BLENDFUNCTION bf;
11515 				bf.BlendOp = AC_SRC_OVER;
11516 				bf.SourceConstantAlpha = 255;
11517 				bf.AlphaFormat = AC_SRC_ALPHA;
11518 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11519 			} else {
11520 				noalpha:
11521 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11522 			}
11523 
11524 			SelectObject(hdcMem, hbmOld);
11525 			DeleteDC(hdcMem);
11526 		}
11527 
11528 		Size textSize(scope const(char)[] text) {
11529 			bool dummyX;
11530 			if(text.length == 0) {
11531 				text = " ";
11532 				dummyX = true;
11533 			}
11534 			RECT rect;
11535 			WCharzBuffer buffer = WCharzBuffer(text);
11536 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11537 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11538 		}
11539 
11540 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11541 			if(text.length && text[$-1] == '\n')
11542 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11543 			if(text.length && text[$-1] == '\r')
11544 				text = text[0 .. $-1];
11545 
11546 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11547 			if(x2 == 0 && y2 == 0) {
11548 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11549 			} else {
11550 				RECT rect;
11551 				rect.left = x;
11552 				rect.top = y;
11553 				rect.right = x2;
11554 				rect.bottom = y2;
11555 
11556 				uint mode = DT_LEFT;
11557 				if(alignment & TextAlignment.Right)
11558 					mode = DT_RIGHT;
11559 				else if(alignment & TextAlignment.Center)
11560 					mode = DT_CENTER;
11561 
11562 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11563 				if(alignment & TextAlignment.VerticalCenter)
11564 					mode |= DT_VCENTER | DT_SINGLELINE;
11565 
11566 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11567 			}
11568 
11569 			/*
11570 			uint mode;
11571 
11572 			if(alignment & TextAlignment.Center)
11573 				mode = TA_CENTER;
11574 
11575 			SetTextAlign(hdc, mode);
11576 			*/
11577 		}
11578 
11579 		int fontHeight() {
11580 			TEXTMETRIC metric;
11581 			if(GetTextMetricsW(hdc, &metric)) {
11582 				return metric.tmHeight;
11583 			}
11584 
11585 			return 16; // idk just guessing here, maybe we should throw
11586 		}
11587 
11588 		void drawPixel(int x, int y) {
11589 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11590 		}
11591 
11592 		// The basic shapes, outlined
11593 
11594 		void drawLine(int x1, int y1, int x2, int y2) {
11595 			MoveToEx(hdc, x1, y1, null);
11596 			LineTo(hdc, x2, y2);
11597 		}
11598 
11599 		void drawRectangle(int x, int y, int width, int height) {
11600 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11601 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11602 		}
11603 
11604 		/// Arguments are the points of the bounding rectangle
11605 		void drawEllipse(int x1, int y1, int x2, int y2) {
11606 			Ellipse(hdc, x1, y1, x2, y2);
11607 		}
11608 
11609 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11610 			if((start % (360*64)) == (finish % (360*64)))
11611 				drawEllipse(x1, y1, x1 + width, y1 + height);
11612 			else {
11613 				import core.stdc.math;
11614 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11615 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11616 
11617 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11618 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11619 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11620 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11621 
11622 				if(_activePen.color.a)
11623 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11624 				if(_fillColor.a)
11625 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11626 			}
11627 		}
11628 
11629 		void drawPolygon(Point[] vertexes) {
11630 			POINT[] points;
11631 			points.length = vertexes.length;
11632 
11633 			foreach(i, p; vertexes) {
11634 				points[i].x = p.x;
11635 				points[i].y = p.y;
11636 			}
11637 
11638 			Polygon(hdc, points.ptr, cast(int) points.length);
11639 		}
11640 	}
11641 
11642 
11643 	// Mix this into the SimpleWindow class
11644 	mixin template NativeSimpleWindowImplementation() {
11645 		int curHidden = 0; // counter
11646 		__gshared static bool[string] knownWinClasses;
11647 		static bool altPressed = false;
11648 
11649 		HANDLE oldCursor;
11650 
11651 		void hideCursor () {
11652 			if(curHidden == 0)
11653 				oldCursor = SetCursor(null);
11654 			++curHidden;
11655 		}
11656 
11657 		void showCursor () {
11658 			--curHidden;
11659 			if(curHidden == 0) {
11660 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11661 			}
11662 		}
11663 
11664 
11665 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11666 
11667 		void setMinSize (int minwidth, int minheight) {
11668 			minWidth = minwidth;
11669 			minHeight = minheight;
11670 		}
11671 		void setMaxSize (int maxwidth, int maxheight) {
11672 			maxWidth = maxwidth;
11673 			maxHeight = maxheight;
11674 		}
11675 
11676 		// FIXME i'm not sure that Windows has this functionality
11677 		// though it is nonessential anyway.
11678 		void setResizeGranularity (int granx, int grany) {}
11679 
11680 		ScreenPainter getPainter(bool manualInvalidations) {
11681 			return ScreenPainter(this, hwnd, manualInvalidations);
11682 		}
11683 
11684 		HBITMAP buffer;
11685 
11686 		void setTitle(string title) {
11687 			WCharzBuffer bfr = WCharzBuffer(title);
11688 			SetWindowTextW(hwnd, bfr.ptr);
11689 		}
11690 
11691 		string getTitle() {
11692 			auto len = GetWindowTextLengthW(hwnd);
11693 			if (!len)
11694 				return null;
11695 			wchar[256] tmpBuffer;
11696 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11697 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11698 			auto str = buffer[0 .. len2];
11699 			return makeUtf8StringFromWindowsString(str);
11700 		}
11701 
11702 		void move(int x, int y) {
11703 			RECT rect;
11704 			GetWindowRect(hwnd, &rect);
11705 			// move it while maintaining the same size...
11706 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11707 		}
11708 
11709 		void resize(int w, int h) {
11710 			RECT rect;
11711 			GetWindowRect(hwnd, &rect);
11712 
11713 			RECT client;
11714 			GetClientRect(hwnd, &client);
11715 
11716 			rect.right = rect.right - client.right + w;
11717 			rect.bottom = rect.bottom - client.bottom + h;
11718 
11719 			// same position, new size for the client rectangle
11720 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11721 
11722 			updateOpenglViewportIfNeeded(w, h);
11723 		}
11724 
11725 		void moveResize (int x, int y, int w, int h) {
11726 			// what's given is the client rectangle, we need to adjust
11727 
11728 			RECT rect;
11729 			rect.left = x;
11730 			rect.top = y;
11731 			rect.right = w + x;
11732 			rect.bottom = h + y;
11733 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11734 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
11735 
11736 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11737 			updateOpenglViewportIfNeeded(w, h);
11738 			if (windowResized !is null) windowResized(w, h);
11739 		}
11740 
11741 		version(without_opengl) {} else {
11742 			HGLRC ghRC;
11743 			HDC ghDC;
11744 		}
11745 
11746 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11747 			string cnamec;
11748 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11749 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11750 				cnamec = "DSimpleWindow";
11751 			} else {
11752 				cnamec = sdpyWindowClass;
11753 			}
11754 
11755 			WCharzBuffer cn = WCharzBuffer(cnamec);
11756 
11757 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11758 
11759 			if(cnamec !in knownWinClasses) {
11760 				WNDCLASSEX wc;
11761 
11762 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11763 				// to the object. Maybe.
11764 				wc.cbSize = wc.sizeof;
11765 				wc.cbClsExtra = 0;
11766 				wc.cbWndExtra = 0;
11767 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11768 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11769 				wc.hIcon = LoadIcon(hInstance, null);
11770 				wc.hInstance = hInstance;
11771 				wc.lpfnWndProc = &WndProc;
11772 				wc.lpszClassName = cn.ptr;
11773 				wc.hIconSm = null;
11774 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11775 				if(!RegisterClassExW(&wc))
11776 					throw new WindowsApiException("RegisterClassExW", GetLastError());
11777 				knownWinClasses[cnamec] = true;
11778 			}
11779 
11780 			int style;
11781 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11782 
11783 			// FIXME: windowType and customizationFlags
11784 			final switch(windowType) {
11785 				case WindowTypes.normal:
11786 					if(resizability == Resizability.fixedSize) {
11787 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11788 					} else {
11789 						style = WS_OVERLAPPEDWINDOW;
11790 					}
11791 				break;
11792 				case WindowTypes.undecorated:
11793 					style = WS_POPUP | WS_SYSMENU;
11794 				break;
11795 				case WindowTypes.eventOnly:
11796 					_hidden = true;
11797 				break;
11798 				case WindowTypes.dropdownMenu:
11799 				case WindowTypes.popupMenu:
11800 				case WindowTypes.notification:
11801 					style = WS_POPUP;
11802 					flags |= WS_EX_NOACTIVATE;
11803 				break;
11804 				case WindowTypes.nestedChild:
11805 					style = WS_CHILD;
11806 				break;
11807 				case WindowTypes.minimallyWrapped:
11808 					assert(0, "construct minimally wrapped through the other ctor overlad");
11809 			}
11810 
11811 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11812 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11813 
11814 			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
11815 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11816 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11817 
11818 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11819 				setOpacity(255);
11820 
11821 			SimpleWindow.nativeMapping[hwnd] = this;
11822 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11823 
11824 			if(windowType == WindowTypes.eventOnly)
11825 				return;
11826 
11827 			HDC hdc = GetDC(hwnd);
11828 
11829 
11830 			version(without_opengl) {}
11831 			else {
11832 				if(opengl == OpenGlOptions.yes) {
11833 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11834 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11835 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11836 					ghDC = hdc;
11837 					PIXELFORMATDESCRIPTOR pfd;
11838 
11839 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11840 					pfd.nVersion = 1;
11841 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11842 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11843 					pfd.iPixelType = PFD_TYPE_RGBA;
11844 					pfd.cColorBits = 24;
11845 					pfd.cDepthBits = 24;
11846 					pfd.cAccumBits = 0;
11847 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11848 
11849 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11850 
11851 					if (pixelformat == 0)
11852 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
11853 
11854 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11855 						throw new WindowsApiException("SetPixelFormat", GetLastError());
11856 
11857 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11858 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11859 						// so we will create fake context to get that stupid address
11860 						auto tmpcc = wglCreateContext(ghDC);
11861 						if (tmpcc !is null) {
11862 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11863 							wglMakeCurrent(ghDC, tmpcc);
11864 							wglInitOtherFunctions();
11865 						}
11866 					}
11867 
11868 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11869 						int[9] contextAttribs = [
11870 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11871 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11872 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11873 							// for modern context, set "forward compatibility" flag too
11874 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11875 							0/*None*/,
11876 						];
11877 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11878 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11879 							// activate fallback mode
11880 							// 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;
11881 							ghRC = wglCreateContext(ghDC);
11882 						}
11883 						if (ghRC is null)
11884 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
11885 					} else {
11886 						// try to do at least something
11887 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11888 							sdpyOpenGLContextVersion = 0;
11889 							ghRC = wglCreateContext(ghDC);
11890 						}
11891 						if (ghRC is null)
11892 							throw new WindowsApiException("wglCreateContext", GetLastError());
11893 					}
11894 				}
11895 			}
11896 
11897 			if(opengl == OpenGlOptions.no) {
11898 				buffer = CreateCompatibleBitmap(hdc, width, height);
11899 
11900 				auto hdcBmp = CreateCompatibleDC(hdc);
11901 				// make sure it's filled with a blank slate
11902 				auto oldBmp = SelectObject(hdcBmp, buffer);
11903 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11904 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11905 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11906 				SelectObject(hdcBmp, oldBmp);
11907 				SelectObject(hdcBmp, oldBrush);
11908 				SelectObject(hdcBmp, oldPen);
11909 				DeleteDC(hdcBmp);
11910 
11911 				bmpWidth = width;
11912 				bmpHeight = height;
11913 
11914 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11915 			}
11916 
11917 			// We want the window's client area to match the image size
11918 			RECT rcClient, rcWindow;
11919 			POINT ptDiff;
11920 			GetClientRect(hwnd, &rcClient);
11921 			GetWindowRect(hwnd, &rcWindow);
11922 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11923 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11924 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11925 
11926 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11927 				ShowWindow(hwnd, SW_SHOWNORMAL);
11928 			} else {
11929 				_hidden = true;
11930 			}
11931 			this._visibleForTheFirstTimeCalled = false; // hack!
11932 		}
11933 
11934 
11935 		void dispose() {
11936 			if(buffer)
11937 				DeleteObject(buffer);
11938 		}
11939 
11940 		void closeWindow() {
11941 			if(ghRC) {
11942 				wglDeleteContext(ghRC);
11943 				ghRC = null;
11944 			}
11945 			DestroyWindow(hwnd);
11946 		}
11947 
11948 		bool setOpacity(ubyte alpha) {
11949 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11950 		}
11951 
11952 		HANDLE currentCursor;
11953 
11954 		// returns zero if it recognized the event
11955 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11956 			MouseEvent mouse;
11957 
11958 			void mouseEvent(bool isScreen, ulong mods) {
11959 				auto x = LOWORD(lParam);
11960 				auto y = HIWORD(lParam);
11961 				if(isScreen) {
11962 					POINT p;
11963 					p.x = x;
11964 					p.y = y;
11965 					ScreenToClient(hwnd, &p);
11966 					x = cast(ushort) p.x;
11967 					y = cast(ushort) p.y;
11968 				}
11969 
11970 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11971 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11972 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11973 				}
11974 
11975 				mouse.x = x + offsetX;
11976 				mouse.y = y + offsetY;
11977 
11978 				wind.mdx(mouse);
11979 				mouse.modifierState = cast(int) mods;
11980 				mouse.window = wind;
11981 
11982 				if(wind.handleMouseEvent)
11983 					wind.handleMouseEvent(mouse);
11984 			}
11985 
11986 			switch(msg) {
11987 				case WM_GETMINMAXINFO:
11988 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11989 
11990 					if(wind.minWidth > 0) {
11991 						RECT rect;
11992 						rect.left = 100;
11993 						rect.top = 100;
11994 						rect.right = wind.minWidth + 100;
11995 						rect.bottom = wind.minHeight + 100;
11996 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11997 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
11998 
11999 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12000 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12001 					}
12002 
12003 					if(wind.maxWidth < int.max) {
12004 						RECT rect;
12005 						rect.left = 100;
12006 						rect.top = 100;
12007 						rect.right = wind.maxWidth + 100;
12008 						rect.bottom = wind.maxHeight + 100;
12009 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12010 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12011 
12012 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12013 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12014 					}
12015 				break;
12016 				case WM_CHAR:
12017 					wchar c = cast(wchar) wParam;
12018 					if(wind.handleCharEvent)
12019 						wind.handleCharEvent(cast(dchar) c);
12020 				break;
12021 				  case WM_SETFOCUS:
12022 				  case WM_KILLFOCUS:
12023 					wind._focused = (msg == WM_SETFOCUS);
12024 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12025 					if(wind.onFocusChange)
12026 						wind.onFocusChange(msg == WM_SETFOCUS);
12027 				  break;
12028 
12029 				case WM_SYSKEYDOWN:
12030 					goto case;
12031 				case WM_SYSKEYUP:
12032 					if(lParam & (1 << 29)) {
12033 						goto case;
12034 					} else {
12035 						// no window has keyboard focus
12036 						goto default;
12037 					}
12038 				case WM_KEYDOWN:
12039 				case WM_KEYUP:
12040 					KeyEvent ev;
12041 					ev.key = cast(Key) wParam;
12042 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12043 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12044 
12045 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12046 
12047 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12048 						ev.modifierState |= ModifierState.shift;
12049 					//k8: this doesn't work; thanks for nothing, windows
12050 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12051 						ev.modifierState |= ModifierState.alt;*/
12052 					// this never seems to actually be set
12053 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12054 
12055 					if (wParam == 0x12) {
12056 						altPressed = (msg == WM_SYSKEYDOWN);
12057 					}
12058 
12059 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12060 						altPressed = false;
12061 					}
12062 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12063 
12064 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12065 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12066 						ev.modifierState |= ModifierState.ctrl;
12067 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12068 						ev.modifierState |= ModifierState.windows;
12069 					if(GetKeyState(Key.NumLock))
12070 						ev.modifierState |= ModifierState.numLock;
12071 					if(GetKeyState(Key.CapsLock))
12072 						ev.modifierState |= ModifierState.capsLock;
12073 
12074 					/+
12075 					// we always want to send the character too, so let's convert it
12076 					ubyte[256] state;
12077 					wchar[16] buffer;
12078 					GetKeyboardState(state.ptr);
12079 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12080 
12081 					foreach(dchar d; buffer) {
12082 						ev.character = d;
12083 						break;
12084 					}
12085 					+/
12086 
12087 					ev.window = wind;
12088 					if(wind.handleKeyEvent)
12089 						wind.handleKeyEvent(ev);
12090 				break;
12091 				case 0x020a /*WM_MOUSEWHEEL*/:
12092 					// send click
12093 					mouse.type = cast(MouseEventType) 1;
12094 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12095 					mouseEvent(true, LOWORD(wParam));
12096 
12097 					// also send release
12098 					mouse.type = cast(MouseEventType) 2;
12099 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12100 					mouseEvent(true, LOWORD(wParam));
12101 				break;
12102 				case WM_MOUSEMOVE:
12103 					mouse.type = cast(MouseEventType) 0;
12104 					mouseEvent(false, wParam);
12105 				break;
12106 				case WM_LBUTTONDOWN:
12107 				case WM_LBUTTONDBLCLK:
12108 					mouse.type = cast(MouseEventType) 1;
12109 					mouse.button = MouseButton.left;
12110 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12111 					mouseEvent(false, wParam);
12112 				break;
12113 				case WM_LBUTTONUP:
12114 					mouse.type = cast(MouseEventType) 2;
12115 					mouse.button = MouseButton.left;
12116 					mouseEvent(false, wParam);
12117 				break;
12118 				case WM_RBUTTONDOWN:
12119 				case WM_RBUTTONDBLCLK:
12120 					mouse.type = cast(MouseEventType) 1;
12121 					mouse.button = MouseButton.right;
12122 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12123 					mouseEvent(false, wParam);
12124 				break;
12125 				case WM_RBUTTONUP:
12126 					mouse.type = cast(MouseEventType) 2;
12127 					mouse.button = MouseButton.right;
12128 					mouseEvent(false, wParam);
12129 				break;
12130 				case WM_MBUTTONDOWN:
12131 				case WM_MBUTTONDBLCLK:
12132 					mouse.type = cast(MouseEventType) 1;
12133 					mouse.button = MouseButton.middle;
12134 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12135 					mouseEvent(false, wParam);
12136 				break;
12137 				case WM_MBUTTONUP:
12138 					mouse.type = cast(MouseEventType) 2;
12139 					mouse.button = MouseButton.middle;
12140 					mouseEvent(false, wParam);
12141 				break;
12142 				case WM_XBUTTONDOWN:
12143 				case WM_XBUTTONDBLCLK:
12144 					mouse.type = cast(MouseEventType) 1;
12145 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12146 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12147 					mouseEvent(false, wParam);
12148 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12149 				case WM_XBUTTONUP:
12150 					mouse.type = cast(MouseEventType) 2;
12151 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12152 					mouseEvent(false, wParam);
12153 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12154 
12155 				default: return 1;
12156 			}
12157 			return 0;
12158 		}
12159 
12160 		HWND hwnd;
12161 		private int oldWidth;
12162 		private int oldHeight;
12163 		private bool inSizeMove;
12164 
12165 		/++
12166 			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.
12167 
12168 			History:
12169 				Added November 23, 2021
12170 
12171 				Not fully stable, may be moved out of the impl struct.
12172 
12173 				Default value changed to `true` on February 15, 2021
12174 		+/
12175 		bool doLiveResizing = true;
12176 
12177 		package int bmpWidth;
12178 		package int bmpHeight;
12179 
12180 		// the extern(Windows) wndproc should just forward to this
12181 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12182 		try {
12183 			assert(hwnd is this.hwnd);
12184 
12185 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12186 			switch(msg) {
12187 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12188 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12189 					// The main things we can do are select, execute, close, or ignore
12190 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12191 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12192 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12193 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12194 
12195 					// returns the value in the *high order word* of the return value
12196 					// hence the << 16
12197 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12198 				case WM_SETCURSOR:
12199 					if(cast(HWND) wParam !is hwnd)
12200 						return 0; // further processing elsewhere
12201 
12202 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12203 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12204 						return 1;
12205 					} else {
12206 						return DefWindowProc(hwnd, msg, wParam, lParam);
12207 					}
12208 				//break;
12209 
12210 				case WM_CLOSE:
12211 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12212 				break;
12213 				case WM_DESTROY:
12214 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12215 					SimpleWindow.nativeMapping.remove(hwnd);
12216 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12217 
12218 					bool anyImportant = false;
12219 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12220 						if(w.beingOpenKeepsAppOpen) {
12221 							anyImportant = true;
12222 							break;
12223 						}
12224 					if(!anyImportant) {
12225 						PostQuitMessage(0);
12226 					}
12227 				break;
12228 				case 0x02E0 /*WM_DPICHANGED*/:
12229 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12230 
12231 					RECT* prcNewWindow = cast(RECT*)lParam;
12232 					// docs say this is the recommended position and we should honor it
12233 					SetWindowPos(hwnd,
12234 							null,
12235 							prcNewWindow.left,
12236 							prcNewWindow.top,
12237 							prcNewWindow.right - prcNewWindow.left,
12238 							prcNewWindow.bottom - prcNewWindow.top,
12239 							SWP_NOZORDER | SWP_NOACTIVATE);
12240 
12241 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12242 					// im not sure it is completely correct
12243 					// but without it the tabs and such do look weird as things change.
12244 					if(SystemParametersInfoForDpi) {
12245 						LOGFONT lfText;
12246 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12247 						HFONT hFontNew = CreateFontIndirect(&lfText);
12248 						if (hFontNew)
12249 						{
12250 							//DeleteObject(hFontOld);
12251 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12252 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12253 								return TRUE;
12254 							}
12255 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12256 						}
12257 					}
12258 
12259 					if(this.onDpiChanged)
12260 						this.onDpiChanged();
12261 				break;
12262 				case WM_ENTERIDLE:
12263 					// when a menu is up, it stops normal event processing (modal message loop)
12264 					// but this at least gives us a chance to SOMETIMES catch up
12265 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12266 					SimpleWindow.processAllCustomEvents;
12267 					SimpleWindow.processAllCustomEvents;
12268 					SleepEx(0, true);
12269 					break;
12270 				case WM_SIZE:
12271 					if(wParam == 1 /* SIZE_MINIMIZED */)
12272 						break;
12273 					_width = LOWORD(lParam);
12274 					_height = HIWORD(lParam);
12275 
12276 					// I want to avoid tearing in the windows (my code is inefficient
12277 					// so this is a hack around that) so while sizing, we don't trigger,
12278 					// but we do want to trigger on events like mazimize.
12279 					if(!inSizeMove || doLiveResizing)
12280 						goto size_changed;
12281 				break;
12282 				/+
12283 				case WM_SIZING:
12284 					writeln("size");
12285 				break;
12286 				+/
12287 				// I don't like the tearing I get when redrawing on WM_SIZE
12288 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12289 				// so instead it is going to redraw only at the end of a size.
12290 				case 0x0231: /* WM_ENTERSIZEMOVE */
12291 					inSizeMove = true;
12292 				break;
12293 				case 0x0232: /* WM_EXITSIZEMOVE */
12294 					inSizeMove = false;
12295 
12296 					size_changed:
12297 
12298 					// nothing relevant changed, don't bother redrawing
12299 					if(oldWidth == _width && oldHeight == _height) {
12300 						break;
12301 					}
12302 
12303 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12304 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12305 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12306 						// gotta get the double buffer bmp to match the window
12307 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12308 
12309 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12310 						if(resizability != Resizability.automaticallyScaleIfPossible)
12311 						if(_width > bmpWidth || _height > bmpHeight) {
12312 							auto hdc = GetDC(hwnd);
12313 							auto oldBuffer = buffer;
12314 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12315 
12316 							auto hdcBmp = CreateCompatibleDC(hdc);
12317 							auto oldBmp = SelectObject(hdcBmp, buffer);
12318 
12319 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12320 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12321 
12322 							/+
12323 							RECT r;
12324 							r.left = 0;
12325 							r.top = 0;
12326 							r.right = width;
12327 							r.bottom = height;
12328 							auto c = Color.green;
12329 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12330 							FillRect(hdcBmp, &r, brush);
12331 							DeleteObject(brush);
12332 							+/
12333 
12334 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12335 
12336 							bmpWidth = _width;
12337 							bmpHeight = _height;
12338 
12339 							SelectObject(hdcOldBmp, oldOldBmp);
12340 							DeleteDC(hdcOldBmp);
12341 
12342 							SelectObject(hdcBmp, oldBmp);
12343 							DeleteDC(hdcBmp);
12344 
12345 							ReleaseDC(hwnd, hdc);
12346 
12347 							DeleteObject(oldBuffer);
12348 						}
12349 					}
12350 
12351 					updateOpenglViewportIfNeeded(_width, _height);
12352 
12353 					if(resizability != Resizability.automaticallyScaleIfPossible)
12354 					if(windowResized !is null)
12355 						windowResized(_width, _height);
12356 
12357 					if(inSizeMove) {
12358 						SimpleWindow.processAllCustomEvents();
12359 						SimpleWindow.processAllCustomEvents();
12360 					} else {
12361 						// when it is all done, make sure everything is freshly drawn or there might be
12362 						// weird bugs left.
12363 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12364 					}
12365 
12366 					oldWidth = this._width;
12367 					oldHeight = this._height;
12368 				break;
12369 				case WM_ERASEBKGND:
12370 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12371 					if (!this._visibleForTheFirstTimeCalled) {
12372 						this._visibleForTheFirstTimeCalled = true;
12373 						if (this.visibleForTheFirstTime !is null) {
12374 							this.visibleForTheFirstTime();
12375 						}
12376 					}
12377 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12378 					version(without_opengl) {} else {
12379 						if (openglMode == OpenGlOptions.yes) return 1;
12380 					}
12381 					// call windows default handler, so it can paint standard controls
12382 					goto default;
12383 				case WM_CTLCOLORBTN:
12384 				case WM_CTLCOLORSTATIC:
12385 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12386 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12387 					GetSysColorBrush(COLOR_3DFACE);
12388 				//break;
12389 				case WM_SHOWWINDOW:
12390 					this._visible = (wParam != 0);
12391 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12392 						this._visibleForTheFirstTimeCalled = true;
12393 						if (this.visibleForTheFirstTime !is null) {
12394 							this.visibleForTheFirstTime();
12395 						}
12396 					}
12397 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12398 					break;
12399 				case WM_PAINT: {
12400 					if (!this._visibleForTheFirstTimeCalled) {
12401 						this._visibleForTheFirstTimeCalled = true;
12402 						if (this.visibleForTheFirstTime !is null) {
12403 							this.visibleForTheFirstTime();
12404 						}
12405 					}
12406 
12407 					BITMAP bm;
12408 					PAINTSTRUCT ps;
12409 
12410 					HDC hdc = BeginPaint(hwnd, &ps);
12411 
12412 					if(openglMode == OpenGlOptions.no) {
12413 
12414 						HDC hdcMem = CreateCompatibleDC(hdc);
12415 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12416 
12417 						GetObject(buffer, bm.sizeof, &bm);
12418 
12419 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12420 						if(resizability == Resizability.automaticallyScaleIfPossible)
12421 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12422 						else
12423 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12424 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12425 
12426 						SelectObject(hdcMem, hbmOld);
12427 						DeleteDC(hdcMem);
12428 						EndPaint(hwnd, &ps);
12429 					} else {
12430 						EndPaint(hwnd, &ps);
12431 						version(without_opengl) {} else
12432 							redrawOpenGlSceneSoon();
12433 					}
12434 				} break;
12435 				  default:
12436 					return DefWindowProc(hwnd, msg, wParam, lParam);
12437 			}
12438 			 return 0;
12439 
12440 		}
12441 		catch(Throwable t) {
12442 			sdpyPrintDebugString(t.toString);
12443 			return 0;
12444 		}
12445 		}
12446 	}
12447 
12448 	mixin template NativeImageImplementation() {
12449 		HBITMAP handle;
12450 		ubyte* rawData;
12451 
12452 	final:
12453 
12454 		Color getPixel(int x, int y) {
12455 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12456 			// remember, bmps are upside down
12457 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12458 
12459 			Color c;
12460 			if(enableAlpha)
12461 				c.a = rawData[offset + 3];
12462 			else
12463 				c.a = 255;
12464 			c.b = rawData[offset + 0];
12465 			c.g = rawData[offset + 1];
12466 			c.r = rawData[offset + 2];
12467 			c.unPremultiply();
12468 			return c;
12469 		}
12470 
12471 		void setPixel(int x, int y, Color c) {
12472 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12473 			// remember, bmps are upside down
12474 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12475 
12476 			if(enableAlpha)
12477 				c.premultiply();
12478 
12479 			rawData[offset + 0] = c.b;
12480 			rawData[offset + 1] = c.g;
12481 			rawData[offset + 2] = c.r;
12482 			if(enableAlpha)
12483 				rawData[offset + 3] = c.a;
12484 		}
12485 
12486 		void convertToRgbaBytes(ubyte[] where) {
12487 			assert(where.length == this.width * this.height * 4);
12488 
12489 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12490 			int idx = 0;
12491 			int offset = itemsPerLine * (height - 1);
12492 			// remember, bmps are upside down
12493 			for(int y = height - 1; y >= 0; y--) {
12494 				auto offsetStart = offset;
12495 				for(int x = 0; x < width; x++) {
12496 					where[idx + 0] = rawData[offset + 2]; // r
12497 					where[idx + 1] = rawData[offset + 1]; // g
12498 					where[idx + 2] = rawData[offset + 0]; // b
12499 					if(enableAlpha) {
12500 						where[idx + 3] = rawData[offset + 3]; // a
12501 						unPremultiplyRgba(where[idx .. idx + 4]);
12502 						offset++;
12503 					} else
12504 						where[idx + 3] = 255; // a
12505 					idx += 4;
12506 					offset += 3;
12507 				}
12508 
12509 				offset = offsetStart - itemsPerLine;
12510 			}
12511 		}
12512 
12513 		void setFromRgbaBytes(in ubyte[] what) {
12514 			assert(what.length == this.width * this.height * 4);
12515 
12516 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12517 			int idx = 0;
12518 			int offset = itemsPerLine * (height - 1);
12519 			// remember, bmps are upside down
12520 			for(int y = height - 1; y >= 0; y--) {
12521 				auto offsetStart = offset;
12522 				for(int x = 0; x < width; x++) {
12523 					if(enableAlpha) {
12524 						auto a = what[idx + 3];
12525 
12526 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12527 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12528 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12529 						rawData[offset + 3] = a; // a
12530 						//premultiplyBgra(rawData[offset .. offset + 4]);
12531 						offset++;
12532 					} else {
12533 						rawData[offset + 2] = what[idx + 0]; // r
12534 						rawData[offset + 1] = what[idx + 1]; // g
12535 						rawData[offset + 0] = what[idx + 2]; // b
12536 					}
12537 					idx += 4;
12538 					offset += 3;
12539 				}
12540 
12541 				offset = offsetStart - itemsPerLine;
12542 			}
12543 		}
12544 
12545 
12546 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12547 			BITMAPINFO infoheader;
12548 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12549 			infoheader.bmiHeader.biWidth = width;
12550 			infoheader.bmiHeader.biHeight = height;
12551 			infoheader.bmiHeader.biPlanes = 1;
12552 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12553 			infoheader.bmiHeader.biCompression = BI_RGB;
12554 
12555 			handle = CreateDIBSection(
12556 				null,
12557 				&infoheader,
12558 				DIB_RGB_COLORS,
12559 				cast(void**) &rawData,
12560 				null,
12561 				0);
12562 			if(handle is null)
12563 				throw new WindowsApiException("create image failed", GetLastError());
12564 
12565 		}
12566 
12567 		void dispose() {
12568 			DeleteObject(handle);
12569 		}
12570 	}
12571 
12572 	enum KEY_ESCAPE = 27;
12573 }
12574 version(X11) {
12575 	/// This is the default font used. You might change this before doing anything else with
12576 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12577 	/// for cross-platform compatibility.
12578 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12579 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12580 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12581 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12582 
12583 	alias int delegate(XEvent) NativeEventHandler;
12584 	alias Window NativeWindowHandle;
12585 
12586 	enum KEY_ESCAPE = 9;
12587 
12588 	mixin template NativeScreenPainterImplementation() {
12589 		Display* display;
12590 		Drawable d;
12591 		Drawable destiny;
12592 
12593 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12594 		GC gc;
12595 
12596 		__gshared bool fontAttempted;
12597 
12598 		__gshared XFontStruct* defaultfont;
12599 		__gshared XFontSet defaultfontset;
12600 
12601 		XFontStruct* font;
12602 		XFontSet fontset;
12603 
12604 		void create(PaintingHandle window) {
12605 			this.display = XDisplayConnection.get();
12606 
12607 			Drawable buffer = None;
12608 			if(auto sw = cast(SimpleWindow) this.window) {
12609 				buffer = sw.impl.buffer;
12610 				this.destiny = cast(Drawable) window;
12611 			} else {
12612 				buffer = cast(Drawable) window;
12613 				this.destiny = None;
12614 			}
12615 
12616 			this.d = cast(Drawable) buffer;
12617 
12618 			auto dgc = DefaultGC(display, DefaultScreen(display));
12619 
12620 			this.gc = XCreateGC(display, d, 0, null);
12621 
12622 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12623 
12624 			ensureDefaultFontLoaded();
12625 
12626 			font = defaultfont;
12627 			fontset = defaultfontset;
12628 
12629 			if(font) {
12630 				XSetFont(display, gc, font.fid);
12631 			}
12632 		}
12633 
12634 		static void ensureDefaultFontLoaded() {
12635 			if(!fontAttempted) {
12636 				auto display = XDisplayConnection.get;
12637 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12638 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12639 				if(font is null) {
12640 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12641 					font = XLoadQueryFont(display, xfontstr.ptr);
12642 				}
12643 
12644 				char** lol;
12645 				int lol2;
12646 				char* lol3;
12647 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12648 
12649 				fontAttempted = true;
12650 
12651 				defaultfont = font;
12652 				defaultfontset = fontset;
12653 			}
12654 		}
12655 
12656 		arsd.color.Rectangle _clipRectangle;
12657 		void setClipRectangle(int x, int y, int width, int height) {
12658 			auto old = _clipRectangle;
12659 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12660 			if(old == _clipRectangle)
12661 				return;
12662 
12663 			if(width == 0 || height == 0) {
12664 				XSetClipMask(display, gc, None);
12665 
12666 				if(xrenderPicturePainter) {
12667 
12668 					XRectangle[1] rects;
12669 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12670 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12671 				}
12672 
12673 				version(with_xft) {
12674 					if(xftFont is null || xftDraw is null)
12675 						return;
12676 					XftDrawSetClip(xftDraw, null);
12677 				}
12678 			} else {
12679 				XRectangle[1] rects;
12680 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12681 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12682 
12683 				if(xrenderPicturePainter)
12684 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12685 
12686 				version(with_xft) {
12687 					if(xftFont is null || xftDraw is null)
12688 						return;
12689 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12690 				}
12691 			}
12692 		}
12693 
12694 		version(with_xft) {
12695 			XftFont* xftFont;
12696 			XftDraw* xftDraw;
12697 
12698 			XftColor xftColor;
12699 
12700 			void updateXftColor() {
12701 				if(xftFont is null)
12702 					return;
12703 
12704 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12705 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12706 
12707 				XftColorAllocValue(
12708 					display,
12709 					DefaultVisual(display, DefaultScreen(display)),
12710 					DefaultColormap(display, 0),
12711 					&colorIn,
12712 					&xftColor
12713 				);
12714 			}
12715 		}
12716 
12717 		private OperatingSystemFont _activeFont;
12718 		void setFont(OperatingSystemFont font) {
12719 			_activeFont = font;
12720 			version(with_xft) {
12721 				if(font && font.isXft && font.xftFont)
12722 					this.xftFont = font.xftFont;
12723 				else
12724 					this.xftFont = null;
12725 
12726 				if(this.xftFont) {
12727 					if(xftDraw is null) {
12728 						xftDraw = XftDrawCreate(
12729 							display,
12730 							d,
12731 							DefaultVisual(display, DefaultScreen(display)),
12732 							DefaultColormap(display, 0)
12733 						);
12734 
12735 						updateXftColor();
12736 					}
12737 
12738 					return;
12739 				}
12740 			}
12741 
12742 			if(font && font.font) {
12743 				this.font = font.font;
12744 				this.fontset = font.fontset;
12745 				XSetFont(display, gc, font.font.fid);
12746 			} else {
12747 				this.font = defaultfont;
12748 				this.fontset = defaultfontset;
12749 			}
12750 
12751 		}
12752 
12753 		private Picture xrenderPicturePainter;
12754 
12755 		bool manualInvalidations;
12756 		void invalidateRect(Rectangle invalidRect) {
12757 			// FIXME if manualInvalidations
12758 		}
12759 
12760 		void dispose() {
12761 			this.rasterOp = RasterOp.normal;
12762 
12763 			if(xrenderPicturePainter) {
12764 				XRenderFreePicture(display, xrenderPicturePainter);
12765 				xrenderPicturePainter = None;
12766 			}
12767 
12768 			// FIXME: this.window.width/height is probably wrong
12769 
12770 			// src x,y     then dest x, y
12771 			if(destiny != None) {
12772 				// FIXME: if manual invalidations we can actually only copy some of the area.
12773 				// if(manualInvalidations)
12774 				XSetClipMask(display, gc, None);
12775 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12776 			}
12777 
12778 			XFreeGC(display, gc);
12779 
12780 			version(with_xft)
12781 			if(xftDraw) {
12782 				XftDrawDestroy(xftDraw);
12783 				xftDraw = null;
12784 			}
12785 
12786 			/+
12787 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12788 			if(font && font !is defaultfont) {
12789 				XFreeFont(display, font);
12790 				font = null;
12791 			}
12792 			if(fontset && fontset !is defaultfontset) {
12793 				XFreeFontSet(display, fontset);
12794 				fontset = null;
12795 			}
12796 			+/
12797 			XFlush(display);
12798 
12799 			if(window.paintingFinishedDg !is null)
12800 				window.paintingFinishedDg()();
12801 		}
12802 
12803 		bool backgroundIsNotTransparent = true;
12804 		bool foregroundIsNotTransparent = true;
12805 
12806 		bool _penInitialized = false;
12807 		Pen _activePen;
12808 
12809 		Color _outlineColor;
12810 		Color _fillColor;
12811 
12812 		@property void pen(Pen p) {
12813 			if(_penInitialized && p == _activePen) {
12814 				return;
12815 			}
12816 			_penInitialized = true;
12817 			_activePen = p;
12818 			_outlineColor = p.color;
12819 
12820 			int style;
12821 
12822 			byte dashLength;
12823 
12824 			final switch(p.style) {
12825 				case Pen.Style.Solid:
12826 					style = 0 /*LineSolid*/;
12827 				break;
12828 				case Pen.Style.Dashed:
12829 					style = 1 /*LineOnOffDash*/;
12830 					dashLength = 4;
12831 				break;
12832 				case Pen.Style.Dotted:
12833 					style = 1 /*LineOnOffDash*/;
12834 					dashLength = 1;
12835 				break;
12836 			}
12837 
12838 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
12839 			if(dashLength)
12840 				XSetDashes(display, gc, 0, &dashLength, 1);
12841 
12842 			if(p.color.a == 0) {
12843 				foregroundIsNotTransparent = false;
12844 				return;
12845 			}
12846 
12847 			foregroundIsNotTransparent = true;
12848 
12849 			XSetForeground(display, gc, colorToX(p.color, display));
12850 
12851 			version(with_xft)
12852 				updateXftColor();
12853 		}
12854 
12855 		RasterOp _currentRasterOp;
12856 		bool _currentRasterOpInitialized = false;
12857 		@property void rasterOp(RasterOp op) {
12858 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12859 				return;
12860 			_currentRasterOp = op;
12861 			_currentRasterOpInitialized = true;
12862 			int mode;
12863 			final switch(op) {
12864 				case RasterOp.normal:
12865 					mode = GXcopy;
12866 				break;
12867 				case RasterOp.xor:
12868 					mode = GXxor;
12869 				break;
12870 			}
12871 			XSetFunction(display, gc, mode);
12872 		}
12873 
12874 
12875 		bool _fillColorInitialized = false;
12876 
12877 		@property void fillColor(Color c) {
12878 			if(_fillColorInitialized && _fillColor == c)
12879 				return; // already good, no need to waste time calling it
12880 			_fillColor = c;
12881 			_fillColorInitialized = true;
12882 			if(c.a == 0) {
12883 				backgroundIsNotTransparent = false;
12884 				return;
12885 			}
12886 
12887 			backgroundIsNotTransparent = true;
12888 
12889 			XSetBackground(display, gc, colorToX(c, display));
12890 
12891 		}
12892 
12893 		void swapColors() {
12894 			auto tmp = _fillColor;
12895 			fillColor = _outlineColor;
12896 			auto newPen = _activePen;
12897 			newPen.color = tmp;
12898 			pen(newPen);
12899 		}
12900 
12901 		uint colorToX(Color c, Display* display) {
12902 			auto visual = DefaultVisual(display, DefaultScreen(display));
12903 			import core.bitop;
12904 			uint color = 0;
12905 			{
12906 			auto startBit = bsf(visual.red_mask);
12907 			auto lastBit = bsr(visual.red_mask);
12908 			auto r = cast(uint) c.r;
12909 			r >>= 7 - (lastBit - startBit);
12910 			r <<= startBit;
12911 			color |= r;
12912 			}
12913 			{
12914 			auto startBit = bsf(visual.green_mask);
12915 			auto lastBit = bsr(visual.green_mask);
12916 			auto g = cast(uint) c.g;
12917 			g >>= 7 - (lastBit - startBit);
12918 			g <<= startBit;
12919 			color |= g;
12920 			}
12921 			{
12922 			auto startBit = bsf(visual.blue_mask);
12923 			auto lastBit = bsr(visual.blue_mask);
12924 			auto b = cast(uint) c.b;
12925 			b >>= 7 - (lastBit - startBit);
12926 			b <<= startBit;
12927 			color |= b;
12928 			}
12929 
12930 
12931 
12932 			return color;
12933 		}
12934 
12935 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12936 			// source x, source y
12937 			if(ix >= i.width) return;
12938 			if(iy >= i.height) return;
12939 			if(ix + w > i.width) w = i.width - ix;
12940 			if(iy + h > i.height) h = i.height - iy;
12941 			if(i.usingXshm)
12942 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12943 			else
12944 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12945 		}
12946 
12947 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12948 			if(s.enableAlpha) {
12949 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12950 				if(this.xrenderPicturePainter == None) {
12951 					XRenderPictureAttributes attrs;
12952 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12953 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12954 
12955 					// need to initialize the clip
12956 					XRectangle[1] rects;
12957 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12958 
12959 					if(_clipRectangle != Rectangle.init)
12960 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12961 				}
12962 
12963 				XRenderComposite(
12964 					display,
12965 					3, // PicOpOver
12966 					s.xrenderPicture,
12967 					None,
12968 					this.xrenderPicturePainter,
12969 					ix,
12970 					iy,
12971 					0,
12972 					0,
12973 					x,
12974 					y,
12975 					w ? w : s.width,
12976 					h ? h : s.height
12977 				);
12978 			} else {
12979 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12980 			}
12981 		}
12982 
12983 		int fontHeight() {
12984 			version(with_xft)
12985 				if(xftFont !is null)
12986 					return xftFont.height;
12987 			if(font)
12988 				return font.max_bounds.ascent + font.max_bounds.descent;
12989 			return 12; // pretty common default...
12990 		}
12991 
12992 		int textWidth(in char[] line) {
12993 			version(with_xft)
12994 			if(xftFont) {
12995 				if(line.length == 0)
12996 					return 0;
12997 				XGlyphInfo extents;
12998 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12999 				return extents.width;
13000 			}
13001 
13002 			if(fontset) {
13003 				if(line.length == 0)
13004 					return 0;
13005 				XRectangle rect;
13006 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13007 
13008 				return rect.width;
13009 			}
13010 
13011 			if(font)
13012 				// FIXME: unicode
13013 				return XTextWidth( font, line.ptr, cast(int) line.length);
13014 			else
13015 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13016 		}
13017 
13018 		Size textSize(in char[] text) {
13019 			auto maxWidth = 0;
13020 			auto lineHeight = fontHeight;
13021 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13022 			foreach(line; text.split('\n')) {
13023 				int textWidth = this.textWidth(line);
13024 				if(textWidth > maxWidth)
13025 					maxWidth = textWidth;
13026 				h += lineHeight + 4;
13027 			}
13028 			return Size(maxWidth, h);
13029 		}
13030 
13031 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13032 			const(char)[] text;
13033 			version(with_xft)
13034 			if(xftFont) {
13035 				text = originalText;
13036 				goto loaded;
13037 			}
13038 
13039 			if(fontset)
13040 				text = originalText;
13041 			else {
13042 				text.reserve(originalText.length);
13043 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13044 				// then strip the rest so there isn't garbage
13045 				foreach(dchar ch; originalText)
13046 					if(ch < 256)
13047 						text ~= cast(ubyte) ch;
13048 					else
13049 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13050 			}
13051 			loaded:
13052 			if(text.length == 0)
13053 				return;
13054 
13055 			// FIXME: should we clip it to the bounding box?
13056 			int textHeight = fontHeight;
13057 
13058 			auto lines = text.split('\n');
13059 
13060 			const lineHeight = textHeight;
13061 			textHeight *= lines.length;
13062 
13063 			int cy = y;
13064 
13065 			if(alignment & TextAlignment.VerticalBottom) {
13066 				if(y2 <= 0)
13067 					return;
13068 				auto h = y2 - y;
13069 				if(h > textHeight) {
13070 					cy += h - textHeight;
13071 					cy -= lineHeight / 2;
13072 				}
13073 			} else if(alignment & TextAlignment.VerticalCenter) {
13074 				if(y2 <= 0)
13075 					return;
13076 				auto h = y2 - y;
13077 				if(textHeight < h) {
13078 					cy += (h - textHeight) / 2;
13079 					//cy -= lineHeight / 4;
13080 				}
13081 			}
13082 
13083 			foreach(line; text.split('\n')) {
13084 				int textWidth = this.textWidth(line);
13085 
13086 				int px = x, py = cy;
13087 
13088 				if(alignment & TextAlignment.Center) {
13089 					if(x2 <= 0)
13090 						return;
13091 					auto w = x2 - x;
13092 					if(w > textWidth)
13093 						px += (w - textWidth) / 2;
13094 				} else if(alignment & TextAlignment.Right) {
13095 					if(x2 <= 0)
13096 						return;
13097 					auto pos = x2 - textWidth;
13098 					if(pos > x)
13099 						px = pos;
13100 				}
13101 
13102 				version(with_xft)
13103 				if(xftFont) {
13104 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13105 
13106 					goto carry_on;
13107 				}
13108 
13109 				if(fontset)
13110 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13111 				else
13112 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13113 				carry_on:
13114 				cy += lineHeight + 4;
13115 			}
13116 		}
13117 
13118 		void drawPixel(int x, int y) {
13119 			XDrawPoint(display, d, gc, x, y);
13120 		}
13121 
13122 		// The basic shapes, outlined
13123 
13124 		void drawLine(int x1, int y1, int x2, int y2) {
13125 			if(foregroundIsNotTransparent)
13126 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13127 		}
13128 
13129 		void drawRectangle(int x, int y, int width, int height) {
13130 			if(backgroundIsNotTransparent) {
13131 				swapColors();
13132 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13133 				swapColors();
13134 			}
13135 			// 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
13136 			if(foregroundIsNotTransparent)
13137 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13138 		}
13139 
13140 		/// Arguments are the points of the bounding rectangle
13141 		void drawEllipse(int x1, int y1, int x2, int y2) {
13142 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13143 		}
13144 
13145 		// NOTE: start and finish are in units of degrees * 64
13146 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
13147 			if(backgroundIsNotTransparent) {
13148 				swapColors();
13149 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
13150 				swapColors();
13151 			}
13152 			if(foregroundIsNotTransparent) {
13153 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
13154 
13155 				// Windows draws the straight lines on the edges too so FIXME sort of
13156 			}
13157 		}
13158 
13159 		void drawPolygon(Point[] vertexes) {
13160 			XPoint[16] pointsBuffer;
13161 			XPoint[] points;
13162 			if(vertexes.length <= pointsBuffer.length)
13163 				points = pointsBuffer[0 .. vertexes.length];
13164 			else
13165 				points.length = vertexes.length;
13166 
13167 			foreach(i, p; vertexes) {
13168 				points[i].x = cast(short) p.x;
13169 				points[i].y = cast(short) p.y;
13170 			}
13171 
13172 			if(backgroundIsNotTransparent) {
13173 				swapColors();
13174 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13175 				swapColors();
13176 			}
13177 			if(foregroundIsNotTransparent) {
13178 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13179 			}
13180 		}
13181 	}
13182 
13183 	/* XRender { */
13184 
13185 	struct XRenderColor {
13186 		ushort red;
13187 		ushort green;
13188 		ushort blue;
13189 		ushort alpha;
13190 	}
13191 
13192 	alias Picture = XID;
13193 	alias PictFormat = XID;
13194 
13195 	struct XGlyphInfo {
13196 		ushort width;
13197 		ushort height;
13198 		short x;
13199 		short y;
13200 		short xOff;
13201 		short yOff;
13202 	}
13203 
13204 struct XRenderDirectFormat {
13205     short   red;
13206     short   redMask;
13207     short   green;
13208     short   greenMask;
13209     short   blue;
13210     short   blueMask;
13211     short   alpha;
13212     short   alphaMask;
13213 }
13214 
13215 struct XRenderPictFormat {
13216     PictFormat		id;
13217     int			type;
13218     int			depth;
13219     XRenderDirectFormat	direct;
13220     Colormap		colormap;
13221 }
13222 
13223 enum PictFormatID	=   (1 << 0);
13224 enum PictFormatType	=   (1 << 1);
13225 enum PictFormatDepth	=   (1 << 2);
13226 enum PictFormatRed	=   (1 << 3);
13227 enum PictFormatRedMask  =(1 << 4);
13228 enum PictFormatGreen	=   (1 << 5);
13229 enum PictFormatGreenMask=(1 << 6);
13230 enum PictFormatBlue	=   (1 << 7);
13231 enum PictFormatBlueMask =(1 << 8);
13232 enum PictFormatAlpha	=   (1 << 9);
13233 enum PictFormatAlphaMask=(1 << 10);
13234 enum PictFormatColormap =(1 << 11);
13235 
13236 struct XRenderPictureAttributes {
13237 	int 		repeat;
13238 	Picture		alpha_map;
13239 	int			alpha_x_origin;
13240 	int			alpha_y_origin;
13241 	int			clip_x_origin;
13242 	int			clip_y_origin;
13243 	Pixmap		clip_mask;
13244 	Bool		graphics_exposures;
13245 	int			subwindow_mode;
13246 	int			poly_edge;
13247 	int			poly_mode;
13248 	Atom		dither;
13249 	Bool		component_alpha;
13250 }
13251 
13252 alias int XFixed;
13253 
13254 struct XPointFixed {
13255     XFixed  x, y;
13256 }
13257 
13258 struct XCircle {
13259     XFixed x;
13260     XFixed y;
13261     XFixed radius;
13262 }
13263 
13264 struct XTransform {
13265     XFixed[3][3]  matrix;
13266 }
13267 
13268 struct XFilters {
13269     int	    nfilter;
13270     char    **filter;
13271     int	    nalias;
13272     short   *alias_;
13273 }
13274 
13275 struct XIndexValue {
13276     c_ulong    pixel;
13277     ushort   red, green, blue, alpha;
13278 }
13279 
13280 struct XAnimCursor {
13281     Cursor	    cursor;
13282     c_ulong   delay;
13283 }
13284 
13285 struct XLinearGradient {
13286     XPointFixed p1;
13287     XPointFixed p2;
13288 }
13289 
13290 struct XRadialGradient {
13291     XCircle inner;
13292     XCircle outer;
13293 }
13294 
13295 struct XConicalGradient {
13296     XPointFixed center;
13297     XFixed angle; /* in degrees */
13298 }
13299 
13300 enum PictStandardARGB32  = 0;
13301 enum PictStandardRGB24   = 1;
13302 enum PictStandardA8	 =  2;
13303 enum PictStandardA4	 =  3;
13304 enum PictStandardA1	 =  4;
13305 enum PictStandardNUM	 =  5;
13306 
13307 interface XRender {
13308 extern(C) @nogc:
13309 
13310 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13311 
13312 	Status XRenderQueryVersion (Display *dpy,
13313 			int     *major_versionp,
13314 			int     *minor_versionp);
13315 
13316 	Status XRenderQueryFormats (Display *dpy);
13317 
13318 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13319 
13320 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13321 
13322 	XRenderPictFormat *
13323 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13324 
13325 	XRenderPictFormat *
13326 		XRenderFindFormat (Display			*dpy,
13327 				c_ulong		mask,
13328 				const XRenderPictFormat	*templ,
13329 				int				count);
13330 	XRenderPictFormat *
13331 		XRenderFindStandardFormat (Display		*dpy,
13332 				int			format);
13333 
13334 	XIndexValue *
13335 		XRenderQueryPictIndexValues(Display			*dpy,
13336 				const XRenderPictFormat	*format,
13337 				int				*num);
13338 
13339 	Picture XRenderCreatePicture(
13340 		Display *dpy,
13341 		Drawable drawable,
13342 		const XRenderPictFormat *format,
13343 		c_ulong valuemask,
13344 		const XRenderPictureAttributes *attributes);
13345 
13346 	void XRenderChangePicture (Display				*dpy,
13347 				Picture				picture,
13348 				c_ulong			valuemask,
13349 				const XRenderPictureAttributes  *attributes);
13350 
13351 	void
13352 		XRenderSetPictureClipRectangles (Display	    *dpy,
13353 				Picture	    picture,
13354 				int		    xOrigin,
13355 				int		    yOrigin,
13356 				const XRectangle *rects,
13357 				int		    n);
13358 
13359 	void
13360 		XRenderSetPictureClipRegion (Display	    *dpy,
13361 				Picture	    picture,
13362 				Region	    r);
13363 
13364 	void
13365 		XRenderSetPictureTransform (Display	    *dpy,
13366 				Picture	    picture,
13367 				XTransform	    *transform);
13368 
13369 	void
13370 		XRenderFreePicture (Display                   *dpy,
13371 				Picture                   picture);
13372 
13373 	void
13374 		XRenderComposite (Display   *dpy,
13375 				int	    op,
13376 				Picture   src,
13377 				Picture   mask,
13378 				Picture   dst,
13379 				int	    src_x,
13380 				int	    src_y,
13381 				int	    mask_x,
13382 				int	    mask_y,
13383 				int	    dst_x,
13384 				int	    dst_y,
13385 				uint	width,
13386 				uint	height);
13387 
13388 
13389 	Picture XRenderCreateSolidFill (Display *dpy,
13390 			const XRenderColor *color);
13391 
13392 	Picture XRenderCreateLinearGradient (Display *dpy,
13393 			const XLinearGradient *gradient,
13394 			const XFixed *stops,
13395 			const XRenderColor *colors,
13396 			int nstops);
13397 
13398 	Picture XRenderCreateRadialGradient (Display *dpy,
13399 			const XRadialGradient *gradient,
13400 			const XFixed *stops,
13401 			const XRenderColor *colors,
13402 			int nstops);
13403 
13404 	Picture XRenderCreateConicalGradient (Display *dpy,
13405 			const XConicalGradient *gradient,
13406 			const XFixed *stops,
13407 			const XRenderColor *colors,
13408 			int nstops);
13409 
13410 
13411 
13412 	Cursor
13413 		XRenderCreateCursor (Display	    *dpy,
13414 				Picture	    source,
13415 				uint   x,
13416 				uint   y);
13417 
13418 	XFilters *
13419 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13420 
13421 	void
13422 		XRenderSetPictureFilter (Display    *dpy,
13423 				Picture    picture,
13424 				const char *filter,
13425 				XFixed	    *params,
13426 				int	    nparams);
13427 
13428 	Cursor
13429 		XRenderCreateAnimCursor (Display	*dpy,
13430 				int		ncursor,
13431 				XAnimCursor	*cursors);
13432 }
13433 
13434 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13435 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13436 
13437 	/* XRender } */
13438 
13439 	/* Xrandr { */
13440 
13441 struct XRRMonitorInfo {
13442     Atom name;
13443     Bool primary;
13444     Bool automatic;
13445     int noutput;
13446     int x;
13447     int y;
13448     int width;
13449     int height;
13450     int mwidth;
13451     int mheight;
13452     /*RROutput*/ void *outputs;
13453 }
13454 
13455 struct XRRScreenChangeNotifyEvent {
13456     int type;                   /* event base */
13457     c_ulong serial;       /* # of last request processed by server */
13458     Bool send_event;            /* true if this came from a SendEvent request */
13459     Display *display;           /* Display the event was read from */
13460     Window window;              /* window which selected for this event */
13461     Window root;                /* Root window for changed screen */
13462     Time timestamp;             /* when the screen change occurred */
13463     Time config_timestamp;      /* when the last configuration change */
13464     ushort/*SizeID*/ size_index;
13465     ushort/*SubpixelOrder*/ subpixel_order;
13466     ushort/*Rotation*/ rotation;
13467     int width;
13468     int height;
13469     int mwidth;
13470     int mheight;
13471 }
13472 
13473 enum RRScreenChangeNotify = 0;
13474 
13475 enum RRScreenChangeNotifyMask = 1;
13476 
13477 __gshared int xrrEventBase = -1;
13478 
13479 
13480 interface XRandr {
13481 extern(C) @nogc:
13482 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13483 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13484 
13485 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13486 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13487 
13488 	void XRRSelectInput(Display *dpy, Window window, int mask);
13489 }
13490 
13491 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13492 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13493 	/* Xrandr } */
13494 
13495 	/* Xft { */
13496 
13497 	// actually freetype
13498 	alias void FT_Face;
13499 
13500 	// actually fontconfig
13501 	private alias FcBool = int;
13502 	alias void FcCharSet;
13503 	alias void FcPattern;
13504 	alias void FcResult;
13505 	enum FcEndian { FcEndianBig, FcEndianLittle }
13506 	struct FcFontSet {
13507 		int nfont;
13508 		int sfont;
13509 		FcPattern** fonts;
13510 	}
13511 
13512 	// actually XRegion
13513 	struct BOX {
13514 		short x1, x2, y1, y2;
13515 	}
13516 	struct _XRegion {
13517 		c_long size;
13518 		c_long numRects;
13519 		BOX* rects;
13520 		BOX extents;
13521 	}
13522 
13523 	alias Region = _XRegion*;
13524 
13525 	// ok actually Xft
13526 
13527 	struct XftFontInfo;
13528 
13529 	struct XftFont {
13530 		int         ascent;
13531 		int         descent;
13532 		int         height;
13533 		int         max_advance_width;
13534 		FcCharSet*  charset;
13535 		FcPattern*  pattern;
13536 	}
13537 
13538 	struct XftDraw;
13539 
13540 	struct XftColor {
13541 		c_ulong pixel;
13542 		XRenderColor color;
13543 	}
13544 
13545 	struct XftCharSpec {
13546 		dchar           ucs4;
13547 		short           x;
13548 		short           y;
13549 	}
13550 
13551 	struct XftCharFontSpec {
13552 		XftFont         *font;
13553 		dchar           ucs4;
13554 		short           x;
13555 		short           y;
13556 	}
13557 
13558 	struct XftGlyphSpec {
13559 		uint            glyph;
13560 		short           x;
13561 		short           y;
13562 	}
13563 
13564 	struct XftGlyphFontSpec {
13565 		XftFont         *font;
13566 		uint            glyph;
13567 		short           x;
13568 		short           y;
13569 	}
13570 
13571 	interface Xft {
13572 	extern(C) @nogc pure:
13573 
13574 	Bool XftColorAllocName (Display  *dpy,
13575 				const Visual   *visual,
13576 				Colormap cmap,
13577 				const char     *name,
13578 				XftColor *result);
13579 
13580 	Bool XftColorAllocValue (Display         *dpy,
13581 				Visual          *visual,
13582 				Colormap        cmap,
13583 				const XRenderColor    *color,
13584 				XftColor        *result);
13585 
13586 	void XftColorFree (Display   *dpy,
13587 				Visual    *visual,
13588 				Colormap  cmap,
13589 				XftColor  *color);
13590 
13591 	Bool XftDefaultHasRender (Display *dpy);
13592 
13593 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13594 
13595 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13596 
13597 	XftDraw * XftDrawCreate (Display   *dpy,
13598 		       Drawable  drawable,
13599 		       Visual    *visual,
13600 		       Colormap  colormap);
13601 
13602 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13603 			     Pixmap   bitmap);
13604 
13605 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13606 			    Pixmap  pixmap,
13607 			    int     depth);
13608 
13609 	void XftDrawChange (XftDraw  *draw,
13610 		       Drawable drawable);
13611 
13612 	Display * XftDrawDisplay (XftDraw *draw);
13613 
13614 	Drawable XftDrawDrawable (XftDraw *draw);
13615 
13616 	Colormap XftDrawColormap (XftDraw *draw);
13617 
13618 	Visual * XftDrawVisual (XftDraw *draw);
13619 
13620 	void XftDrawDestroy (XftDraw *draw);
13621 
13622 	Picture XftDrawPicture (XftDraw *draw);
13623 
13624 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13625 
13626 	void XftDrawGlyphs (XftDraw          *draw,
13627 				const XftColor *color,
13628 				XftFont          *pub,
13629 				int              x,
13630 				int              y,
13631 				const uint  *glyphs,
13632 				int              nglyphs);
13633 
13634 	void XftDrawString8 (XftDraw             *draw,
13635 				const XftColor    *color,
13636 				XftFont             *pub,
13637 				int                 x,
13638 				int                 y,
13639 				const char     *string,
13640 				int                 len);
13641 
13642 	void XftDrawString16 (XftDraw            *draw,
13643 				const XftColor   *color,
13644 				XftFont            *pub,
13645 				int                x,
13646 				int                y,
13647 				const wchar   *string,
13648 				int                len);
13649 
13650 	void XftDrawString32 (XftDraw            *draw,
13651 				const XftColor   *color,
13652 				XftFont            *pub,
13653 				int                x,
13654 				int                y,
13655 				const dchar   *string,
13656 				int                len);
13657 
13658 	void XftDrawStringUtf8 (XftDraw          *draw,
13659 				const XftColor *color,
13660 				XftFont          *pub,
13661 				int              x,
13662 				int              y,
13663 				const char  *string,
13664 				int              len);
13665 	void XftDrawStringUtf16 (XftDraw             *draw,
13666 				const XftColor    *color,
13667 				XftFont             *pub,
13668 				int                 x,
13669 				int                 y,
13670 				const char     *string,
13671 				FcEndian            endian,
13672 				int                 len);
13673 
13674 	void XftDrawCharSpec (XftDraw                *draw,
13675 				const XftColor       *color,
13676 				XftFont                *pub,
13677 				const XftCharSpec    *chars,
13678 				int                    len);
13679 
13680 	void XftDrawCharFontSpec (XftDraw                    *draw,
13681 				const XftColor           *color,
13682 				const XftCharFontSpec    *chars,
13683 				int                        len);
13684 
13685 	void XftDrawGlyphSpec (XftDraw               *draw,
13686 				const XftColor      *color,
13687 				XftFont               *pub,
13688 				const XftGlyphSpec  *glyphs,
13689 				int                   len);
13690 
13691 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13692 				const XftColor          *color,
13693 				const XftGlyphFontSpec  *glyphs,
13694 				int                       len);
13695 
13696 	void XftDrawRect (XftDraw            *draw,
13697 				const XftColor   *color,
13698 				int                x,
13699 				int                y,
13700 				uint       width,
13701 				uint       height);
13702 
13703 	Bool XftDrawSetClip (XftDraw     *draw,
13704 				Region      r);
13705 
13706 
13707 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13708 				int                   xOrigin,
13709 				int                   yOrigin,
13710 				const XRectangle    *rects,
13711 				int                   n);
13712 
13713 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13714 				int        mode);
13715 
13716 	void XftGlyphExtents (Display            *dpy,
13717 				XftFont            *pub,
13718 				const uint    *glyphs,
13719 				int                nglyphs,
13720 				XGlyphInfo         *extents);
13721 
13722 	void XftTextExtents8 (Display            *dpy,
13723 				XftFont            *pub,
13724 				const char    *string,
13725 				int                len,
13726 				XGlyphInfo         *extents);
13727 
13728 	void XftTextExtents16 (Display           *dpy,
13729 				XftFont           *pub,
13730 				const wchar  *string,
13731 				int               len,
13732 				XGlyphInfo        *extents);
13733 
13734 	void XftTextExtents32 (Display           *dpy,
13735 				XftFont           *pub,
13736 				const dchar  *string,
13737 				int               len,
13738 				XGlyphInfo        *extents);
13739 
13740 	void XftTextExtentsUtf8 (Display         *dpy,
13741 				XftFont         *pub,
13742 				const char *string,
13743 				int             len,
13744 				XGlyphInfo      *extents);
13745 
13746 	void XftTextExtentsUtf16 (Display            *dpy,
13747 				XftFont            *pub,
13748 				const char    *string,
13749 				FcEndian           endian,
13750 				int                len,
13751 				XGlyphInfo         *extents);
13752 
13753 	FcPattern * XftFontMatch (Display           *dpy,
13754 				int               screen,
13755 				const FcPattern *pattern,
13756 				FcResult          *result);
13757 
13758 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13759 
13760 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13761 
13762 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13763 
13764 	FT_Face XftLockFace (XftFont *pub);
13765 
13766 	void XftUnlockFace (XftFont *pub);
13767 
13768 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13769 
13770 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13771 
13772 	dchar XftFontInfoHash (const XftFontInfo *fi);
13773 
13774 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13775 
13776 	XftFont * XftFontOpenInfo (Display        *dpy,
13777 				FcPattern      *pattern,
13778 				XftFontInfo    *fi);
13779 
13780 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13781 
13782 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13783 
13784 	void XftFontClose (Display *dpy, XftFont *pub);
13785 
13786 	FcBool XftInitFtLibrary();
13787 	void XftFontLoadGlyphs (Display          *dpy,
13788 				XftFont          *pub,
13789 				FcBool           need_bitmaps,
13790 				const uint  *glyphs,
13791 				int              nglyph);
13792 
13793 	void XftFontUnloadGlyphs (Display            *dpy,
13794 				XftFont            *pub,
13795 				const uint    *glyphs,
13796 				int                nglyph);
13797 
13798 	FcBool XftFontCheckGlyph (Display  *dpy,
13799 				XftFont  *pub,
13800 				FcBool   need_bitmaps,
13801 				uint  glyph,
13802 				uint  *missing,
13803 				int      *nmissing);
13804 
13805 	FcBool XftCharExists (Display      *dpy,
13806 				XftFont      *pub,
13807 				dchar    ucs4);
13808 
13809 	uint XftCharIndex (Display       *dpy,
13810 				XftFont       *pub,
13811 				dchar      ucs4);
13812 	FcBool XftInit (const char *config);
13813 
13814 	int XftGetVersion ();
13815 
13816 	FcFontSet * XftListFonts (Display   *dpy,
13817 				int       screen,
13818 				...);
13819 
13820 	FcPattern *XftNameParse (const char *name);
13821 
13822 	void XftGlyphRender (Display         *dpy,
13823 				int             op,
13824 				Picture         src,
13825 				XftFont         *pub,
13826 				Picture         dst,
13827 				int             srcx,
13828 				int             srcy,
13829 				int             x,
13830 				int             y,
13831 				const uint *glyphs,
13832 				int             nglyphs);
13833 
13834 	void XftGlyphSpecRender (Display                 *dpy,
13835 				int                     op,
13836 				Picture                 src,
13837 				XftFont                 *pub,
13838 				Picture                 dst,
13839 				int                     srcx,
13840 				int                     srcy,
13841 				const XftGlyphSpec    *glyphs,
13842 				int                     nglyphs);
13843 
13844 	void XftCharSpecRender (Display              *dpy,
13845 				int                  op,
13846 				Picture              src,
13847 				XftFont              *pub,
13848 				Picture              dst,
13849 				int                  srcx,
13850 				int                  srcy,
13851 				const XftCharSpec  *chars,
13852 				int                  len);
13853 	void XftGlyphFontSpecRender (Display                     *dpy,
13854 				int                         op,
13855 				Picture                     src,
13856 				Picture                     dst,
13857 				int                         srcx,
13858 				int                         srcy,
13859 				const XftGlyphFontSpec    *glyphs,
13860 				int                         nglyphs);
13861 
13862 	void XftCharFontSpecRender (Display                  *dpy,
13863 				int                      op,
13864 				Picture                  src,
13865 				Picture                  dst,
13866 				int                      srcx,
13867 				int                      srcy,
13868 				const XftCharFontSpec  *chars,
13869 				int                      len);
13870 
13871 	void XftTextRender8 (Display         *dpy,
13872 				int             op,
13873 				Picture         src,
13874 				XftFont         *pub,
13875 				Picture         dst,
13876 				int             srcx,
13877 				int             srcy,
13878 				int             x,
13879 				int             y,
13880 				const char *string,
13881 				int             len);
13882 	void XftTextRender16 (Display            *dpy,
13883 				int                op,
13884 				Picture            src,
13885 				XftFont            *pub,
13886 				Picture            dst,
13887 				int                srcx,
13888 				int                srcy,
13889 				int                x,
13890 				int                y,
13891 				const wchar   *string,
13892 				int                len);
13893 
13894 	void XftTextRender16BE (Display          *dpy,
13895 				int              op,
13896 				Picture          src,
13897 				XftFont          *pub,
13898 				Picture          dst,
13899 				int              srcx,
13900 				int              srcy,
13901 				int              x,
13902 				int              y,
13903 				const char  *string,
13904 				int              len);
13905 
13906 	void XftTextRender16LE (Display          *dpy,
13907 				int              op,
13908 				Picture          src,
13909 				XftFont          *pub,
13910 				Picture          dst,
13911 				int              srcx,
13912 				int              srcy,
13913 				int              x,
13914 				int              y,
13915 				const char  *string,
13916 				int              len);
13917 
13918 	void XftTextRender32 (Display            *dpy,
13919 				int                op,
13920 				Picture            src,
13921 				XftFont            *pub,
13922 				Picture            dst,
13923 				int                srcx,
13924 				int                srcy,
13925 				int                x,
13926 				int                y,
13927 				const dchar   *string,
13928 				int                len);
13929 
13930 	void XftTextRender32BE (Display          *dpy,
13931 				int              op,
13932 				Picture          src,
13933 				XftFont          *pub,
13934 				Picture          dst,
13935 				int              srcx,
13936 				int              srcy,
13937 				int              x,
13938 				int              y,
13939 				const char  *string,
13940 				int              len);
13941 
13942 	void XftTextRender32LE (Display          *dpy,
13943 				int              op,
13944 				Picture          src,
13945 				XftFont          *pub,
13946 				Picture          dst,
13947 				int              srcx,
13948 				int              srcy,
13949 				int              x,
13950 				int              y,
13951 				const char  *string,
13952 				int              len);
13953 
13954 	void XftTextRenderUtf8 (Display          *dpy,
13955 				int              op,
13956 				Picture          src,
13957 				XftFont          *pub,
13958 				Picture          dst,
13959 				int              srcx,
13960 				int              srcy,
13961 				int              x,
13962 				int              y,
13963 				const char  *string,
13964 				int              len);
13965 
13966 	void XftTextRenderUtf16 (Display         *dpy,
13967 				int             op,
13968 				Picture         src,
13969 				XftFont         *pub,
13970 				Picture         dst,
13971 				int             srcx,
13972 				int             srcy,
13973 				int             x,
13974 				int             y,
13975 				const char *string,
13976 				FcEndian        endian,
13977 				int             len);
13978 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13979 
13980 	}
13981 
13982 	interface FontConfig {
13983 	extern(C) @nogc pure:
13984 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13985 		void FcFontSetDestroy(FcFontSet*);
13986 		char* FcNameUnparse(const FcPattern *);
13987 	}
13988 
13989 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13990 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13991 
13992 
13993 	/* Xft } */
13994 
13995 	class XDisconnectException : Exception {
13996 		bool userRequested;
13997 		this(bool userRequested = true) {
13998 			this.userRequested = userRequested;
13999 			super("X disconnected");
14000 		}
14001 	}
14002 
14003 	/++
14004 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14005 
14006 		Please note that it returns
14007 	+/
14008 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14009 
14010 		static XErrorEvent[] errorBuffer;
14011 
14012 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14013 			errorBuffer ~= *evt;
14014 			return 0;
14015 		}
14016 
14017 		auto savedErrorHandler = XSetErrorHandler(&handler);
14018 
14019 		try {
14020 			dg();
14021 		} finally {
14022 			XSync(XDisplayConnection.get, 0/*False*/);
14023 			XSetErrorHandler(savedErrorHandler);
14024 		}
14025 
14026 		auto bfr = errorBuffer;
14027 		errorBuffer = null;
14028 
14029 		return bfr;
14030 	}
14031 
14032 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14033 	class XDisplayConnection {
14034 		private __gshared Display* display;
14035 		private __gshared XIM xim;
14036 		private __gshared char* displayName;
14037 
14038 		private __gshared int connectionSequence_;
14039 		private __gshared bool isLocal_;
14040 
14041 		/// use this for lazy caching when reconnection
14042 		static int connectionSequenceNumber() { return connectionSequence_; }
14043 
14044 		/++
14045 			Guesses if the connection appears to be local.
14046 
14047 			History:
14048 				Added June 3, 2021
14049 		+/
14050 		static @property bool isLocal() nothrow @trusted @nogc {
14051 			return isLocal_;
14052 		}
14053 
14054 		/// Attempts recreation of state, may require application assistance
14055 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14056 		/// then call this, and if successful, reenter the loop.
14057 		static void discardAndRecreate(string newDisplayString = null) {
14058 			if(insideXEventLoop)
14059 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14060 
14061 			// 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
14062 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14063 
14064 			foreach(handle; chnenhm) {
14065 				handle.discardConnectionState();
14066 			}
14067 
14068 			discardState();
14069 
14070 			if(newDisplayString !is null)
14071 				setDisplayName(newDisplayString);
14072 
14073 			auto display = get();
14074 
14075 			foreach(handle; chnenhm) {
14076 				handle.recreateAfterDisconnect();
14077 			}
14078 		}
14079 
14080 		private __gshared EventMask rootEventMask;
14081 
14082 		/++
14083 			Requests the specified input from the root window on the connection, in addition to any other request.
14084 
14085 
14086 			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.
14087 
14088 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14089 		+/
14090 		static void addRootInput(EventMask mask) {
14091 			auto old = rootEventMask;
14092 			rootEventMask |= mask;
14093 			get(); // to ensure display connected
14094 			if(display !is null && rootEventMask != old)
14095 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14096 		}
14097 
14098 		static void discardState() {
14099 			freeImages();
14100 
14101 			foreach(atomPtr; interredAtoms)
14102 				*atomPtr = 0;
14103 			interredAtoms = null;
14104 			interredAtoms.assumeSafeAppend();
14105 
14106 			ScreenPainterImplementation.fontAttempted = false;
14107 			ScreenPainterImplementation.defaultfont = null;
14108 			ScreenPainterImplementation.defaultfontset = null;
14109 
14110 			Image.impl.xshmQueryCompleted = false;
14111 			Image.impl._xshmAvailable = false;
14112 
14113 			SimpleWindow.nativeMapping = null;
14114 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14115 			// GlobalHotkeyManager
14116 
14117 			display = null;
14118 			xim = null;
14119 		}
14120 
14121 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14122 		private static void createXIM () {
14123 			import core.stdc.locale : setlocale, LC_ALL;
14124 			import core.stdc.stdio : stderr, fprintf;
14125 			import core.stdc.stdlib : free;
14126 			import core.stdc.string : strdup;
14127 
14128 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14129 
14130 			auto olocale = strdup(setlocale(LC_ALL, null));
14131 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14132 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14133 
14134 			//fprintf(stderr, "opening IM...\n");
14135 			foreach (string s; mtry) {
14136 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14137 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14138 			}
14139 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14140 		}
14141 
14142 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14143 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14144 		static struct ImgList {
14145 			size_t img; // class; hide it from GC
14146 			ImgList* next;
14147 		}
14148 
14149 		static __gshared ImgList* imglist = null;
14150 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14151 
14152 		static void registerImage (Image img) {
14153 			if (!imglistLocked && img !is null) {
14154 				import core.stdc.stdlib : malloc;
14155 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14156 				assert(it !is null); // do proper checks
14157 				it.img = cast(size_t)cast(void*)img;
14158 				it.next = imglist;
14159 				imglist = it;
14160 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14161 			}
14162 		}
14163 
14164 		static void unregisterImage (Image img) {
14165 			if (!imglistLocked && img !is null) {
14166 				import core.stdc.stdlib : free;
14167 				ImgList* prev = null;
14168 				ImgList* cur = imglist;
14169 				while (cur !is null) {
14170 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14171 					prev = cur;
14172 					cur = cur.next;
14173 				}
14174 				if (cur !is null) {
14175 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14176 					free(cur);
14177 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14178 				} else {
14179 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14180 				}
14181 			}
14182 		}
14183 
14184 		static void freeImages () { // needed for discardAndRecreate
14185 			imglistLocked = true;
14186 			scope(exit) imglistLocked = false;
14187 			ImgList* cur = imglist;
14188 			ImgList* next = null;
14189 			while (cur !is null) {
14190 				import core.stdc.stdlib : free;
14191 				next = cur.next;
14192 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14193 				(cast(Image)cast(void*)cur.img).dispose();
14194 				free(cur);
14195 				cur = next;
14196 			}
14197 			imglist = null;
14198 		}
14199 
14200 		/// can be used to override normal handling of display name
14201 		/// from environment and/or command line
14202 		static setDisplayName(string newDisplayName) {
14203 			displayName = cast(char*) (newDisplayName ~ '\0');
14204 		}
14205 
14206 		/// resets to the default display string
14207 		static resetDisplayName() {
14208 			displayName = null;
14209 		}
14210 
14211 		///
14212 		static Display* get() {
14213 			if(display is null) {
14214 				if(!librariesSuccessfullyLoaded)
14215 					throw new Exception("Unable to load X11 client libraries");
14216 				display = XOpenDisplay(displayName);
14217 
14218 				isLocal_ = false;
14219 
14220 				connectionSequence_++;
14221 				if(display is null)
14222 					throw new Exception("Unable to open X display");
14223 
14224 				auto str = display.display_name;
14225 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14226 				// and otherwise it probably isn't
14227 				if(str is null || (str[0] != ':' && str[0] != '/'))
14228 					isLocal_ = false;
14229 				else
14230 					isLocal_ = true;
14231 
14232 				debug(sdpy_x_errors) {
14233 					XSetErrorHandler(&adrlogger);
14234 					XSynchronize(display, true);
14235 
14236 					extern(C) int wtf() {
14237 						if(errorHappened) {
14238 							asm { int 3; }
14239 							errorHappened = false;
14240 						}
14241 						return 0;
14242 					}
14243 					XSetAfterFunction(display, &wtf);
14244 				}
14245 
14246 
14247 				XSetIOErrorHandler(&x11ioerrCB);
14248 				Bool sup;
14249 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14250 				createXIM();
14251 				version(with_eventloop) {
14252 					import arsd.eventloop;
14253 					addFileEventListeners(display.fd, &eventListener, null, null);
14254 				}
14255 			}
14256 
14257 			return display;
14258 		}
14259 
14260 		extern(C)
14261 		static int x11ioerrCB(Display* dpy) {
14262 			throw new XDisconnectException(false);
14263 		}
14264 
14265 		version(with_eventloop) {
14266 			import arsd.eventloop;
14267 			static void eventListener(OsFileHandle fd) {
14268 				//this.mtLock();
14269 				//scope(exit) this.mtUnlock();
14270 				while(XPending(display))
14271 					doXNextEvent(display);
14272 			}
14273 		}
14274 
14275 		// close connection on program exit -- we need this to properly free all images
14276 		static ~this () {
14277 			// the gui thread must clean up after itself or else Xlib might deadlock
14278 			// using this flag on any thread destruction is the easiest way i know of
14279 			// (shared static this is run by the LAST thread to exit, which may not be
14280 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14281 			if(thisIsGuiThread)
14282 				close();
14283 		}
14284 
14285 		///
14286 		static void close() {
14287 			if(display is null)
14288 				return;
14289 
14290 			version(with_eventloop) {
14291 				import arsd.eventloop;
14292 				removeFileEventListeners(display.fd);
14293 			}
14294 
14295 			// now remove all registered images to prevent shared memory leaks
14296 			freeImages();
14297 
14298 			// tbh I don't know why it is doing this but like if this happens to run
14299 			// from the other thread there's frequent hanging inside here.
14300 			if(thisIsGuiThread)
14301 				XCloseDisplay(display);
14302 			display = null;
14303 		}
14304 	}
14305 
14306 	mixin template NativeImageImplementation() {
14307 		XImage* handle;
14308 		ubyte* rawData;
14309 
14310 		XShmSegmentInfo shminfo;
14311 		bool premultiply = true;
14312 
14313 		__gshared bool xshmQueryCompleted;
14314 		__gshared bool _xshmAvailable;
14315 		public static @property bool xshmAvailable() {
14316 			if(!xshmQueryCompleted) {
14317 				int i1, i2, i3;
14318 				xshmQueryCompleted = true;
14319 
14320 				if(!XDisplayConnection.isLocal)
14321 					_xshmAvailable = false;
14322 				else
14323 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14324 			}
14325 			return _xshmAvailable;
14326 		}
14327 
14328 		bool usingXshm;
14329 	final:
14330 
14331 		private __gshared bool xshmfailed;
14332 
14333 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14334 			auto display = XDisplayConnection.get();
14335 			assert(display !is null);
14336 			auto screen = DefaultScreen(display);
14337 
14338 			// it will only use shared memory for somewhat largish images,
14339 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14340 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14341 
14342 
14343 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14344 				// the actual use still fails. For example, if the program is in a container and permission denied
14345 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14346 				//
14347 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14348 
14349 
14350 				// synchronize so preexisting buffers are clear
14351 				XSync(display, false);
14352 				xshmfailed = false;
14353 
14354 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14355 
14356 
14357 				usingXshm = true;
14358 				handle = XShmCreateImage(
14359 					display,
14360 					DefaultVisual(display, screen),
14361 					enableAlpha ? 32: 24,
14362 					ImageFormat.ZPixmap,
14363 					null,
14364 					&shminfo,
14365 					width, height);
14366 				if(handle is null)
14367 					goto abortXshm1;
14368 
14369 				if(handle.bytes_per_line != 4 * width)
14370 					goto abortXshm2;
14371 
14372 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14373 				if(shminfo.shmid < 0)
14374 					goto abortXshm3;
14375 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14376 				if(rawData == cast(ubyte*) -1)
14377 					goto abortXshm4;
14378 				shminfo.readOnly = 0;
14379 				XShmAttach(display, &shminfo);
14380 
14381 				// and now to the final error check to ensure it actually worked.
14382 				XSync(display, false);
14383 				if(xshmfailed)
14384 					goto abortXshm5;
14385 
14386 				XSetErrorHandler(oldErrorHandler);
14387 
14388 				XDisplayConnection.registerImage(this);
14389 				// if I don't flush here there's a chance the dtor will run before the
14390 				// ctor and lead to a bad value X error. While this hurts the efficiency
14391 				// it is local anyway so prolly better to keep it simple
14392 				XFlush(display);
14393 
14394 				return;
14395 
14396 				abortXshm5:
14397 					shmdt(shminfo.shmaddr);
14398 					rawData = null;
14399 
14400 				abortXshm4:
14401 					shmctl(shminfo.shmid, IPC_RMID, null);
14402 
14403 				abortXshm3:
14404 					// nothing needed, the shmget failed so there's nothing to free
14405 
14406 				abortXshm2:
14407 					XDestroyImage(handle);
14408 					handle = null;
14409 
14410 				abortXshm1:
14411 					XSetErrorHandler(oldErrorHandler);
14412 					usingXshm = false;
14413 					handle = null;
14414 
14415 					shminfo = typeof(shminfo).init;
14416 
14417 					_xshmAvailable = false; // don't try again in the future
14418 
14419 					// writeln("fallingback");
14420 
14421 					goto fallback;
14422 
14423 			} else {
14424 				fallback:
14425 
14426 				if (forcexshm) throw new Exception("can't create XShm Image");
14427 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14428 				import core.stdc.stdlib : malloc;
14429 				rawData = cast(ubyte*) malloc(width * height * 4);
14430 
14431 				handle = XCreateImage(
14432 					display,
14433 					DefaultVisual(display, screen),
14434 					enableAlpha ? 32 : 24, // bpp
14435 					ImageFormat.ZPixmap,
14436 					0, // offset
14437 					rawData,
14438 					width, height,
14439 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14440 			}
14441 		}
14442 
14443 		void dispose() {
14444 			// note: this calls free(rawData) for us
14445 			if(handle) {
14446 				if (usingXshm) {
14447 					XDisplayConnection.unregisterImage(this);
14448 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14449 				}
14450 				XDestroyImage(handle);
14451 				if(usingXshm) {
14452 					shmdt(shminfo.shmaddr);
14453 					shmctl(shminfo.shmid, IPC_RMID, null);
14454 				}
14455 				handle = null;
14456 			}
14457 		}
14458 
14459 		Color getPixel(int x, int y) {
14460 			auto offset = (y * width + x) * 4;
14461 			Color c;
14462 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14463 			c.b = rawData[offset + 0];
14464 			c.g = rawData[offset + 1];
14465 			c.r = rawData[offset + 2];
14466 			if(enableAlpha && premultiply)
14467 				c.unPremultiply;
14468 			return c;
14469 		}
14470 
14471 		void setPixel(int x, int y, Color c) {
14472 			if(enableAlpha && premultiply)
14473 				c.premultiply();
14474 			auto offset = (y * width + x) * 4;
14475 			rawData[offset + 0] = c.b;
14476 			rawData[offset + 1] = c.g;
14477 			rawData[offset + 2] = c.r;
14478 			if(enableAlpha)
14479 				rawData[offset + 3] = c.a;
14480 		}
14481 
14482 		void convertToRgbaBytes(ubyte[] where) {
14483 			assert(where.length == this.width * this.height * 4);
14484 
14485 			// if rawData had a length....
14486 			//assert(rawData.length == where.length);
14487 			for(int idx = 0; idx < where.length; idx += 4) {
14488 				where[idx + 0] = rawData[idx + 2]; // r
14489 				where[idx + 1] = rawData[idx + 1]; // g
14490 				where[idx + 2] = rawData[idx + 0]; // b
14491 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14492 
14493 				if(enableAlpha && premultiply)
14494 					unPremultiplyRgba(where[idx .. idx + 4]);
14495 			}
14496 		}
14497 
14498 		void setFromRgbaBytes(in ubyte[] where) {
14499 			assert(where.length == this.width * this.height * 4);
14500 
14501 			// if rawData had a length....
14502 			//assert(rawData.length == where.length);
14503 			for(int idx = 0; idx < where.length; idx += 4) {
14504 				rawData[idx + 2] = where[idx + 0]; // r
14505 				rawData[idx + 1] = where[idx + 1]; // g
14506 				rawData[idx + 0] = where[idx + 2]; // b
14507 				if(enableAlpha) {
14508 					rawData[idx + 3] = where[idx + 3]; // a
14509 					if(premultiply)
14510 						premultiplyBgra(rawData[idx .. idx + 4]);
14511 				}
14512 			}
14513 		}
14514 
14515 	}
14516 
14517 	mixin template NativeSimpleWindowImplementation() {
14518 		GC gc;
14519 		Window window;
14520 		Display* display;
14521 
14522 		Pixmap buffer;
14523 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14524 		XIC xic; // input context
14525 		int curHidden = 0; // counter
14526 		Cursor blankCurPtr = 0;
14527 		int cursorSequenceNumber = 0;
14528 		int warpEventCount = 0; // number of mouse movement events to eat
14529 
14530 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14531 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14532 
14533 		version(without_opengl) {} else
14534 		GLXContext glc;
14535 
14536 		private void fixFixedSize(bool forced=false) (int width, int height) {
14537 			if (forced || this.resizability == Resizability.fixedSize) {
14538 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14539 				XSizeHints sh;
14540 				static if (!forced) {
14541 					c_long spr;
14542 					XGetWMNormalHints(display, window, &sh, &spr);
14543 					sh.flags |= PMaxSize | PMinSize;
14544 				} else {
14545 					sh.flags = PMaxSize | PMinSize;
14546 				}
14547 				sh.min_width = width;
14548 				sh.min_height = height;
14549 				sh.max_width = width;
14550 				sh.max_height = height;
14551 				XSetWMNormalHints(display, window, &sh);
14552 				//XFlush(display);
14553 			}
14554 		}
14555 
14556 		ScreenPainter getPainter(bool manualInvalidations) {
14557 			return ScreenPainter(this, window, manualInvalidations);
14558 		}
14559 
14560 		void move(int x, int y) {
14561 			XMoveWindow(display, window, x, y);
14562 		}
14563 
14564 		void resize(int w, int h) {
14565 			if (w < 1) w = 1;
14566 			if (h < 1) h = 1;
14567 			XResizeWindow(display, window, w, h);
14568 
14569 			// calling this now to avoid waiting for the server to
14570 			// acknowledge the resize; draws without returning to the
14571 			// event loop will thus actually work. the server's event
14572 			// btw might overrule this and resize it again
14573 			recordX11Resize(display, this, w, h);
14574 
14575 			updateOpenglViewportIfNeeded(w, h);
14576 		}
14577 
14578 		void moveResize (int x, int y, int w, int h) {
14579 			if (w < 1) w = 1;
14580 			if (h < 1) h = 1;
14581 			XMoveResizeWindow(display, window, x, y, w, h);
14582 			updateOpenglViewportIfNeeded(w, h);
14583 		}
14584 
14585 		void hideCursor () {
14586 			if (curHidden++ == 0) {
14587 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14588 					static const(char)[1] cmbmp = 0;
14589 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14590 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14591 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14592 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14593 					XFreePixmap(display, pm);
14594 				}
14595 				XDefineCursor(display, window, blankCurPtr);
14596 			}
14597 		}
14598 
14599 		void showCursor () {
14600 			if (--curHidden == 0) XUndefineCursor(display, window);
14601 		}
14602 
14603 		void warpMouse (int x, int y) {
14604 			// here i will send dummy "ignore next mouse motion" event,
14605 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14606 			// and we don't need to report it to the user (as warping is
14607 			// used when the user needs movement deltas).
14608 			//XClientMessageEvent xclient;
14609 			XEvent e;
14610 			e.xclient.type = EventType.ClientMessage;
14611 			e.xclient.window = window;
14612 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14613 			e.xclient.format = 32;
14614 			e.xclient.data.l[0] = 0;
14615 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14616 			//{ 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]); }
14617 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14618 			// now warp pointer...
14619 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14620 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14621 			// ...and flush
14622 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14623 			XFlush(display);
14624 		}
14625 
14626 		void sendDummyEvent () {
14627 			// here i will send dummy event to ping event queue
14628 			XEvent e;
14629 			e.xclient.type = EventType.ClientMessage;
14630 			e.xclient.window = window;
14631 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14632 			e.xclient.format = 32;
14633 			e.xclient.data.l[0] = 0;
14634 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14635 			XFlush(display);
14636 		}
14637 
14638 		void setTitle(string title) {
14639 			if (title.ptr is null) title = "";
14640 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14641 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14642 			XTextProperty windowName;
14643 			windowName.value = title.ptr;
14644 			windowName.encoding = XA_UTF8; //XA_STRING;
14645 			windowName.format = 8;
14646 			windowName.nitems = cast(uint)title.length;
14647 			XSetWMName(display, window, &windowName);
14648 			char[1024] namebuf = 0;
14649 			auto maxlen = namebuf.length-1;
14650 			if (maxlen > title.length) maxlen = title.length;
14651 			namebuf[0..maxlen] = title[0..maxlen];
14652 			XStoreName(display, window, namebuf.ptr);
14653 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14654 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14655 		}
14656 
14657 		string[] getTitles() {
14658 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14659 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14660 			XTextProperty textProp;
14661 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14662 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14663 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14664 				} else
14665 					return [];
14666 			} else
14667 				return null;
14668 		}
14669 
14670 		string getTitle() {
14671 			auto titles = getTitles();
14672 			return titles.length ? titles[0] : null;
14673 		}
14674 
14675 		void setMinSize (int minwidth, int minheight) {
14676 			import core.stdc.config : c_long;
14677 			if (minwidth < 1) minwidth = 1;
14678 			if (minheight < 1) minheight = 1;
14679 			XSizeHints sh;
14680 			c_long spr;
14681 			XGetWMNormalHints(display, window, &sh, &spr);
14682 			sh.min_width = minwidth;
14683 			sh.min_height = minheight;
14684 			sh.flags |= PMinSize;
14685 			XSetWMNormalHints(display, window, &sh);
14686 			flushGui();
14687 		}
14688 
14689 		void setMaxSize (int maxwidth, int maxheight) {
14690 			import core.stdc.config : c_long;
14691 			if (maxwidth < 1) maxwidth = 1;
14692 			if (maxheight < 1) maxheight = 1;
14693 			XSizeHints sh;
14694 			c_long spr;
14695 			XGetWMNormalHints(display, window, &sh, &spr);
14696 			sh.max_width = maxwidth;
14697 			sh.max_height = maxheight;
14698 			sh.flags |= PMaxSize;
14699 			XSetWMNormalHints(display, window, &sh);
14700 			flushGui();
14701 		}
14702 
14703 		void setResizeGranularity (int granx, int grany) {
14704 			import core.stdc.config : c_long;
14705 			if (granx < 1) granx = 1;
14706 			if (grany < 1) grany = 1;
14707 			XSizeHints sh;
14708 			c_long spr;
14709 			XGetWMNormalHints(display, window, &sh, &spr);
14710 			sh.width_inc = granx;
14711 			sh.height_inc = grany;
14712 			sh.flags |= PResizeInc;
14713 			XSetWMNormalHints(display, window, &sh);
14714 			flushGui();
14715 		}
14716 
14717 		void setOpacity (uint opacity) {
14718 			arch_ulong o = opacity;
14719 			if (opacity == uint.max)
14720 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14721 			else
14722 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14723 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14724 		}
14725 
14726 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14727 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14728 			display = XDisplayConnection.get();
14729 			auto screen = DefaultScreen(display);
14730 
14731 			bool overrideRedirect = false;
14732 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14733 				overrideRedirect = true;
14734 
14735 			version(without_opengl) {}
14736 			else {
14737 				if(opengl == OpenGlOptions.yes) {
14738 					GLXFBConfig fbconf = null;
14739 					XVisualInfo* vi = null;
14740 					bool useLegacy = false;
14741 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14742 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14743 						int[23] visualAttribs = [
14744 							GLX_X_RENDERABLE , 1/*True*/,
14745 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14746 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14747 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14748 							GLX_RED_SIZE     , 8,
14749 							GLX_GREEN_SIZE   , 8,
14750 							GLX_BLUE_SIZE    , 8,
14751 							GLX_ALPHA_SIZE   , 8,
14752 							GLX_DEPTH_SIZE   , 24,
14753 							GLX_STENCIL_SIZE , 8,
14754 							GLX_DOUBLEBUFFER , 1/*True*/,
14755 							0/*None*/,
14756 						];
14757 						int fbcount;
14758 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14759 						if (fbcount == 0) {
14760 							useLegacy = true; // try to do at least something
14761 						} else {
14762 							// pick the FB config/visual with the most samples per pixel
14763 							int bestidx = -1, bestns = -1;
14764 							foreach (int fbi; 0..fbcount) {
14765 								int sb, samples;
14766 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14767 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14768 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14769 							}
14770 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14771 							fbconf = fbc[bestidx];
14772 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14773 							XFree(fbc);
14774 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14775 						}
14776 					}
14777 					if (vi is null || useLegacy) {
14778 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14779 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14780 						useLegacy = true;
14781 					}
14782 					if (vi is null) throw new Exception("no open gl visual found");
14783 
14784 					XSetWindowAttributes swa;
14785 					auto root = RootWindow(display, screen);
14786 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14787 
14788 					swa.override_redirect = overrideRedirect;
14789 
14790 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14791 						0, 0, width, height,
14792 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14793 
14794 					// now try to use `glXCreateContextAttribsARB()` if it's here
14795 					if (!useLegacy) {
14796 						// request fairly advanced context, even with stencil buffer!
14797 						int[9] contextAttribs = [
14798 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14799 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14800 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14801 							// for modern context, set "forward compatibility" flag too
14802 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14803 							0/*None*/,
14804 						];
14805 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14806 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14807 							sdpyOpenGLContextVersion = 0;
14808 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14809 						}
14810 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14811 					} else {
14812 						// fallback to old GLX call
14813 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14814 							sdpyOpenGLContextVersion = 0;
14815 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14816 						}
14817 					}
14818 					// sync to ensure any errors generated are processed
14819 					XSync(display, 0/*False*/);
14820 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14821 					if(glc is null)
14822 						throw new Exception("glc");
14823 				}
14824 			}
14825 
14826 			if(opengl == OpenGlOptions.no) {
14827 
14828 				XSetWindowAttributes swa;
14829 				swa.background_pixel = WhitePixel(display, screen);
14830 				swa.border_pixel = BlackPixel(display, screen);
14831 				swa.override_redirect = overrideRedirect;
14832 				auto root = RootWindow(display, screen);
14833 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14834 
14835 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14836 					0, 0, width, height,
14837 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14838 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14839 
14840 
14841 
14842 				/*
14843 				window = XCreateSimpleWindow(
14844 					display,
14845 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14846 					0, 0, // x, y
14847 					width, height,
14848 					1, // border width
14849 					BlackPixel(display, screen), // border
14850 					WhitePixel(display, screen)); // background
14851 				*/
14852 
14853 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14854 				bufferw = width;
14855 				bufferh = height;
14856 
14857 				gc = DefaultGC(display, screen);
14858 
14859 				// clear out the buffer to get us started...
14860 				XSetForeground(display, gc, WhitePixel(display, screen));
14861 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14862 				XSetForeground(display, gc, BlackPixel(display, screen));
14863 			}
14864 
14865 			// input context
14866 			//TODO: create this only for top-level windows, and reuse that?
14867 			populateXic();
14868 
14869 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14870 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14871 			// window class
14872 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14873 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14874 				XClassHint klass;
14875 				XWMHints wh;
14876 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14877 					wh.input = true;
14878 					wh.flags |= InputHint;
14879 				}
14880 				XSizeHints size;
14881 				klass.res_name = sdpyWindowClassStr;
14882 				klass.res_class = sdpyWindowClassStr;
14883 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14884 			}
14885 
14886 			setTitle(title);
14887 			SimpleWindow.nativeMapping[window] = this;
14888 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14889 
14890 			// This gives our window a close button
14891 			if (windowType != WindowTypes.eventOnly) {
14892 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14893 				int useAtoms;
14894 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14895 					useAtoms = 2;
14896 				} else {
14897 					useAtoms = 1;
14898 				}
14899 				assert(useAtoms <= atoms.length);
14900 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14901 			}
14902 
14903 			// FIXME: windowType and customizationFlags
14904 			Atom[8] wsatoms; // here, due to goto
14905 			int wmsacount = 0; // here, due to goto
14906 
14907 			try
14908 			final switch(windowType) {
14909 				case WindowTypes.normal:
14910 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14911 				break;
14912 				case WindowTypes.undecorated:
14913 					motifHideDecorations();
14914 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14915 				break;
14916 				case WindowTypes.eventOnly:
14917 					_hidden = true;
14918 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14919 					goto hiddenWindow;
14920 				//break;
14921 				case WindowTypes.nestedChild:
14922 					// handled in XCreateWindow calls
14923 				break;
14924 
14925 				case WindowTypes.dropdownMenu:
14926 					motifHideDecorations();
14927 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14928 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14929 				break;
14930 				case WindowTypes.popupMenu:
14931 					motifHideDecorations();
14932 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14933 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14934 				break;
14935 				case WindowTypes.notification:
14936 					motifHideDecorations();
14937 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14938 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14939 				break;
14940 				case WindowTypes.minimallyWrapped:
14941 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14942 				/+
14943 				case WindowTypes.menu:
14944 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14945 					motifHideDecorations();
14946 				break;
14947 				case WindowTypes.desktop:
14948 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14949 				break;
14950 				case WindowTypes.dock:
14951 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14952 				break;
14953 				case WindowTypes.toolbar:
14954 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14955 				break;
14956 				case WindowTypes.menu:
14957 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14958 				break;
14959 				case WindowTypes.utility:
14960 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14961 				break;
14962 				case WindowTypes.splash:
14963 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14964 				break;
14965 				case WindowTypes.dialog:
14966 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14967 				break;
14968 				case WindowTypes.tooltip:
14969 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14970 				break;
14971 				case WindowTypes.notification:
14972 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
14973 				break;
14974 				case WindowTypes.combo:
14975 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
14976 				break;
14977 				case WindowTypes.dnd:
14978 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
14979 				break;
14980 				+/
14981 			}
14982 			catch(Exception e) {
14983 				// XInternAtom failed, prolly a WM
14984 				// that doesn't support these things
14985 			}
14986 
14987 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
14988 			// the two following flags may be ignored by WM
14989 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
14990 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
14991 
14992 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
14993 
14994 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
14995 
14996 			// What would be ideal here is if they only were
14997 			// selected if there was actually an event handler
14998 			// for them...
14999 
15000 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15001 
15002 			hiddenWindow:
15003 
15004 			// set the pid property for lookup later by window managers
15005 			// a standard convenience
15006 			import core.sys.posix.unistd;
15007 			arch_ulong pid = getpid();
15008 
15009 			XChangeProperty(
15010 				display,
15011 				impl.window,
15012 				GetAtom!("_NET_WM_PID", true)(display),
15013 				XA_CARDINAL,
15014 				32 /* bits */,
15015 				0 /*PropModeReplace*/,
15016 				&pid,
15017 				1);
15018 
15019 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15020 				if(parent is null) assert(0);
15021 				XChangeProperty(
15022 					display,
15023 					impl.window,
15024 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15025 					XA_WINDOW,
15026 					32 /* bits */,
15027 					0 /*PropModeReplace*/,
15028 					&parent.impl.window,
15029 					1);
15030 
15031 			}
15032 
15033 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15034 				XMapWindow(display, window);
15035 			} else {
15036 				_hidden = true;
15037 			}
15038 		}
15039 
15040 		void populateXic() {
15041 			if (XDisplayConnection.xim !is null) {
15042 				xic = XCreateIC(XDisplayConnection.xim,
15043 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15044 						/*XNClientWindow*/"clientWindow".ptr, window,
15045 						/*XNFocusWindow*/"focusWindow".ptr, window,
15046 						null);
15047 				if (xic is null) {
15048 					import core.stdc.stdio : stderr, fprintf;
15049 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15050 				}
15051 			}
15052 		}
15053 
15054 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15055 			auto mask = EventMask.ExposureMask |
15056 				EventMask.KeyPressMask |
15057 				EventMask.KeyReleaseMask |
15058 				EventMask.PropertyChangeMask |
15059 				EventMask.FocusChangeMask |
15060 				EventMask.StructureNotifyMask |
15061 				EventMask.SubstructureNotifyMask |
15062 				EventMask.VisibilityChangeMask
15063 				| EventMask.ButtonPressMask
15064 				| EventMask.ButtonReleaseMask
15065 			;
15066 
15067 			// xshm is our shortcut for local connections
15068 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15069 				mask |= EventMask.PointerMotionMask;
15070 			else
15071 				mask |= EventMask.ButtonMotionMask;
15072 
15073 			XSelectInput(display, window, mask);
15074 		}
15075 
15076 
15077 		void setNetWMWindowType(Atom type) {
15078 			Atom[2] atoms;
15079 
15080 			atoms[0] = type;
15081 			// generic fallback
15082 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15083 
15084 			XChangeProperty(
15085 				display,
15086 				impl.window,
15087 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15088 				XA_ATOM,
15089 				32 /* bits */,
15090 				0 /*PropModeReplace*/,
15091 				atoms.ptr,
15092 				cast(int) atoms.length);
15093 		}
15094 
15095 		void motifHideDecorations(bool hide = true) {
15096 			MwmHints hints;
15097 			hints.flags = MWM_HINTS_DECORATIONS;
15098 			hints.decorations = hide ? 0 : 1;
15099 
15100 			XChangeProperty(
15101 				display,
15102 				impl.window,
15103 				GetAtom!"_MOTIF_WM_HINTS"(display),
15104 				GetAtom!"_MOTIF_WM_HINTS"(display),
15105 				32 /* bits */,
15106 				0 /*PropModeReplace*/,
15107 				&hints,
15108 				hints.sizeof / 4);
15109 		}
15110 
15111 		/*k8: unused
15112 		void createOpenGlContext() {
15113 
15114 		}
15115 		*/
15116 
15117 		void closeWindow() {
15118 			// I can't close this or a child window closing will
15119 			// break events for everyone. So I'm just leaking it right
15120 			// now and that is probably perfectly fine...
15121 			version(none)
15122 			if (customEventFDRead != -1) {
15123 				import core.sys.posix.unistd : close;
15124 				auto same = customEventFDRead == customEventFDWrite;
15125 
15126 				close(customEventFDRead);
15127 				if(!same)
15128 					close(customEventFDWrite);
15129 				customEventFDRead = -1;
15130 				customEventFDWrite = -1;
15131 			}
15132 
15133 			version(without_opengl) {} else
15134 			if(glc !is null) {
15135 				glXDestroyContext(display, glc);
15136 				glc = null;
15137 			}
15138 
15139 			if(buffer)
15140 				XFreePixmap(display, buffer);
15141 			bufferw = bufferh = 0;
15142 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15143 			XDestroyWindow(display, window);
15144 			XFlush(display);
15145 		}
15146 
15147 		void dispose() {
15148 		}
15149 
15150 		bool destroyed = false;
15151 	}
15152 
15153 	bool insideXEventLoop;
15154 }
15155 
15156 version(X11) {
15157 
15158 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15159 
15160 	private class ResizeEvent {
15161 		int width, height;
15162 	}
15163 
15164 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15165 		if(win.windowType == WindowTypes.minimallyWrapped)
15166 			return;
15167 
15168 		if(win.pendingResizeEvent is null) {
15169 			win.pendingResizeEvent = new ResizeEvent();
15170 			win.addEventListener((ResizeEvent re) {
15171 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15172 			});
15173 		}
15174 		win.pendingResizeEvent.width = width;
15175 		win.pendingResizeEvent.height = height;
15176 		if(!win.eventQueued!ResizeEvent) {
15177 			win.postEvent(win.pendingResizeEvent);
15178 		}
15179 	}
15180 
15181 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15182 		if(win.windowType == WindowTypes.minimallyWrapped)
15183 			return;
15184 		if(win.closed)
15185 			return;
15186 
15187 		if(width != win.width || height != win.height) {
15188 
15189 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15190 			win._width = width;
15191 			win._height = height;
15192 
15193 			if(win.openglMode == OpenGlOptions.no) {
15194 				// FIXME: could this be more efficient?
15195 
15196 				if (win.bufferw < width || win.bufferh < height) {
15197 					//{ 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); }
15198 					// grow the internal buffer to match the window...
15199 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15200 					{
15201 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15202 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15203 						scope(exit) XFreeGC(win.display, xgc);
15204 						XSetClipMask(win.display, xgc, None);
15205 						XSetForeground(win.display, xgc, 0);
15206 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15207 					}
15208 					XCopyArea(display,
15209 						cast(Drawable) win.buffer,
15210 						cast(Drawable) newPixmap,
15211 						win.gc, 0, 0,
15212 						win.bufferw < width ? win.bufferw : win.width,
15213 						win.bufferh < height ? win.bufferh : win.height,
15214 						0, 0);
15215 
15216 					XFreePixmap(display, win.buffer);
15217 					win.buffer = newPixmap;
15218 					win.bufferw = width;
15219 					win.bufferh = height;
15220 				}
15221 
15222 				// clear unused parts of the buffer
15223 				if (win.bufferw > width || win.bufferh > height) {
15224 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15225 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15226 					scope(exit) XFreeGC(win.display, xgc);
15227 					XSetClipMask(win.display, xgc, None);
15228 					XSetForeground(win.display, xgc, 0);
15229 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15230 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15231 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15232 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15233 				}
15234 
15235 			}
15236 
15237 			win.updateOpenglViewportIfNeeded(width, height);
15238 
15239 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15240 
15241 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15242 			if(win.windowResized !is null) {
15243 				XUnlockDisplay(display);
15244 				scope(exit) XLockDisplay(display);
15245 				win.windowResized(width, height);
15246 			}
15247 		}
15248 	}
15249 
15250 
15251 	/// Platform-specific, you might use it when doing a custom event loop.
15252 	bool doXNextEvent(Display* display) {
15253 		bool done;
15254 		XEvent e;
15255 		XNextEvent(display, &e);
15256 		version(sddddd) {
15257 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15258 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15259 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15260 			}
15261 		}
15262 
15263 		// filter out compose events
15264 		if (XFilterEvent(&e, None)) {
15265 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15266 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15267 			return false;
15268 		}
15269 		// process keyboard mapping changes
15270 		if (e.type == EventType.KeymapNotify) {
15271 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15272 			XRefreshKeyboardMapping(&e.xmapping);
15273 			return false;
15274 		}
15275 
15276 		version(with_eventloop)
15277 			import arsd.eventloop;
15278 
15279 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15280 			// see windows impl's comments
15281 			XUnlockDisplay(display);
15282 			scope(exit) XLockDisplay(display);
15283 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15284 			if(ret == 0)
15285 				return done;
15286 		}
15287 
15288 
15289 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15290 			if(win.getNativeEventHandler !is null) {
15291 				XUnlockDisplay(display);
15292 				scope(exit) XLockDisplay(display);
15293 				auto ret = win.getNativeEventHandler()(e);
15294 				if(ret == 0)
15295 					return done;
15296 			}
15297 		}
15298 
15299 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15300 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15301 				// we get this because of the RRScreenChangeNotifyMask
15302 
15303 				// this isn't actually an ideal way to do it since it wastes time
15304 				// but meh it is simple and it works.
15305 				win.actualDpiLoadAttempted = false;
15306 				SimpleWindow.xRandrInfoLoadAttemped = false;
15307 				win.updateActualDpi(); // trigger a reload
15308 			}
15309 		}
15310 
15311 		switch(e.type) {
15312 		  case EventType.SelectionClear:
15313 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15314 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15315 				// writeln("SelectionClear");
15316 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15317 			}
15318 		  break;
15319 		  case EventType.SelectionRequest:
15320 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15321 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15322 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15323 				XUnlockDisplay(display);
15324 				scope(exit) XLockDisplay(display);
15325 				(*ssh).handleRequest(e);
15326 			}
15327 		  break;
15328 		  case EventType.PropertyNotify:
15329 			// printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15330 
15331 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15332 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15333 					ssh.sendMoreIncr(&e.xproperty);
15334 			}
15335 
15336 
15337 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15338 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15339 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15340 					Atom target;
15341 					int format;
15342 					arch_ulong bytesafter, length;
15343 					void* value;
15344 
15345 					ubyte[] s;
15346 					Atom targetToKeep;
15347 
15348 					XGetWindowProperty(
15349 						e.xproperty.display,
15350 						e.xproperty.window,
15351 						e.xproperty.atom,
15352 						0,
15353 						100000 /* length */,
15354 						true, /* erase it to signal we got it and want more */
15355 						0 /*AnyPropertyType*/,
15356 						&target, &format, &length, &bytesafter, &value);
15357 
15358 					if(!targetToKeep)
15359 						targetToKeep = target;
15360 
15361 					auto id = (cast(ubyte*) value)[0 .. length];
15362 
15363 					handler.handleIncrData(targetToKeep, id);
15364 
15365 					XFree(value);
15366 				}
15367 			}
15368 		  break;
15369 		  case EventType.SelectionNotify:
15370 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15371 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15372 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15373 					XUnlockDisplay(display);
15374 					scope(exit) XLockDisplay(display);
15375 					handler.handleData(None, null);
15376 				} else {
15377 					Atom target;
15378 					int format;
15379 					arch_ulong bytesafter, length;
15380 					void* value;
15381 					XGetWindowProperty(
15382 						e.xselection.display,
15383 						e.xselection.requestor,
15384 						e.xselection.property,
15385 						0,
15386 						100000 /* length */,
15387 						//false, /* don't erase it */
15388 						true, /* do erase it lol */
15389 						0 /*AnyPropertyType*/,
15390 						&target, &format, &length, &bytesafter, &value);
15391 
15392 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15393 
15394 					{
15395 						XUnlockDisplay(display);
15396 						scope(exit) XLockDisplay(display);
15397 
15398 						if(target == XA_ATOM) {
15399 							// initial request, see what they are able to work with and request the best one
15400 							// we can handle, if available
15401 
15402 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15403 							Atom best = handler.findBestFormat(answer);
15404 
15405 							/+
15406 							writeln("got ", answer);
15407 							foreach(a; answer)
15408 								printf("%s\n", XGetAtomName(display, a));
15409 							writeln("best ", best);
15410 							+/
15411 
15412 							if(best != None) {
15413 								// actually request the best format
15414 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15415 							}
15416 						} else if(target == GetAtom!"INCR"(display)) {
15417 							// incremental
15418 
15419 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15420 
15421 							// signal the sending program that we see
15422 							// the incr and are ready to receive more.
15423 							XDeleteProperty(
15424 								e.xselection.display,
15425 								e.xselection.requestor,
15426 								e.xselection.property);
15427 						} else {
15428 							// unsupported type... maybe, forward
15429 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15430 						}
15431 					}
15432 					XFree(value);
15433 					/*
15434 					XDeleteProperty(
15435 						e.xselection.display,
15436 						e.xselection.requestor,
15437 						e.xselection.property);
15438 					*/
15439 				}
15440 			}
15441 		  break;
15442 		  case EventType.ConfigureNotify:
15443 			auto event = e.xconfigure;
15444 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15445 				if(win.windowType == WindowTypes.minimallyWrapped)
15446 					break;
15447 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
15448 
15449 				/+
15450 					The ICCCM says window managers must send a synthetic event when the window
15451 					is moved but NOT when it is resized. In the resize case, an event is sent
15452 					with position (0, 0) which can be wrong and break the dpi calculations.
15453 
15454 					So we only consider the synthetic events from the WM and otherwise
15455 					need to wait for some other event to get the position which... sucks.
15456 
15457 					I'd rather not have windows changing their layout on mouse motion after
15458 					switching monitors... might be forced to but for now just ignoring it.
15459 
15460 					Easiest way to switch monitors without sending a size position is by
15461 					maximize or fullscreen in a setup like mine, but on most setups those
15462 					work on the monitor it is already living on, so it should be ok most the
15463 					time.
15464 				+/
15465 				if(event.send_event) {
15466 					win.screenPositionKnown = true;
15467 					win.screenPositionX = event.x;
15468 					win.screenPositionY = event.y;
15469 					win.updateActualDpi();
15470 				}
15471 
15472 				win.updateIMEPopupLocation();
15473 				recordX11ResizeAsync(display, *win, event.width, event.height);
15474 			}
15475 		  break;
15476 		  case EventType.Expose:
15477 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15478 				if(win.windowType == WindowTypes.minimallyWrapped)
15479 					break;
15480 				// if it is closing from a popup menu, it can get
15481 				// an Expose event right by the end and trigger a
15482 				// BadDrawable error ... we'll just check
15483 				// closed to handle that.
15484 				if((*win).closed) break;
15485 				if((*win).openglMode == OpenGlOptions.no) {
15486 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15487 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15488 					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);
15489 				} else {
15490 					// need to redraw the scene somehow
15491 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15492 						XUnlockDisplay(display);
15493 						scope(exit) XLockDisplay(display);
15494 						version(without_opengl) {} else
15495 						win.redrawOpenGlSceneSoon();
15496 					}
15497 				}
15498 			}
15499 		  break;
15500 		  case EventType.FocusIn:
15501 		  case EventType.FocusOut:
15502 
15503 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15504 				/+
15505 
15506 				void info(string detail) {
15507 					string s;
15508 					// import std.conv;
15509 					// import std.datetime;
15510 					s ~= to!string(Clock.currTime);
15511 					s ~= " ";
15512 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15513 					s ~= " ";
15514 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15515 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15516 					s ~= detail;
15517 					s ~= " ";
15518 
15519 					sdpyPrintDebugString(s);
15520 
15521 				}
15522 
15523 				switch(e.xfocus.detail) {
15524 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15525 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15526 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15527 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15528 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15529 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15530 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15531 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15532 					default:
15533 
15534 				}
15535 				+/
15536 
15537 
15538 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15539 					break; // just ignore these they seem irrelevant
15540 
15541 				auto old = win._focused;
15542 				win._focused = e.type == EventType.FocusIn;
15543 
15544 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15545 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15546 					win._focused = true;
15547 
15548 				if(win.demandingAttention)
15549 					demandAttention(*win, false);
15550 
15551 				win.updateIMEFocused();
15552 
15553 				if(old != win._focused && win.onFocusChange) {
15554 					XUnlockDisplay(display);
15555 					scope(exit) XLockDisplay(display);
15556 					win.onFocusChange(win._focused);
15557 				}
15558 			}
15559 		  break;
15560 		  case EventType.VisibilityNotify:
15561 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15562 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15563 						if (win.visibilityChanged !is null) {
15564 								XUnlockDisplay(display);
15565 								scope(exit) XLockDisplay(display);
15566 								win.visibilityChanged(false);
15567 							}
15568 					} else {
15569 						if (win.visibilityChanged !is null) {
15570 							XUnlockDisplay(display);
15571 							scope(exit) XLockDisplay(display);
15572 							win.visibilityChanged(true);
15573 						}
15574 					}
15575 				}
15576 				break;
15577 		  case EventType.ClientMessage:
15578 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15579 					// "ignore next mouse motion" event, increment ignore counter for teh window
15580 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15581 						++(*win).warpEventCount;
15582 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15583 					} else {
15584 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15585 					}
15586 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15587 					// user clicked the close button on the window manager
15588 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15589 						XUnlockDisplay(display);
15590 						scope(exit) XLockDisplay(display);
15591 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15592 					}
15593 
15594 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15595 					// writeln("HAPPENED");
15596 					// user clicked the close button on the window manager
15597 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15598 						XUnlockDisplay(display);
15599 						scope(exit) XLockDisplay(display);
15600 
15601 						auto setTo = *win;
15602 
15603 						if(win.setRequestedInputFocus !is null) {
15604 							auto s = win.setRequestedInputFocus();
15605 							if(s !is null) {
15606 								setTo = s;
15607 							}
15608 						}
15609 
15610 						assert(setTo !is null);
15611 
15612 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15613 
15614 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15615 					}
15616 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15617 					foreach(nai; NotificationAreaIcon.activeIcons)
15618 						nai.newManager();
15619 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15620 
15621 					bool xDragWindow = true;
15622 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15623 						//XDefineCursor(display, xDragWindow.impl.window,
15624 							//writeln("XdndStatus ", e.xclient.data.l);
15625 					}
15626 					if(auto dh = win.dropHandler) {
15627 
15628 						static Atom[3] xFormatsBuffer;
15629 						static Atom[] xFormats;
15630 
15631 						void resetXFormats() {
15632 							xFormatsBuffer[] = 0;
15633 							xFormats = xFormatsBuffer[];
15634 						}
15635 
15636 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15637 							// on Windows it is supposed to return the effect you actually do FIXME
15638 
15639 							auto sourceWindow =  e.xclient.data.l[0];
15640 
15641 							xFormatsBuffer[0] = e.xclient.data.l[2];
15642 							xFormatsBuffer[1] = e.xclient.data.l[3];
15643 							xFormatsBuffer[2] = e.xclient.data.l[4];
15644 
15645 							if(e.xclient.data.l[1] & 1) {
15646 								// can just grab it all but like we don't necessarily need them...
15647 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15648 							} else {
15649 								int len;
15650 								foreach(fmt; xFormatsBuffer)
15651 									if(fmt) len++;
15652 								xFormats = xFormatsBuffer[0 .. len];
15653 							}
15654 
15655 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15656 
15657 							dh.dragEnter(&pkg);
15658 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15659 
15660 							auto pack = e.xclient.data.l[2];
15661 
15662 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15663 
15664 
15665 							XClientMessageEvent xclient;
15666 
15667 							xclient.type = EventType.ClientMessage;
15668 							xclient.window = e.xclient.data.l[0];
15669 							xclient.message_type = GetAtom!"XdndStatus"(display);
15670 							xclient.format = 32;
15671 							xclient.data.l[0] = win.impl.window;
15672 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15673 							auto r = result.consistentWithin;
15674 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15675 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15676 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15677 
15678 							XSendEvent(
15679 								display,
15680 								e.xclient.data.l[0],
15681 								false,
15682 								EventMask.NoEventMask,
15683 								cast(XEvent*) &xclient
15684 							);
15685 
15686 
15687 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15688 							//writeln("XdndLeave");
15689 							// drop cancelled.
15690 							// data.l[0] is the source window
15691 							dh.dragLeave();
15692 
15693 							resetXFormats();
15694 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15695 							// drop happening, should fetch data, then send finished
15696 							// writeln("XdndDrop");
15697 
15698 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15699 
15700 							dh.drop(&pkg);
15701 
15702 							resetXFormats();
15703 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15704 							// writeln("XdndFinished");
15705 
15706 							dh.finish();
15707 						}
15708 
15709 					}
15710 				}
15711 		  break;
15712 		  case EventType.MapNotify:
15713 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15714 					(*win)._visible = true;
15715 					if (!(*win)._visibleForTheFirstTimeCalled) {
15716 						(*win)._visibleForTheFirstTimeCalled = true;
15717 						if ((*win).visibleForTheFirstTime !is null) {
15718 							XUnlockDisplay(display);
15719 							scope(exit) XLockDisplay(display);
15720 							(*win).visibleForTheFirstTime();
15721 						}
15722 					}
15723 					if ((*win).visibilityChanged !is null) {
15724 						XUnlockDisplay(display);
15725 						scope(exit) XLockDisplay(display);
15726 						(*win).visibilityChanged(true);
15727 					}
15728 				}
15729 		  break;
15730 		  case EventType.UnmapNotify:
15731 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15732 					win._visible = false;
15733 					if (win.visibilityChanged !is null) {
15734 						XUnlockDisplay(display);
15735 						scope(exit) XLockDisplay(display);
15736 						win.visibilityChanged(false);
15737 					}
15738 			}
15739 		  break;
15740 		  case EventType.DestroyNotify:
15741 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15742 				if(win.destroyed)
15743 					break; // might get a notification both for itself and from its parent
15744 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15745 				win._closed = true; // just in case
15746 				win.destroyed = true;
15747 				if (win.xic !is null) {
15748 					XDestroyIC(win.xic);
15749 					win.xic = null; // just in case
15750 				}
15751 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15752 				bool anyImportant = false;
15753 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15754 					if(w.beingOpenKeepsAppOpen) {
15755 						anyImportant = true;
15756 						break;
15757 					}
15758 				if(!anyImportant) {
15759 					EventLoop.quitApplication();
15760 					done = true;
15761 				}
15762 			}
15763 			auto window = e.xdestroywindow.window;
15764 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15765 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15766 
15767 			version(with_eventloop) {
15768 				if(done) exit();
15769 			}
15770 		  break;
15771 
15772 		  case EventType.MotionNotify:
15773 			MouseEvent mouse;
15774 			auto event = e.xmotion;
15775 
15776 			mouse.type = MouseEventType.motion;
15777 			mouse.x = event.x;
15778 			mouse.y = event.y;
15779 			mouse.modifierState = event.state;
15780 
15781 			mouse.timestamp = event.time;
15782 
15783 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15784 				mouse.window = *win;
15785 				if (win.warpEventCount > 0) {
15786 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15787 					--(*win).warpEventCount;
15788 					(*win).mdx(mouse); // so deltas will be correctly updated
15789 				} else {
15790 					win.warpEventCount = 0; // just in case
15791 					(*win).mdx(mouse);
15792 					if((*win).handleMouseEvent) {
15793 						XUnlockDisplay(display);
15794 						scope(exit) XLockDisplay(display);
15795 						(*win).handleMouseEvent(mouse);
15796 					}
15797 				}
15798 			}
15799 
15800 		  	version(with_eventloop)
15801 				send(mouse);
15802 		  break;
15803 		  case EventType.ButtonPress:
15804 		  case EventType.ButtonRelease:
15805 			MouseEvent mouse;
15806 			auto event = e.xbutton;
15807 
15808 			mouse.timestamp = event.time;
15809 
15810 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15811 			mouse.x = event.x;
15812 			mouse.y = event.y;
15813 
15814 			static Time lastMouseDownTime = 0;
15815 			static int lastMouseDownButton = -1;
15816 
15817 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15818 			if(e.type == EventType.ButtonPress) {
15819 				lastMouseDownTime = event.time;
15820 				lastMouseDownButton = event.button;
15821 			}
15822 
15823 			switch(event.button) {
15824 				case 1: mouse.button = MouseButton.left; break; // left
15825 				case 2: mouse.button = MouseButton.middle; break; // middle
15826 				case 3: mouse.button = MouseButton.right; break; // right
15827 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15828 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15829 				case 6: break; // idk
15830 				case 7: break; // idk
15831 				case 8: mouse.button = MouseButton.backButton; break;
15832 				case 9: mouse.button = MouseButton.forwardButton; break;
15833 				default:
15834 			}
15835 
15836 			// FIXME: double check this
15837 			mouse.modifierState = event.state;
15838 
15839 			//mouse.modifierState = event.detail;
15840 
15841 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15842 				mouse.window = *win;
15843 				(*win).mdx(mouse);
15844 				if((*win).handleMouseEvent) {
15845 					XUnlockDisplay(display);
15846 					scope(exit) XLockDisplay(display);
15847 					(*win).handleMouseEvent(mouse);
15848 				}
15849 			}
15850 			version(with_eventloop)
15851 				send(mouse);
15852 		  break;
15853 
15854 		  case EventType.KeyPress:
15855 		  case EventType.KeyRelease:
15856 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15857 			KeyEvent ke;
15858 			ke.pressed = e.type == EventType.KeyPress;
15859 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15860 
15861 			auto sym = XKeycodeToKeysym(
15862 				XDisplayConnection.get(),
15863 				e.xkey.keycode,
15864 				0);
15865 
15866 			ke.key = cast(Key) sym;//e.xkey.keycode;
15867 
15868 			ke.modifierState = e.xkey.state;
15869 
15870 			// writefln("%x", sym);
15871 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15872 			int charbuflen = 0; // return value of XwcLookupString
15873 			if (ke.pressed) {
15874 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15875 				if (win !is null && win.xic !is null) {
15876 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15877 					Status status;
15878 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15879 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15880 				} else {
15881 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15882 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15883 					char[16] buffer;
15884 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15885 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15886 				}
15887 			}
15888 
15889 			// if there's no char, subst one
15890 			if (charbuflen == 0) {
15891 				switch (sym) {
15892 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15893 					case 0xff8d: // keypad enter
15894 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15895 					default : // ignore
15896 				}
15897 			}
15898 
15899 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15900 				ke.window = *win;
15901 
15902 
15903 				if(win.inputProxy)
15904 					win = &win.inputProxy;
15905 
15906 				// char events are separate since they are on Windows too
15907 				// also, xcompose can generate long char sequences
15908 				// don't send char events if Meta and/or Hyper is pressed
15909 				// TODO: ctrl+char should only send control chars; not yet
15910 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15911 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15912 				}
15913 
15914 				dchar[32] charsComingBuffer;
15915 				int charsComingPosition;
15916 				dchar[] charsComing = charsComingBuffer[];
15917 
15918 				if (ke.pressed && charbuflen > 0) {
15919 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15920 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15921 						if(charsComingPosition >= charsComing.length)
15922 							charsComing.length = charsComingPosition + 8;
15923 
15924 						charsComing[charsComingPosition++] = ch;
15925 					}
15926 
15927 					charsComing = charsComing[0 .. charsComingPosition];
15928 				} else {
15929 					charsComing = null;
15930 				}
15931 
15932 				ke.charsPossible = charsComing;
15933 
15934 				if (win.handleKeyEvent) {
15935 					XUnlockDisplay(display);
15936 					scope(exit) XLockDisplay(display);
15937 					win.handleKeyEvent(ke);
15938 				}
15939 
15940 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15941 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15942 					XUnlockDisplay(display);
15943 					scope(exit) XLockDisplay(display);
15944 					foreach(ch; charsComing)
15945 						win.handleCharEvent(ch);
15946 				}
15947 			}
15948 
15949 			version(with_eventloop)
15950 				send(ke);
15951 		  break;
15952 		  default:
15953 		}
15954 
15955 		return done;
15956 	}
15957 }
15958 
15959 /* *************************************** */
15960 /*      Done with simpledisplay stuff      */
15961 /* *************************************** */
15962 
15963 // Necessary C library bindings follow
15964 version(Windows) {} else
15965 version(X11) {
15966 
15967 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15968 
15969 // X11 bindings needed here
15970 /*
15971 	A little of this is from the bindings project on
15972 	D Source and some of it is copy/paste from the C
15973 	header.
15974 
15975 	The DSource listing consistently used D's long
15976 	where C used long. That's wrong - C long is 32 bit, so
15977 	it should be int in D. I changed that here.
15978 
15979 	Note:
15980 	This isn't complete, just took what I needed for myself.
15981 */
15982 
15983 import core.stdc.stddef : wchar_t;
15984 
15985 interface XLib {
15986 extern(C) nothrow @nogc {
15987 	char* XResourceManagerString(Display*);
15988 	void XrmInitialize();
15989 	XrmDatabase XrmGetStringDatabase(char* data);
15990 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
15991 
15992 	Cursor XCreateFontCursor(Display*, uint shape);
15993 	int XDefineCursor(Display* display, Window w, Cursor cursor);
15994 	int XUndefineCursor(Display* display, Window w);
15995 
15996 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
15997 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
15998 	int XFreeCursor(Display* display, Cursor cursor);
15999 
16000 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16001 
16002 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16003 
16004 	XVaNestedList XVaCreateNestedList(int unused, ...);
16005 
16006 	char *XKeysymToString(KeySym keysym);
16007 	KeySym XKeycodeToKeysym(
16008 		Display*		/* display */,
16009 		KeyCode		/* keycode */,
16010 		int			/* index */
16011 	);
16012 
16013 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16014 
16015 	int XFree(void*);
16016 	int XDeleteProperty(Display *display, Window w, Atom property);
16017 
16018 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16019 
16020 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16021 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16022 		*actual_type_return, int *actual_format_return, arch_ulong
16023 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16024 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16025 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16026 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16027 
16028 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16029 
16030 	Window XGetSelectionOwner(Display *display, Atom selection);
16031 
16032 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16033 
16034 	char** XListFonts(Display*, const char*, int, int*);
16035 	void XFreeFontNames(char**);
16036 
16037 	Display* XOpenDisplay(const char*);
16038 	int XCloseDisplay(Display*);
16039 
16040 	int function() XSynchronize(Display*, bool);
16041 	int function() XSetAfterFunction(Display*, int function() proc);
16042 
16043 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16044 
16045 	Bool XSupportsLocale();
16046 	char* XSetLocaleModifiers(const(char)* modifier_list);
16047 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16048 	Status XCloseOM(XOM om);
16049 
16050 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16051 	Status XCloseIM(XIM im);
16052 
16053 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16054 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16055 	Display* XDisplayOfIM(XIM im);
16056 	char* XLocaleOfIM(XIM im);
16057 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16058 	void XDestroyIC(XIC ic);
16059 	void XSetICFocus(XIC ic);
16060 	void XUnsetICFocus(XIC ic);
16061 	//wchar_t* XwcResetIC(XIC ic);
16062 	char* XmbResetIC(XIC ic);
16063 	char* Xutf8ResetIC(XIC ic);
16064 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16065 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16066 	XIM XIMOfIC(XIC ic);
16067 
16068 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16069 
16070 
16071 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16072 	int XFreeFont(Display *display, XFontStruct *font_struct);
16073 	int XSetFont(Display* display, GC gc, Font font);
16074 	int XTextWidth(XFontStruct*, scope const char*, int);
16075 
16076 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16077 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16078 
16079 	Window XCreateSimpleWindow(
16080 		Display*	/* display */,
16081 		Window		/* parent */,
16082 		int			/* x */,
16083 		int			/* y */,
16084 		uint		/* width */,
16085 		uint		/* height */,
16086 		uint		/* border_width */,
16087 		uint		/* border */,
16088 		uint		/* background */
16089 	);
16090 	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);
16091 
16092 	int XReparentWindow(Display*, Window, Window, int, int);
16093 	int XClearWindow(Display*, Window);
16094 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16095 	int XMoveWindow(Display*, Window, int, int);
16096 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16097 
16098 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16099 
16100 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16101 
16102 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16103 
16104 	XImage *XCreateImage(
16105 		Display*		/* display */,
16106 		Visual*		/* visual */,
16107 		uint	/* depth */,
16108 		int			/* format */,
16109 		int			/* offset */,
16110 		ubyte*		/* data */,
16111 		uint	/* width */,
16112 		uint	/* height */,
16113 		int			/* bitmap_pad */,
16114 		int			/* bytes_per_line */
16115 	);
16116 
16117 	Status XInitImage (XImage* image);
16118 
16119 	Atom XInternAtom(
16120 		Display*		/* display */,
16121 		const char*	/* atom_name */,
16122 		Bool		/* only_if_exists */
16123 	);
16124 
16125 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16126 	char* XGetAtomName(Display*, Atom);
16127 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16128 
16129 	int XPutImage(
16130 		Display*	/* display */,
16131 		Drawable	/* d */,
16132 		GC			/* gc */,
16133 		XImage*	/* image */,
16134 		int			/* src_x */,
16135 		int			/* src_y */,
16136 		int			/* dest_x */,
16137 		int			/* dest_y */,
16138 		uint		/* width */,
16139 		uint		/* height */
16140 	);
16141 
16142 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16143 
16144 
16145 	int XDestroyWindow(
16146 		Display*	/* display */,
16147 		Window		/* w */
16148 	);
16149 
16150 	int XDestroyImage(XImage*);
16151 
16152 	int XSelectInput(
16153 		Display*	/* display */,
16154 		Window		/* w */,
16155 		EventMask	/* event_mask */
16156 	);
16157 
16158 	int XMapWindow(
16159 		Display*	/* display */,
16160 		Window		/* w */
16161 	);
16162 
16163 	Status XIconifyWindow(Display*, Window, int);
16164 	int XMapRaised(Display*, Window);
16165 	int XMapSubwindows(Display*, Window);
16166 
16167 	int XNextEvent(
16168 		Display*	/* display */,
16169 		XEvent*		/* event_return */
16170 	);
16171 
16172 	int XMaskEvent(Display*, arch_long, XEvent*);
16173 
16174 	Bool XFilterEvent(XEvent *event, Window window);
16175 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16176 
16177 	Status XSetWMProtocols(
16178 		Display*	/* display */,
16179 		Window		/* w */,
16180 		Atom*		/* protocols */,
16181 		int			/* count */
16182 	);
16183 
16184 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16185 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16186 
16187 
16188 	Status XInitThreads();
16189 	void XLockDisplay (Display* display);
16190 	void XUnlockDisplay (Display* display);
16191 
16192 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16193 
16194 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16195 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16196 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16197 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16198 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16199 
16200 
16201 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16202 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16203 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16204 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16205 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16206 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16207 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16208 	int XDrawPoint(Display*, Drawable, GC, int, int);
16209 	int XSetForeground(Display*, GC, uint);
16210 	int XSetBackground(Display*, GC, uint);
16211 
16212 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16213 	void XFreeFontSet(Display*, XFontSet);
16214 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16215 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16216 
16217 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16218 
16219 
16220 //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);
16221 
16222 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16223 	int XSetFunction(Display*, GC, int);
16224 
16225 	GC XCreateGC(Display*, Drawable, uint, void*);
16226 	int XCopyGC(Display*, GC, uint, GC);
16227 	int XFreeGC(Display*, GC);
16228 
16229 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16230 	bool XCheckMaskEvent(Display*, int, XEvent*);
16231 
16232 	int XPending(Display*);
16233 	int XEventsQueued(Display* display, int mode);
16234 
16235 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16236 	int XFreePixmap(Display*, Pixmap);
16237 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16238 	int XFlush(Display*);
16239 	int XBell(Display*, int);
16240 	int XSync(Display*, bool);
16241 
16242 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16243 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16244 
16245 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16246 	int XUngrabKeyboard(Display*, Time);
16247 
16248 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16249 
16250 	KeySym XStringToKeysym(const char *string);
16251 
16252 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16253 
16254 	Window XDefaultRootWindow(Display*);
16255 
16256 	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);
16257 
16258 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16259 
16260 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16261 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16262 
16263 	Status XAllocColor(Display*, Colormap, XColor*);
16264 
16265 	int XWithdrawWindow(Display*, Window, int);
16266 	int XUnmapWindow(Display*, Window);
16267 	int XLowerWindow(Display*, Window);
16268 	int XRaiseWindow(Display*, Window);
16269 
16270 	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);
16271 	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);
16272 
16273 	int XGetInputFocus(Display*, Window*, int*);
16274 	int XSetInputFocus(Display*, Window, int, Time);
16275 
16276 	XErrorHandler XSetErrorHandler(XErrorHandler);
16277 
16278 	int XGetErrorText(Display*, int, char*, int);
16279 
16280 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16281 
16282 
16283 	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);
16284 	int XUngrabPointer(Display *display, Time time);
16285 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16286 
16287 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16288 
16289 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16290 	int XSetClipMask(Display*, GC, Pixmap);
16291 	int XSetClipOrigin(Display*, GC, int, int);
16292 
16293 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16294 
16295 	void XSetWMName(Display*, Window, XTextProperty*);
16296 	Status XGetWMName(Display*, Window, XTextProperty*);
16297 	int XStoreName(Display* display, Window w, const(char)* window_name);
16298 
16299 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16300 
16301 }
16302 }
16303 
16304 interface Xext {
16305 extern(C) nothrow @nogc {
16306 	Status XShmAttach(Display*, XShmSegmentInfo*);
16307 	Status XShmDetach(Display*, XShmSegmentInfo*);
16308 	Status XShmPutImage(
16309 		Display*            /* dpy */,
16310 		Drawable            /* d */,
16311 		GC                  /* gc */,
16312 		XImage*             /* image */,
16313 		int                 /* src_x */,
16314 		int                 /* src_y */,
16315 		int                 /* dst_x */,
16316 		int                 /* dst_y */,
16317 		uint        /* src_width */,
16318 		uint        /* src_height */,
16319 		Bool                /* send_event */
16320 	);
16321 
16322 	Status XShmQueryExtension(Display*);
16323 
16324 	XImage *XShmCreateImage(
16325 		Display*            /* dpy */,
16326 		Visual*             /* visual */,
16327 		uint        /* depth */,
16328 		int                 /* format */,
16329 		char*               /* data */,
16330 		XShmSegmentInfo*    /* shminfo */,
16331 		uint        /* width */,
16332 		uint        /* height */
16333 	);
16334 
16335 	Pixmap XShmCreatePixmap(
16336 		Display*            /* dpy */,
16337 		Drawable            /* d */,
16338 		char*               /* data */,
16339 		XShmSegmentInfo*    /* shminfo */,
16340 		uint        /* width */,
16341 		uint        /* height */,
16342 		uint        /* depth */
16343 	);
16344 
16345 }
16346 }
16347 
16348 	// this requires -lXpm
16349 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16350 
16351 
16352 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16353 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16354 shared static this() {
16355 	xlib.loadDynamicLibrary();
16356 	xext.loadDynamicLibrary();
16357 }
16358 
16359 
16360 extern(C) nothrow @nogc {
16361 
16362 alias XrmDatabase = void*;
16363 struct XrmValue {
16364 	uint size;
16365 	void* addr;
16366 }
16367 
16368 struct XVisualInfo {
16369 	Visual* visual;
16370 	VisualID visualid;
16371 	int screen;
16372 	uint depth;
16373 	int c_class;
16374 	c_ulong red_mask;
16375 	c_ulong green_mask;
16376 	c_ulong blue_mask;
16377 	int colormap_size;
16378 	int bits_per_rgb;
16379 }
16380 
16381 enum VisualNoMask=	0x0;
16382 enum VisualIDMask=	0x1;
16383 enum VisualScreenMask=0x2;
16384 enum VisualDepthMask=	0x4;
16385 enum VisualClassMask=	0x8;
16386 enum VisualRedMaskMask=0x10;
16387 enum VisualGreenMaskMask=0x20;
16388 enum VisualBlueMaskMask=0x40;
16389 enum VisualColormapSizeMask=0x80;
16390 enum VisualBitsPerRGBMask=0x100;
16391 enum VisualAllMask=	0x1FF;
16392 
16393 enum AnyKey = 0;
16394 enum AnyModifier = 1 << 15;
16395 
16396 // XIM and other crap
16397 struct _XOM {}
16398 struct _XIM {}
16399 struct _XIC {}
16400 alias XOM = _XOM*;
16401 alias XIM = _XIM*;
16402 alias XIC = _XIC*;
16403 
16404 alias XVaNestedList = void*;
16405 
16406 alias XIMStyle = arch_ulong;
16407 enum : arch_ulong {
16408 	XIMPreeditArea      = 0x0001,
16409 	XIMPreeditCallbacks = 0x0002,
16410 	XIMPreeditPosition  = 0x0004,
16411 	XIMPreeditNothing   = 0x0008,
16412 	XIMPreeditNone      = 0x0010,
16413 	XIMStatusArea       = 0x0100,
16414 	XIMStatusCallbacks  = 0x0200,
16415 	XIMStatusNothing    = 0x0400,
16416 	XIMStatusNone       = 0x0800,
16417 }
16418 
16419 
16420 /* X Shared Memory Extension functions */
16421 	//pragma(lib, "Xshm");
16422 	alias arch_ulong ShmSeg;
16423 	struct XShmSegmentInfo {
16424 		ShmSeg shmseg;
16425 		int shmid;
16426 		ubyte* shmaddr;
16427 		Bool readOnly;
16428 	}
16429 
16430 	// and the necessary OS functions
16431 	int shmget(int, size_t, int);
16432 	void* shmat(int, scope const void*, int);
16433 	int shmdt(scope const void*);
16434 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16435 
16436 	enum IPC_PRIVATE = 0;
16437 	enum IPC_CREAT = 512;
16438 	enum IPC_RMID = 0;
16439 
16440 /* MIT-SHM end */
16441 
16442 
16443 enum MappingType:int {
16444 	MappingModifier		=0,
16445 	MappingKeyboard		=1,
16446 	MappingPointer		=2
16447 }
16448 
16449 /* ImageFormat -- PutImage, GetImage */
16450 enum ImageFormat:int {
16451 	XYBitmap	=0,	/* depth 1, XYFormat */
16452 	XYPixmap	=1,	/* depth == drawable depth */
16453 	ZPixmap	=2	/* depth == drawable depth */
16454 }
16455 
16456 enum ModifierName:int {
16457 	ShiftMapIndex	=0,
16458 	LockMapIndex	=1,
16459 	ControlMapIndex	=2,
16460 	Mod1MapIndex	=3,
16461 	Mod2MapIndex	=4,
16462 	Mod3MapIndex	=5,
16463 	Mod4MapIndex	=6,
16464 	Mod5MapIndex	=7
16465 }
16466 
16467 enum ButtonMask:int {
16468 	Button1Mask	=1<<8,
16469 	Button2Mask	=1<<9,
16470 	Button3Mask	=1<<10,
16471 	Button4Mask	=1<<11,
16472 	Button5Mask	=1<<12,
16473 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16474 }
16475 
16476 enum KeyOrButtonMask:uint {
16477 	ShiftMask	=1<<0,
16478 	LockMask	=1<<1,
16479 	ControlMask	=1<<2,
16480 	Mod1Mask	=1<<3,
16481 	Mod2Mask	=1<<4,
16482 	Mod3Mask	=1<<5,
16483 	Mod4Mask	=1<<6,
16484 	Mod5Mask	=1<<7,
16485 	Button1Mask	=1<<8,
16486 	Button2Mask	=1<<9,
16487 	Button3Mask	=1<<10,
16488 	Button4Mask	=1<<11,
16489 	Button5Mask	=1<<12,
16490 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16491 }
16492 
16493 enum ButtonName:int {
16494 	Button1	=1,
16495 	Button2	=2,
16496 	Button3	=3,
16497 	Button4	=4,
16498 	Button5	=5
16499 }
16500 
16501 /* Notify modes */
16502 enum NotifyModes:int
16503 {
16504 	NotifyNormal		=0,
16505 	NotifyGrab			=1,
16506 	NotifyUngrab		=2,
16507 	NotifyWhileGrabbed	=3
16508 }
16509 enum NotifyHint = 1;	/* for MotionNotify events */
16510 
16511 /* Notify detail */
16512 enum NotifyDetail:int
16513 {
16514 	NotifyAncestor			=0,
16515 	NotifyVirtual			=1,
16516 	NotifyInferior			=2,
16517 	NotifyNonlinear			=3,
16518 	NotifyNonlinearVirtual	=4,
16519 	NotifyPointer			=5,
16520 	NotifyPointerRoot		=6,
16521 	NotifyDetailNone		=7
16522 }
16523 
16524 /* Visibility notify */
16525 
16526 enum VisibilityNotify:int
16527 {
16528 VisibilityUnobscured		=0,
16529 VisibilityPartiallyObscured	=1,
16530 VisibilityFullyObscured		=2
16531 }
16532 
16533 
16534 enum WindowStackingMethod:int
16535 {
16536 	Above		=0,
16537 	Below		=1,
16538 	TopIf		=2,
16539 	BottomIf	=3,
16540 	Opposite	=4
16541 }
16542 
16543 /* Circulation request */
16544 enum CirculationRequest:int
16545 {
16546 	PlaceOnTop		=0,
16547 	PlaceOnBottom	=1
16548 }
16549 
16550 enum PropertyNotification:int
16551 {
16552 	PropertyNewValue	=0,
16553 	PropertyDelete		=1
16554 }
16555 
16556 enum ColorMapNotification:int
16557 {
16558 	ColormapUninstalled	=0,
16559 	ColormapInstalled		=1
16560 }
16561 
16562 
16563 	struct _XPrivate {}
16564 	struct _XrmHashBucketRec {}
16565 
16566 	alias void* XPointer;
16567 	alias void* XExtData;
16568 
16569 	version( X86_64 ) {
16570 		alias ulong XID;
16571 		alias ulong arch_ulong;
16572 		alias long arch_long;
16573 	} else version (AArch64) {
16574 		alias ulong XID;
16575 		alias ulong arch_ulong;
16576 		alias long arch_long;
16577 	} else {
16578 		alias uint XID;
16579 		alias uint arch_ulong;
16580 		alias int arch_long;
16581 	}
16582 
16583 	alias XID Window;
16584 	alias XID Drawable;
16585 	alias XID Pixmap;
16586 
16587 	alias arch_ulong Atom;
16588 	alias int Bool;
16589 	alias Display XDisplay;
16590 
16591 	alias int ByteOrder;
16592 	alias arch_ulong Time;
16593 	alias void ScreenFormat;
16594 
16595 	struct XImage {
16596 		int width, height;			/* size of image */
16597 		int xoffset;				/* number of pixels offset in X direction */
16598 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16599 		void *data;					/* pointer to image data */
16600 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16601 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16602 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16603 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16604 		int depth;					/* depth of image */
16605 		int bytes_per_line;			/* accelarator to next line */
16606 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16607 		arch_ulong red_mask;	/* bits in z arrangment */
16608 		arch_ulong green_mask;
16609 		arch_ulong blue_mask;
16610 		XPointer obdata;			/* hook for the object routines to hang on */
16611 		static struct F {				/* image manipulation routines */
16612 			XImage* function(
16613 				XDisplay* 			/* display */,
16614 				Visual*				/* visual */,
16615 				uint				/* depth */,
16616 				int					/* format */,
16617 				int					/* offset */,
16618 				ubyte*				/* data */,
16619 				uint				/* width */,
16620 				uint				/* height */,
16621 				int					/* bitmap_pad */,
16622 				int					/* bytes_per_line */) create_image;
16623 			int function(XImage *) destroy_image;
16624 			arch_ulong function(XImage *, int, int) get_pixel;
16625 			int function(XImage *, int, int, arch_ulong) put_pixel;
16626 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16627 			int function(XImage *, arch_long) add_pixel;
16628 		}
16629 		F f;
16630 	}
16631 	version(X86_64) static assert(XImage.sizeof == 136);
16632 	else version(X86) static assert(XImage.sizeof == 88);
16633 
16634 struct XCharStruct {
16635 	short       lbearing;       /* origin to left edge of raster */
16636 	short       rbearing;       /* origin to right edge of raster */
16637 	short       width;          /* advance to next char's origin */
16638 	short       ascent;         /* baseline to top edge of raster */
16639 	short       descent;        /* baseline to bottom edge of raster */
16640 	ushort attributes;  /* per char flags (not predefined) */
16641 }
16642 
16643 /*
16644  * To allow arbitrary information with fonts, there are additional properties
16645  * returned.
16646  */
16647 struct XFontProp {
16648 	Atom name;
16649 	arch_ulong card32;
16650 }
16651 
16652 alias Atom Font;
16653 
16654 struct XFontStruct {
16655 	XExtData *ext_data;           /* Hook for extension to hang data */
16656 	Font fid;                     /* Font ID for this font */
16657 	uint direction;           /* Direction the font is painted */
16658 	uint min_char_or_byte2;   /* First character */
16659 	uint max_char_or_byte2;   /* Last character */
16660 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16661 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16662 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16663 	uint default_char;        /* Char to print for undefined character */
16664 	int n_properties;             /* How many properties there are */
16665 	XFontProp *properties;        /* Pointer to array of additional properties*/
16666 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16667 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16668 	XCharStruct *per_char;        /* first_char to last_char information */
16669 	int ascent;                   /* Max extent above baseline for spacing */
16670 	int descent;                  /* Max descent below baseline for spacing */
16671 }
16672 
16673 
16674 /*
16675  * Definitions of specific events.
16676  */
16677 struct XKeyEvent
16678 {
16679 	int type;			/* of event */
16680 	arch_ulong serial;		/* # of last request processed by server */
16681 	Bool send_event;	/* true if this came from a SendEvent request */
16682 	Display *display;	/* Display the event was read from */
16683 	Window window;	        /* "event" window it is reported relative to */
16684 	Window root;	        /* root window that the event occurred on */
16685 	Window subwindow;	/* child window */
16686 	Time time;		/* milliseconds */
16687 	int x, y;		/* pointer x, y coordinates in event window */
16688 	int x_root, y_root;	/* coordinates relative to root */
16689 	KeyOrButtonMask state;	/* key or button mask */
16690 	uint keycode;	/* detail */
16691 	Bool same_screen;	/* same screen flag */
16692 }
16693 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16694 alias XKeyEvent XKeyPressedEvent;
16695 alias XKeyEvent XKeyReleasedEvent;
16696 
16697 struct XButtonEvent
16698 {
16699 	int type;		/* of event */
16700 	arch_ulong serial;	/* # of last request processed by server */
16701 	Bool send_event;	/* true if this came from a SendEvent request */
16702 	Display *display;	/* Display the event was read from */
16703 	Window window;	        /* "event" window it is reported relative to */
16704 	Window root;	        /* root window that the event occurred on */
16705 	Window subwindow;	/* child window */
16706 	Time time;		/* milliseconds */
16707 	int x, y;		/* pointer x, y coordinates in event window */
16708 	int x_root, y_root;	/* coordinates relative to root */
16709 	KeyOrButtonMask state;	/* key or button mask */
16710 	uint button;	/* detail */
16711 	Bool same_screen;	/* same screen flag */
16712 }
16713 alias XButtonEvent XButtonPressedEvent;
16714 alias XButtonEvent XButtonReleasedEvent;
16715 
16716 struct XMotionEvent{
16717 	int type;		/* of event */
16718 	arch_ulong serial;	/* # of last request processed by server */
16719 	Bool send_event;	/* true if this came from a SendEvent request */
16720 	Display *display;	/* Display the event was read from */
16721 	Window window;	        /* "event" window reported relative to */
16722 	Window root;	        /* root window that the event occurred on */
16723 	Window subwindow;	/* child window */
16724 	Time time;		/* milliseconds */
16725 	int x, y;		/* pointer x, y coordinates in event window */
16726 	int x_root, y_root;	/* coordinates relative to root */
16727 	KeyOrButtonMask state;	/* key or button mask */
16728 	byte is_hint;		/* detail */
16729 	Bool same_screen;	/* same screen flag */
16730 }
16731 alias XMotionEvent XPointerMovedEvent;
16732 
16733 struct XCrossingEvent{
16734 	int type;		/* of event */
16735 	arch_ulong serial;	/* # of last request processed by server */
16736 	Bool send_event;	/* true if this came from a SendEvent request */
16737 	Display *display;	/* Display the event was read from */
16738 	Window window;	        /* "event" window reported relative to */
16739 	Window root;	        /* root window that the event occurred on */
16740 	Window subwindow;	/* child window */
16741 	Time time;		/* milliseconds */
16742 	int x, y;		/* pointer x, y coordinates in event window */
16743 	int x_root, y_root;	/* coordinates relative to root */
16744 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16745 	NotifyDetail detail;
16746 	/*
16747 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16748 	 * NotifyNonlinear,NotifyNonlinearVirtual
16749 	 */
16750 	Bool same_screen;	/* same screen flag */
16751 	Bool focus;		/* Boolean focus */
16752 	KeyOrButtonMask state;	/* key or button mask */
16753 }
16754 alias XCrossingEvent XEnterWindowEvent;
16755 alias XCrossingEvent XLeaveWindowEvent;
16756 
16757 struct XFocusChangeEvent{
16758 	int type;		/* FocusIn or FocusOut */
16759 	arch_ulong serial;	/* # of last request processed by server */
16760 	Bool send_event;	/* true if this came from a SendEvent request */
16761 	Display *display;	/* Display the event was read from */
16762 	Window window;		/* window of event */
16763 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16764 				   NotifyGrab, NotifyUngrab */
16765 	NotifyDetail detail;
16766 	/*
16767 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16768 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16769 	 * NotifyPointerRoot, NotifyDetailNone
16770 	 */
16771 }
16772 alias XFocusChangeEvent XFocusInEvent;
16773 alias XFocusChangeEvent XFocusOutEvent;
16774 
16775 enum CWBackPixmap              = (1L<<0);
16776 enum CWBackPixel               = (1L<<1);
16777 enum CWBorderPixmap            = (1L<<2);
16778 enum CWBorderPixel             = (1L<<3);
16779 enum CWBitGravity              = (1L<<4);
16780 enum CWWinGravity              = (1L<<5);
16781 enum CWBackingStore            = (1L<<6);
16782 enum CWBackingPlanes           = (1L<<7);
16783 enum CWBackingPixel            = (1L<<8);
16784 enum CWOverrideRedirect        = (1L<<9);
16785 enum CWSaveUnder               = (1L<<10);
16786 enum CWEventMask               = (1L<<11);
16787 enum CWDontPropagate           = (1L<<12);
16788 enum CWColormap                = (1L<<13);
16789 enum CWCursor                  = (1L<<14);
16790 
16791 struct XWindowAttributes {
16792 	int x, y;			/* location of window */
16793 	int width, height;		/* width and height of window */
16794 	int border_width;		/* border width of window */
16795 	int depth;			/* depth of window */
16796 	Visual *visual;			/* the associated visual structure */
16797 	Window root;			/* root of screen containing window */
16798 	int class_;			/* InputOutput, InputOnly*/
16799 	int bit_gravity;		/* one of the bit gravity values */
16800 	int win_gravity;		/* one of the window gravity values */
16801 	int backing_store;		/* NotUseful, WhenMapped, Always */
16802 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16803 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16804 	Bool save_under;		/* boolean, should bits under be saved? */
16805 	Colormap colormap;		/* color map to be associated with window */
16806 	Bool map_installed;		/* boolean, is color map currently installed*/
16807 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16808 	arch_long all_event_masks;		/* set of events all people have interest in*/
16809 	arch_long your_event_mask;		/* my event mask */
16810 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16811 	Bool override_redirect;		/* boolean value for override-redirect */
16812 	Screen *screen;			/* back pointer to correct screen */
16813 }
16814 
16815 enum IsUnmapped = 0;
16816 enum IsUnviewable = 1;
16817 enum IsViewable = 2;
16818 
16819 struct XSetWindowAttributes {
16820 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16821 	arch_ulong background_pixel;/* background pixel */
16822 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16823 	arch_ulong border_pixel;/* border pixel value */
16824 	int bit_gravity;         /* one of 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 use in restoring planes */
16829 	Bool save_under;         /* should bits under be saved? (popups) */
16830 	arch_long event_mask;         /* set of events that should be saved */
16831 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16832 	Bool override_redirect;  /* boolean value for override_redirect */
16833 	Colormap colormap;       /* color map to be associated with window */
16834 	Cursor cursor;           /* cursor to be displayed (or None) */
16835 }
16836 
16837 
16838 alias int Status;
16839 
16840 
16841 enum EventMask:int
16842 {
16843 	NoEventMask				=0,
16844 	KeyPressMask			=1<<0,
16845 	KeyReleaseMask			=1<<1,
16846 	ButtonPressMask			=1<<2,
16847 	ButtonReleaseMask		=1<<3,
16848 	EnterWindowMask			=1<<4,
16849 	LeaveWindowMask			=1<<5,
16850 	PointerMotionMask		=1<<6,
16851 	PointerMotionHintMask	=1<<7,
16852 	Button1MotionMask		=1<<8,
16853 	Button2MotionMask		=1<<9,
16854 	Button3MotionMask		=1<<10,
16855 	Button4MotionMask		=1<<11,
16856 	Button5MotionMask		=1<<12,
16857 	ButtonMotionMask		=1<<13,
16858 	KeymapStateMask		=1<<14,
16859 	ExposureMask			=1<<15,
16860 	VisibilityChangeMask	=1<<16,
16861 	StructureNotifyMask		=1<<17,
16862 	ResizeRedirectMask		=1<<18,
16863 	SubstructureNotifyMask	=1<<19,
16864 	SubstructureRedirectMask=1<<20,
16865 	FocusChangeMask			=1<<21,
16866 	PropertyChangeMask		=1<<22,
16867 	ColormapChangeMask		=1<<23,
16868 	OwnerGrabButtonMask		=1<<24
16869 }
16870 
16871 struct MwmHints {
16872 	c_ulong flags;
16873 	c_ulong functions;
16874 	c_ulong decorations;
16875 	c_long input_mode;
16876 	c_ulong status;
16877 }
16878 
16879 enum {
16880 	MWM_HINTS_FUNCTIONS = (1L << 0),
16881 	MWM_HINTS_DECORATIONS =  (1L << 1),
16882 
16883 	MWM_FUNC_ALL = (1L << 0),
16884 	MWM_FUNC_RESIZE = (1L << 1),
16885 	MWM_FUNC_MOVE = (1L << 2),
16886 	MWM_FUNC_MINIMIZE = (1L << 3),
16887 	MWM_FUNC_MAXIMIZE = (1L << 4),
16888 	MWM_FUNC_CLOSE = (1L << 5),
16889 
16890 	MWM_DECOR_ALL = (1L << 0),
16891 	MWM_DECOR_BORDER = (1L << 1),
16892 	MWM_DECOR_RESIZEH = (1L << 2),
16893 	MWM_DECOR_TITLE = (1L << 3),
16894 	MWM_DECOR_MENU = (1L << 4),
16895 	MWM_DECOR_MINIMIZE = (1L << 5),
16896 	MWM_DECOR_MAXIMIZE = (1L << 6),
16897 }
16898 
16899 import core.stdc.config : c_long, c_ulong;
16900 
16901 	/* Size hints mask bits */
16902 
16903 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16904 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16905 	enum   PPosition   = (1L << 2)          /* program specified position */;
16906 	enum   PSize       = (1L << 3)          /* program specified size */;
16907 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16908 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16909 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16910 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16911 	enum   PBaseSize   = (1L << 8);
16912 	enum   PWinGravity = (1L << 9);
16913 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16914 	struct XSizeHints {
16915 		arch_long flags;         /* marks which fields in this structure are defined */
16916 		int x, y;           /* Obsolete */
16917 		int width, height;  /* Obsolete */
16918 		int min_width, min_height;
16919 		int max_width, max_height;
16920 		int width_inc, height_inc;
16921 		struct Aspect {
16922 			int x;       /* numerator */
16923 			int y;       /* denominator */
16924 		}
16925 
16926 		Aspect min_aspect;
16927 		Aspect max_aspect;
16928 		int base_width, base_height;
16929 		int win_gravity;
16930 		/* this structure may be extended in the future */
16931 	}
16932 
16933 
16934 
16935 enum EventType:int
16936 {
16937 	KeyPress			=2,
16938 	KeyRelease			=3,
16939 	ButtonPress			=4,
16940 	ButtonRelease		=5,
16941 	MotionNotify		=6,
16942 	EnterNotify			=7,
16943 	LeaveNotify			=8,
16944 	FocusIn				=9,
16945 	FocusOut			=10,
16946 	KeymapNotify		=11,
16947 	Expose				=12,
16948 	GraphicsExpose		=13,
16949 	NoExpose			=14,
16950 	VisibilityNotify	=15,
16951 	CreateNotify		=16,
16952 	DestroyNotify		=17,
16953 	UnmapNotify		=18,
16954 	MapNotify			=19,
16955 	MapRequest			=20,
16956 	ReparentNotify		=21,
16957 	ConfigureNotify		=22,
16958 	ConfigureRequest	=23,
16959 	GravityNotify		=24,
16960 	ResizeRequest		=25,
16961 	CirculateNotify		=26,
16962 	CirculateRequest	=27,
16963 	PropertyNotify		=28,
16964 	SelectionClear		=29,
16965 	SelectionRequest	=30,
16966 	SelectionNotify		=31,
16967 	ColormapNotify		=32,
16968 	ClientMessage		=33,
16969 	MappingNotify		=34,
16970 	LASTEvent			=35	/* must be bigger than any event # */
16971 }
16972 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
16973 struct XKeymapEvent
16974 {
16975 	int type;
16976 	arch_ulong serial;	/* # of last request processed by server */
16977 	Bool send_event;	/* true if this came from a SendEvent request */
16978 	Display *display;	/* Display the event was read from */
16979 	Window window;
16980 	byte[32] key_vector;
16981 }
16982 
16983 struct XExposeEvent
16984 {
16985 	int type;
16986 	arch_ulong serial;	/* # of last request processed by server */
16987 	Bool send_event;	/* true if this came from a SendEvent request */
16988 	Display *display;	/* Display the event was read from */
16989 	Window window;
16990 	int x, y;
16991 	int width, height;
16992 	int count;		/* if non-zero, at least this many more */
16993 }
16994 
16995 struct XGraphicsExposeEvent{
16996 	int type;
16997 	arch_ulong serial;	/* # of last request processed by server */
16998 	Bool send_event;	/* true if this came from a SendEvent request */
16999 	Display *display;	/* Display the event was read from */
17000 	Drawable drawable;
17001 	int x, y;
17002 	int width, height;
17003 	int count;		/* if non-zero, at least this many more */
17004 	int major_code;		/* core is CopyArea or CopyPlane */
17005 	int minor_code;		/* not defined in the core */
17006 }
17007 
17008 struct XNoExposeEvent{
17009 	int type;
17010 	arch_ulong serial;	/* # of last request processed by server */
17011 	Bool send_event;	/* true if this came from a SendEvent request */
17012 	Display *display;	/* Display the event was read from */
17013 	Drawable drawable;
17014 	int major_code;		/* core is CopyArea or CopyPlane */
17015 	int minor_code;		/* not defined in the core */
17016 }
17017 
17018 struct XVisibilityEvent{
17019 	int type;
17020 	arch_ulong serial;	/* # of last request processed by server */
17021 	Bool send_event;	/* true if this came from a SendEvent request */
17022 	Display *display;	/* Display the event was read from */
17023 	Window window;
17024 	VisibilityNotify state;		/* Visibility state */
17025 }
17026 
17027 struct XCreateWindowEvent{
17028 	int type;
17029 	arch_ulong serial;	/* # of last request processed by server */
17030 	Bool send_event;	/* true if this came from a SendEvent request */
17031 	Display *display;	/* Display the event was read from */
17032 	Window parent;		/* parent of the window */
17033 	Window window;		/* window id of window created */
17034 	int x, y;		/* window location */
17035 	int width, height;	/* size of window */
17036 	int border_width;	/* border width */
17037 	Bool override_redirect;	/* creation should be overridden */
17038 }
17039 
17040 struct XDestroyWindowEvent
17041 {
17042 	int type;
17043 	arch_ulong serial;		/* # of last request processed by server */
17044 	Bool send_event;	/* true if this came from a SendEvent request */
17045 	Display *display;	/* Display the event was read from */
17046 	Window event;
17047 	Window window;
17048 }
17049 
17050 struct XUnmapEvent
17051 {
17052 	int type;
17053 	arch_ulong serial;		/* # of last request processed by server */
17054 	Bool send_event;	/* true if this came from a SendEvent request */
17055 	Display *display;	/* Display the event was read from */
17056 	Window event;
17057 	Window window;
17058 	Bool from_configure;
17059 }
17060 
17061 struct XMapEvent
17062 {
17063 	int type;
17064 	arch_ulong serial;		/* # of last request processed by server */
17065 	Bool send_event;	/* true if this came from a SendEvent request */
17066 	Display *display;	/* Display the event was read from */
17067 	Window event;
17068 	Window window;
17069 	Bool override_redirect;	/* Boolean, is override set... */
17070 }
17071 
17072 struct XMapRequestEvent
17073 {
17074 	int type;
17075 	arch_ulong serial;	/* # of last request processed by server */
17076 	Bool send_event;	/* true if this came from a SendEvent request */
17077 	Display *display;	/* Display the event was read from */
17078 	Window parent;
17079 	Window window;
17080 }
17081 
17082 struct XReparentEvent
17083 {
17084 	int type;
17085 	arch_ulong serial;	/* # of last request processed by server */
17086 	Bool send_event;	/* true if this came from a SendEvent request */
17087 	Display *display;	/* Display the event was read from */
17088 	Window event;
17089 	Window window;
17090 	Window parent;
17091 	int x, y;
17092 	Bool override_redirect;
17093 }
17094 
17095 struct XConfigureEvent
17096 {
17097 	int type;
17098 	arch_ulong serial;	/* # of last request processed by server */
17099 	Bool send_event;	/* true if this came from a SendEvent request */
17100 	Display *display;	/* Display the event was read from */
17101 	Window event;
17102 	Window window;
17103 	int x, y;
17104 	int width, height;
17105 	int border_width;
17106 	Window above;
17107 	Bool override_redirect;
17108 }
17109 
17110 struct XGravityEvent
17111 {
17112 	int type;
17113 	arch_ulong serial;	/* # of last request processed by server */
17114 	Bool send_event;	/* true if this came from a SendEvent request */
17115 	Display *display;	/* Display the event was read from */
17116 	Window event;
17117 	Window window;
17118 	int x, y;
17119 }
17120 
17121 struct XResizeRequestEvent
17122 {
17123 	int type;
17124 	arch_ulong serial;	/* # of last request processed by server */
17125 	Bool send_event;	/* true if this came from a SendEvent request */
17126 	Display *display;	/* Display the event was read from */
17127 	Window window;
17128 	int width, height;
17129 }
17130 
17131 struct  XConfigureRequestEvent
17132 {
17133 	int type;
17134 	arch_ulong serial;	/* # of last request processed by server */
17135 	Bool send_event;	/* true if this came from a SendEvent request */
17136 	Display *display;	/* Display the event was read from */
17137 	Window parent;
17138 	Window window;
17139 	int x, y;
17140 	int width, height;
17141 	int border_width;
17142 	Window above;
17143 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17144 	arch_ulong value_mask;
17145 }
17146 
17147 struct XCirculateEvent
17148 {
17149 	int type;
17150 	arch_ulong serial;	/* # of last request processed by server */
17151 	Bool send_event;	/* true if this came from a SendEvent request */
17152 	Display *display;	/* Display the event was read from */
17153 	Window event;
17154 	Window window;
17155 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17156 }
17157 
17158 struct XCirculateRequestEvent
17159 {
17160 	int type;
17161 	arch_ulong serial;	/* # of last request processed by server */
17162 	Bool send_event;	/* true if this came from a SendEvent request */
17163 	Display *display;	/* Display the event was read from */
17164 	Window parent;
17165 	Window window;
17166 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17167 }
17168 
17169 struct XPropertyEvent
17170 {
17171 	int type;
17172 	arch_ulong serial;	/* # of last request processed by server */
17173 	Bool send_event;	/* true if this came from a SendEvent request */
17174 	Display *display;	/* Display the event was read from */
17175 	Window window;
17176 	Atom atom;
17177 	Time time;
17178 	PropertyNotification state;		/* NewValue, Deleted */
17179 }
17180 
17181 struct XSelectionClearEvent
17182 {
17183 	int type;
17184 	arch_ulong serial;	/* # of last request processed by server */
17185 	Bool send_event;	/* true if this came from a SendEvent request */
17186 	Display *display;	/* Display the event was read from */
17187 	Window window;
17188 	Atom selection;
17189 	Time time;
17190 }
17191 
17192 struct XSelectionRequestEvent
17193 {
17194 	int type;
17195 	arch_ulong serial;	/* # of last request processed by server */
17196 	Bool send_event;	/* true if this came from a SendEvent request */
17197 	Display *display;	/* Display the event was read from */
17198 	Window owner;
17199 	Window requestor;
17200 	Atom selection;
17201 	Atom target;
17202 	Atom property;
17203 	Time time;
17204 }
17205 
17206 struct XSelectionEvent
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 requestor;
17213 	Atom selection;
17214 	Atom target;
17215 	Atom property;		/* ATOM or None */
17216 	Time time;
17217 }
17218 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17219 
17220 struct XColormapEvent
17221 {
17222 	int type;
17223 	arch_ulong serial;	/* # of last request processed by server */
17224 	Bool send_event;	/* true if this came from a SendEvent request */
17225 	Display *display;	/* Display the event was read from */
17226 	Window window;
17227 	Colormap colormap;	/* COLORMAP or None */
17228 	Bool new_;		/* C++ */
17229 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17230 }
17231 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17232 
17233 struct XClientMessageEvent
17234 {
17235 	int type;
17236 	arch_ulong serial;	/* # of last request processed by server */
17237 	Bool send_event;	/* true if this came from a SendEvent request */
17238 	Display *display;	/* Display the event was read from */
17239 	Window window;
17240 	Atom message_type;
17241 	int format;
17242 	union Data{
17243 		byte[20] b;
17244 		short[10] s;
17245 		arch_ulong[5] l;
17246 	}
17247 	Data data;
17248 
17249 }
17250 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17251 
17252 struct XMappingEvent
17253 {
17254 	int type;
17255 	arch_ulong serial;	/* # of last request processed by server */
17256 	Bool send_event;	/* true if this came from a SendEvent request */
17257 	Display *display;	/* Display the event was read from */
17258 	Window window;		/* unused */
17259 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17260 				   MappingPointer */
17261 	int first_keycode;	/* first keycode */
17262 	int count;		/* defines range of change w. first_keycode*/
17263 }
17264 
17265 struct XErrorEvent
17266 {
17267 	int type;
17268 	Display *display;	/* Display the event was read from */
17269 	XID resourceid;		/* resource id */
17270 	arch_ulong serial;	/* serial number of failed request */
17271 	ubyte error_code;	/* error code of failed request */
17272 	ubyte request_code;	/* Major op-code of failed request */
17273 	ubyte minor_code;	/* Minor op-code of failed request */
17274 }
17275 
17276 struct XAnyEvent
17277 {
17278 	int type;
17279 	arch_ulong serial;	/* # of last request processed by server */
17280 	Bool send_event;	/* true if this came from a SendEvent request */
17281 	Display *display;/* Display the event was read from */
17282 	Window window;	/* window on which event was requested in event mask */
17283 }
17284 
17285 union XEvent{
17286 	int type;		/* must not be changed; first element */
17287 	XAnyEvent xany;
17288 	XKeyEvent xkey;
17289 	XButtonEvent xbutton;
17290 	XMotionEvent xmotion;
17291 	XCrossingEvent xcrossing;
17292 	XFocusChangeEvent xfocus;
17293 	XExposeEvent xexpose;
17294 	XGraphicsExposeEvent xgraphicsexpose;
17295 	XNoExposeEvent xnoexpose;
17296 	XVisibilityEvent xvisibility;
17297 	XCreateWindowEvent xcreatewindow;
17298 	XDestroyWindowEvent xdestroywindow;
17299 	XUnmapEvent xunmap;
17300 	XMapEvent xmap;
17301 	XMapRequestEvent xmaprequest;
17302 	XReparentEvent xreparent;
17303 	XConfigureEvent xconfigure;
17304 	XGravityEvent xgravity;
17305 	XResizeRequestEvent xresizerequest;
17306 	XConfigureRequestEvent xconfigurerequest;
17307 	XCirculateEvent xcirculate;
17308 	XCirculateRequestEvent xcirculaterequest;
17309 	XPropertyEvent xproperty;
17310 	XSelectionClearEvent xselectionclear;
17311 	XSelectionRequestEvent xselectionrequest;
17312 	XSelectionEvent xselection;
17313 	XColormapEvent xcolormap;
17314 	XClientMessageEvent xclient;
17315 	XMappingEvent xmapping;
17316 	XErrorEvent xerror;
17317 	XKeymapEvent xkeymap;
17318 	arch_ulong[24] pad;
17319 }
17320 
17321 
17322 	struct Display {
17323 		XExtData *ext_data;	/* hook for extension to hang data */
17324 		_XPrivate *private1;
17325 		int fd;			/* Network socket. */
17326 		int private2;
17327 		int proto_major_version;/* major version of server's X protocol */
17328 		int proto_minor_version;/* minor version of servers X protocol */
17329 		char *vendor;		/* vendor of the server hardware */
17330 	    	XID private3;
17331 		XID private4;
17332 		XID private5;
17333 		int private6;
17334 		XID function(Display*)resource_alloc;/* allocator function */
17335 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17336 		int bitmap_unit;	/* padding and data requirements */
17337 		int bitmap_pad;		/* padding requirements on bitmaps */
17338 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17339 		int nformats;		/* number of pixmap formats in list */
17340 		ScreenFormat *pixmap_format;	/* pixmap format list */
17341 		int private8;
17342 		int release;		/* release of the server */
17343 		_XPrivate *private9;
17344 		_XPrivate *private10;
17345 		int qlen;		/* Length of input event queue */
17346 		arch_ulong last_request_read; /* seq number of last event read */
17347 		arch_ulong request;	/* sequence number of last request. */
17348 		XPointer private11;
17349 		XPointer private12;
17350 		XPointer private13;
17351 		XPointer private14;
17352 		uint max_request_size; /* maximum number 32 bit words in request*/
17353 		_XrmHashBucketRec *db;
17354 		int function  (Display*)private15;
17355 		char *display_name;	/* "host:display" string used on this connect*/
17356 		int default_screen;	/* default screen for operations */
17357 		int nscreens;		/* number of screens on this server*/
17358 		Screen *screens;	/* pointer to list of screens */
17359 		arch_ulong motion_buffer;	/* size of motion buffer */
17360 		arch_ulong private16;
17361 		int min_keycode;	/* minimum defined keycode */
17362 		int max_keycode;	/* maximum defined keycode */
17363 		XPointer private17;
17364 		XPointer private18;
17365 		int private19;
17366 		byte *xdefaults;	/* contents of defaults from server */
17367 		/* there is more to this structure, but it is private to Xlib */
17368 	}
17369 
17370 	// I got these numbers from a C program as a sanity test
17371 	version(X86_64) {
17372 		static assert(Display.sizeof == 296);
17373 		static assert(XPointer.sizeof == 8);
17374 		static assert(XErrorEvent.sizeof == 40);
17375 		static assert(XAnyEvent.sizeof == 40);
17376 		static assert(XMappingEvent.sizeof == 56);
17377 		static assert(XEvent.sizeof == 192);
17378     	} else version (AArch64) {
17379         	// omit check for aarch64
17380 	} else {
17381 		static assert(Display.sizeof == 176);
17382 		static assert(XPointer.sizeof == 4);
17383 		static assert(XEvent.sizeof == 96);
17384 	}
17385 
17386 struct Depth
17387 {
17388 	int depth;		/* this depth (Z) of the depth */
17389 	int nvisuals;		/* number of Visual types at this depth */
17390 	Visual *visuals;	/* list of visuals possible at this depth */
17391 }
17392 
17393 alias void* GC;
17394 alias c_ulong VisualID;
17395 alias XID Colormap;
17396 alias XID Cursor;
17397 alias XID KeySym;
17398 alias uint KeyCode;
17399 enum None = 0;
17400 }
17401 
17402 version(without_opengl) {}
17403 else {
17404 extern(C) nothrow @nogc {
17405 
17406 
17407 static if(!SdpyIsUsingIVGLBinds) {
17408 enum GLX_USE_GL=            1;       /* support GLX rendering */
17409 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17410 enum GLX_LEVEL=             3;       /* level in plane stacking */
17411 enum GLX_RGBA=              4;       /* true if RGBA mode */
17412 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17413 enum GLX_STEREO=            6;       /* stereo buffering supported */
17414 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17415 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17416 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17417 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17418 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17419 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17420 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17421 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17422 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17423 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17424 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17425 
17426 
17427 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17428 
17429 
17430 
17431 enum GL_TRUE = 1;
17432 enum GL_FALSE = 0;
17433 }
17434 
17435 alias XID GLXContextID;
17436 alias XID GLXPixmap;
17437 alias XID GLXDrawable;
17438 alias XID GLXPbuffer;
17439 alias XID GLXWindow;
17440 alias XID GLXFBConfigID;
17441 alias void* GLXContext;
17442 
17443 }
17444 }
17445 
17446 enum AllocNone = 0;
17447 
17448 extern(C) {
17449 	/* WARNING, this type not in Xlib spec */
17450 	extern(C) alias XIOErrorHandler = int function (Display* display);
17451 }
17452 
17453 extern(C) nothrow
17454 alias XErrorHandler = int function(Display*, XErrorEvent*);
17455 
17456 extern(C) nothrow @nogc {
17457 struct Screen{
17458 	XExtData *ext_data;		/* hook for extension to hang data */
17459 	Display *display;		/* back pointer to display structure */
17460 	Window root;			/* Root window id. */
17461 	int width, height;		/* width and height of screen */
17462 	int mwidth, mheight;	/* width and height of  in millimeters */
17463 	int ndepths;			/* number of depths possible */
17464 	Depth *depths;			/* list of allowable depths on the screen */
17465 	int root_depth;			/* bits per pixel */
17466 	Visual *root_visual;	/* root visual */
17467 	GC default_gc;			/* GC for the root root visual */
17468 	Colormap cmap;			/* default color map */
17469 	uint white_pixel;
17470 	uint black_pixel;		/* White and Black pixel values */
17471 	int max_maps, min_maps;	/* max and min color maps */
17472 	int backing_store;		/* Never, WhenMapped, Always */
17473 	bool save_unders;
17474 	int root_input_mask;	/* initial root input mask */
17475 }
17476 
17477 struct Visual
17478 {
17479 	XExtData *ext_data;	/* hook for extension to hang data */
17480 	VisualID visualid;	/* visual id of this visual */
17481 	int class_;			/* class of screen (monochrome, etc.) */
17482 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17483 	int bits_per_rgb;	/* log base 2 of distinct color values */
17484 	int map_entries;	/* color map entries */
17485 }
17486 
17487 	alias Display* _XPrivDisplay;
17488 
17489 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17490 		assert(dpy !is null);
17491 		return &dpy.screens[scr];
17492 	}
17493 
17494 	extern(D) Window RootWindow(Display *dpy,int scr) {
17495 		return ScreenOfDisplay(dpy,scr).root;
17496 	}
17497 
17498 	struct XWMHints {
17499 		arch_long flags;
17500 		Bool input;
17501 		int initial_state;
17502 		Pixmap icon_pixmap;
17503 		Window icon_window;
17504 		int icon_x, icon_y;
17505 		Pixmap icon_mask;
17506 		XID window_group;
17507 	}
17508 
17509 	struct XClassHint {
17510 		char* res_name;
17511 		char* res_class;
17512 	}
17513 
17514 	extern(D) int DefaultScreen(Display *dpy) {
17515 		return dpy.default_screen;
17516 	}
17517 
17518 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17519 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17520 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17521 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17522 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17523 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17524 
17525 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17526 
17527 	enum int AnyPropertyType = 0;
17528 	enum int Success = 0;
17529 
17530 	enum int RevertToNone = None;
17531 	enum int PointerRoot = 1;
17532 	enum Time CurrentTime = 0;
17533 	enum int RevertToPointerRoot = PointerRoot;
17534 	enum int RevertToParent = 2;
17535 
17536 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17537 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17538 	}
17539 
17540 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17541 		return ScreenOfDisplay(dpy,scr).root_visual;
17542 	}
17543 
17544 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17545 		return ScreenOfDisplay(dpy,scr).default_gc;
17546 	}
17547 
17548 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17549 		return ScreenOfDisplay(dpy,scr).black_pixel;
17550 	}
17551 
17552 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17553 		return ScreenOfDisplay(dpy,scr).white_pixel;
17554 	}
17555 
17556 	alias void* XFontSet; // i think
17557 	struct XmbTextItem {
17558 		char* chars;
17559 		int nchars;
17560 		int delta;
17561 		XFontSet font_set;
17562 	}
17563 
17564 	struct XTextItem {
17565 		char* chars;
17566 		int nchars;
17567 		int delta;
17568 		Font font;
17569 	}
17570 
17571 	enum {
17572 		GXclear        = 0x0, /* 0 */
17573 		GXand          = 0x1, /* src AND dst */
17574 		GXandReverse   = 0x2, /* src AND NOT dst */
17575 		GXcopy         = 0x3, /* src */
17576 		GXandInverted  = 0x4, /* NOT src AND dst */
17577 		GXnoop         = 0x5, /* dst */
17578 		GXxor          = 0x6, /* src XOR dst */
17579 		GXor           = 0x7, /* src OR dst */
17580 		GXnor          = 0x8, /* NOT src AND NOT dst */
17581 		GXequiv        = 0x9, /* NOT src XOR dst */
17582 		GXinvert       = 0xa, /* NOT dst */
17583 		GXorReverse    = 0xb, /* src OR NOT dst */
17584 		GXcopyInverted = 0xc, /* NOT src */
17585 		GXorInverted   = 0xd, /* NOT src OR dst */
17586 		GXnand         = 0xe, /* NOT src OR NOT dst */
17587 		GXset          = 0xf, /* 1 */
17588 	}
17589 	enum QueueMode : int {
17590 		QueuedAlready,
17591 		QueuedAfterReading,
17592 		QueuedAfterFlush
17593 	}
17594 
17595 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17596 
17597 	struct XPoint {
17598 		short x;
17599 		short y;
17600 	}
17601 
17602 	enum CoordMode:int {
17603 		CoordModeOrigin = 0,
17604 		CoordModePrevious = 1
17605 	}
17606 
17607 	enum PolygonShape:int {
17608 		Complex = 0,
17609 		Nonconvex = 1,
17610 		Convex = 2
17611 	}
17612 
17613 	struct XTextProperty {
17614 		const(char)* value;		/* same as Property routines */
17615 		Atom encoding;			/* prop type */
17616 		int format;				/* prop data format: 8, 16, or 32 */
17617 		arch_ulong nitems;		/* number of data items in value */
17618 	}
17619 
17620 	version( X86_64 ) {
17621 		static assert(XTextProperty.sizeof == 32);
17622 	}
17623 
17624 
17625 	struct XGCValues {
17626 		int function_;           /* logical operation */
17627 		arch_ulong plane_mask;/* plane mask */
17628 		arch_ulong foreground;/* foreground pixel */
17629 		arch_ulong background;/* background pixel */
17630 		int line_width;         /* line width */
17631 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17632 		int cap_style;          /* CapNotLast, CapButt,
17633 					   CapRound, CapProjecting */
17634 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17635 		int fill_style;         /* FillSolid, FillTiled,
17636 					   FillStippled, FillOpaeueStippled */
17637 		int fill_rule;          /* EvenOddRule, WindingRule */
17638 		int arc_mode;           /* ArcChord, ArcPieSlice */
17639 		Pixmap tile;            /* tile pixmap for tiling operations */
17640 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17641 		int ts_x_origin;        /* offset for tile or stipple operations */
17642 		int ts_y_origin;
17643 		Font font;              /* default text font for text operations */
17644 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17645 		Bool graphics_exposures;/* boolean, should exposures be generated */
17646 		int clip_x_origin;      /* origin for clipping */
17647 		int clip_y_origin;
17648 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17649 		int dash_offset;        /* patterned/dashed line information */
17650 		char dashes;
17651 	}
17652 
17653 	struct XColor {
17654 		arch_ulong pixel;
17655 		ushort red, green, blue;
17656 		byte flags;
17657 		byte pad;
17658 	}
17659 
17660 	struct XRectangle {
17661 		short x;
17662 		short y;
17663 		ushort width;
17664 		ushort height;
17665 	}
17666 
17667 	enum ClipByChildren = 0;
17668 	enum IncludeInferiors = 1;
17669 
17670 	enum Atom XA_PRIMARY = 1;
17671 	enum Atom XA_SECONDARY = 2;
17672 	enum Atom XA_STRING = 31;
17673 	enum Atom XA_CARDINAL = 6;
17674 	enum Atom XA_WM_NAME = 39;
17675 	enum Atom XA_ATOM = 4;
17676 	enum Atom XA_WINDOW = 33;
17677 	enum Atom XA_WM_HINTS = 35;
17678 	enum int PropModeAppend = 2;
17679 	enum int PropModeReplace = 0;
17680 	enum int PropModePrepend = 1;
17681 
17682 	enum int CopyFromParent = 0;
17683 	enum int InputOutput = 1;
17684 
17685 	// XWMHints
17686 	enum InputHint = 1 << 0;
17687 	enum StateHint = 1 << 1;
17688 	enum IconPixmapHint = (1L << 2);
17689 	enum IconWindowHint = (1L << 3);
17690 	enum IconPositionHint = (1L << 4);
17691 	enum IconMaskHint = (1L << 5);
17692 	enum WindowGroupHint = (1L << 6);
17693 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17694 	enum XUrgencyHint = (1L << 8);
17695 
17696 	// GC Components
17697 	enum GCFunction           =   (1L<<0);
17698 	enum GCPlaneMask         =    (1L<<1);
17699 	enum GCForeground       =     (1L<<2);
17700 	enum GCBackground      =      (1L<<3);
17701 	enum GCLineWidth      =       (1L<<4);
17702 	enum GCLineStyle     =        (1L<<5);
17703 	enum GCCapStyle     =         (1L<<6);
17704 	enum GCJoinStyle   =          (1L<<7);
17705 	enum GCFillStyle  =           (1L<<8);
17706 	enum GCFillRule  =            (1L<<9);
17707 	enum GCTile     =             (1L<<10);
17708 	enum GCStipple           =    (1L<<11);
17709 	enum GCTileStipXOrigin  =     (1L<<12);
17710 	enum GCTileStipYOrigin =      (1L<<13);
17711 	enum GCFont               =   (1L<<14);
17712 	enum GCSubwindowMode     =    (1L<<15);
17713 	enum GCGraphicsExposures=     (1L<<16);
17714 	enum GCClipXOrigin     =      (1L<<17);
17715 	enum GCClipYOrigin    =       (1L<<18);
17716 	enum GCClipMask      =        (1L<<19);
17717 	enum GCDashOffset   =         (1L<<20);
17718 	enum GCDashList    =          (1L<<21);
17719 	enum GCArcMode    =           (1L<<22);
17720 	enum GCLastBit   =            22;
17721 
17722 
17723 	enum int WithdrawnState = 0;
17724 	enum int NormalState = 1;
17725 	enum int IconicState = 3;
17726 
17727 }
17728 } else version (OSXCocoa) {
17729 
17730 /+
17731 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
17732 +/
17733 
17734 	private __gshared AppDelegate globalAppDelegate;
17735 
17736 	extern(Objective-C)
17737 	class AppDelegate : NSObject, NSApplicationDelegate {
17738 		override static AppDelegate alloc() @selector("alloc");
17739 
17740 
17741 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
17742 			SimpleWindow.processAllCustomEvents();
17743 		}
17744 
17745 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
17746 			immutable style = NSWindowStyleMask.resizable |
17747 				NSWindowStyleMask.closable |
17748 				NSWindowStyleMask.miniaturizable |
17749 				NSWindowStyleMask.titled;
17750 
17751 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
17752 
17753 			{
17754 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
17755 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
17756 				mainMenu.setSubmenu(menu, item);
17757 
17758 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
17759 				newItem.target = NSApp;
17760 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
17761 				newItem2.target = NSApp;
17762 			}
17763 
17764 			{
17765 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
17766 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
17767 				mainMenu.setSubmenu(menu, item);
17768 
17769 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
17770 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
17771 			}
17772 
17773 
17774 			NSApp.menu = mainMenu;
17775 
17776 
17777 			// auto controller = ViewController.alloc.init;
17778 
17779 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
17780 
17781 			/+
17782 			this.window = window;
17783 			this.controller = controller;
17784 			+/
17785 		}
17786 
17787 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
17788 			NSApplication.shared_.activateIgnoringOtherApps(true);
17789 		}
17790 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
17791 			return true;
17792 		}
17793 	}
17794 
17795 	extern(Objective-C)
17796 	class SDWindowDelegate : NSObject, NSWindowDelegate {
17797 		override static SDWindowDelegate alloc() @selector("alloc");
17798 		override SDWindowDelegate init() @selector("init");
17799 
17800 		SimpleWindow simpleWindow;
17801 
17802 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
17803 			if(simpleWindow.windowResized) {
17804 				// FIXME: automaticallyScaleIfPossible behaviors
17805 
17806 				simpleWindow._width = cast(int) frameSize.width;
17807 				simpleWindow._height = cast(int) frameSize.height;
17808 
17809 				simpleWindow.view.setFrameSize(frameSize);
17810 
17811 				/+
17812 				auto size = simpleWindow.view.frame.size;
17813 				writeln(cast(int) size.width, "x", cast(int) size.height);
17814 				+/
17815 
17816 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
17817 
17818 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
17819 
17820 				// simpleWindow.view.setNeedsDisplay(true);
17821 			}
17822 
17823 			return frameSize;
17824 		}
17825 
17826 		/+
17827 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
17828 			if(simpleWindow.windowResized) {
17829 				auto window = simpleWindow.window;
17830 				auto rect = window.contentRectForFrameRect(window.frame);
17831 				import std.stdio; writeln(window.frame.size);
17832 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
17833 			}
17834 		}
17835 		+/
17836 	}
17837 
17838 	extern(Objective-C)
17839 	class SDGraphicsView : NSView {
17840 		SimpleWindow simpleWindow;
17841 
17842 		override static SDGraphicsView alloc() @selector("alloc");
17843 		override SDGraphicsView init() @selector("init") {
17844 			super.init();
17845 			return this;
17846 		}
17847 
17848 		override void drawRect(NSRect rect) @selector("drawRect:") {
17849 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
17850 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
17851 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
17852 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
17853 			CGImageRelease(cgImage);
17854 		}
17855 
17856 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
17857 			MouseEvent me;
17858 			me.type = type;
17859 
17860 			auto pos = event.locationInWindow;
17861 
17862 			me.x = cast(int) pos.x;
17863 			me.y = cast(int) (simpleWindow.height - pos.y);
17864 
17865 			me.dx = 0; // FIXME
17866 			me.dy = 0; // FIXME
17867 
17868 			me.button = button;
17869 			me.modifierState = cast(uint) event.modifierFlags;
17870 			me.window = simpleWindow;
17871 
17872 			me.doubleClick = false;
17873 
17874 			if(simpleWindow && simpleWindow.handleMouseEvent)
17875 				simpleWindow.handleMouseEvent(me);
17876 		}
17877 
17878 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
17879 			// writeln(event.pressedMouseButtons);
17880 
17881 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17882 		}
17883 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
17884 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
17885 		}
17886 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
17887 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
17888 		}
17889 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
17890 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
17891 		}
17892 		/+
17893 			// FIXME
17894 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
17895 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17896 		}
17897 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
17898 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17899 		}
17900 		+/
17901 
17902 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
17903 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
17904 		}
17905 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
17906 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
17907 		}
17908 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
17909 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
17910 		}
17911 
17912 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
17913 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
17914 		}
17915 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
17916 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
17917 		}
17918 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
17919 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
17920 		}
17921 
17922 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
17923 			import std.stdio;
17924 			writeln(event.deltaY);
17925 		}
17926 
17927 		override void keyDown(NSEvent event) @selector("keyDown:") {
17928 			// the event may have multiple characters, and we send them all at once.
17929 			if (simpleWindow.handleCharEvent) {
17930 				auto chars = DeifiedNSString(event.characters);
17931 				foreach (dchar dc; chars.str)
17932 					simpleWindow.handleCharEvent(dc);
17933 			}
17934 
17935 			keyHelper(event, true);
17936 		}
17937 
17938 		override void keyUp(NSEvent event) @selector("keyUp:") {
17939 			keyHelper(event, false);
17940 		}
17941 
17942 		private void keyHelper(NSEvent event, bool pressed) {
17943 			if(simpleWindow.handleKeyEvent) {
17944 				KeyEvent ev;
17945 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
17946 				ev.pressed = pressed;
17947 				ev.hardwareCode = cast(ubyte) event.keyCode;
17948 				ev.modifierState = cast(uint) event.modifierFlags;
17949 				ev.window = simpleWindow;
17950 
17951 				simpleWindow.handleKeyEvent(ev);
17952 			}
17953 		}
17954 
17955 		override bool isFlipped() @selector("isFlipped") {
17956 			return true;
17957 		}
17958 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
17959 			return true;
17960 		}
17961 
17962 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
17963 			if(simpleWindow && simpleWindow.handlePulse)
17964 				simpleWindow.handlePulse();
17965 			/+
17966 			setNeedsDisplay = true;
17967 			+/
17968 		}
17969 	}
17970 
17971 private:
17972 	alias const(void)* CFStringRef;
17973 	alias const(void)* CFAllocatorRef;
17974 	alias const(void)* CFTypeRef;
17975 	alias const(void)* CGColorSpaceRef;
17976 	alias const(void)* CGImageRef;
17977 	alias ulong CGBitmapInfo;
17978 	alias NSGraphicsContext CGContextRef;
17979 
17980 	alias NSPoint CGPoint;
17981 	alias NSSize CGSize;
17982 	alias NSRect CGRect;
17983 
17984 	struct CGAffineTransform {
17985 		double a, b, c, d, tx, ty;
17986 	}
17987 
17988 	enum NSApplicationActivationPolicyRegular = 0;
17989 	enum NSBackingStoreBuffered = 2;
17990 	enum kCFStringEncodingUTF8 = 0x08000100;
17991 
17992 	enum : size_t {
17993 		NSBorderlessWindowMask = 0,
17994 		NSTitledWindowMask = 1 << 0,
17995 		NSClosableWindowMask = 1 << 1,
17996 		NSMiniaturizableWindowMask = 1 << 2,
17997 		NSResizableWindowMask = 1 << 3,
17998 		NSTexturedBackgroundWindowMask = 1 << 8
17999 	}
18000 
18001 	enum : ulong {
18002 		kCGImageAlphaNone,
18003 		kCGImageAlphaPremultipliedLast,
18004 		kCGImageAlphaPremultipliedFirst,
18005 		kCGImageAlphaLast,
18006 		kCGImageAlphaFirst,
18007 		kCGImageAlphaNoneSkipLast,
18008 		kCGImageAlphaNoneSkipFirst
18009 	}
18010 	enum : ulong {
18011 		kCGBitmapAlphaInfoMask = 0x1F,
18012 		kCGBitmapFloatComponents = (1 << 8),
18013 		kCGBitmapByteOrderMask = 0x7000,
18014 		kCGBitmapByteOrderDefault = (0 << 12),
18015 		kCGBitmapByteOrder16Little = (1 << 12),
18016 		kCGBitmapByteOrder32Little = (2 << 12),
18017 		kCGBitmapByteOrder16Big = (3 << 12),
18018 		kCGBitmapByteOrder32Big = (4 << 12)
18019 	}
18020 	enum CGPathDrawingMode {
18021 		kCGPathFill,
18022 		kCGPathEOFill,
18023 		kCGPathStroke,
18024 		kCGPathFillStroke,
18025 		kCGPathEOFillStroke
18026 	}
18027 	enum objc_AssociationPolicy : size_t {
18028 		OBJC_ASSOCIATION_ASSIGN = 0,
18029 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18030 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18031 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18032 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18033 	}
18034 
18035 	extern(C) {
18036 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18037 		void CGContextRelease(CGContextRef c);
18038 		ubyte* CGBitmapContextGetData(CGContextRef c);
18039 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18040 		size_t CGBitmapContextGetWidth(CGContextRef c);
18041 		size_t CGBitmapContextGetHeight(CGContextRef c);
18042 
18043 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18044 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18045 
18046 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18047 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18048 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18049 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18050 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18051 
18052 		void CGContextBeginPath(CGContextRef c);
18053 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18054 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18055 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18056 		void CGContextAddRect(CGContextRef c, CGRect rect);
18057 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18058 		void CGContextSaveGState(CGContextRef c);
18059 		void CGContextRestoreGState(CGContextRef c);
18060 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18061 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18062 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18063 
18064 		void CGImageRelease(CGImageRef image);
18065 	}
18066 } else static assert(0, "Unsupported operating system");
18067 
18068 
18069 version(OSXCocoa) {
18070 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18071 	//
18072 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18073 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18074 	//
18075 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18076 	// Probably won't even fully compile right now
18077 
18078 	private enum double PI = 3.14159265358979323;
18079 
18080 	alias NSWindow NativeWindowHandle;
18081 	alias void delegate(NSid) NativeEventHandler;
18082 
18083 	enum KEY_ESCAPE = 27;
18084 
18085 	mixin template NativeImageImplementation() {
18086 		CGContextRef context;
18087 		ubyte* rawData;
18088 
18089 		final:
18090 
18091 		void convertToRgbaBytes(ubyte[] where) {
18092 			assert(where.length == this.width * this.height * 4);
18093 
18094 			// if rawData had a length....
18095 			//assert(rawData.length == where.length);
18096 			for(long idx = 0; idx < where.length; idx += 4) {
18097 				auto alpha = rawData[idx + 3];
18098 				if(alpha == 255) {
18099 					where[idx + 0] = rawData[idx + 0]; // r
18100 					where[idx + 1] = rawData[idx + 1]; // g
18101 					where[idx + 2] = rawData[idx + 2]; // b
18102 					where[idx + 3] = rawData[idx + 3]; // a
18103 				} else {
18104 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18105 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18106 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18107 					where[idx + 3] = rawData[idx + 3]; // a
18108 
18109 				}
18110 			}
18111 		}
18112 
18113 		void setFromRgbaBytes(in ubyte[] where) {
18114 			// FIXME: this is probably wrong
18115 			assert(where.length == this.width * this.height * 4);
18116 
18117 			// if rawData had a length....
18118 			//assert(rawData.length == where.length);
18119 			for(long idx = 0; idx < where.length; idx += 4) {
18120 				auto alpha = where[idx + 3];
18121 				if(alpha == 255) {
18122 					rawData[idx + 0] = where[idx + 0]; // r
18123 					rawData[idx + 1] = where[idx + 1]; // g
18124 					rawData[idx + 2] = where[idx + 2]; // b
18125 					rawData[idx + 3] = where[idx + 3]; // a
18126 				} else if(alpha == 0) {
18127 					rawData[idx + 0] = 0;
18128 					rawData[idx + 1] = 0;
18129 					rawData[idx + 2] = 0;
18130 					rawData[idx + 3] = 0;
18131 				} else {
18132 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18133 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18134 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18135 					rawData[idx + 3] = where[idx + 3]; // a
18136 				}
18137 			}
18138 		}
18139 
18140 
18141 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18142 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18143 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18144 			CGColorSpaceRelease(colorSpace);
18145 			rawData = CGBitmapContextGetData(context);
18146 		}
18147 		void dispose() {
18148 			CGContextRelease(context);
18149 		}
18150 
18151 		void setPixel(int x, int y, Color c) {
18152 			auto offset = (y * width + x) * 4;
18153 			if (c.a == 255) {
18154 				rawData[offset + 0] = c.r;
18155 				rawData[offset + 1] = c.g;
18156 				rawData[offset + 2] = c.b;
18157 				rawData[offset + 3] = c.a;
18158 			} else {
18159 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18160 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18161 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18162 				rawData[offset + 3] = c.a;
18163 			}
18164 		}
18165 	}
18166 
18167 	mixin template NativeScreenPainterImplementation() {
18168 		CGContextRef context;
18169 		ubyte[4] _outlineComponents;
18170 		NSView view;
18171 
18172 		void create(PaintingHandle window) {
18173 			// this.destiny = window;
18174 			if(auto sw = cast(SimpleWindow) this.window) {
18175 				context = sw.drawingContext;
18176 				view = sw.view;
18177 			} else {
18178 				throw new NotYetImplementedException();
18179 			}
18180 		}
18181 
18182 		void dispose() {
18183 			view.setNeedsDisplay(true);
18184 		}
18185 
18186 		bool manualInvalidations;
18187 		void invalidateRect(Rectangle invalidRect) { }
18188 
18189 		// NotYetImplementedException
18190 		Size textSize(in char[] txt) { return Size(32, 16); /*throw new NotYetImplementedException();*/ }
18191 		void rasterOp(RasterOp op) {}
18192 		Pen _activePen;
18193 		Color _fillColor;
18194 		Rectangle _clipRectangle;
18195 		void setClipRectangle(int, int, int, int) {}
18196 		void setFont(OperatingSystemFont) {}
18197 		int fontHeight() { return 14; }
18198 
18199 		// end
18200 
18201 		void pen(Pen pen) {
18202 			_activePen = pen;
18203 			auto color = pen.color; // FIXME
18204 			double alphaComponent = color.a/255.0f;
18205 			CGContextSetRGBStrokeColor(context,
18206 									   color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
18207 
18208 			if (color.a != 255) {
18209 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
18210 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
18211 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
18212 				_outlineComponents[3] = color.a;
18213 			} else {
18214 				_outlineComponents[0] = color.r;
18215 				_outlineComponents[1] = color.g;
18216 				_outlineComponents[2] = color.b;
18217 				_outlineComponents[3] = color.a;
18218 			}
18219 		}
18220 
18221 		@property void fillColor(Color color) {
18222 			CGContextSetRGBFillColor(context,
18223 									 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
18224 		}
18225 
18226 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
18227 		// NotYetImplementedException for upper left/width/height
18228 			auto cgImage = CGBitmapContextCreateImage(image.context);
18229 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
18230 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18231 			CGImageRelease(cgImage);
18232 		}
18233 
18234 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
18235 		// FIXME: is this efficient?
18236 			auto cgImage = CGBitmapContextCreateImage(s.handle);
18237 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
18238 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18239 			CGImageRelease(cgImage);
18240 		}
18241 
18242 
18243 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
18244 		// FIXME: alignment
18245 			if (_outlineComponents[3] != 0) {
18246 				CGContextSaveGState(context);
18247 				auto invAlpha = 1.0f/_outlineComponents[3];
18248 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
18249 												  _outlineComponents[1]*invAlpha,
18250 												  _outlineComponents[2]*invAlpha,
18251 												  _outlineComponents[3]/255.0f);
18252 				CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
18253 // auto cfstr = cast(NSid)createCFString(text);
18254 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
18255 // NSPoint(x, y), null);
18256 // CFRelease(cfstr);
18257 				CGContextRestoreGState(context);
18258 			}
18259 		}
18260 
18261 		void drawPixel(int x, int y) {
18262 			auto rawData = CGBitmapContextGetData(context);
18263 			auto width = CGBitmapContextGetWidth(context);
18264 			auto height = CGBitmapContextGetHeight(context);
18265 			auto offset = ((height - y - 1) * width + x) * 4;
18266 			rawData[offset .. offset+4] = _outlineComponents;
18267 		}
18268 
18269 		void drawLine(int x1, int y1, int x2, int y2) {
18270 			CGPoint[2] linePoints;
18271 			linePoints[0] = CGPoint(x1, y1);
18272 			linePoints[1] = CGPoint(x2, y2);
18273 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
18274 		}
18275 
18276 		void drawRectangle(int x, int y, int width, int height) {
18277 			CGContextBeginPath(context);
18278 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18279 			CGContextAddRect(context, rect);
18280 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18281 		}
18282 
18283 		void drawEllipse(int x1, int y1, int x2, int y2) {
18284 			CGContextBeginPath(context);
18285 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18286 			CGContextAddEllipseInRect(context, rect);
18287 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18288 		}
18289 
18290 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
18291 			// @@@BUG@@@ Does not support elliptic arc (width != height).
18292 			CGContextBeginPath(context);
18293 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18294 							start*PI/(180*64), finish*PI/(180*64), 0);
18295 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18296 		}
18297 
18298 		void drawPolygon(Point[] intPoints) {
18299 			CGContextBeginPath(context);
18300 			CGPoint[16] pointsBuffer;
18301 			CGPoint[] points;
18302 			if(intPoints.length <= pointsBuffer.length)
18303 				points = pointsBuffer[0 .. intPoints.length];
18304 			else
18305 				points = new CGPoint[](intPoints.length);
18306 
18307 			foreach(idx, pt; intPoints)
18308 				points[idx] = CGPoint(pt.x, pt.y);
18309 
18310 			CGContextAddLines(context, points.ptr, points.length);
18311 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18312 		}
18313 	}
18314 
18315 	private bool appInitialized = false;
18316 	void initializeApp() {
18317 		if(appInitialized)
18318 			return;
18319 		synchronized {
18320 			if(appInitialized)
18321 				return;
18322 
18323 			auto app = NSApp(); // ensure the is initialized
18324 
18325 			auto dg = AppDelegate.alloc;
18326 			globalAppDelegate = dg;
18327 			NSApp.delegate_ = dg;
18328 
18329 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
18330 
18331 			appInitialized = true;
18332 		}
18333 	}
18334 
18335 	mixin template NativeSimpleWindowImplementation() {
18336 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18337 			initializeApp();
18338 
18339 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18340 
18341 			auto window = NSWindow.alloc.initWithContentRect(
18342 				contentRect,
18343 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
18344 				NSBackingStoreType.buffered,
18345 				true
18346 			);
18347 
18348 			SimpleWindow.nativeMapping[cast(void*) window] = this;
18349 
18350 			window.title = MacString(title).borrow;
18351 
18352 			auto dg = SDWindowDelegate.alloc.init;
18353 			dg.simpleWindow = this;
18354 			window.delegate_ = dg;
18355 
18356 			auto view = SDGraphicsView.alloc.init;
18357 			assert(view !is null);
18358 			window.contentView = view;
18359 			this.view = view;
18360 			view.simpleWindow = this;
18361 
18362 			window.center();
18363 
18364 			window.makeKeyAndOrderFront(null);
18365 
18366 			// no need to make a bitmap on mac since everything is double buffered already
18367 
18368 			// create area to draw on.
18369 			createNewDrawingContext(width, height);
18370 
18371 			window.setBackgroundColor(NSColor.whiteColor);
18372 		}
18373 
18374 		void createNewDrawingContext(int width, int height) {
18375 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
18376 			if(this.drawingContext)
18377 				CGContextRelease(this.drawingContext);
18378 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18379 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18380 			CGColorSpaceRelease(colorSpace);
18381 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18382 			auto matrix = CGContextGetTextMatrix(drawingContext);
18383 			matrix.c = -matrix.c;
18384 			matrix.d = -matrix.d;
18385 			CGContextSetTextMatrix(drawingContext, matrix);
18386 
18387 		}
18388 
18389 		void dispose() {
18390 			closeWindow();
18391 			// window.release(); // closing the window does this automatically i think
18392 		}
18393 		void closeWindow() {
18394 			if(timer)
18395 				timer.invalidate();
18396 			window.close();
18397 		}
18398 
18399 		ScreenPainter getPainter(bool manualInvalidations) {
18400 			return ScreenPainter(this, this.window, manualInvalidations);
18401 		}
18402 
18403 		NSWindow window;
18404 		NSTimer timer;
18405 		NSView view;
18406 		CGContextRef drawingContext;
18407 	}
18408 }
18409 
18410 version(without_opengl) {} else
18411 extern(System) nothrow @nogc {
18412 	//enum uint GL_VERSION = 0x1F02;
18413 	//const(char)* glGetString (/*GLenum*/uint);
18414 	version(X11) {
18415 	static if (!SdpyIsUsingIVGLBinds) {
18416 
18417 		enum GLX_X_RENDERABLE = 0x8012;
18418 		enum GLX_DRAWABLE_TYPE = 0x8010;
18419 		enum GLX_RENDER_TYPE = 0x8011;
18420 		enum GLX_X_VISUAL_TYPE = 0x22;
18421 		enum GLX_TRUE_COLOR = 0x8002;
18422 		enum GLX_WINDOW_BIT = 0x00000001;
18423 		enum GLX_RGBA_BIT = 0x00000001;
18424 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18425 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18426 		enum GLX_SAMPLES = 0x186a1;
18427 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18428 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18429 	}
18430 
18431 		// GLX_EXT_swap_control
18432 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18433 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18434 
18435 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18436 		extern(System) {
18437 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18438 		}
18439 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18440 
18441 		// this made public so we don't have to get it again and again
18442 		public bool glXCreateContextAttribsARB_present () {
18443 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18444 				// get it
18445 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18446 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18447 			}
18448 			return (glXCreateContextAttribsARBFn !is null);
18449 		}
18450 
18451 		// this made public so we don't have to get it again and again
18452 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18453 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18454 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18455 		}
18456 
18457 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18458 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18459 
18460 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18461 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18462 			if (_glx_swapInterval_fn is null) {
18463 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18464 				if (_glx_swapInterval_fn is null) {
18465 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18466 					return;
18467 				}
18468 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
18469 			}
18470 
18471 			if(glXSwapIntervalMESA is null) {
18472 				// it seems to require both to actually take effect on many computers
18473 				// idk why
18474 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18475 				if(glXSwapIntervalMESA is null)
18476 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18477 			}
18478 
18479 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18480 				glXSwapIntervalMESA(wait ? 1 : 0);
18481 
18482 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18483 		}
18484 	} else version(Windows) {
18485 	static if (!SdpyIsUsingIVGLBinds) {
18486 	enum GL_TRUE = 1;
18487 	enum GL_FALSE = 0;
18488 
18489 	public void* glbindGetProcAddress (const(char)* name) {
18490 		void* res = wglGetProcAddress(name);
18491 		if (res is null) {
18492 			/+
18493 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18494 			import core.sys.windows.windef, core.sys.windows.winbase;
18495 			__gshared HINSTANCE dll = null;
18496 			if (dll is null) {
18497 				dll = LoadLibraryA("opengl32.dll");
18498 				if (dll is null) return null; // <32, but idc
18499 			}
18500 			res = GetProcAddress(dll, name);
18501 			+/
18502 			res = GetProcAddress(gl.libHandle, name);
18503 		}
18504 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18505 		return res;
18506 	}
18507 	}
18508 
18509 
18510  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18511         void wglSetVSync(bool wait) {
18512 		if(wglSwapIntervalEXT is null) {
18513 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18514 			if(wglSwapIntervalEXT is null)
18515 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18516 		}
18517 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18518 			return;
18519 
18520 		wglSwapIntervalEXT(wait ? 1 : 0);
18521 	}
18522 
18523 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18524 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18525 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18526 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18527 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18528 
18529 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18530 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18531 
18532 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18533 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18534 
18535 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18536 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18537 
18538 		void wglInitOtherFunctions () {
18539 			if (wglCreateContextAttribsARB is null) {
18540 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18541 			}
18542 		}
18543 	}
18544 
18545 	static if (!SdpyIsUsingIVGLBinds) {
18546 
18547 	interface GL {
18548 		extern(System) @nogc nothrow:
18549 
18550 		void glGetIntegerv(int, void*);
18551 		void glMatrixMode(int);
18552 		void glPushMatrix();
18553 		void glLoadIdentity();
18554 		void glOrtho(double, double, double, double, double, double);
18555 		void glFrustum(double, double, double, double, double, double);
18556 
18557 		void glPopMatrix();
18558 		void glEnable(int);
18559 		void glDisable(int);
18560 		void glClear(int);
18561 		void glBegin(int);
18562 		void glVertex2f(float, float);
18563 		void glVertex3f(float, float, float);
18564 		void glEnd();
18565 		void glColor3b(byte, byte, byte);
18566 		void glColor3ub(ubyte, ubyte, ubyte);
18567 		void glColor4b(byte, byte, byte, byte);
18568 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18569 		void glColor3i(int, int, int);
18570 		void glColor3ui(uint, uint, uint);
18571 		void glColor4i(int, int, int, int);
18572 		void glColor4ui(uint, uint, uint, uint);
18573 		void glColor3f(float, float, float);
18574 		void glColor4f(float, float, float, float);
18575 		void glTranslatef(float, float, float);
18576 		void glScalef(float, float, float);
18577 		version(X11) {
18578 			void glSecondaryColor3b(byte, byte, byte);
18579 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18580 			void glSecondaryColor3i(int, int, int);
18581 			void glSecondaryColor3ui(uint, uint, uint);
18582 			void glSecondaryColor3f(float, float, float);
18583 		}
18584 
18585 		void glDrawElements(int, int, int, void*);
18586 
18587 		void glRotatef(float, float, float, float);
18588 
18589 		uint glGetError();
18590 
18591 		void glDeleteTextures(int, uint*);
18592 
18593 
18594 		void glRasterPos2i(int, int);
18595 		void glDrawPixels(int, int, uint, uint, void*);
18596 		void glClearColor(float, float, float, float);
18597 
18598 
18599 		void glPixelStorei(uint, int);
18600 
18601 		void glGenTextures(uint, uint*);
18602 		void glBindTexture(int, int);
18603 		void glTexParameteri(uint, uint, int);
18604 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18605 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
18606 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18607 			/*GLsizei*/int width, /*GLsizei*/int height,
18608 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18609 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18610 
18611 		void glLineWidth(int);
18612 
18613 
18614 		void glTexCoord2f(float, float);
18615 		void glVertex2i(int, int);
18616 		void glBlendFunc (int, int);
18617 		void glDepthFunc (int);
18618 		void glViewport(int, int, int, int);
18619 
18620 		void glClearDepth(double);
18621 
18622 		void glReadBuffer(uint);
18623 		void glReadPixels(int, int, int, int, int, int, void*);
18624 
18625 		void glFlush();
18626 		void glFinish();
18627 
18628 		version(Windows) {
18629 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18630 			HGLRC wglCreateContext(HDC);
18631 			HGLRC wglCreateLayerContext(HDC, int);
18632 			BOOL wglDeleteContext(HGLRC);
18633 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18634 			HGLRC wglGetCurrentContext();
18635 			HDC wglGetCurrentDC();
18636 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18637 			PROC wglGetProcAddress(LPCSTR);
18638 			BOOL wglMakeCurrent(HDC, HGLRC);
18639 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18640 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18641 			BOOL wglShareLists(HGLRC, HGLRC);
18642 			BOOL wglSwapLayerBuffers(HDC, UINT);
18643 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18644 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18645 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18646 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18647 		}
18648 
18649 	}
18650 
18651 	interface GL3 {
18652 		extern(System) @nogc nothrow:
18653 
18654 		void glGenVertexArrays(GLsizei, GLuint*);
18655 		void glBindVertexArray(GLuint);
18656 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18657 		void glGenerateMipmap(GLenum);
18658 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18659 		void glStencilMask(GLuint);
18660 		void glStencilFunc(GLenum, GLint, GLuint);
18661 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18662 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18663 		GLuint glCreateProgram();
18664 		GLuint glCreateShader(GLenum);
18665 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18666 		void glCompileShader(GLuint);
18667 		void glGetShaderiv(GLuint, GLenum, GLint*);
18668 		void glAttachShader(GLuint, GLuint);
18669 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18670 		void glLinkProgram(GLuint);
18671 		void glGetProgramiv(GLuint, GLenum, GLint*);
18672 		void glDeleteProgram(GLuint);
18673 		void glDeleteShader(GLuint);
18674 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18675 		void glGenBuffers(GLsizei, GLuint*);
18676 
18677 		void glUniform1f(GLint location, GLfloat v0);
18678 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18679 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18680 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18681 		void glUniform1i(GLint location, GLint v0);
18682 		void glUniform2i(GLint location, GLint v0, GLint v1);
18683 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18684 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18685 		void glUniform1ui(GLint location, GLuint v0);
18686 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18687 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18688 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18689 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18690 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18691 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18692 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18693 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18694 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18695 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18696 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18697 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18698 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18699 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18700 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18701 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18702 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18703 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18704 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18705 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18706 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18707 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18708 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18709 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18710 
18711 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18712 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18713 		void glDrawArrays(GLenum, GLint, GLsizei);
18714 		void glStencilOp(GLenum, GLenum, GLenum);
18715 		void glUseProgram(GLuint);
18716 		void glCullFace(GLenum);
18717 		void glFrontFace(GLenum);
18718 		void glActiveTexture(GLenum);
18719 		void glBindBuffer(GLenum, GLuint);
18720 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18721 		void glEnableVertexAttribArray(GLuint);
18722 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18723 		void glUniform1i(GLint, GLint);
18724 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18725 		void glDisableVertexAttribArray(GLuint);
18726 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18727 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18728 		void glLogicOp (GLenum opcode);
18729 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18730 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18731 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18732 		GLenum glCheckFramebufferStatus (GLenum target);
18733 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18734 	}
18735 
18736 	interface GL4 {
18737 		extern(System) @nogc nothrow:
18738 
18739 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18740 			/*GLsizei*/int width, /*GLsizei*/int height,
18741 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18742 	}
18743 
18744 	interface GLU {
18745 		extern(System) @nogc nothrow:
18746 
18747 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18748 		void gluPerspective(double, double, double, double);
18749 
18750 		char* gluErrorString(uint);
18751 	}
18752 
18753 
18754 	enum GL_RED = 0x1903;
18755 	enum GL_ALPHA = 0x1906;
18756 
18757 	enum uint GL_FRONT = 0x0404;
18758 
18759 	enum uint GL_BLEND = 0x0be2;
18760 	enum uint GL_LEQUAL = 0x0203;
18761 
18762 
18763 	enum uint GL_RGB = 0x1907;
18764 	enum uint GL_BGRA = 0x80e1;
18765 	enum uint GL_RGBA = 0x1908;
18766 	enum uint GL_TEXTURE_2D =   0x0DE1;
18767 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18768 	enum uint GL_NEAREST = 0x2600;
18769 	enum uint GL_LINEAR = 0x2601;
18770 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18771 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18772 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18773 	enum uint GL_REPEAT = 0x2901;
18774 	enum uint GL_CLAMP = 0x2900;
18775 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18776 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18777 	enum uint GL_DECAL = 0x2101;
18778 	enum uint GL_MODULATE = 0x2100;
18779 	enum uint GL_TEXTURE_ENV = 0x2300;
18780 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18781 	enum uint GL_REPLACE = 0x1E01;
18782 	enum uint GL_LIGHTING = 0x0B50;
18783 	enum uint GL_DITHER = 0x0BD0;
18784 
18785 	enum uint GL_NO_ERROR = 0;
18786 
18787 
18788 
18789 	enum int GL_VIEWPORT = 0x0BA2;
18790 	enum int GL_MODELVIEW = 0x1700;
18791 	enum int GL_TEXTURE = 0x1702;
18792 	enum int GL_PROJECTION = 0x1701;
18793 	enum int GL_DEPTH_TEST = 0x0B71;
18794 
18795 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18796 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18797 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18798 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18799 
18800 	enum int GL_POINTS = 0x0000;
18801 	enum int GL_LINES =  0x0001;
18802 	enum int GL_LINE_LOOP = 0x0002;
18803 	enum int GL_LINE_STRIP = 0x0003;
18804 	enum int GL_TRIANGLES = 0x0004;
18805 	enum int GL_TRIANGLE_STRIP = 5;
18806 	enum int GL_TRIANGLE_FAN = 6;
18807 	enum int GL_QUADS = 7;
18808 	enum int GL_QUAD_STRIP = 8;
18809 	enum int GL_POLYGON = 9;
18810 
18811 	alias GLvoid = void;
18812 	alias GLboolean = ubyte;
18813 	alias GLint = int;
18814 	alias GLuint = uint;
18815 	alias GLenum = uint;
18816 	alias GLchar = char;
18817 	alias GLsizei = int;
18818 	alias GLfloat = float;
18819 	alias GLintptr = size_t;
18820 	alias GLsizeiptr = ptrdiff_t;
18821 
18822 
18823 	enum uint GL_INVALID_ENUM = 0x0500;
18824 
18825 	enum uint GL_ZERO = 0;
18826 	enum uint GL_ONE = 1;
18827 
18828 	enum uint GL_BYTE = 0x1400;
18829 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18830 	enum uint GL_SHORT = 0x1402;
18831 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18832 	enum uint GL_INT = 0x1404;
18833 	enum uint GL_UNSIGNED_INT = 0x1405;
18834 	enum uint GL_FLOAT = 0x1406;
18835 	enum uint GL_2_BYTES = 0x1407;
18836 	enum uint GL_3_BYTES = 0x1408;
18837 	enum uint GL_4_BYTES = 0x1409;
18838 	enum uint GL_DOUBLE = 0x140A;
18839 
18840 	enum uint GL_STREAM_DRAW = 0x88E0;
18841 
18842 	enum uint GL_CCW = 0x0901;
18843 
18844 	enum uint GL_STENCIL_TEST = 0x0B90;
18845 	enum uint GL_SCISSOR_TEST = 0x0C11;
18846 
18847 	enum uint GL_EQUAL = 0x0202;
18848 	enum uint GL_NOTEQUAL = 0x0205;
18849 
18850 	enum uint GL_ALWAYS = 0x0207;
18851 	enum uint GL_KEEP = 0x1E00;
18852 
18853 	enum uint GL_INCR = 0x1E02;
18854 
18855 	enum uint GL_INCR_WRAP = 0x8507;
18856 	enum uint GL_DECR_WRAP = 0x8508;
18857 
18858 	enum uint GL_CULL_FACE = 0x0B44;
18859 	enum uint GL_BACK = 0x0405;
18860 
18861 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
18862 	enum uint GL_VERTEX_SHADER = 0x8B31;
18863 
18864 	enum uint GL_COMPILE_STATUS = 0x8B81;
18865 	enum uint GL_LINK_STATUS = 0x8B82;
18866 
18867 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
18868 
18869 	enum uint GL_STATIC_DRAW = 0x88E4;
18870 
18871 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
18872 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
18873 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
18874 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
18875 
18876 	enum uint GL_GENERATE_MIPMAP = 0x8191;
18877 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
18878 
18879 	enum uint GL_TEXTURE0 = 0x84C0U;
18880 	enum uint GL_TEXTURE1 = 0x84C1U;
18881 
18882 	enum uint GL_ARRAY_BUFFER = 0x8892;
18883 
18884 	enum uint GL_SRC_COLOR = 0x0300;
18885 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
18886 	enum uint GL_SRC_ALPHA = 0x0302;
18887 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
18888 	enum uint GL_DST_ALPHA = 0x0304;
18889 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
18890 	enum uint GL_DST_COLOR = 0x0306;
18891 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
18892 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
18893 
18894 	enum uint GL_INVERT = 0x150AU;
18895 
18896 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
18897 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
18898 
18899 	enum uint GL_FRAMEBUFFER = 0x8D40U;
18900 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
18901 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
18902 
18903 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
18904 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
18905 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
18906 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
18907 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
18908 
18909 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
18910 	enum uint GL_CLEAR = 0x1500U;
18911 	enum uint GL_COPY = 0x1503U;
18912 	enum uint GL_XOR = 0x1506U;
18913 
18914 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
18915 
18916 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
18917 
18918 	}
18919 }
18920 
18921 /++
18922 	History:
18923 		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.
18924 +/
18925 __gshared bool gluSuccessfullyLoaded = true;
18926 
18927 version(without_opengl) {} else {
18928 static if(!SdpyIsUsingIVGLBinds) {
18929 	version(Windows) {
18930 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
18931 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
18932 	} else {
18933 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
18934 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
18935 	}
18936 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
18937 
18938 
18939 	shared static this() {
18940 		gl.loadDynamicLibrary();
18941 
18942 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
18943 		// unless those functions are actually used
18944 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
18945 		glu.loadDynamicLibrary();
18946 	}
18947 }
18948 }
18949 
18950 /++
18951 	Convenience method for converting D arrays to opengl buffer data
18952 
18953 	I would LOVE to overload it with the original glBufferData, but D won't
18954 	let me since glBufferData is a function pointer :(
18955 
18956 	Added: August 25, 2020 (version 8.5)
18957 +/
18958 version(without_opengl) {} else
18959 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
18960 	glBufferData(target, data.length, data.ptr, usage);
18961 }
18962 
18963 /+
18964 /++
18965 	A matrix for simple uses that easily integrates with [OpenGlShader].
18966 
18967 	Might not be useful to you since it only as some simple functions and
18968 	probably isn't that fast.
18969 
18970 	Note it uses an inline static array for its storage, so copying it
18971 	may be expensive.
18972 +/
18973 struct BasicMatrix(int columns, int rows, T = float) {
18974 	import core.stdc.math;
18975 
18976 	T[columns * rows] data = 0.0;
18977 
18978 	/++
18979 		Basic operations that operate *in place*.
18980 	+/
18981 	void translate() {
18982 
18983 	}
18984 
18985 	/// ditto
18986 	void scale() {
18987 
18988 	}
18989 
18990 	/// ditto
18991 	void rotate() {
18992 
18993 	}
18994 
18995 	/++
18996 
18997 	+/
18998 	static if(columns == rows)
18999 	static BasicMatrix identity() {
19000 		BasicMatrix m;
19001 		foreach(i; 0 .. columns)
19002 			data[0 + i + i * columns] = 1.0;
19003 		return m;
19004 	}
19005 
19006 	static BasicMatrix ortho() {
19007 		return BasicMatrix.init;
19008 	}
19009 }
19010 +/
19011 
19012 /++
19013 	Convenience class for using opengl shaders.
19014 
19015 	Ensure that you've loaded opengl 3+ and set your active
19016 	context before trying to use this.
19017 
19018 	Added: August 25, 2020 (version 8.5)
19019 +/
19020 version(without_opengl) {} else
19021 final class OpenGlShader {
19022 	private int shaderProgram_;
19023 	private @property void shaderProgram(int a) {
19024 		shaderProgram_ = a;
19025 	}
19026 	/// Get the program ID for use in OpenGL functions.
19027 	public @property int shaderProgram() {
19028 		return shaderProgram_;
19029 	}
19030 
19031 	/++
19032 
19033 	+/
19034 	static struct Source {
19035 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19036 		string code; ///
19037 	}
19038 
19039 	/++
19040 		Helper method to just compile some shader code and check for errors
19041 		while you do glCreateShader, etc. on the outside yourself.
19042 
19043 		This just does `glShaderSource` and `glCompileShader` for the given code.
19044 
19045 		If you the OpenGlShader class constructor, you never need to call this yourself.
19046 	+/
19047 	static void compile(int sid, Source code) {
19048 		const(char)*[1] buffer;
19049 		int[1] lengthBuffer;
19050 
19051 		buffer[0] = code.code.ptr;
19052 		lengthBuffer[0] = cast(int) code.code.length;
19053 
19054 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19055 		glCompileShader(sid);
19056 
19057 		int success;
19058 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19059 		if(!success) {
19060 			char[512] info;
19061 			int len;
19062 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19063 
19064 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19065 		}
19066 	}
19067 
19068 	/++
19069 		Calls `glLinkProgram` and throws if error a occurs.
19070 
19071 		If you the OpenGlShader class constructor, you never need to call this yourself.
19072 	+/
19073 	static void link(int shaderProgram) {
19074 		glLinkProgram(shaderProgram);
19075 		int success;
19076 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19077 		if(!success) {
19078 			char[512] info;
19079 			int len;
19080 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19081 
19082 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19083 		}
19084 	}
19085 
19086 	/++
19087 		Constructs the shader object by calling `glCreateProgram`, then
19088 		compiling each given [Source], and finally, linking them together.
19089 
19090 		Throws: on compile or link failure.
19091 	+/
19092 	this(Source[] codes...) {
19093 		shaderProgram = glCreateProgram();
19094 
19095 		int[16] shadersBufferStack;
19096 
19097 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19098 			shadersBufferStack[0 .. codes.length] :
19099 			new int[](codes.length);
19100 
19101 		foreach(idx, code; codes) {
19102 			shadersBuffer[idx] = glCreateShader(code.type);
19103 
19104 			compile(shadersBuffer[idx], code);
19105 
19106 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19107 		}
19108 
19109 		link(shaderProgram);
19110 
19111 		foreach(s; shadersBuffer)
19112 			glDeleteShader(s);
19113 	}
19114 
19115 	/// Calls `glUseProgram(this.shaderProgram)`
19116 	void use() {
19117 		glUseProgram(this.shaderProgram);
19118 	}
19119 
19120 	/// Deletes the program.
19121 	void delete_() {
19122 		glDeleteProgram(shaderProgram);
19123 		shaderProgram = 0;
19124 	}
19125 
19126 	/++
19127 		[OpenGlShader.uniforms].name gives you one of these.
19128 
19129 		You can get the id out of it or just assign
19130 	+/
19131 	static struct Uniform {
19132 		/// the id passed to glUniform*
19133 		int id;
19134 
19135 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
19136 		void opAssign(float x, float y, float z, float w) {
19137 			if(id != -1)
19138 			glUniform4f(id, x, y, z, w);
19139 		}
19140 
19141 		void opAssign(float x) {
19142 			if(id != -1)
19143 			glUniform1f(id, x);
19144 		}
19145 
19146 		void opAssign(float x, float y) {
19147 			if(id != -1)
19148 			glUniform2f(id, x, y);
19149 		}
19150 
19151 		void opAssign(T)(T t) {
19152 			t.glUniform(id);
19153 		}
19154 	}
19155 
19156 	static struct UniformsHelper {
19157 		OpenGlShader _shader;
19158 
19159 		@property Uniform opDispatch(string name)() {
19160 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
19161 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
19162 			//if(i == -1)
19163 				//throw new Exception("Could not find uniform " ~ name);
19164 			return Uniform(i);
19165 		}
19166 
19167 		@property void opDispatch(string name, T)(T t) {
19168 			Uniform f = this.opDispatch!name;
19169 			t.glUniform(f);
19170 		}
19171 	}
19172 
19173 	/++
19174 		Gives access to the uniforms through dot access.
19175 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
19176 	+/
19177 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
19178 }
19179 
19180 version(without_opengl) {} else {
19181 /++
19182 	A static container of experimental types and value constructors for opengl 3+ shaders.
19183 
19184 
19185 	You can declare variables like:
19186 
19187 	```
19188 	OGL.vec3f something;
19189 	```
19190 
19191 	But generally it would be used with [OpenGlShader]'s uniform helpers like
19192 
19193 	```
19194 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19195 	```
19196 
19197 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19198 
19199 
19200 	History:
19201 		Added December 7, 2021. Not yet stable.
19202 +/
19203 final class OGL {
19204 	static:
19205 
19206 	private template typeFromSpecifier(string specifier) {
19207 		static if(specifier == "f")
19208 			alias typeFromSpecifier = GLfloat;
19209 		else static if(specifier == "i")
19210 			alias typeFromSpecifier = GLint;
19211 		else static if(specifier == "ui")
19212 			alias typeFromSpecifier = GLuint;
19213 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19214 	}
19215 
19216 	private template CommonType(T...) {
19217 		static if(T.length == 1)
19218 			alias CommonType = T[0];
19219 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19220 			alias CommonType = CommonType!(C, T[2 .. $]);
19221 	}
19222 
19223 	private template typesToSpecifier(T...) {
19224 		static if(is(CommonType!T == float))
19225 			enum typesToSpecifier = "f";
19226 		else static if(is(CommonType!T == int))
19227 			enum typesToSpecifier = "i";
19228 		else static if(is(CommonType!T == uint))
19229 			enum typesToSpecifier = "ui";
19230 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19231 	}
19232 
19233 	private template genNames(size_t dim, size_t dim2 = 0) {
19234 		string helper() {
19235 			string s;
19236 			if(dim2) {
19237 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
19238 			} else {
19239 				if(dim > 0) s ~= "type x = 0;";
19240 				if(dim > 1) s ~= "type y = 0;";
19241 				if(dim > 2) s ~= "type z = 0;";
19242 				if(dim > 3) s ~= "type w = 0;";
19243 			}
19244 			return s;
19245 		}
19246 
19247 		enum genNames = helper();
19248 	}
19249 
19250 	// there's vec, arrays of vec, mat, and arrays of mat
19251 	template opDispatch(string name)
19252 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19253 	{
19254 		static if(name[4] == 'x') {
19255 			enum dimX = cast(int) (name[3] - '0');
19256 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19257 
19258 			enum dimY = cast(int) (name[5] - '0');
19259 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19260 
19261 			enum isArray = name[$ - 1] == 'v';
19262 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19263 			alias type = typeFromSpecifier!typeSpecifier;
19264 		} else {
19265 			enum dim = cast(int) (name[3] - '0');
19266 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19267 			enum isArray = name[$ - 1] == 'v';
19268 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19269 			alias type = typeFromSpecifier!typeSpecifier;
19270 		}
19271 
19272 		align(1)
19273 		struct opDispatch {
19274 			align(1):
19275 			static if(name[4] == 'x')
19276 				mixin(genNames!(dimX, dimY));
19277 			else
19278 				mixin(genNames!dim);
19279 
19280 			private void glUniform(OpenGlShader.Uniform assignTo) {
19281 				glUniform(assignTo.id);
19282 			}
19283 			private void glUniform(int assignTo) {
19284 				static if(name[4] == 'x') {
19285 					// FIXME
19286 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19287 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19288 				} else
19289 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19290 			}
19291 		}
19292 	}
19293 
19294 	auto vec(T...)(T members) {
19295 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19296 	}
19297 }
19298 }
19299 
19300 version(linux) {
19301 	version(with_eventloop) {} else {
19302 		private int epollFd = -1;
19303 		void prepareEventLoop() {
19304 			if(epollFd != -1)
19305 				return; // already initialized, no need to do it again
19306 			import ep = core.sys.linux.epoll;
19307 
19308 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19309 			if(epollFd == -1)
19310 				throw new Exception("epoll create failure");
19311 		}
19312 	}
19313 } else version(Posix) {
19314 	void prepareEventLoop() {}
19315 }
19316 
19317 version(X11) {
19318 	import core.stdc.locale : LC_ALL; // rdmd fix
19319 	__gshared bool sdx_isUTF8Locale;
19320 
19321 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19322 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19323 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19324 	// anal magic is here. I (Ketmar) hope you like it.
19325 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19326 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19327 	// later.
19328 
19329 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19330 	shared static this () {
19331 		if(!librariesSuccessfullyLoaded)
19332 			return;
19333 
19334 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19335 
19336 		// this doesn't hurt; it may add some locking, but the speed is still
19337 		// allows doing 60 FPS videogames; also, ignore the result, as most
19338 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19339 		// never seen this failing).
19340 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19341 
19342 		setlocale(LC_ALL, "");
19343 		// check if out locale is UTF-8
19344 		auto lct = setlocale(LC_CTYPE, null);
19345 		if (lct is null) {
19346 			sdx_isUTF8Locale = false;
19347 		} else {
19348 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19349 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19350 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19351 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19352 				{
19353 					sdx_isUTF8Locale = true;
19354 					break;
19355 				}
19356 			}
19357 		}
19358 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19359 	}
19360 }
19361 
19362 class ExperimentalTextComponent2 {
19363 	/+
19364 		Stage 1: get it working monospace
19365 		Stage 2: use proportional font
19366 		Stage 3: allow changes in inline style
19367 		Stage 4: allow new fonts and sizes in the middle
19368 		Stage 5: optimize gap buffer
19369 		Stage 6: optimize layout
19370 		Stage 7: word wrap
19371 		Stage 8: justification
19372 		Stage 9: editing, selection, etc.
19373 
19374 			Operations:
19375 				insert text
19376 				overstrike text
19377 				select
19378 				cut
19379 				modify
19380 	+/
19381 
19382 	/++
19383 		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.
19384 	+/
19385 	this(SimpleWindow window) {
19386 		this.window = window;
19387 	}
19388 
19389 	private SimpleWindow window;
19390 
19391 
19392 	/++
19393 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19394 		representing the internal parts. The first pass is focused on the x parameter, then the
19395 		renderer is responsible for going back to the parts in the current line and calling
19396 		adjustDownForAscent to change the y params.
19397 	+/
19398 	static interface ComponentRenderHelper {
19399 
19400 		/+
19401 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19402 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19403 			to move (adjust y to make room for new line) until you get back to the same position,
19404 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19405 
19406 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19407 			once you reach something that is unchanged, you can stop.
19408 		+/
19409 
19410 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19411 
19412 		int ascent() const;
19413 		int descent() const;
19414 
19415 		int advance() const;
19416 
19417 		bool endsWithExplititLineBreak() const;
19418 	}
19419 
19420 	static interface RenderResult {
19421 		/++
19422 			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.
19423 		+/
19424 		void popFront();
19425 		@property bool empty() const;
19426 		@property ComponentRenderHelper front() const;
19427 
19428 		void repositionForNextLine(Point baseline, int availableWidth);
19429 	}
19430 
19431 	static interface ComponentInFlow {
19432 		void draw(ScreenPainter painter);
19433 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19434 
19435 		bool startsWithExplicitLineBreak() const;
19436 	}
19437 
19438 	static class TextFlowComponent : ComponentInFlow {
19439 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19440 
19441 		Color foreground;
19442 		Color background;
19443 
19444 		OperatingSystemFont font; // should NEVER be null
19445 
19446 		ubyte attributes; // underline, strike through, display on new block
19447 
19448 		version(Windows)
19449 			const(wchar)[] content;
19450 		else
19451 			const(char)[] content; // this should NEVER have a newline, except at the end
19452 
19453 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19454 
19455 		// could prolly put some spacing around it too like margin / padding
19456 
19457 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19458 			in { assert(font !is null);
19459 			     assert(!font.isNull); }
19460 			do
19461 		{
19462 			this.foreground = f;
19463 			this.background = b;
19464 			this.font = font;
19465 
19466 			this.attributes = attr;
19467 			version(Windows) {
19468 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19469 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19470 				auto buffer = new wchar[](sz);
19471 				this.content = makeWindowsString(c, buffer, conversionFlags);
19472 			} else {
19473 				this.content = c.dup;
19474 			}
19475 		}
19476 
19477 		void draw(ScreenPainter painter) {
19478 			painter.setFont(this.font);
19479 			painter.outlineColor = this.foreground;
19480 			painter.fillColor = Color.transparent;
19481 			foreach(rendered; this.rendered) {
19482 				// the component works in term of baseline,
19483 				// but the painter works in term of upper left bounding box
19484 				// so need to translate that
19485 
19486 				if(this.background.a) {
19487 					painter.fillColor = this.background;
19488 					painter.outlineColor = this.background;
19489 
19490 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19491 
19492 					painter.outlineColor = this.foreground;
19493 					painter.fillColor = Color.transparent;
19494 				}
19495 
19496 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19497 
19498 				// FIXME: strike through, underline, highlight selection, etc.
19499 			}
19500 		}
19501 	}
19502 
19503 	// I could split the parts into words on render
19504 	// for easier word-wrap, each one being an unbreakable "inline-block"
19505 	private TextFlowComponent[] parts;
19506 	private int needsRerenderFrom;
19507 
19508 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19509 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19510 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19511 	}
19512 
19513 	static struct RenderedComponent {
19514 		int startX;
19515 		int startY;
19516 		short width;
19517 		// 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!
19518 		// for individual chars in here you've gotta process on demand
19519 		version(Windows)
19520 			const(wchar)[] slice;
19521 		else
19522 			const(char)[] slice;
19523 	}
19524 
19525 
19526 	void rerender(Rectangle boundingBox) {
19527 		Point baseline = boundingBox.upperLeft;
19528 
19529 		this.boundingBox.left = boundingBox.left;
19530 		this.boundingBox.top = boundingBox.top;
19531 
19532 		auto remainingParts = parts;
19533 
19534 		int largestX;
19535 
19536 
19537 		foreach(part; parts)
19538 			part.font.prepareContext(window);
19539 		scope(exit)
19540 		foreach(part; parts)
19541 			part.font.releaseContext();
19542 
19543 		calculateNextLine:
19544 
19545 		int nextLineHeight = 0;
19546 		int nextBiggestDescent = 0;
19547 
19548 		foreach(part; remainingParts) {
19549 			auto height = part.font.ascent;
19550 			if(height > nextLineHeight)
19551 				nextLineHeight = height;
19552 			if(part.font.descent > nextBiggestDescent)
19553 				nextBiggestDescent = part.font.descent;
19554 			if(part.content.length && part.content[$-1] == '\n')
19555 				break;
19556 		}
19557 
19558 		baseline.y += nextLineHeight;
19559 		auto lineStart = baseline;
19560 
19561 		while(remainingParts.length) {
19562 			remainingParts[0].rendered = null;
19563 
19564 			bool eol;
19565 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19566 				eol = true;
19567 
19568 			// FIXME: word wrap
19569 			auto font = remainingParts[0].font;
19570 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19571 			auto width = font.stringWidth(slice, window);
19572 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19573 
19574 			remainingParts = remainingParts[1 .. $];
19575 			baseline.x += width;
19576 
19577 			if(eol) {
19578 				baseline.y += nextBiggestDescent;
19579 				if(baseline.x > largestX)
19580 					largestX = baseline.x;
19581 				baseline.x = lineStart.x;
19582 				goto calculateNextLine;
19583 			}
19584 		}
19585 
19586 		if(baseline.x > largestX)
19587 			largestX = baseline.x;
19588 
19589 		this.boundingBox.right = largestX;
19590 		this.boundingBox.bottom = baseline.y;
19591 	}
19592 
19593 	// you must call rerender first!
19594 	void draw(ScreenPainter painter) {
19595 		foreach(part; parts) {
19596 			part.draw(painter);
19597 		}
19598 	}
19599 
19600 	struct IdentifyResult {
19601 		TextFlowComponent part;
19602 		int charIndexInPart;
19603 		int totalCharIndex = -1; // if this is -1, it just means the end
19604 
19605 		Rectangle boundingBox;
19606 	}
19607 
19608 	IdentifyResult identify(Point pt, bool exact = false) {
19609 		if(parts.length == 0)
19610 			return IdentifyResult(null, 0);
19611 
19612 		if(pt.y < boundingBox.top) {
19613 			if(exact)
19614 				return IdentifyResult(null, 1);
19615 			return IdentifyResult(parts[0], 0);
19616 		}
19617 		if(pt.y > boundingBox.bottom) {
19618 			if(exact)
19619 				return IdentifyResult(null, 2);
19620 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19621 		}
19622 
19623 		int tci = 0;
19624 
19625 		// I should probably like binary search this or something...
19626 		foreach(ref part; parts) {
19627 			foreach(rendered; part.rendered) {
19628 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19629 				if(rect.contains(pt)) {
19630 					auto x = pt.x - rendered.startX;
19631 					auto estimatedIdx = x / part.font.averageWidth;
19632 
19633 					if(estimatedIdx < 0)
19634 						estimatedIdx = 0;
19635 
19636 					if(estimatedIdx > rendered.slice.length)
19637 						estimatedIdx = cast(int) rendered.slice.length;
19638 
19639 					int idx;
19640 					int x1, x2;
19641 					if(part.font.isMonospace) {
19642 						auto w = part.font.averageWidth;
19643 						if(!exact && x > (estimatedIdx + 1) * w)
19644 							return IdentifyResult(null, 4);
19645 						idx = estimatedIdx;
19646 						x1 = idx * w;
19647 						x2 = (idx + 1) * w;
19648 					} else {
19649 						idx = estimatedIdx;
19650 
19651 						part.font.prepareContext(window);
19652 						scope(exit) part.font.releaseContext();
19653 
19654 						// int iterations;
19655 
19656 						while(true) {
19657 							// iterations++;
19658 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19659 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19660 
19661 							x1 += rendered.startX;
19662 							x2 += rendered.startX;
19663 
19664 							if(pt.x < x1) {
19665 								if(idx == 0) {
19666 									if(exact)
19667 										return IdentifyResult(null, 6);
19668 									else
19669 										break;
19670 								}
19671 								idx--;
19672 							} else if(pt.x > x2) {
19673 								idx++;
19674 								if(idx > rendered.slice.length) {
19675 									if(exact)
19676 										return IdentifyResult(null, 5);
19677 									else
19678 										break;
19679 								}
19680 							} else if(pt.x >= x1 && pt.x <= x2) {
19681 								if(idx)
19682 									idx--; // point it at the original index
19683 								break; // we fit
19684 							}
19685 						}
19686 
19687 						// writeln(iterations)
19688 					}
19689 
19690 
19691 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19692 				}
19693 			}
19694 			tci += cast(int) part.content.length; // FIXME: utf-8?
19695 		}
19696 		return IdentifyResult(null, 3);
19697 	}
19698 
19699 	Rectangle boundingBox; // only set after [rerender]
19700 
19701 	// text will be positioned around the exclusion zone
19702 	static struct ExclusionZone {
19703 
19704 	}
19705 
19706 	ExclusionZone[] exclusionZones;
19707 }
19708 
19709 
19710 // Don't use this yet. When I'm happy with it, I will move it to the
19711 // regular module namespace.
19712 mixin template ExperimentalTextComponent() {
19713 
19714 static:
19715 
19716 	alias Rectangle = arsd.color.Rectangle;
19717 
19718 	struct ForegroundColor {
19719 		Color color;
19720 		alias color this;
19721 
19722 		this(Color c) {
19723 			color = c;
19724 		}
19725 
19726 		this(int r, int g, int b, int a = 255) {
19727 			color = Color(r, g, b, a);
19728 		}
19729 
19730 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19731 			return ForegroundColor(mixin("Color." ~ s));
19732 		}
19733 	}
19734 
19735 	struct BackgroundColor {
19736 		Color color;
19737 		alias color this;
19738 
19739 		this(Color c) {
19740 			color = c;
19741 		}
19742 
19743 		this(int r, int g, int b, int a = 255) {
19744 			color = Color(r, g, b, a);
19745 		}
19746 
19747 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19748 			return BackgroundColor(mixin("Color." ~ s));
19749 		}
19750 	}
19751 
19752 	static class InlineElement {
19753 		string text;
19754 
19755 		BlockElement containingBlock;
19756 
19757 		Color color = Color.black;
19758 		Color backgroundColor = Color.transparent;
19759 		ushort styles;
19760 
19761 		string font;
19762 		int fontSize;
19763 
19764 		int lineHeight;
19765 
19766 		void* identifier;
19767 
19768 		Rectangle boundingBox;
19769 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19770 
19771 		bool isMergeCompatible(InlineElement other) {
19772 			return
19773 				containingBlock is other.containingBlock &&
19774 				color == other.color &&
19775 				backgroundColor == other.backgroundColor &&
19776 				styles == other.styles &&
19777 				font == other.font &&
19778 				fontSize == other.fontSize &&
19779 				lineHeight == other.lineHeight &&
19780 				true;
19781 		}
19782 
19783 		int xOfIndex(size_t index) {
19784 			if(index < letterXs.length)
19785 				return letterXs[index];
19786 			else
19787 				return boundingBox.right;
19788 		}
19789 
19790 		InlineElement clone() {
19791 			auto ie = new InlineElement();
19792 			ie.tupleof = this.tupleof;
19793 			return ie;
19794 		}
19795 
19796 		InlineElement getPreviousInlineElement() {
19797 			InlineElement prev = null;
19798 			foreach(ie; this.containingBlock.parts) {
19799 				if(ie is this)
19800 					break;
19801 				prev = ie;
19802 			}
19803 			if(prev is null) {
19804 				BlockElement pb;
19805 				BlockElement cb = this.containingBlock;
19806 				moar:
19807 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19808 					if(ie is cb)
19809 						break;
19810 					pb = ie;
19811 				}
19812 				if(pb is null)
19813 					return null;
19814 				if(pb.parts.length == 0) {
19815 					cb = pb;
19816 					goto moar;
19817 				}
19818 
19819 				prev = pb.parts[$-1];
19820 
19821 			}
19822 			return prev;
19823 		}
19824 
19825 		InlineElement getNextInlineElement() {
19826 			InlineElement next = null;
19827 			foreach(idx, ie; this.containingBlock.parts) {
19828 				if(ie is this) {
19829 					if(idx + 1 < this.containingBlock.parts.length)
19830 						next = this.containingBlock.parts[idx + 1];
19831 					break;
19832 				}
19833 			}
19834 			if(next is null) {
19835 				BlockElement n;
19836 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19837 					if(ie is this.containingBlock) {
19838 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19839 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19840 						break;
19841 					}
19842 				}
19843 				if(n is null)
19844 					return null;
19845 
19846 				if(n.parts.length)
19847 					next = n.parts[0];
19848 				else {} // FIXME
19849 
19850 			}
19851 			return next;
19852 		}
19853 
19854 	}
19855 
19856 	// Block elements are used entirely for positioning inline elements,
19857 	// which are the things that are actually drawn.
19858 	class BlockElement {
19859 		InlineElement[] parts;
19860 		uint alignment;
19861 
19862 		int whiteSpace; // pre, pre-wrap, wrap
19863 
19864 		TextLayout containingLayout;
19865 
19866 		// inputs
19867 		Point where;
19868 		Size minimumSize;
19869 		Size maximumSize;
19870 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
19871 		void* identifier;
19872 
19873 		Rectangle margin;
19874 		Rectangle padding;
19875 
19876 		// outputs
19877 		Rectangle[] boundingBoxes;
19878 	}
19879 
19880 	struct TextIdentifyResult {
19881 		InlineElement element;
19882 		int offset;
19883 
19884 		private TextIdentifyResult fixupNewline() {
19885 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
19886 				offset--;
19887 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
19888 				offset--;
19889 			}
19890 			return this;
19891 		}
19892 	}
19893 
19894 	class TextLayout {
19895 		BlockElement[] blocks;
19896 		Rectangle boundingBox_;
19897 		Rectangle boundingBox() { return boundingBox_; }
19898 		void boundingBox(Rectangle r) {
19899 			if(r != boundingBox_) {
19900 				boundingBox_ = r;
19901 				layoutInvalidated = true;
19902 			}
19903 		}
19904 
19905 		Rectangle contentBoundingBox() {
19906 			Rectangle r;
19907 			foreach(block; blocks)
19908 			foreach(ie; block.parts) {
19909 				if(ie.boundingBox.right > r.right)
19910 					r.right = ie.boundingBox.right;
19911 				if(ie.boundingBox.bottom > r.bottom)
19912 					r.bottom = ie.boundingBox.bottom;
19913 			}
19914 			return r;
19915 		}
19916 
19917 		BlockElement[] getBlocks() {
19918 			return blocks;
19919 		}
19920 
19921 		InlineElement[] getTexts() {
19922 			InlineElement[] elements;
19923 			foreach(block; blocks)
19924 				elements ~= block.parts;
19925 			return elements;
19926 		}
19927 
19928 		string getPlainText() {
19929 			string text;
19930 			foreach(block; blocks)
19931 				foreach(part; block.parts)
19932 					text ~= part.text;
19933 			return text;
19934 		}
19935 
19936 		string getHtml() {
19937 			return null; // FIXME
19938 		}
19939 
19940 		this(Rectangle boundingBox) {
19941 			this.boundingBox = boundingBox;
19942 		}
19943 
19944 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
19945 			auto be = new BlockElement();
19946 			be.containingLayout = this;
19947 			if(after is null)
19948 				blocks ~= be;
19949 			else {
19950 				foreach(idx, b; blocks) {
19951 					if(b is after.containingBlock) {
19952 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
19953 						break;
19954 					}
19955 				}
19956 			}
19957 			return be;
19958 		}
19959 
19960 		void clear() {
19961 			blocks = null;
19962 			selectionStart = selectionEnd = caret = Caret.init;
19963 		}
19964 
19965 		void addText(Args...)(Args args) {
19966 			if(blocks.length == 0)
19967 				addBlock();
19968 
19969 			InlineElement ie = new InlineElement();
19970 			foreach(idx, arg; args) {
19971 				static if(is(typeof(arg) == ForegroundColor))
19972 					ie.color = arg;
19973 				else static if(is(typeof(arg) == TextFormat)) {
19974 					if(arg & 0x8000) // ~TextFormat.something turns it off
19975 						ie.styles &= arg;
19976 					else
19977 						ie.styles |= arg;
19978 				} else static if(is(typeof(arg) == string)) {
19979 					static if(idx == 0 && args.length > 1)
19980 						static assert(0, "Put styles before the string.");
19981 					size_t lastLineIndex;
19982 					foreach(cidx, char a; arg) {
19983 						if(a == '\n') {
19984 							ie.text = arg[lastLineIndex .. cidx + 1];
19985 							lastLineIndex = cidx + 1;
19986 							ie.containingBlock = blocks[$-1];
19987 							blocks[$-1].parts ~= ie.clone;
19988 							ie.text = null;
19989 						} else {
19990 
19991 						}
19992 					}
19993 
19994 					ie.text = arg[lastLineIndex .. $];
19995 					ie.containingBlock = blocks[$-1];
19996 					blocks[$-1].parts ~= ie.clone;
19997 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
19998 				}
19999 			}
20000 
20001 			invalidateLayout();
20002 		}
20003 
20004 		void tryMerge(InlineElement into, InlineElement what) {
20005 			if(!into.isMergeCompatible(what)) {
20006 				return; // cannot merge, different configs
20007 			}
20008 
20009 			// cool, can merge, bring text together...
20010 			into.text ~= what.text;
20011 
20012 			// and remove what
20013 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
20014 				if(what.containingBlock.parts[a] is what) {
20015 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
20016 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
20017 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
20018 
20019 				}
20020 			}
20021 
20022 			// FIXME: ensure no other carets have a reference to it
20023 		}
20024 
20025 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
20026 		TextIdentifyResult identify(int x, int y, bool exact = false) {
20027 			TextIdentifyResult inexactMatch;
20028 			foreach(block; blocks) {
20029 				foreach(part; block.parts) {
20030 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
20031 
20032 						// FIXME binary search
20033 						int tidx;
20034 						int lastX;
20035 						foreach_reverse(idxo, lx; part.letterXs) {
20036 							int idx = cast(int) idxo;
20037 							if(lx <= x) {
20038 								if(lastX && lastX - x < x - lx)
20039 									tidx = idx + 1;
20040 								else
20041 									tidx = idx;
20042 								break;
20043 							}
20044 							lastX = lx;
20045 						}
20046 
20047 						return TextIdentifyResult(part, tidx).fixupNewline;
20048 					} else if(!exact) {
20049 						// we're not in the box, but are we on the same line?
20050 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
20051 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
20052 					}
20053 				}
20054 			}
20055 
20056 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
20057 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
20058 
20059 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
20060 		}
20061 
20062 		void moveCaretToPixelCoordinates(int x, int y) {
20063 			auto result = identify(x, y);
20064 			caret.inlineElement = result.element;
20065 			caret.offset = result.offset;
20066 		}
20067 
20068 		void selectToPixelCoordinates(int x, int y) {
20069 			auto result = identify(x, y);
20070 
20071 			if(y < caretLastDrawnY1) {
20072 				// on a previous line, carat is selectionEnd
20073 				selectionEnd = caret;
20074 
20075 				selectionStart = Caret(this, result.element, result.offset);
20076 			} else if(y > caretLastDrawnY2) {
20077 				// on a later line
20078 				selectionStart = caret;
20079 
20080 				selectionEnd = Caret(this, result.element, result.offset);
20081 			} else {
20082 				// on the same line...
20083 				if(x <= caretLastDrawnX) {
20084 					selectionEnd = caret;
20085 					selectionStart = Caret(this, result.element, result.offset);
20086 				} else {
20087 					selectionStart = caret;
20088 					selectionEnd = Caret(this, result.element, result.offset);
20089 				}
20090 
20091 			}
20092 		}
20093 
20094 
20095 		/// Call this if the inputs change. It will reflow everything
20096 		void redoLayout(ScreenPainter painter) {
20097 			//painter.setClipRectangle(boundingBox);
20098 			auto pos = Point(boundingBox.left, boundingBox.top);
20099 
20100 			int lastHeight;
20101 			void nl() {
20102 				pos.x = boundingBox.left;
20103 				pos.y += lastHeight;
20104 			}
20105 			foreach(block; blocks) {
20106 				nl();
20107 				foreach(part; block.parts) {
20108 					part.letterXs = null;
20109 
20110 					auto size = painter.textSize(part.text);
20111 					version(Windows)
20112 						if(part.text.length && part.text[$-1] == '\n')
20113 							size.height /= 2; // windows counts the new line at the end, but we don't want that
20114 
20115 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
20116 
20117 					foreach(idx, char c; part.text) {
20118 							// FIXME: unicode
20119 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
20120 					}
20121 
20122 					pos.x += size.width;
20123 					if(pos.x >= boundingBox.right) {
20124 						pos.y += size.height;
20125 						pos.x = boundingBox.left;
20126 						lastHeight = 0;
20127 					} else {
20128 						lastHeight = size.height;
20129 					}
20130 
20131 					if(part.text.length && part.text[$-1] == '\n')
20132 						nl();
20133 				}
20134 			}
20135 
20136 			layoutInvalidated = false;
20137 		}
20138 
20139 		bool layoutInvalidated = true;
20140 		void invalidateLayout() {
20141 			layoutInvalidated = true;
20142 		}
20143 
20144 // FIXME: caret can remain sometimes when inserting
20145 // FIXME: inserting at the beginning once you already have something can eff it up.
20146 		void drawInto(ScreenPainter painter, bool focused = false) {
20147 			if(layoutInvalidated)
20148 				redoLayout(painter);
20149 			foreach(block; blocks) {
20150 				foreach(part; block.parts) {
20151 					painter.outlineColor = part.color;
20152 					painter.fillColor = part.backgroundColor;
20153 
20154 					auto pos = part.boundingBox.upperLeft;
20155 					auto size = part.boundingBox.size;
20156 
20157 					painter.drawText(pos, part.text);
20158 					if(part.styles & TextFormat.underline)
20159 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
20160 					if(part.styles & TextFormat.strikethrough)
20161 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
20162 				}
20163 			}
20164 
20165 			// on every redraw, I will force the caret to be
20166 			// redrawn too, in order to eliminate perceived lag
20167 			// when moving around with the mouse.
20168 			eraseCaret(painter);
20169 
20170 			if(focused) {
20171 				highlightSelection(painter);
20172 				drawCaret(painter);
20173 			}
20174 		}
20175 
20176 		Color selectionXorColor = Color(255, 255, 127);
20177 
20178 		void highlightSelection(ScreenPainter painter) {
20179 			if(selectionStart is selectionEnd)
20180 				return; // no selection
20181 
20182 			if(selectionStart.inlineElement is null) return;
20183 			if(selectionEnd.inlineElement is null) return;
20184 
20185 			assert(selectionStart.inlineElement !is null);
20186 			assert(selectionEnd.inlineElement !is null);
20187 
20188 			painter.rasterOp = RasterOp.xor;
20189 			painter.outlineColor = Color.transparent;
20190 			painter.fillColor = selectionXorColor;
20191 
20192 			auto at = selectionStart.inlineElement;
20193 			auto atOffset = selectionStart.offset;
20194 			bool done;
20195 			while(at) {
20196 				auto box = at.boundingBox;
20197 				if(atOffset < at.letterXs.length)
20198 					box.left = at.letterXs[atOffset];
20199 
20200 				if(at is selectionEnd.inlineElement) {
20201 					if(selectionEnd.offset < at.letterXs.length)
20202 						box.right = at.letterXs[selectionEnd.offset];
20203 					done = true;
20204 				}
20205 
20206 				painter.drawRectangle(box.upperLeft, box.width, box.height);
20207 
20208 				if(done)
20209 					break;
20210 
20211 				at = at.getNextInlineElement();
20212 				atOffset = 0;
20213 			}
20214 		}
20215 
20216 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
20217 		bool caretShowingOnScreen = false;
20218 		void drawCaret(ScreenPainter painter) {
20219 			//painter.setClipRectangle(boundingBox);
20220 			int x, y1, y2;
20221 			if(caret.inlineElement is null) {
20222 				x = boundingBox.left;
20223 				y1 = boundingBox.top + 2;
20224 				y2 = boundingBox.top + painter.fontHeight;
20225 			} else {
20226 				x = caret.inlineElement.xOfIndex(caret.offset);
20227 				y1 = caret.inlineElement.boundingBox.top + 2;
20228 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20229 			}
20230 
20231 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
20232 				eraseCaret(painter);
20233 
20234 			painter.pen = Pen(Color.white, 1);
20235 			painter.rasterOp = RasterOp.xor;
20236 			painter.drawLine(
20237 				Point(x, y1),
20238 				Point(x, y2)
20239 			);
20240 			painter.rasterOp = RasterOp.normal;
20241 			caretShowingOnScreen = !caretShowingOnScreen;
20242 
20243 			if(caretShowingOnScreen) {
20244 				caretLastDrawnX = x;
20245 				caretLastDrawnY1 = y1;
20246 				caretLastDrawnY2 = y2;
20247 			}
20248 		}
20249 
20250 		Rectangle caretBoundingBox() {
20251 			int x, y1, y2;
20252 			if(caret.inlineElement is null) {
20253 				x = boundingBox.left;
20254 				y1 = boundingBox.top + 2;
20255 				y2 = boundingBox.top + 16;
20256 			} else {
20257 				x = caret.inlineElement.xOfIndex(caret.offset);
20258 				y1 = caret.inlineElement.boundingBox.top + 2;
20259 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20260 			}
20261 
20262 			return Rectangle(x, y1, x + 1, y2);
20263 		}
20264 
20265 		void eraseCaret(ScreenPainter painter) {
20266 			//painter.setClipRectangle(boundingBox);
20267 			if(!caretShowingOnScreen) return;
20268 			painter.pen = Pen(Color.white, 1);
20269 			painter.rasterOp = RasterOp.xor;
20270 			painter.drawLine(
20271 				Point(caretLastDrawnX, caretLastDrawnY1),
20272 				Point(caretLastDrawnX, caretLastDrawnY2)
20273 			);
20274 
20275 			caretShowingOnScreen = false;
20276 			painter.rasterOp = RasterOp.normal;
20277 		}
20278 
20279 		/// Caret movement api
20280 		/// These should give the user a logical result based on what they see on screen...
20281 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20282 		void moveUp() {
20283 			if(caret.inlineElement is null) return;
20284 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20285 			auto y = caret.inlineElement.boundingBox.top + 2;
20286 
20287 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20288 			if(y < 0)
20289 				return;
20290 
20291 			auto i = identify(x, y);
20292 
20293 			if(i.element) {
20294 				caret.inlineElement = i.element;
20295 				caret.offset = i.offset;
20296 			}
20297 		}
20298 		void moveDown() {
20299 			if(caret.inlineElement is null) return;
20300 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20301 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20302 
20303 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20304 
20305 			auto i = identify(x, y);
20306 			if(i.element) {
20307 				caret.inlineElement = i.element;
20308 				caret.offset = i.offset;
20309 			}
20310 		}
20311 		void moveLeft() {
20312 			if(caret.inlineElement is null) return;
20313 			if(caret.offset)
20314 				caret.offset--;
20315 			else {
20316 				auto p = caret.inlineElement.getPreviousInlineElement();
20317 				if(p) {
20318 					caret.inlineElement = p;
20319 					if(p.text.length && p.text[$-1] == '\n')
20320 						caret.offset = cast(int) p.text.length - 1;
20321 					else
20322 						caret.offset = cast(int) p.text.length;
20323 				}
20324 			}
20325 		}
20326 		void moveRight() {
20327 			if(caret.inlineElement is null) return;
20328 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20329 				caret.offset++;
20330 			} else {
20331 				auto p = caret.inlineElement.getNextInlineElement();
20332 				if(p) {
20333 					caret.inlineElement = p;
20334 					caret.offset = 0;
20335 				}
20336 			}
20337 		}
20338 		void moveHome() {
20339 			if(caret.inlineElement is null) return;
20340 			auto x = 0;
20341 			auto y = caret.inlineElement.boundingBox.top + 2;
20342 
20343 			auto i = identify(x, y);
20344 
20345 			if(i.element) {
20346 				caret.inlineElement = i.element;
20347 				caret.offset = i.offset;
20348 			}
20349 		}
20350 		void moveEnd() {
20351 			if(caret.inlineElement is null) return;
20352 			auto x = int.max;
20353 			auto y = caret.inlineElement.boundingBox.top + 2;
20354 
20355 			auto i = identify(x, y);
20356 
20357 			if(i.element) {
20358 				caret.inlineElement = i.element;
20359 				caret.offset = i.offset;
20360 			}
20361 
20362 		}
20363 		void movePageUp(ref Caret caret) {}
20364 		void movePageDown(ref Caret caret) {}
20365 
20366 		void moveDocumentStart(ref Caret caret) {
20367 			if(blocks.length && blocks[0].parts.length)
20368 				caret = Caret(this, blocks[0].parts[0], 0);
20369 			else
20370 				caret = Caret.init;
20371 		}
20372 
20373 		void moveDocumentEnd(ref Caret caret) {
20374 			if(blocks.length) {
20375 				auto parts = blocks[$-1].parts;
20376 				if(parts.length) {
20377 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20378 				} else {
20379 					caret = Caret.init;
20380 				}
20381 			} else
20382 				caret = Caret.init;
20383 		}
20384 
20385 		void deleteSelection() {
20386 			if(selectionStart is selectionEnd)
20387 				return;
20388 
20389 			if(selectionStart.inlineElement is null) return;
20390 			if(selectionEnd.inlineElement is null) return;
20391 
20392 			assert(selectionStart.inlineElement !is null);
20393 			assert(selectionEnd.inlineElement !is null);
20394 
20395 			auto at = selectionStart.inlineElement;
20396 
20397 			if(selectionEnd.inlineElement is at) {
20398 				// same element, need to chop out
20399 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20400 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20401 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20402 			} else {
20403 				// different elements, we can do it with slicing
20404 				at.text = at.text[0 .. selectionStart.offset];
20405 				if(selectionStart.offset < at.letterXs.length)
20406 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20407 
20408 				at = at.getNextInlineElement();
20409 
20410 				while(at) {
20411 					if(at is selectionEnd.inlineElement) {
20412 						at.text = at.text[selectionEnd.offset .. $];
20413 						if(selectionEnd.offset < at.letterXs.length)
20414 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20415 						selectionEnd.offset = 0;
20416 						break;
20417 					} else {
20418 						auto cfd = at;
20419 						cfd.text = null; // delete the whole thing
20420 
20421 						at = at.getNextInlineElement();
20422 
20423 						if(cfd.text.length == 0) {
20424 							// and remove cfd
20425 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20426 								if(cfd.containingBlock.parts[a] is cfd) {
20427 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20428 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20429 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20430 
20431 								}
20432 							}
20433 						}
20434 					}
20435 				}
20436 			}
20437 
20438 			caret = selectionEnd;
20439 			selectNone();
20440 
20441 			invalidateLayout();
20442 
20443 		}
20444 
20445 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20446 		void insert(in char[] text) {
20447 			foreach(dchar ch; text)
20448 				insert(ch);
20449 		}
20450 		/// ditto
20451 		void insert(dchar ch) {
20452 
20453 			bool selectionDeleted = false;
20454 			if(selectionStart !is selectionEnd) {
20455 				deleteSelection();
20456 				selectionDeleted = true;
20457 			}
20458 
20459 			if(ch == 127) {
20460 				delete_();
20461 				return;
20462 			}
20463 			if(ch == 8) {
20464 				if(!selectionDeleted)
20465 					backspace();
20466 				return;
20467 			}
20468 
20469 			invalidateLayout();
20470 
20471 			if(ch == 13) ch = 10;
20472 			auto e = caret.inlineElement;
20473 			if(e is null) {
20474 				addText("" ~ cast(char) ch) ; // FIXME
20475 				return;
20476 			}
20477 
20478 			if(caret.offset == e.text.length) {
20479 				e.text ~= cast(char) ch; // FIXME
20480 				caret.offset++;
20481 				if(ch == 10) {
20482 					auto c = caret.inlineElement.clone;
20483 					c.text = null;
20484 					c.letterXs = null;
20485 					insertPartAfter(c,e);
20486 					caret = Caret(this, c, 0);
20487 				}
20488 			} else {
20489 				// FIXME cast char sucks
20490 				if(ch == 10) {
20491 					auto c = caret.inlineElement.clone;
20492 					c.text = e.text[caret.offset .. $];
20493 					if(caret.offset < c.letterXs.length)
20494 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20495 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20496 					if(caret.offset <= e.letterXs.length) {
20497 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20498 					}
20499 					insertPartAfter(c,e);
20500 					caret = Caret(this, c, 0);
20501 				} else {
20502 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20503 					caret.offset++;
20504 				}
20505 			}
20506 		}
20507 
20508 		void insertPartAfter(InlineElement what, InlineElement where) {
20509 			foreach(idx, p; where.containingBlock.parts) {
20510 				if(p is where) {
20511 					if(idx + 1 == where.containingBlock.parts.length)
20512 						where.containingBlock.parts ~= what;
20513 					else
20514 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20515 					return;
20516 				}
20517 			}
20518 		}
20519 
20520 		void cleanupStructures() {
20521 			for(size_t i = 0; i < blocks.length; i++) {
20522 				auto block = blocks[i];
20523 				for(size_t a = 0; a < block.parts.length; a++) {
20524 					auto part = block.parts[a];
20525 					if(part.text.length == 0) {
20526 						for(size_t b = a; b < block.parts.length - 1; b++)
20527 							block.parts[b] = block.parts[b+1];
20528 						block.parts = block.parts[0 .. $-1];
20529 					}
20530 				}
20531 				if(block.parts.length == 0) {
20532 					for(size_t a = i; a < blocks.length - 1; a++)
20533 						blocks[a] = blocks[a+1];
20534 					blocks = blocks[0 .. $-1];
20535 				}
20536 			}
20537 		}
20538 
20539 		void backspace() {
20540 			try_again:
20541 			auto e = caret.inlineElement;
20542 			if(e is null)
20543 				return;
20544 			if(caret.offset == 0) {
20545 				auto prev = e.getPreviousInlineElement();
20546 				if(prev is null)
20547 					return;
20548 				auto newOffset = cast(int) prev.text.length;
20549 				tryMerge(prev, e);
20550 				caret.inlineElement = prev;
20551 				caret.offset = prev is null ? 0 : newOffset;
20552 
20553 				goto try_again;
20554 			} else if(caret.offset == e.text.length) {
20555 				e.text = e.text[0 .. $-1];
20556 				caret.offset--;
20557 			} else {
20558 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20559 				caret.offset--;
20560 			}
20561 			//cleanupStructures();
20562 
20563 			invalidateLayout();
20564 		}
20565 		void delete_() {
20566 			if(selectionStart !is selectionEnd)
20567 				deleteSelection();
20568 			else {
20569 				auto before = caret;
20570 				moveRight();
20571 				if(caret != before) {
20572 					backspace();
20573 				}
20574 			}
20575 
20576 			invalidateLayout();
20577 		}
20578 		void overstrike() {}
20579 
20580 		/// Selection API. See also: caret movement.
20581 		void selectAll() {
20582 			moveDocumentStart(selectionStart);
20583 			moveDocumentEnd(selectionEnd);
20584 		}
20585 		bool selectNone() {
20586 			if(selectionStart != selectionEnd) {
20587 				selectionStart = selectionEnd = Caret.init;
20588 				return true;
20589 			}
20590 			return false;
20591 		}
20592 
20593 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20594 		/// They will modify the current selection if there is one and will splice one in if needed.
20595 		void changeAttributes() {}
20596 
20597 
20598 		/// Text search api. They manipulate the selection and/or caret.
20599 		void findText(string text) {}
20600 		void findIndex(size_t textIndex) {}
20601 
20602 		// sample event handlers
20603 
20604 		void handleEvent(KeyEvent event) {
20605 			//if(event.type == KeyEvent.Type.KeyPressed) {
20606 
20607 			//}
20608 		}
20609 
20610 		void handleEvent(dchar ch) {
20611 
20612 		}
20613 
20614 		void handleEvent(MouseEvent event) {
20615 
20616 		}
20617 
20618 		bool contentEditable; // can it be edited?
20619 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20620 		bool contentSelectable; // selectable?
20621 
20622 		Caret caret;
20623 		Caret selectionStart;
20624 		Caret selectionEnd;
20625 
20626 		bool insertMode;
20627 	}
20628 
20629 	struct Caret {
20630 		TextLayout layout;
20631 		InlineElement inlineElement;
20632 		int offset;
20633 	}
20634 
20635 	enum TextFormat : ushort {
20636 		// decorations
20637 		underline = 1,
20638 		strikethrough = 2,
20639 
20640 		// font selectors
20641 
20642 		bold = 0x4000 | 1, // weight 700
20643 		light = 0x4000 | 2, // weight 300
20644 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20645 		// bold | light is really invalid but should give weight 500
20646 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20647 
20648 		italic = 0x4000 | 8,
20649 		smallcaps = 0x4000 | 16,
20650 	}
20651 
20652 	void* findFont(string family, int weight, TextFormat formats) {
20653 		return null;
20654 	}
20655 
20656 }
20657 
20658 /++
20659 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20660 
20661 	History:
20662 		Added February 19, 2021
20663 +/
20664 /// Group: drag_and_drop
20665 interface DropHandler {
20666 	/++
20667 		Called when the drag enters the handler's area.
20668 	+/
20669 	DragAndDropAction dragEnter(DropPackage*);
20670 	/++
20671 		Called when the drag leaves the handler's area or is
20672 		cancelled. You should free your resources when this is called.
20673 	+/
20674 	void dragLeave();
20675 	/++
20676 		Called continually as the drag moves over the handler's area.
20677 
20678 		Returns: feedback to the dragger
20679 	+/
20680 	DropParameters dragOver(Point pt);
20681 	/++
20682 		The user dropped the data and you should process it now. You can
20683 		access the data through the given [DropPackage].
20684 	+/
20685 	void drop(scope DropPackage*);
20686 	/++
20687 		Called when the drop is complete. You should free whatever temporary
20688 		resources you were using. It is often reasonable to simply forward
20689 		this call to [dragLeave].
20690 	+/
20691 	void finish();
20692 
20693 	/++
20694 		Parameters returned by [DropHandler.drop].
20695 	+/
20696 	static struct DropParameters {
20697 		/++
20698 			Acceptable action over this area.
20699 		+/
20700 		DragAndDropAction action;
20701 		/++
20702 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20703 
20704 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20705 		+/
20706 		Rectangle consistentWithin;
20707 	}
20708 }
20709 
20710 /++
20711 	History:
20712 		Added February 19, 2021
20713 +/
20714 /// Group: drag_and_drop
20715 enum DragAndDropAction {
20716 	none = 0,
20717 	copy,
20718 	move,
20719 	link,
20720 	ask,
20721 	custom
20722 }
20723 
20724 /++
20725 	An opaque structure representing dropped data. It contains
20726 	private, platform-specific data that your `drop` function
20727 	should simply forward.
20728 
20729 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20730 
20731 	History:
20732 		Added February 19, 2021
20733 +/
20734 /// Group: drag_and_drop
20735 struct DropPackage {
20736 	/++
20737 		Lists the available formats as magic numbers. You should compare these
20738 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20739 		understand the passed data.
20740 	+/
20741 	DraggableData.FormatId[] availableFormats() {
20742 		version(X11) {
20743 			return xFormats;
20744 		} else version(Windows) {
20745 			if(pDataObj is null)
20746 				return null;
20747 
20748 			typeof(return) ret;
20749 
20750 			IEnumFORMATETC ef;
20751 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20752 				FORMATETC fmt;
20753 				ULONG fetched;
20754 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20755 					if(fetched == 0)
20756 						break;
20757 
20758 					if(fmt.lindex != -1)
20759 						continue;
20760 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20761 						continue;
20762 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20763 						continue;
20764 
20765 					ret ~= fmt.cfFormat;
20766 				}
20767 			}
20768 
20769 			return ret;
20770 		} else throw new NotYetImplementedException();
20771 	}
20772 
20773 	/++
20774 		Gets data from the drop and optionally accepts it.
20775 
20776 		Returns:
20777 			void because the data is fed asynchronously through the `dg` parameter.
20778 
20779 		Params:
20780 			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.
20781 
20782 			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.
20783 
20784 			Calling `getData` again after accepting a drop is not permitted.
20785 
20786 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20787 
20788 			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.
20789 
20790 		Throws:
20791 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20792 
20793 		History:
20794 			Included in first release of [DropPackage].
20795 	+/
20796 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20797 		version(X11) {
20798 
20799 			auto display = XDisplayConnection.get();
20800 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20801 			auto best = format;
20802 
20803 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20804 
20805 				XDisplay* display;
20806 				Atom selectionAtom;
20807 				DraggableData.FormatId best;
20808 				DraggableData.FormatId format;
20809 				void delegate(scope ubyte[] data) dg;
20810 				DragAndDropAction acceptedAction;
20811 				Window sourceWindow;
20812 				SimpleWindow win;
20813 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20814 					this.display = display;
20815 					this.win = win;
20816 					this.sourceWindow = sourceWindow;
20817 					this.format = format;
20818 					this.selectionAtom = selectionAtom;
20819 					this.best = best;
20820 					this.dg = dg;
20821 					this.acceptedAction = acceptedAction;
20822 				}
20823 
20824 
20825 				mixin X11GetSelectionHandler_Basics;
20826 
20827 				void handleData(Atom target, in ubyte[] data) {
20828 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20829 
20830 					dg(cast(ubyte[]) data);
20831 
20832 					if(acceptedAction != DragAndDropAction.none) {
20833 						auto display = XDisplayConnection.get;
20834 
20835 						XClientMessageEvent xclient;
20836 
20837 						xclient.type = EventType.ClientMessage;
20838 						xclient.window = sourceWindow;
20839 						xclient.message_type = GetAtom!"XdndFinished"(display);
20840 						xclient.format = 32;
20841 						xclient.data.l[0] = win.impl.window;
20842 						xclient.data.l[1] = 1; // drop successful
20843 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20844 
20845 						XSendEvent(
20846 							display,
20847 							sourceWindow,
20848 							false,
20849 							EventMask.NoEventMask,
20850 							cast(XEvent*) &xclient
20851 						);
20852 
20853 						XFlush(display);
20854 					}
20855 				}
20856 
20857 				Atom findBestFormat(Atom[] answer) {
20858 					Atom best = None;
20859 					foreach(option; answer) {
20860 						if(option == format) {
20861 							best = option;
20862 							break;
20863 						}
20864 						/*
20865 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
20866 							best = option;
20867 							break;
20868 						} else if(option == XA_STRING) {
20869 							best = option;
20870 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
20871 							best = option;
20872 						}
20873 						*/
20874 					}
20875 					return best;
20876 				}
20877 			}
20878 
20879 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
20880 
20881 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
20882 
20883 		} else version(Windows) {
20884 
20885 			// clean up like DragLeave
20886 			// pass effect back up
20887 
20888 			FORMATETC t;
20889 			assert(format >= 0 && format <= ushort.max);
20890 			t.cfFormat = cast(ushort) format;
20891 			t.lindex = -1;
20892 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20893 			t.tymed = TYMED.TYMED_HGLOBAL;
20894 
20895 			STGMEDIUM m;
20896 
20897 			if(pDataObj.GetData(&t, &m) != S_OK) {
20898 				// fail
20899 			} else {
20900 				// succeed, take the data and clean up
20901 
20902 				// FIXME: ensure it is legit HGLOBAL
20903 				auto handle = m.hGlobal;
20904 
20905 				if(handle) {
20906 					auto sz = GlobalSize(handle);
20907 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
20908 						scope(exit) GlobalUnlock(handle);
20909 						scope(exit) GlobalFree(handle);
20910 
20911 						auto data = ptr[0 .. sz];
20912 
20913 						dg(data);
20914 					}
20915 				}
20916 			}
20917 		}
20918 	}
20919 
20920 	private:
20921 
20922 	version(X11) {
20923 		SimpleWindow win;
20924 		Window sourceWindow;
20925 		Time dataTimestamp;
20926 
20927 		Atom[] xFormats;
20928 	}
20929 	version(Windows) {
20930 		IDataObject pDataObj;
20931 	}
20932 }
20933 
20934 /++
20935 	A generic helper base class for making a drop handler with a preference list of custom types.
20936 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
20937 	droppers too.
20938 
20939 	It assumes the whole window it used, but you can subclass to change that.
20940 
20941 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20942 
20943 	History:
20944 		Added February 19, 2021
20945 +/
20946 /// Group: drag_and_drop
20947 class GenericDropHandlerBase : DropHandler {
20948 	// no fancy state here so no need to do anything here
20949 	void finish() { }
20950 	void dragLeave() { }
20951 
20952 	private DragAndDropAction acceptedAction;
20953 	private DraggableData.FormatId acceptedFormat;
20954 	private void delegate(scope ubyte[]) acceptedHandler;
20955 
20956 	struct FormatHandler {
20957 		DraggableData.FormatId format;
20958 		void delegate(scope ubyte[]) handler;
20959 	}
20960 
20961 	protected abstract FormatHandler[] formatHandlers();
20962 
20963 	DragAndDropAction dragEnter(DropPackage* pkg) {
20964 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
20965 		foreach(fmt; formatHandlers())
20966 		foreach(f; pkg.availableFormats())
20967 			if(f == fmt.format) {
20968 				acceptedFormat = f;
20969 				acceptedHandler = fmt.handler;
20970 				return acceptedAction = DragAndDropAction.copy;
20971 			}
20972 		return acceptedAction = DragAndDropAction.none;
20973 	}
20974 	DropParameters dragOver(Point pt) {
20975 		return DropParameters(acceptedAction);
20976 	}
20977 
20978 	void drop(scope DropPackage* dropPackage) {
20979 		if(!acceptedFormat || acceptedHandler is null) {
20980 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
20981 			return; // prolly shouldn't happen anyway...
20982 		}
20983 
20984 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
20985 	}
20986 }
20987 
20988 /++
20989 	A simple handler for making your window accept drops of plain text.
20990 
20991 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20992 
20993 	History:
20994 		Added February 22, 2021
20995 +/
20996 /// Group: drag_and_drop
20997 class TextDropHandler : GenericDropHandlerBase {
20998 	private void delegate(in char[] text) dg;
20999 
21000 	/++
21001 
21002 	+/
21003 	this(void delegate(in char[] text) dg) {
21004 		this.dg = dg;
21005 	}
21006 
21007 	protected override FormatHandler[] formatHandlers() {
21008 		version(X11)
21009 			return [
21010 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21011 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21012 			];
21013 		else version(Windows)
21014 			return [
21015 				FormatHandler(CF_UNICODETEXT, &translator),
21016 			];
21017 		else throw new NotYetImplementedException();
21018 	}
21019 
21020 	private void translator(scope ubyte[] data) {
21021 		version(X11)
21022 			dg(cast(char[]) data);
21023 		else version(Windows)
21024 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21025 	}
21026 }
21027 
21028 /++
21029 	A simple handler for making your window accept drops of files, issued to you as file names.
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 
21038 class FilesDropHandler : GenericDropHandlerBase {
21039 	private void delegate(in char[][]) dg;
21040 
21041 	/++
21042 
21043 	+/
21044 	this(void delegate(in char[][] fileNames) dg) {
21045 		this.dg = dg;
21046 	}
21047 
21048 	protected override FormatHandler[] formatHandlers() {
21049 		version(X11)
21050 			return [
21051 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
21052 			];
21053 		else version(Windows)
21054 			return [
21055 				FormatHandler(CF_HDROP, &translator),
21056 			];
21057 		else throw new NotYetImplementedException();
21058 	}
21059 
21060 	private void translator(scope ubyte[] data) {
21061 		version(X11) {
21062 			char[] listString = cast(char[]) data;
21063 			char[][16] buffer;
21064 			int count;
21065 			char[][] result = buffer[];
21066 
21067 			void commit(char[] s) {
21068 				if(count == result.length)
21069 					result.length += 16;
21070 				if(s.length > 7 && s[0 ..7] == "file://")
21071 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
21072 				result[count++] = s;
21073 			}
21074 
21075 			size_t last;
21076 			foreach(idx, char c; listString) {
21077 				if(c == '\n') {
21078 					commit(listString[last .. idx - 1]); // a \r
21079 					last = idx + 1; // a \n
21080 				}
21081 			}
21082 
21083 			if(last < listString.length) {
21084 				commit(listString[last .. $]);
21085 			}
21086 
21087 			// FIXME: they are uris now, should I translate it to local file names?
21088 			// of course the host name is supposed to be there cuz of X rokking...
21089 
21090 			dg(result[0 .. count]);
21091 		} else version(Windows) {
21092 
21093 			static struct DROPFILES {
21094 				DWORD pFiles;
21095 				POINT pt;
21096 				BOOL  fNC;
21097 				BOOL  fWide;
21098 			}
21099 
21100 
21101 			const(char)[][16] buffer;
21102 			int count;
21103 			const(char)[][] result = buffer[];
21104 			size_t last;
21105 
21106 			void commitA(in char[] stuff) {
21107 				if(count == result.length)
21108 					result.length += 16;
21109 				result[count++] = stuff;
21110 			}
21111 
21112 			void commitW(in wchar[] stuff) {
21113 				commitA(makeUtf8StringFromWindowsString(stuff));
21114 			}
21115 
21116 			void magic(T)(T chars) {
21117 				size_t idx;
21118 				while(chars[idx]) {
21119 					last = idx;
21120 					while(chars[idx]) {
21121 						idx++;
21122 					}
21123 					static if(is(T == char*))
21124 						commitA(chars[last .. idx]);
21125 					else
21126 						commitW(chars[last .. idx]);
21127 					idx++;
21128 				}
21129 			}
21130 
21131 			auto df = cast(DROPFILES*) data.ptr;
21132 			if(df.fWide) {
21133 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
21134 				magic(chars);
21135 			} else {
21136 				char* chars = cast(char*) (data.ptr + df.pFiles);
21137 				magic(chars);
21138 			}
21139 			dg(result[0 .. count]);
21140 		}
21141 		else throw new NotYetImplementedException();
21142 	}
21143 }
21144 
21145 /++
21146 	Interface to describe data being dragged. See also [draggable] helper function.
21147 
21148 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21149 
21150 	History:
21151 		Added February 19, 2021
21152 +/
21153 interface DraggableData {
21154 	version(X11)
21155 		alias FormatId = Atom;
21156 	else
21157 		alias FormatId = uint;
21158 	/++
21159 		Gets the platform-specific FormatId associated with the given named format.
21160 
21161 		This may be a MIME type, but may also be other various strings defined by the
21162 		programs you want to interoperate with.
21163 
21164 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
21165 		and convert it to some particular type for you.
21166 	+/
21167 	static FormatId getFormatId(string name)() {
21168 		version(X11)
21169 			return GetAtom!name(XDisplayConnection.get);
21170 		else version(Windows) {
21171 			static UINT cache;
21172 			if(!cache)
21173 				cache = RegisterClipboardFormatA(name);
21174 			return cache;
21175 		} else
21176 			throw new NotYetImplementedException();
21177 	}
21178 
21179 	/++
21180 		Looks up a string to represent the name for the given format, if there is one.
21181 
21182 		You should avoid using this function because it is slow. It is provided more for
21183 		debugging than for primary use.
21184 	+/
21185 	static string getFormatName(FormatId format) {
21186 		version(X11) {
21187 			if(format == 0)
21188 				return "None";
21189 			else
21190 				return getAtomName(format, XDisplayConnection.get);
21191 		} else version(Windows) {
21192 			switch(format) {
21193 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
21194 				case CF_DIBV5: return "CF_DIBV5";
21195 				case CF_RIFF: return "CF_RIFF";
21196 				case CF_WAVE: return "CF_WAVE";
21197 				case CF_HDROP: return "CF_HDROP";
21198 				default:
21199 					char[1024] name;
21200 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
21201 					return name[0 .. count].idup;
21202 			}
21203 		} else throw new NotYetImplementedException();
21204 	}
21205 
21206 	FormatId[] availableFormats();
21207 	// Return the slice of data you filled, empty slice if done.
21208 	// this is to support the incremental thing
21209 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21210 
21211 	size_t dataLength(FormatId format);
21212 }
21213 
21214 /++
21215 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21216 
21217 	History:
21218 		Added February 19, 2021
21219 +/
21220 DraggableData draggable(string s) {
21221 	version(X11)
21222 	return new class X11SetSelectionHandler_Text, DraggableData {
21223 		this() {
21224 			super(s);
21225 		}
21226 
21227 		override FormatId[] availableFormats() {
21228 			return X11SetSelectionHandler_Text.availableFormats();
21229 		}
21230 
21231 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21232 			return X11SetSelectionHandler_Text.getData(format, data);
21233 		}
21234 
21235 		size_t dataLength(FormatId format) {
21236 			return s.length;
21237 		}
21238 	};
21239 	else version(Windows)
21240 	return new class DraggableData {
21241 		FormatId[] availableFormats() {
21242 			return [CF_UNICODETEXT];
21243 		}
21244 
21245 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21246 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21247 		}
21248 
21249 		size_t dataLength(FormatId format) {
21250 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21251 		}
21252 	};
21253 	else
21254 	throw new NotYetImplementedException();
21255 }
21256 
21257 /++
21258 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21259 
21260 	History:
21261 		Added February 19, 2021
21262 +/
21263 /// Group: drag_and_drop
21264 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21265 in {
21266 	assert(window !is null);
21267 	assert(handler !is null);
21268 }
21269 do
21270 {
21271 	version(X11) {
21272 		auto sh = cast(X11SetSelectionHandler) handler;
21273 		if(sh is null) {
21274 			// gotta make my own adapter.
21275 			sh = new class X11SetSelectionHandler {
21276 				mixin X11SetSelectionHandler_Basics;
21277 
21278 				Atom[] availableFormats() { return handler.availableFormats(); }
21279 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21280 					return handler.getData(format, data);
21281 				}
21282 
21283 				// since the drop selection is only ever used once it isn't important
21284 				// to reset it.
21285 				void done() {}
21286 			};
21287 		}
21288 		return doDragDropX11(window, sh, action);
21289 	} else version(Windows) {
21290 		return doDragDropWindows(window, handler, action);
21291 	} else throw new NotYetImplementedException();
21292 }
21293 
21294 version(Windows)
21295 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21296 	IDataObject obj = new class IDataObject {
21297 		ULONG refCount;
21298 		ULONG AddRef() {
21299 			return ++refCount;
21300 		}
21301 		ULONG Release() {
21302 			return --refCount;
21303 		}
21304 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21305 			if (IID_IUnknown == *riid) {
21306 				*ppv = cast(void*) cast(IUnknown) this;
21307 			}
21308 			else if (IID_IDataObject == *riid) {
21309 				*ppv = cast(void*) cast(IDataObject) this;
21310 			}
21311 			else {
21312 				*ppv = null;
21313 				return E_NOINTERFACE;
21314 			}
21315 
21316 			AddRef();
21317 			return NOERROR;
21318 		}
21319 
21320 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21321 			//  writeln("Advise");
21322 			return E_NOTIMPL;
21323 		}
21324 		HRESULT DUnadvise(DWORD dwConnection) {
21325 			return E_NOTIMPL;
21326 		}
21327 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21328 			//  writeln("EnumDAdvise");
21329 			return OLE_E_ADVISENOTSUPPORTED;
21330 		}
21331 		// tell what formats it supports
21332 
21333 		FORMATETC[] types;
21334 		this() {
21335 			FORMATETC t;
21336 			foreach(ty; handler.availableFormats()) {
21337 				assert(ty <= ushort.max && ty >= 0);
21338 				t.cfFormat = cast(ushort) ty;
21339 				t.lindex = -1;
21340 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21341 				t.tymed = TYMED.TYMED_HGLOBAL;
21342 			}
21343 			types ~= t;
21344 		}
21345 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21346 			if(dwDirection == DATADIR.DATADIR_GET) {
21347 				*ppenumFormatEtc = new class IEnumFORMATETC {
21348 					ULONG refCount;
21349 					ULONG AddRef() {
21350 						return ++refCount;
21351 					}
21352 					ULONG Release() {
21353 						return --refCount;
21354 					}
21355 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21356 						if (IID_IUnknown == *riid) {
21357 							*ppv = cast(void*) cast(IUnknown) this;
21358 						}
21359 						else if (IID_IEnumFORMATETC == *riid) {
21360 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21361 						}
21362 						else {
21363 							*ppv = null;
21364 							return E_NOINTERFACE;
21365 						}
21366 
21367 						AddRef();
21368 						return NOERROR;
21369 					}
21370 
21371 
21372 					int pos;
21373 					this() {
21374 						pos = 0;
21375 					}
21376 
21377 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21378 						// writeln("clone");
21379 						return E_NOTIMPL; // FIXME
21380 					}
21381 
21382 					// Caller is responsible for freeing memory
21383 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21384 						// fetched may be null if celt is one
21385 						if(celt != 1)
21386 							return E_NOTIMPL; // FIXME
21387 
21388 						if(celt + pos > types.length)
21389 							return S_FALSE;
21390 
21391 						*rgelt = types[pos++];
21392 
21393 						if(pceltFetched !is null)
21394 							*pceltFetched = 1;
21395 
21396 						// writeln("ok celt ", celt);
21397 						return S_OK;
21398 					}
21399 
21400 					HRESULT Reset() {
21401 						pos = 0;
21402 						return S_OK;
21403 					}
21404 
21405 					HRESULT Skip(ULONG celt) {
21406 						if(celt + pos <= types.length) {
21407 							pos += celt;
21408 							return S_OK;
21409 						}
21410 						return S_FALSE;
21411 					}
21412 				};
21413 
21414 				return S_OK;
21415 			} else
21416 				return E_NOTIMPL;
21417 		}
21418 		// given a format, return the format you'd prefer to use cuz it is identical
21419 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21420 			// FIXME: prolly could be better but meh
21421 			// writeln("gcf: ", *pformatectIn);
21422 			*pformatetcOut = *pformatectIn;
21423 			return S_OK;
21424 		}
21425 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21426 			foreach(ty; types) {
21427 				if(ty == *pformatetcIn) {
21428 					auto format = ty.cfFormat;
21429 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
21430 					STGMEDIUM medium;
21431 					medium.tymed = TYMED.TYMED_HGLOBAL;
21432 
21433 					auto sz = handler.dataLength(format);
21434 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21435 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21436 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21437 						auto slice = data[0 .. sz];
21438 						scope(exit)
21439 							GlobalUnlock(handle);
21440 
21441 						handler.getData(format, cast(ubyte[]) slice[]);
21442 					}
21443 
21444 
21445 					medium.hGlobal = handle; // FIXME
21446 					*pmedium = medium;
21447 					return S_OK;
21448 				}
21449 			}
21450 			return DV_E_FORMATETC;
21451 		}
21452 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21453 			// writeln("GDH: ", *pformatetcIn);
21454 			return E_NOTIMPL; // FIXME
21455 		}
21456 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21457 			auto search = *pformatetc;
21458 			search.tymed &= TYMED.TYMED_HGLOBAL;
21459 			foreach(ty; types)
21460 				if(ty == search) {
21461 					// writeln("QueryGetData ", search, " ", types[0]);
21462 					return S_OK;
21463 				}
21464 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21465 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
21466 			}
21467 			return S_FALSE;
21468 		}
21469 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21470 			//  writeln("SetData: ");
21471 			return E_NOTIMPL;
21472 		}
21473 	};
21474 
21475 
21476 	IDropSource src = new class IDropSource {
21477 		ULONG refCount;
21478 		ULONG AddRef() {
21479 			return ++refCount;
21480 		}
21481 		ULONG Release() {
21482 			return --refCount;
21483 		}
21484 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21485 			if (IID_IUnknown == *riid) {
21486 				*ppv = cast(void*) cast(IUnknown) this;
21487 			}
21488 			else if (IID_IDropSource == *riid) {
21489 				*ppv = cast(void*) cast(IDropSource) this;
21490 			}
21491 			else {
21492 				*ppv = null;
21493 				return E_NOINTERFACE;
21494 			}
21495 
21496 			AddRef();
21497 			return NOERROR;
21498 		}
21499 
21500 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21501 			if(fEscapePressed)
21502 				return DRAGDROP_S_CANCEL;
21503 			if(!(grfKeyState & MK_LBUTTON))
21504 				return DRAGDROP_S_DROP;
21505 			return S_OK;
21506 		}
21507 
21508 		int GiveFeedback(uint dwEffect) {
21509 			return DRAGDROP_S_USEDEFAULTCURSORS;
21510 		}
21511 	};
21512 
21513 	DWORD effect;
21514 
21515 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21516 
21517 	DROPEFFECT de = win32DragAndDropAction(action);
21518 
21519 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21520 	// but still prolly a FIXME
21521 
21522 	auto ret = DoDragDrop(obj, src, de, &effect);
21523 	/+
21524 	if(ret == DRAGDROP_S_DROP)
21525 		writeln("drop ", effect);
21526 	else if(ret == DRAGDROP_S_CANCEL)
21527 		writeln("cancel");
21528 	else if(ret == S_OK)
21529 		writeln("ok");
21530 	else writeln(ret);
21531 	+/
21532 
21533 	return ret;
21534 }
21535 
21536 version(Windows)
21537 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21538 	DROPEFFECT de;
21539 
21540 	with(DragAndDropAction)
21541 	with(DROPEFFECT)
21542 	final switch(action) {
21543 		case none: de = DROPEFFECT_NONE; break;
21544 		case copy: de = DROPEFFECT_COPY; break;
21545 		case move: de = DROPEFFECT_MOVE; break;
21546 		case link: de = DROPEFFECT_LINK; break;
21547 		case ask: throw new Exception("ask not implemented yet");
21548 		case custom: throw new Exception("custom not implemented yet");
21549 	}
21550 
21551 	return de;
21552 }
21553 
21554 
21555 /++
21556 	History:
21557 		Added February 19, 2021
21558 +/
21559 /// Group: drag_and_drop
21560 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21561 	version(X11) {
21562 		auto display = XDisplayConnection.get;
21563 
21564 		Atom atom = 5; // right???
21565 
21566 		XChangeProperty(
21567 			display,
21568 			window.impl.window,
21569 			GetAtom!"XdndAware"(display),
21570 			XA_ATOM,
21571 			32 /* bits */,
21572 			PropModeReplace,
21573 			&atom,
21574 			1);
21575 
21576 		window.dropHandler = handler;
21577 	} else version(Windows) {
21578 
21579 		initDnd();
21580 
21581 		auto dropTarget = new class (handler) IDropTarget {
21582 			DropHandler handler;
21583 			this(DropHandler handler) {
21584 				this.handler = handler;
21585 			}
21586 			ULONG refCount;
21587 			ULONG AddRef() {
21588 				return ++refCount;
21589 			}
21590 			ULONG Release() {
21591 				return --refCount;
21592 			}
21593 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21594 				if (IID_IUnknown == *riid) {
21595 					*ppv = cast(void*) cast(IUnknown) this;
21596 				}
21597 				else if (IID_IDropTarget == *riid) {
21598 					*ppv = cast(void*) cast(IDropTarget) this;
21599 				}
21600 				else {
21601 					*ppv = null;
21602 					return E_NOINTERFACE;
21603 				}
21604 
21605 				AddRef();
21606 				return NOERROR;
21607 			}
21608 
21609 
21610 			// ///////////////////
21611 
21612 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21613 				DropPackage dropPackage = DropPackage(pDataObj);
21614 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21615 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21616 			}
21617 
21618 			HRESULT DragLeave() {
21619 				handler.dragLeave();
21620 				// release the IDataObject if needed
21621 				return S_OK;
21622 			}
21623 
21624 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21625 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21626 
21627 				*pdwEffect = win32DragAndDropAction(res.action);
21628 				// same as DragEnter basically
21629 				return S_OK;
21630 			}
21631 
21632 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21633 				DropPackage pkg = DropPackage(pDataObj);
21634 				handler.drop(&pkg);
21635 
21636 				return S_OK;
21637 			}
21638 		};
21639 		// Windows can hold on to the handler and try to call it
21640 		// during which time the GC can't see it. so important to
21641 		// manually manage this. At some point i'll FIXME and make
21642 		// all my com instances manually managed since they supposed
21643 		// to respect the refcount.
21644 		import core.memory;
21645 		GC.addRoot(cast(void*) dropTarget);
21646 
21647 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21648 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21649 
21650 		window.dropHandler = handler;
21651 	} else throw new NotYetImplementedException();
21652 }
21653 
21654 
21655 
21656 static if(UsingSimpledisplayX11) {
21657 
21658 enum _NET_WM_STATE_ADD = 1;
21659 enum _NET_WM_STATE_REMOVE = 0;
21660 enum _NET_WM_STATE_TOGGLE = 2;
21661 
21662 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21663 void demandAttention(SimpleWindow window, bool needs = true) {
21664 	demandAttention(window.impl.window, needs);
21665 }
21666 
21667 /// ditto
21668 void demandAttention(Window window, bool needs = true) {
21669 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21670 }
21671 
21672 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21673 	auto display = XDisplayConnection.get();
21674 	if(atom == None)
21675 		return; // non-failure error
21676 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21677 
21678 	XClientMessageEvent xclient;
21679 
21680 	xclient.type = EventType.ClientMessage;
21681 	xclient.window = window;
21682 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21683 	xclient.format = 32;
21684 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21685 	xclient.data.l[1] = atom;
21686 	xclient.data.l[2] = atom2;
21687 	xclient.data.l[3] = 1;
21688 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21689 
21690 	XSendEvent(
21691 		display,
21692 		RootWindow(display, DefaultScreen(display)),
21693 		false,
21694 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21695 		cast(XEvent*) &xclient
21696 	);
21697 
21698 	/+
21699 	XChangeProperty(
21700 		display,
21701 		window.impl.window,
21702 		GetAtom!"_NET_WM_STATE"(display),
21703 		XA_ATOM,
21704 		32 /* bits */,
21705 		PropModeAppend,
21706 		&atom,
21707 		1);
21708 	+/
21709 }
21710 
21711 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21712 	Atom actionAtom;
21713 	with(DragAndDropAction)
21714 	final switch(action) {
21715 		case none: actionAtom = None; break;
21716 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21717 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21718 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21719 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21720 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21721 	}
21722 
21723 	return actionAtom;
21724 }
21725 
21726 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21727 	// FIXME: I need to show user feedback somehow.
21728 	auto display = XDisplayConnection.get;
21729 
21730 	auto actionAtom = dndActionAtom(display, action);
21731 	assert(actionAtom, "Don't use action none to accept a drop");
21732 
21733 	setX11Selection!"XdndSelection"(window, handler, null);
21734 
21735 	auto oldKeyHandler = window.handleKeyEvent;
21736 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21737 
21738 	auto oldCharHandler = window.handleCharEvent;
21739 	scope(exit) window.handleCharEvent = oldCharHandler;
21740 
21741 	auto oldMouseHandler = window.handleMouseEvent;
21742 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21743 
21744 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21745 
21746 	import core.sys.posix.sys.time;
21747 	timeval tv;
21748 	gettimeofday(&tv, null);
21749 
21750 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21751 
21752 	Time lastMouseTimestamp;
21753 
21754 	bool dnding = true;
21755 	Window lastIn = None;
21756 
21757 	void leave() {
21758 		if(lastIn == None)
21759 			return;
21760 
21761 		XEvent ev;
21762 		ev.xclient.type = EventType.ClientMessage;
21763 		ev.xclient.window = lastIn;
21764 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21765 		ev.xclient.format = 32;
21766 		ev.xclient.data.l[0] = window.impl.window;
21767 
21768 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21769 		XFlush(display);
21770 
21771 		lastIn = None;
21772 	}
21773 
21774 	void enter(Window w) {
21775 		assert(lastIn == None);
21776 
21777 		lastIn = w;
21778 
21779 		XEvent ev;
21780 		ev.xclient.type = EventType.ClientMessage;
21781 		ev.xclient.window = lastIn;
21782 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21783 		ev.xclient.format = 32;
21784 		ev.xclient.data.l[0] = window.impl.window;
21785 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21786 
21787 		auto types = handler.availableFormats();
21788 		assert(types.length > 0);
21789 
21790 		ev.xclient.data.l[2] = types[0];
21791 		if(types.length > 1)
21792 			ev.xclient.data.l[3] = types[1];
21793 		if(types.length > 2)
21794 			ev.xclient.data.l[4] = types[2];
21795 
21796 		// FIXME: other types?!?!? and make sure we skip TARGETS
21797 
21798 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21799 		XFlush(display);
21800 	}
21801 
21802 	void position(int rootX, int rootY) {
21803 		assert(lastIn != None);
21804 
21805 		XEvent ev;
21806 		ev.xclient.type = EventType.ClientMessage;
21807 		ev.xclient.window = lastIn;
21808 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21809 		ev.xclient.format = 32;
21810 		ev.xclient.data.l[0] = window.impl.window;
21811 		ev.xclient.data.l[1] = 0; // reserved
21812 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21813 		ev.xclient.data.l[3] = dataTimestamp;
21814 		ev.xclient.data.l[4] = actionAtom;
21815 
21816 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21817 		XFlush(display);
21818 
21819 	}
21820 
21821 	void drop() {
21822 		XEvent ev;
21823 		ev.xclient.type = EventType.ClientMessage;
21824 		ev.xclient.window = lastIn;
21825 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21826 		ev.xclient.format = 32;
21827 		ev.xclient.data.l[0] = window.impl.window;
21828 		ev.xclient.data.l[1] = 0; // reserved
21829 		ev.xclient.data.l[2] = dataTimestamp;
21830 
21831 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21832 		XFlush(display);
21833 
21834 		lastIn = None;
21835 		dnding = false;
21836 	}
21837 
21838 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21839 	// but idk if i should...
21840 
21841 	window.setEventHandlers(
21842 		delegate(KeyEvent ev) {
21843 			if(ev.pressed == true && ev.key == Key.Escape) {
21844 				// cancel
21845 				dnding = false;
21846 			}
21847 		},
21848 		delegate(MouseEvent ev) {
21849 			if(ev.timestamp < lastMouseTimestamp)
21850 				return;
21851 
21852 			lastMouseTimestamp = ev.timestamp;
21853 
21854 			if(ev.type == MouseEventType.motion) {
21855 				auto display = XDisplayConnection.get;
21856 				auto root = RootWindow(display, DefaultScreen(display));
21857 
21858 				Window topWindow;
21859 				int rootX, rootY;
21860 
21861 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
21862 
21863 				if(topWindow == None)
21864 					return;
21865 
21866 				top:
21867 				if(auto result = topWindow in eligibility) {
21868 					auto dropWindow = *result;
21869 					if(dropWindow == None) {
21870 						leave();
21871 						return;
21872 					}
21873 
21874 					if(dropWindow != lastIn) {
21875 						leave();
21876 						enter(dropWindow);
21877 						position(rootX, rootY);
21878 					} else {
21879 						position(rootX, rootY);
21880 					}
21881 				} else {
21882 					// determine eligibility
21883 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
21884 					if(data.length == 1) {
21885 						// in case there is no WM or it isn't reparenting
21886 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
21887 					} else {
21888 
21889 						Window tryScanChildren(Window search, int maxRecurse) {
21890 							// could be reparenting window manager, so gotta check the next few children too
21891 							Window child;
21892 							int x;
21893 							int y;
21894 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
21895 
21896 							if(child == None)
21897 								return None;
21898 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
21899 							if(data.length == 1) {
21900 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
21901 							} else {
21902 								if(maxRecurse)
21903 									return tryScanChildren(child, maxRecurse - 1);
21904 								else
21905 									return None;
21906 							}
21907 
21908 						}
21909 
21910 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
21911 						auto topResult = tryScanChildren(topWindow, 3);
21912 						// it is easy to have a false negative due to the mouse going over a WM
21913 						// child window like the close button if separate from the frame... so I
21914 						// can't really cache negatives, :(
21915 						if(topResult != None) {
21916 							eligibility[topWindow] = topResult;
21917 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
21918 						}
21919 					}
21920 
21921 				}
21922 
21923 			} else if(ev.type == MouseEventType.buttonReleased) {
21924 				drop();
21925 				dnding = false;
21926 			}
21927 		}
21928 	);
21929 
21930 	window.grabInput();
21931 	scope(exit)
21932 		window.releaseInputGrab();
21933 
21934 
21935 	EventLoop.get.run(() => dnding);
21936 
21937 	return 0;
21938 }
21939 
21940 /// X-specific
21941 TrueColorImage getWindowNetWmIcon(Window window) {
21942 	try {
21943 		auto display = XDisplayConnection.get;
21944 
21945 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
21946 
21947 		if (data.length > arch_ulong.sizeof * 2) {
21948 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
21949 			// these are an array of rgba images that we have to convert into pixmaps ourself
21950 
21951 			int width = cast(int) meta[0];
21952 			int height = cast(int) meta[1];
21953 
21954 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
21955 
21956 			static if(arch_ulong.sizeof == 4) {
21957 				bytes = bytes[0 .. width * height * 4];
21958 				alias imageData = bytes;
21959 			} else static if(arch_ulong.sizeof == 8) {
21960 				bytes = bytes[0 .. width * height * 8];
21961 				auto imageData = new ubyte[](4 * width * height);
21962 			} else static assert(0);
21963 
21964 
21965 
21966 			// this returns ARGB. Remember it is little-endian so
21967 			//                                         we have BGRA
21968 			// our thing uses RGBA, which in little endian, is ABGR
21969 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
21970 				auto r = bytes[idx + 2];
21971 				auto g = bytes[idx + 1];
21972 				auto b = bytes[idx + 0];
21973 				auto a = bytes[idx + 3];
21974 
21975 				imageData[idx2 + 0] = r;
21976 				imageData[idx2 + 1] = g;
21977 				imageData[idx2 + 2] = b;
21978 				imageData[idx2 + 3] = a;
21979 			}
21980 
21981 			return new TrueColorImage(width, height, imageData);
21982 		}
21983 
21984 		return null;
21985 	} catch(Exception e) {
21986 		return null;
21987 	}
21988 }
21989 
21990 } /* UsingSimpledisplayX11 */
21991 
21992 
21993 void loadBinNameToWindowClassName () {
21994 	import core.stdc.stdlib : realloc;
21995 	version(linux) {
21996 		// args[0] MAY be empty, so we'll just use this
21997 		import core.sys.posix.unistd : readlink;
21998 		char[1024] ebuf = void; // 1KB should be enough for everyone!
21999 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
22000 		if (len < 1) return;
22001 	} else /*version(Windows)*/ {
22002 		import core.runtime : Runtime;
22003 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
22004 		auto ebuf = Runtime.args[0];
22005 		auto len = ebuf.length;
22006 	}
22007 	auto pos = len;
22008 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22009 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22010 	if (sdpyWindowClassStr is null) return; // oops
22011 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22012 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22013 }
22014 
22015 /++
22016 	An interface representing a font that is drawn with custom facilities.
22017 
22018 	You might want [OperatingSystemFont] instead, which represents
22019 	a font loaded and drawn by functions native to the operating system.
22020 
22021 	WARNING: I might still change this.
22022 +/
22023 interface DrawableFont : MeasurableFont {
22024 	/++
22025 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22026 
22027 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22028 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22029 		fill color, but that's up to the implementation.
22030 	+/
22031 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22032 
22033 	/++
22034 		Requests that the given string is added to the image cache. You should only do this rarely, but
22035 		if you have a string that you know will be used over and over again, adding it to a cache can
22036 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22037 		to implement this as a do-nothing method).
22038 	+/
22039 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22040 }
22041 
22042 /++
22043 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
22044 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
22045 
22046 	You should also consider [OperatingSystemFont], which loads and draws a font with
22047 	facilities native to the user's operating system. You might also consider
22048 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
22049 	of game, as they have their own ways to draw text too.
22050 
22051 	Be warned: this can be slow, especially on remote connections to the X server, since
22052 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
22053 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
22054 	experiment in your specific case.
22055 
22056 	Please note that the return type of [DrawableFont] also includes an implementation of
22057 	[MeasurableFont].
22058 +/
22059 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
22060 	import arsd.ttf;
22061 	static class ArsdTtfFont : DrawableFont {
22062 		TtfFont font;
22063 		int size;
22064 		this(in ubyte[] data, int size) {
22065 			font = TtfFont(data);
22066 			this.size = size;
22067 
22068 
22069 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
22070 			int ascent_, descent_, line_gap;
22071 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
22072 
22073 			int advance, lsb;
22074 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
22075 			xWidth = cast(int) (advance * scale);
22076 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
22077 			MWidth = cast(int) (advance * scale);
22078 		}
22079 
22080 		private int ascent_;
22081 		private int descent_;
22082 		private int xWidth;
22083 		private int MWidth;
22084 
22085 		bool isMonospace() {
22086 			return xWidth == MWidth;
22087 		}
22088 		int averageWidth() {
22089 			return xWidth;
22090 		}
22091 		int height() {
22092 			return size;
22093 		}
22094 		int ascent() {
22095 			return ascent_;
22096 		}
22097 		int descent() {
22098 			return descent_;
22099 		}
22100 
22101 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
22102 			int width, height;
22103 			font.getStringSize(s, size, width, height);
22104 			return width;
22105 		}
22106 
22107 
22108 
22109 		Sprite[string] cache;
22110 
22111 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
22112 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
22113 			cache[text] = sprite;
22114 		}
22115 
22116 		Image stringToImage(Color fg, Color bg, in char[] text) {
22117 			int width, height;
22118 			auto data = font.renderString(text, size, width, height);
22119 			auto image = new TrueColorImage(width, height);
22120 			int pos = 0;
22121 			foreach(y; 0 .. height)
22122 			foreach(x; 0 .. width) {
22123 				fg.a = data[0];
22124 				bg.a = 255;
22125 				auto color = alphaBlend(fg, bg);
22126 				image.imageData.bytes[pos++] = color.r;
22127 				image.imageData.bytes[pos++] = color.g;
22128 				image.imageData.bytes[pos++] = color.b;
22129 				image.imageData.bytes[pos++] = data[0];
22130 				data = data[1 .. $];
22131 			}
22132 			assert(data.length == 0);
22133 
22134 			return Image.fromMemoryImage(image);
22135 		}
22136 
22137 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
22138 			Sprite sprite = (text in cache) ? *(text in cache) : null;
22139 
22140 			auto fg = painter.impl._outlineColor;
22141 			auto bg = painter.impl._fillColor;
22142 
22143 			if(sprite !is null) {
22144 				auto w = cast(SimpleWindow) painter.window;
22145 				assert(w !is null);
22146 
22147 				sprite.drawAt(painter, upperLeft);
22148 			} else {
22149 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
22150 			}
22151 		}
22152 	}
22153 
22154 	return new ArsdTtfFont(data, size);
22155 }
22156 
22157 class NotYetImplementedException : Exception {
22158 	this(string file = __FILE__, size_t line = __LINE__) {
22159 		super("Not yet implemented", file, line);
22160 	}
22161 }
22162 
22163 ///
22164 __gshared bool librariesSuccessfullyLoaded = true;
22165 ///
22166 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
22167 
22168 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
22169 	mixin(staticForeachReplacement!Iface);
22170 
22171 	void loadDynamicLibrary() @nogc {
22172 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22173 	}
22174 
22175         void loadDynamicLibraryForReal() {
22176                 foreach(name; __traits(derivedMembers, Iface)) {
22177                         mixin("alias tmp = " ~ name ~ ";");
22178                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
22179                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
22180                 }
22181         }
22182 }
22183 
22184 private const(char)[] staticForeachReplacement(Iface)() pure {
22185 /*
22186 	// just this for gdc 9....
22187 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
22188 
22189         static foreach(name; __traits(derivedMembers, Iface))
22190                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22191 */
22192 
22193 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
22194 	size_t pos;
22195 
22196 	void append(in char[] what) {
22197 		if(pos + what.length > code.length)
22198 			code.length = (code.length * 3) / 2;
22199 		code[pos .. pos + what.length] = what[];
22200 		pos += what.length;
22201 	}
22202 
22203         foreach(name; __traits(derivedMembers, Iface)) {
22204                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
22205 		append(name);
22206 		append(`")) `);
22207 		append(name);
22208 		append(";");
22209 	}
22210 
22211 	return code[0 .. pos];
22212 }
22213 
22214 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22215 	mixin(staticForeachReplacement!Iface);
22216 
22217 	private __gshared void* libHandle;
22218 	private __gshared bool attempted;
22219 
22220         void loadDynamicLibrary() @nogc {
22221 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22222 	}
22223 
22224 	bool loadAttempted() {
22225 		return attempted;
22226 	}
22227 	bool loadSuccessful() {
22228 		return libHandle !is null;
22229 	}
22230 
22231         void loadDynamicLibraryForReal() {
22232 		attempted = true;
22233                 version(Posix) {
22234                         import core.sys.posix.dlfcn;
22235 			version(OSX) {
22236 				version(X11)
22237                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22238 				else
22239                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22240 			} else {
22241                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22242 				if(libHandle is null)
22243                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22244 			}
22245 
22246 			static void* loadsym(void* l, const char* name) {
22247 				import core.stdc.stdlib;
22248 				if(l is null)
22249 					return &abort;
22250 				return dlsym(l, name);
22251 			}
22252                 } else version(Windows) {
22253                         import core.sys.windows.winbase;
22254                         libHandle = LoadLibrary(library ~ ".dll");
22255 			static void* loadsym(void* l, const char* name) {
22256 				import core.stdc.stdlib;
22257 				if(l is null)
22258 					return &abort;
22259 				return GetProcAddress(l, name);
22260 			}
22261                 }
22262                 if(libHandle is null) {
22263 			success = false;
22264                         //throw new Exception("load failure of library " ~ library);
22265 		}
22266                 foreach(name; __traits(derivedMembers, Iface)) {
22267                         mixin("alias tmp = " ~ name ~ ";");
22268                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22269                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22270                 }
22271         }
22272 
22273         void unloadDynamicLibrary() {
22274                 version(Posix) {
22275                         import core.sys.posix.dlfcn;
22276                         dlclose(libHandle);
22277                 } else version(Windows) {
22278                         import core.sys.windows.winbase;
22279                         FreeLibrary(libHandle);
22280                 }
22281                 foreach(name; __traits(derivedMembers, Iface))
22282                         mixin(name ~ " = null;");
22283         }
22284 }
22285 
22286 /+
22287 	The GC can be called from any thread, and a lot of cleanup must be done
22288 	on the gui thread. Since the GC can interrupt any locks - including being
22289 	triggered inside a critical section - it is vital to avoid deadlocks to get
22290 	these functions called from the right place.
22291 
22292 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22293 	right now.
22294 
22295 	The cleanup function is run when the event loop gets around to it, which is just
22296 	whenever there's something there after it has been woken up for other work. It does
22297 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22298 	(Well actually it might be ok but i don't wanna mess with it right now.)
22299 +/
22300 private struct CleanupQueue {
22301 	import core.stdc.stdlib;
22302 
22303 	void queue(alias func, T...)(T args) {
22304 		static struct Args {
22305 			T args;
22306 		}
22307 		static struct RealJob {
22308 			Job j;
22309 			Args a;
22310 		}
22311 		static void call(Job* data) {
22312 			auto rj = cast(RealJob*) data;
22313 			func(rj.a.args);
22314 		}
22315 
22316 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22317 		thing.j.call = &call;
22318 		thing.a.args = args;
22319 
22320 		buffer[tail++] = cast(Job*) thing;
22321 
22322 		// FIXME: set overflowed
22323 	}
22324 
22325 	void process() {
22326 		const tail = this.tail;
22327 
22328 		while(tail != head) {
22329 			Job* job = cast(Job*) buffer[head++];
22330 			job.call(job);
22331 			free(job);
22332 		}
22333 
22334 		if(overflowed)
22335 			throw new Exception("cleanup overflowed");
22336 	}
22337 
22338 	private:
22339 
22340 	ubyte tail; // must ONLY be written by queue
22341 	ubyte head; // must ONLY be written by process
22342 	bool overflowed;
22343 
22344 	static struct Job {
22345 		void function(Job*) call;
22346 	}
22347 
22348 	void*[256] buffer;
22349 }
22350 private __gshared CleanupQueue cleanupQueue;
22351 
22352 version(X11)
22353 /++
22354 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22355 
22356 	$(WARNING
22357 		This function is exempted from stability guarantees.
22358 	)
22359 +/
22360 float customScalingFactorForMonitor(int monitorNumber) {
22361 	import core.stdc.stdlib;
22362 	auto val = getenv("ARSD_SCALING_FACTOR");
22363 
22364 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
22365 	if(val is null)
22366 		return 1.0;
22367 
22368 	char[16] buffer = 0;
22369 	int pos;
22370 
22371 	const(char)* at = val;
22372 
22373 	foreach(item; 0 .. monitorNumber + 1) {
22374 		if(*at == 0)
22375 			break; // reuse the last number when we at the end of the string
22376 		pos = 0;
22377 		while(pos + 1 < buffer.length && *at && *at != ';') {
22378 			buffer[pos++] = *at;
22379 			at++;
22380 		}
22381 		if(*at)
22382 			at++; // skip the semicolon
22383 		buffer[pos] = 0;
22384 	}
22385 
22386 	//sdpyPrintDebugString(buffer[0 .. pos]);
22387 
22388 	import core.stdc.math;
22389 	auto f = atof(buffer.ptr);
22390 
22391 	if(f <= 0.0 || isnan(f) || isinf(f))
22392 		return 1.0;
22393 
22394 	return f;
22395 }
22396 
22397 void guiAbortProcess(string msg) {
22398 	import core.stdc.stdlib;
22399 	version(Windows) {
22400 		WCharzBuffer t = WCharzBuffer(msg);
22401 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22402 	} else {
22403 		import core.stdc.stdio;
22404 		fwrite(msg.ptr, 1, msg.length, stderr);
22405 		msg = "\n";
22406 		fwrite(msg.ptr, 1, msg.length, stderr);
22407 		fflush(stderr);
22408 	}
22409 
22410 	abort();
22411 }
22412 
22413 private int minInternal(int a, int b) {
22414 	return (a < b) ? a : b;
22415 }
22416 
22417 private alias scriptable = arsd_jsvar_compatible;