1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 /+
4 	To share some stuff between two opengl threads:
5 	windows
6 	https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading
7 	linux
8 	https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux
9 +/
10 
11 
12 // Search for: FIXME: leaks if multithreaded gc
13 
14 // https://freedesktop.org/wiki/Specifications/XDND/
15 
16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
17 
18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
20 
21 
22 // on Mac with X11: -L-L/usr/X11/lib
23 
24 /+
25 
26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
27 
28 	Progress bar in taskbar
29 		- i can probably just set a property on the window...
30 		  it sets that prop to an integer 0 .. 100. Taskbar
31 		  deletes it or window deletes it when it is handled.
32 		- prolly display it as a nice little line at the bottom.
33 
34 
35 from gtk:
36 
37 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
38 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
39 
40 >+  if (cardinal > 0)
41 >+  {
42 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
45 >+                     XA_CARDINAL, 32,
46 >+                     PropModeReplace,
47 >+                     (guchar *) &cardinal, 1);
48 >+  }
49 >+  else
50 >+  {
51 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
52 >+                     xid,
53 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
54 >+  }
55 
56 from Windows:
57 
58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
59 
60 interface
61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
63 listen for msg, return TRUE
64 interface->SetProgressState(hwnd, TBPF_NORMAL);
65 interface->SetProgressValue(hwnd, 40, 100);
66 
67 
68 	My new notification system.
69 		- use a unix socket? or a x property? or a udp port?
70 		- could of course also get on the dbus train but ugh.
71 		- it could also reply with the info as a string for easy remote examination.
72 
73 +/
74 
75 /*
76 	Event Loop would be nices:
77 
78 	* add on idle - runs when nothing else happens
79 		* which can specify how long to yield for
80 	* send messages without a recipient window
81 	* setTimeout
82 	* setInterval
83 */
84 
85 /*
86 	Classic games I want to add:
87 		* my tetris clone
88 		* pac man
89 */
90 
91 /*
92 	Text layout needs a lot of work. Plain drawText is useful but too
93 	limited. It will need some kind of text context thing which it will
94 	update and you can pass it on and get more details out of it.
95 
96 	It will need a bounding box, a current cursor location that is updated
97 	as drawing continues, and various changable facts (which can also be
98 	changed on the painter i guess) like font, color, size, background,
99 	etc.
100 
101 	We can also fetch the caret location from it somehow.
102 
103 	Should prolly be an overload of drawText
104 
105 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
106 
107 		WS_EX_NOACTIVATE
108 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
109 		full screen windows. Can just set the atom on X. Windows will be harder.
110 
111 		moving windows. resizing windows.
112 
113 		hide cursor, capture cursor, change cursor.
114 
115 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
116 	sure the pieces are there to do its job easily and make other jobs possible.
117 */
118 
119 /++
120 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
121 	including creating windows, drawing on them, working with the clipboard,
122 	timers, OpenGL, and more. However, it does NOT provide high level GUI
123 	widgets. See my minigui.d, an extension to this module, for that
124 	functionality.
125 
126 	simpledisplay provides cross-platform wrapping for Windows and Linux
127 	(and perhaps other OSes that use X11), but also does not prevent you
128 	from using the underlying facilities if you need them. It has a goal
129 	of working efficiently over a remote X link (at least as far as Xlib
130 	reasonably allows.)
131 
132 	simpledisplay depends on [arsd.color|color.d], which should be available from the
133 	same place where you got this file. Other than that, however, it has
134 	very few dependencies and ones that don't come with the OS and/or the
135 	compiler are all opt-in.
136 
137 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
138 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
139 
140 	simpledisplay is basically stable. I plan to refactor the internals,
141 	and may add new features and fix bugs, but It do not expect to
142 	significantly change the API. It has been stable a few years already now.
143 
144 	Installation_instructions:
145 
146 	`simpledisplay.d` does not have any dependencies outside the
147 	operating system and `color.d`, so it should just work most the
148 	time, but there are a few caveats on some systems:
149 
150 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
151 	console to be automatically allocated.
152 
153 	Please note when compiling on Win64, you need to explicitly list
154 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
155 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
156 
157 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
158 	note the "w".
159 
160 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
161 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
162 	See [EnableWindowsSubsystem] for more information.
163 
164 	$(PITFALL
165 		With the Windows subsystem, there is no console, so standard writeln will throw!
166 		You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
167 		create a console as needed.
168 	)
169 
170 	On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command.
171 
172 	On Ubuntu, you might need to install X11 development libraries to
173 	successfully link.
174 
175 	$(CONSOLE
176 		$ sudo apt-get install libglc-dev
177 		$ sudo apt-get install libx11-dev
178 	)
179 
180 
181 	Jump_list:
182 
183 	Don't worry, you don't have to read this whole documentation file!
184 
185 	Check out the [#event-example] and [#Pong-example] to get started quickly.
186 
187 	The main classes you may want to create are [SimpleWindow], [Timer],
188 	[Image], and [Sprite].
189 
190 	The main functions you'll want are [setClipboardText] and [getClipboardText].
191 
192 	There are also platform-specific functions available such as [XDisplayConnection]
193 	and [GetAtom] for X11, among others.
194 
195 	See the examples and topics list below to learn more.
196 
197 	$(WARNING
198 		There should only be one GUI thread per application,
199 		and all windows should be created in it and your
200 		event loop should run there.
201 
202 		To do otherwise is undefined behavior and has no
203 		cross platform guarantees.
204 	)
205 
206 	$(H2 About this documentation)
207 
208 	The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow.
209 
210 	Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples!
211 
212 	All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them.
213 
214 	To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example.
215 
216 	If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file.
217 
218 	At points, I will talk about implementation details in the documentation. These are sometimes
219 	subject to change, but nevertheless useful to understand what is really going on. You can learn
220 	more about some of the referenced things by searching the web for info about using them from C.
221 	You can always look at the source of simpledisplay.d too for the most authoritative source on
222 	its specific implementation. If you disagree with how I did something, please contact me so we
223 	can discuss it!
224 
225 	$(H2 Using with fibers)
226 
227 	simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor).
228 
229 	$(H2 Topics)
230 
231 	$(H3 $(ID topic-windows) Windows)
232 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
233 		window on the user's screen.
234 
235 		You may create multiple windows, if the underlying platform supports it. You may check
236 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
237 		SimpleWindow's constructor at runtime to handle those cases.
238 
239 		A single running event loop will handle as many windows as needed.
240 
241 	$(H3 $(ID topic-event-loops) Event loops)
242 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
243 
244 		The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there:
245 
246 		---
247 		// dmd example.d simpledisplay.d color.d
248 		import arsd.simpledisplay;
249 		void main() {
250 			auto window = new SimpleWindow(200, 200);
251 			window.eventLoop(0,
252 			  delegate (dchar) { /* got a character key press */ }
253 			);
254 		}
255 		---
256 
257 		$(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.)
258 
259 		On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run.
260 
261 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
262 
263 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
264 
265 		You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway.
266 
267 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
268 		Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like.
269 
270 		See the [NotificationAreaIcon] class.
271 
272 	$(H3 $(ID topic-input-handling) Input handling)
273 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
274 
275 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
276 
277 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
278 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
279 
280 		Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example:
281 
282 		---
283 		// dmd example.d simpledisplay.d color.d
284 		import arsd.simpledisplay;
285 		void main() {
286 			auto window = new SimpleWindow(200, 200);
287 			{ // introduce sub-scope
288 				auto painter = window.draw(); // begin drawing
289 				/* draw here */
290 				painter.outlineColor = Color.red;
291 				painter.fillColor = Color.black;
292 				painter.drawRectangle(Point(0, 0), 200, 200);
293 			} // end scope, calling `painter`'s destructor, drawing to the screen.
294 			window.eventLoop(0); // handle events
295 		}
296 		---
297 
298 		Painting is done based on two color properties, a pen and a brush.
299 
300 		At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead.
301 
302 		FIXME Add example of 2d opengl drawing here.
303 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
304 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
305 
306 		Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it.
307 
308 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
309 
310 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
311 
312 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon].
313 
314 		simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program.
315 
316 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
317 
318 		---
319 		import arsd.simpledisplay;
320 
321 		void main() {
322 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
323 
324 			float otherColor = 0.0;
325 			float colorDelta = 0.05;
326 
327 			window.redrawOpenGlScene = delegate() {
328 				glLoadIdentity();
329 				glBegin(GL_QUADS);
330 
331 				glColor3f(1.0, otherColor, 0);
332 				glVertex3f(-0.8, -0.8, 0);
333 
334 				glColor3f(1.0, otherColor, 1.0);
335 				glVertex3f(0.8, -0.8, 0);
336 
337 				glColor3f(0, 1.0, otherColor);
338 				glVertex3f(0.8, 0.8, 0);
339 
340 				glColor3f(otherColor, 0, 1.0);
341 				glVertex3f(-0.8, 0.8, 0);
342 
343 				glEnd();
344 			};
345 
346 			window.eventLoop(50, () {
347 				otherColor += colorDelta;
348 				if(otherColor > 1.0) {
349 					otherColor = 1.0;
350 					colorDelta = -0.05;
351 				}
352 				if(otherColor < 0) {
353 					otherColor = 0;
354 					colorDelta = 0.05;
355 				}
356 				// at the end of the timer, we have to request a redraw
357 				// or we won't see the changes.
358 				window.redrawOpenGlSceneSoon();
359 			});
360 		}
361 		---
362 
363 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
364 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
365 		simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too.
366 
367 		This example program shows how you can set up a shader to draw a rectangle:
368 
369 		---
370 		module opengl3test;
371 		import arsd.simpledisplay;
372 
373 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
374 
375 		void main() {
376 			// First thing we do, before creating the window, is declare what version we want.
377 			setOpenGLContextVersion(3, 3);
378 			// turning off legacy compat is required to use version 3.3 and newer
379 			openGLContextCompatible = false;
380 
381 			uint VAO;
382 			OpenGlShader shader;
383 
384 			// then we can create the window.
385 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
386 
387 			// additional setup needs to be done when it is visible, simpledisplay offers a property
388 			// for exactly that:
389 			window.visibleForTheFirstTime = delegate() {
390 				// now with the window loaded, we can start loading the modern opengl functions.
391 
392 				// you MUST set the context first.
393 				window.setAsCurrentOpenGlContext;
394 				// then load the remainder of the library
395 				gl3.loadDynamicLibrary();
396 
397 				// now you can create the shaders, etc.
398 				shader = new OpenGlShader(
399 					OpenGlShader.Source(GL_VERTEX_SHADER, `
400 						#version 330 core
401 						layout (location = 0) in vec3 aPos;
402 						void main() {
403 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
404 						}
405 					`),
406 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
407 						#version 330 core
408 						out vec4 FragColor;
409 						uniform vec4 mycolor;
410 						void main() {
411 							FragColor = mycolor;
412 						}
413 					`),
414 				);
415 
416 				// and do whatever other setup you want.
417 
418 				float[] vertices = [
419 					0.5f,  0.5f, 0.0f,  // top right
420 					0.5f, -0.5f, 0.0f,  // bottom right
421 					-0.5f, -0.5f, 0.0f,  // bottom left
422 					-0.5f,  0.5f, 0.0f   // top left
423 				];
424 				uint[] indices = [  // note that we start from 0!
425 					0, 1, 3,  // first Triangle
426 					1, 2, 3   // second Triangle
427 				];
428 				uint VBO, EBO;
429 				glGenVertexArrays(1, &VAO);
430 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
431 				glBindVertexArray(VAO);
432 
433 				glGenBuffers(1, &VBO);
434 				glGenBuffers(1, &EBO);
435 
436 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
437 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
438 
439 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
440 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
441 
442 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
443 				glEnableVertexAttribArray(0);
444 
445 				// the library will set the initial viewport and trigger our first draw,
446 				// so these next two lines are NOT needed. they are just here as comments
447 				// to show what would happen next.
448 
449 				// glViewport(0, 0, window.width, window.height);
450 				// window.redrawOpenGlSceneNow();
451 			};
452 
453 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
454 			// it is our render method.
455 			window.redrawOpenGlScene = delegate() {
456 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
457 				glClear(GL_COLOR_BUFFER_BIT);
458 
459 				glUseProgram(shader.shaderProgram);
460 
461 				// the shader helper class has methods to set uniforms too
462 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
463 
464 				glBindVertexArray(VAO);
465 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
466 			};
467 
468 			window.eventLoop(0);
469 		}
470 		---
471 
472 	This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread.
473 
474 	$(H3 $(ID vulkan) Vulkan)
475 
476 	See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings:
477 
478 	https://github.com/adamdruppe/VulkanizeDSdpy
479 
480 	https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo
481 
482 	$(H3 $(ID topic-images) Displaying images)
483 		You can also load PNG images using [arsd.png].
484 
485 		---
486 		// dmd example.d simpledisplay.d color.d png.d
487 		import arsd.simpledisplay;
488 		import arsd.png;
489 
490 		void main() {
491 			auto image = Image.fromMemoryImage(readPng("image.png"));
492 			displayImage(image);
493 		}
494 		---
495 
496 		Compile with `dmd example.d simpledisplay.d png.d`.
497 
498 		If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel.
499 
500 	$(H3 $(ID topic-sprites) Sprites)
501 		The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link.
502 
503 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
504 
505 	$(H3 $(ID topic-clipboard) Clipboard)
506 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
507 
508 		It also has helpers for handling X-specific events.
509 
510 	$(H3 $(ID topic-dnd) Drag and Drop)
511 		See [enableDragAndDrop] and [draggable].
512 
513 	$(H3 $(ID topic-timers) Timers)
514 		There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer].
515 
516 		The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse.
517 
518 		---
519 			import arsd.simpledisplay;
520 
521 			void main() {
522 				auto window = new SimpleWindow(400, 400);
523 				// every 100 ms, it will draw a random line
524 				// on the window.
525 				window.eventLoop(100, {
526 					auto painter = window.draw();
527 
528 					import std.random;
529 					// random color
530 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
531 					// random line
532 					painter.drawLine(
533 						Point(uniform(0, window.width), uniform(0, window.height)),
534 						Point(uniform(0, window.width), uniform(0, window.height)));
535 
536 				});
537 			}
538 		---
539 
540 		The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish.
541 
542 		The pulse timer and instances of the [Timer] class may be combined at will.
543 
544 		---
545 			import arsd.simpledisplay;
546 
547 			void main() {
548 				auto window = new SimpleWindow(400, 400);
549 				auto timer = new Timer(1000, delegate {
550 					auto painter = window.draw();
551 					painter.clear();
552 				});
553 
554 				window.eventLoop(0);
555 			}
556 		---
557 
558 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
559 
560 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
561 		simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself.
562 
563 		See also: `xwindows.d` from my github.
564 
565 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
566 		`handleNativeEvent` and `handleNativeGlobalEvent`.
567 
568 	$(H3 $(ID topic-integration) Integration with other libraries)
569 		Integration with a third-party event loop is possible.
570 
571 		On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d.
572 
573 	$(H3 $(ID topic-guis) GUI widgets)
574 		simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you!
575 
576 		Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes.
577 
578 		Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.)
579 
580 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
581 
582 	$(H2 Platform-specific tips and tricks)
583 
584 	X_tips:
585 
586 	On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release.
587 
588 	Windows_tips:
589 
590 	You can add icons or manifest files to your exe using a resource file.
591 
592 	To create a Windows .ico file, use the gimp or something. I'll write a helper
593 	program later.
594 
595 	Create `yourapp.rc`:
596 
597 	```rc
598 		1 ICON filename.ico
599 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
600 	```
601 
602 	And `yourapp.exe.manifest`:
603 
604 	```xml
605 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
606 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
607 		<assemblyIdentity
608 		    version="1.0.0.0"
609 		    processorArchitecture="*"
610 		    name="CompanyName.ProductName.YourApplication"
611 		    type="win32"
612 		/>
613 		<description>Your application description here.</description>
614 		<dependency>
615 		    <dependentAssembly>
616 			<assemblyIdentity
617 			    type="win32"
618 			    name="Microsoft.Windows.Common-Controls"
619 			    version="6.0.0.0"
620 			    processorArchitecture="*"
621 			    publicKeyToken="6595b64144ccf1df"
622 			    language="*"
623 			/>
624 		    </dependentAssembly>
625 		</dependency>
626 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
627 			<windowsSettings>
628 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
629 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
630 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
631 				<!-- to render crisply in DPI-unaware contexts -->
632 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
633 			</windowsSettings>
634 		</application>
635 		</assembly>
636 	```
637 
638 	You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`.
639 
640 	Doing this lets you opt into various new things since Windows XP.
641 
642 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
643 
644 	$(H2 Tips)
645 
646 	$(H3 Name conflicts)
647 
648 	simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import:
649 
650 	---
651 	static import sdpy = arsd.simpledisplay;
652 	import arsd.simpledisplay : SimpleWindow;
653 
654 	void main() {
655 		auto window = new SimpleWindow();
656 		sdpy.EventLoop.get.run();
657 	}
658 	---
659 
660 	$(H2 $(ID developer-notes) Developer notes)
661 
662 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
663 	implementation though.
664 
665 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
666 	suck. If I was rewriting it, I wouldn't do it that way again.
667 
668 	This file must not have any more required dependencies. If you need bindings, add
669 	them right to this file. Once it gets into druntime and is there for a while, remove
670 	bindings from here to avoid conflicts (or put them in an appropriate version block
671 	so it continues to just work on old dmd), but wait a couple releases before making the
672 	transition so this module remains usable with older versions of dmd.
673 
674 	You may have optional dependencies if needed by putting them in version blocks or
675 	template functions. You may also extend the module with other modules with UFCS without
676 	actually editing this - that is nice to do if you can.
677 
678 	Try to make functions work the same way across operating systems. I typically make
679 	it thinly wrap Windows, then emulate that on Linux.
680 
681 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
682 	Phobos! So try to avoid it.
683 
684 	See more comments throughout the source.
685 
686 	I realize this file is fairly large, but over half that is just bindings at the bottom
687 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
688 	to understand. I suggest you jump around the source by looking for a particular
689 	declaration you're interested in, like `class SimpleWindow` using your editor's search
690 	function, then look at one piece at a time.
691 
692 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
693 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
694 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
695 
696 	I live in the eastern United States, so I will most likely not be around at night in
697 	that US east timezone.
698 
699 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
700 
701 	Building documentation: use my adrdox generator, `dub run adrdox`.
702 
703 	Examples:
704 
705 	$(DIV $(ID Event-example))
706 	$(H3 $(ID event-example) Event example)
707 	This program creates a window and draws events inside them as they
708 	happen, scrolling the text in the window as needed. Run this program
709 	and experiment to get a feel for where basic input events take place
710 	in the library.
711 
712 	---
713 	// dmd example.d simpledisplay.d color.d
714 	import arsd.simpledisplay;
715 	import std.conv;
716 
717 	void main() {
718 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
719 
720 		int y = 0;
721 
722 		void addLine(string text) {
723 			auto painter = window.draw();
724 
725 			if(y + painter.fontHeight >= window.height) {
726 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
727 				y -= painter.fontHeight;
728 			}
729 
730 			painter.outlineColor = Color.red;
731 			painter.fillColor = Color.black;
732 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
733 
734 			painter.outlineColor = Color.white;
735 
736 			painter.drawText(Point(10, y), text);
737 
738 			y += painter.fontHeight;
739 		}
740 
741 		window.eventLoop(1000,
742 		  () {
743 			addLine("Timer went off!");
744 		  },
745 		  (KeyEvent event) {
746 			addLine(to!string(event));
747 		  },
748 		  (MouseEvent event) {
749 			addLine(to!string(event));
750 		  },
751 		  (dchar ch) {
752 			addLine(to!string(ch));
753 		  }
754 		);
755 	}
756 	---
757 
758 	If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too.
759 
760 	$(COMMENT
761 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
762 
763 	---
764 
765 	---
766 	)
767 
768 	History:
769 		Initial release in April 2011.
770 
771 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
772 
773 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
774 +/
775 module arsd.simpledisplay;
776 
777 import arsd.core;
778 
779 // FIXME: tetris demo
780 // FIXME: space invaders demo
781 // FIXME: asteroids demo
782 
783 /++ $(ID Pong-example)
784 	$(H3 Pong)
785 
786 	This program creates a little Pong-like game. Player one is controlled
787 	with the keyboard.  Player two is controlled with the mouse. It demos
788 	the pulse timer, event handling, and some basic drawing.
789 +/
790 version(demos)
791 unittest {
792 	// dmd example.d simpledisplay.d color.d
793 	import arsd.simpledisplay;
794 
795 	enum paddleMovementSpeed = 8;
796 	enum paddleHeight = 48;
797 
798 	void main() {
799 		auto window = new SimpleWindow(600, 400, "Pong game!");
800 
801 		int playerOnePosition, playerTwoPosition;
802 		int playerOneMovement, playerTwoMovement;
803 		int playerOneScore, playerTwoScore;
804 
805 		int ballX, ballY;
806 		int ballDx, ballDy;
807 
808 		void serve() {
809 			import std.random;
810 
811 			ballX = window.width / 2;
812 			ballY = window.height / 2;
813 			ballDx = uniform(-4, 4) * 3;
814 			ballDy = uniform(-4, 4) * 3;
815 			if(ballDx == 0)
816 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
817 		}
818 
819 		serve();
820 
821 		window.eventLoop(50, // set a 50 ms timer pulls
822 			// This runs once per timer pulse
823 			delegate () {
824 				auto painter = window.draw();
825 
826 				painter.clear();
827 
828 				// Update everyone's motion
829 				playerOnePosition += playerOneMovement;
830 				playerTwoPosition += playerTwoMovement;
831 
832 				ballX += ballDx;
833 				ballY += ballDy;
834 
835 				// Bounce off the top and bottom edges of the window
836 				if(ballY + 7 >= window.height)
837 					ballDy = -ballDy;
838 				if(ballY - 8 <= 0)
839 					ballDy = -ballDy;
840 
841 				// Bounce off the paddle, if it is in position
842 				if(ballX - 8 <= 16) {
843 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
844 						ballDx = -ballDx + 1; // add some speed to keep it interesting
845 						ballDy += playerOneMovement; // and y movement based on your controls too
846 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
847 					} else {
848 						// Missed it
849 						playerTwoScore ++;
850 						serve();
851 					}
852 				}
853 
854 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
855 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
856 						ballDx = -ballDx - 1;
857 						ballDy += playerTwoMovement;
858 						ballX = window.width - 24;
859 					} else {
860 						// Missed it
861 						playerOneScore ++;
862 						serve();
863 					}
864 				}
865 
866 				// Draw the paddles
867 				painter.outlineColor = Color.black;
868 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
869 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
870 
871 				// Draw the ball
872 				painter.fillColor = Color.red;
873 				painter.outlineColor = Color.yellow;
874 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
875 
876 				// Draw the score
877 				painter.outlineColor = Color.blue;
878 				import std.conv;
879 				painter.drawText(Point(64, 4), to!string(playerOneScore));
880 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
881 
882 			},
883 			delegate (KeyEvent event) {
884 				// Player 1's controls are the arrow keys on the keyboard
885 				if(event.key == Key.Down)
886 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
887 				if(event.key == Key.Up)
888 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
889 
890 			},
891 			delegate (MouseEvent event) {
892 				// Player 2's controls are mouse movement while the left button is held down
893 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
894 					if(event.dy > 0)
895 						playerTwoMovement = paddleMovementSpeed;
896 					else if(event.dy < 0)
897 						playerTwoMovement = -paddleMovementSpeed;
898 				} else {
899 					playerTwoMovement = 0;
900 				}
901 			}
902 		);
903 	}
904 }
905 
906 /++ $(H3 $(ID example-minesweeper) Minesweeper)
907 
908 	This minesweeper demo shows how we can implement another classic
909 	game with simpledisplay and shows some mouse input and basic output
910 	code.
911 +/
912 version(demos)
913 unittest {
914 	import arsd.simpledisplay;
915 
916 	enum GameSquare {
917 		mine = 0,
918 		clear,
919 		m1, m2, m3, m4, m5, m6, m7, m8
920 	}
921 
922 	enum UserSquare {
923 		unknown,
924 		revealed,
925 		flagged,
926 		questioned
927 	}
928 
929 	enum GameState {
930 		inProgress,
931 		lose,
932 		win
933 	}
934 
935 	GameSquare[] board;
936 	UserSquare[] userState;
937 	GameState gameState;
938 	int boardWidth;
939 	int boardHeight;
940 
941 	bool isMine(int x, int y) {
942 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
943 			return false;
944 		return board[y * boardWidth + x] == GameSquare.mine;
945 	}
946 
947 	GameState reveal(int x, int y) {
948 		if(board[y * boardWidth + x] == GameSquare.clear) {
949 			floodFill(userState, boardWidth, boardHeight,
950 				UserSquare.unknown, UserSquare.revealed,
951 				x, y,
952 				(x, y) {
953 					if(board[y * boardWidth + x] == GameSquare.clear)
954 						return true;
955 					else {
956 						userState[y * boardWidth + x] = UserSquare.revealed;
957 						return false;
958 					}
959 				});
960 		} else {
961 			userState[y * boardWidth + x] = UserSquare.revealed;
962 			if(isMine(x, y))
963 				return GameState.lose;
964 		}
965 
966 		foreach(state; userState) {
967 			if(state == UserSquare.unknown || state == UserSquare.questioned)
968 				return GameState.inProgress;
969 		}
970 
971 		return GameState.win;
972 	}
973 
974 	void initializeBoard(int width, int height, int numberOfMines) {
975 		boardWidth = width;
976 		boardHeight = height;
977 		board.length = width * height;
978 
979 		userState.length = width * height;
980 		userState[] = UserSquare.unknown;
981 
982 		import std.algorithm, std.random, std.range;
983 
984 		board[] = GameSquare.clear;
985 
986 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
987 			board[minePosition] = GameSquare.mine;
988 
989 		int x;
990 		int y;
991 		foreach(idx, ref square; board) {
992 			if(square == GameSquare.clear) {
993 				int danger = 0;
994 				danger += isMine(x-1, y-1)?1:0;
995 				danger += isMine(x-1, y)?1:0;
996 				danger += isMine(x-1, y+1)?1:0;
997 				danger += isMine(x, y-1)?1:0;
998 				danger += isMine(x, y+1)?1:0;
999 				danger += isMine(x+1, y-1)?1:0;
1000 				danger += isMine(x+1, y)?1:0;
1001 				danger += isMine(x+1, y+1)?1:0;
1002 
1003 				square = cast(GameSquare) (danger + 1);
1004 			}
1005 
1006 			x++;
1007 			if(x == width) {
1008 				x = 0;
1009 				y++;
1010 			}
1011 		}
1012 	}
1013 
1014 	void redraw(SimpleWindow window) {
1015 		import std.conv;
1016 
1017 		auto painter = window.draw();
1018 
1019 		painter.clear();
1020 
1021 		final switch(gameState) with(GameState) {
1022 			case inProgress:
1023 				break;
1024 			case win:
1025 				painter.fillColor = Color.green;
1026 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1027 				return;
1028 			case lose:
1029 				painter.fillColor = Color.red;
1030 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1031 				return;
1032 		}
1033 
1034 		int x = 0;
1035 		int y = 0;
1036 
1037 		foreach(idx, square; board) {
1038 			auto state = userState[idx];
1039 
1040 			final switch(state) with(UserSquare) {
1041 				case unknown:
1042 					painter.outlineColor = Color.black;
1043 					painter.fillColor = Color(128,128,128);
1044 
1045 					painter.drawRectangle(
1046 						Point(x * 20, y * 20),
1047 						20, 20
1048 					);
1049 				break;
1050 				case revealed:
1051 					if(square == GameSquare.clear) {
1052 						painter.outlineColor = Color.white;
1053 						painter.fillColor = Color.white;
1054 
1055 						painter.drawRectangle(
1056 							Point(x * 20, y * 20),
1057 							20, 20
1058 						);
1059 					} else {
1060 						painter.outlineColor = Color.black;
1061 						painter.fillColor = Color.white;
1062 
1063 						painter.drawText(
1064 							Point(x * 20, y * 20),
1065 							to!string(square)[1..2],
1066 							Point(x * 20 + 20, y * 20 + 20),
1067 							TextAlignment.Center | TextAlignment.VerticalCenter);
1068 					}
1069 				break;
1070 				case flagged:
1071 					painter.outlineColor = Color.black;
1072 					painter.fillColor = Color.red;
1073 					painter.drawRectangle(
1074 						Point(x * 20, y * 20),
1075 						20, 20
1076 					);
1077 				break;
1078 				case questioned:
1079 					painter.outlineColor = Color.black;
1080 					painter.fillColor = Color.yellow;
1081 					painter.drawRectangle(
1082 						Point(x * 20, y * 20),
1083 						20, 20
1084 					);
1085 				break;
1086 			}
1087 
1088 			x++;
1089 			if(x == boardWidth) {
1090 				x = 0;
1091 				y++;
1092 			}
1093 		}
1094 
1095 	}
1096 
1097 	void main() {
1098 		auto window = new SimpleWindow(200, 200);
1099 
1100 		initializeBoard(10, 10, 10);
1101 
1102 		redraw(window);
1103 		window.eventLoop(0,
1104 			delegate (MouseEvent me) {
1105 				if(me.type != MouseEventType.buttonPressed)
1106 					return;
1107 				auto x = me.x / 20;
1108 				auto y = me.y / 20;
1109 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1110 					if(me.button == MouseButton.left) {
1111 						gameState = reveal(x, y);
1112 					} else {
1113 						userState[y*boardWidth+x] = UserSquare.flagged;
1114 					}
1115 					redraw(window);
1116 				}
1117 			}
1118 		);
1119 	}
1120 }
1121 
1122 import arsd.core;
1123 
1124 // FIXME: tetris demo
1125 // FIXME: space invaders demo
1126 // FIXME: asteroids demo
1127 
1128 version(OSX) version(DigitalMars) version=OSXCocoa;
1129 
1130 
1131 version(OSXCocoa) {
1132 	version=without_opengl;
1133 	version=allow_unimplemented_features;
1134 	// version=OSXCocoa;
1135 	// pragma(linkerDirective, "-framework Cocoa");
1136 }
1137 
1138 version(without_opengl) {
1139 	enum SdpyIsUsingIVGLBinds = false;
1140 } else /*version(Posix)*/ {
1141 	static if (__traits(compiles, (){import iv.glbinds;})) {
1142 		enum SdpyIsUsingIVGLBinds = true;
1143 		public import iv.glbinds;
1144 		//pragma(msg, "SDPY: using iv.glbinds");
1145 	} else {
1146 		enum SdpyIsUsingIVGLBinds = false;
1147 	}
1148 //} else {
1149 //	enum SdpyIsUsingIVGLBinds = false;
1150 }
1151 
1152 
1153 version(Windows) {
1154 	//import core.sys.windows.windows;
1155 	import core.sys.windows.winnls;
1156 	import core.sys.windows.windef;
1157 	import core.sys.windows.basetyps;
1158 	import core.sys.windows.winbase;
1159 	import core.sys.windows.winuser;
1160 	import core.sys.windows.shellapi;
1161 	import core.sys.windows.wingdi;
1162 	static import gdi = core.sys.windows.wingdi; // so i
1163 
1164 	pragma(lib, "gdi32");
1165 	pragma(lib, "user32");
1166 
1167 	// for AlphaBlend... a breaking change....
1168 	version(CRuntime_DigitalMars) { } else
1169 		pragma(lib, "msimg32");
1170 } else version (linux) {
1171 	//k8: this is hack for rdmd. sorry.
1172 	static import core.sys.linux.epoll;
1173 	static import core.sys.linux.timerfd;
1174 }
1175 
1176 
1177 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1178 
1179 // http://wiki.dlang.org/Simpledisplay.d
1180 
1181 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1182 
1183 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1184 // but can i control the scroll lock led
1185 
1186 
1187 // Note: if you are using Image on X, you might want to do:
1188 /*
1189 	static if(UsingSimpledisplayX11) {
1190 		if(!Image.impl.xshmAvailable) {
1191 			// the images will use the slower XPutImage, you might
1192 			// want to consider an alternative method to get better speed
1193 		}
1194 	}
1195 
1196 	If the shared memory extension is available though, simpledisplay uses it
1197 	for a significant speed boost whenever you draw large Images.
1198 */
1199 
1200 // CHANGE FROM LAST VERSION: the window background is no longer fixed, so you might want to fill the screen with a particular color before drawing.
1201 
1202 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1203 
1204 /*
1205 	Biggest FIXME:
1206 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1207 
1208 		clean up opengl contexts when their windows close
1209 
1210 		fix resizing the bitmaps/pixmaps
1211 */
1212 
1213 // BTW on Windows:
1214 // -L/SUBSYSTEM:WINDOWS:5.0
1215 // to dmd will make a nice windows binary w/o a console if you want that.
1216 
1217 /*
1218 	Stuff to add:
1219 
1220 	use multibyte functions everywhere we can
1221 
1222 	OpenGL windows
1223 	more event stuff
1224 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1225 
1226 
1227 	resizeEvent
1228 		and make the windows non-resizable by default,
1229 		or perhaps stretched (if I can find something in X like StretchBlt)
1230 
1231 	take a screenshot function!
1232 
1233 	Pens and brushes?
1234 	Maybe a global event loop?
1235 
1236 	Mouse deltas
1237 	Key items
1238 */
1239 
1240 /*
1241 From MSDN:
1242 
1243 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1244 
1245 Important  Do not use the LOWORD or HIWORD macros to extract the x- and y- coordinates of the cursor position because these macros return incorrect results on systems with multiple monitors. Systems with multiple monitors can have negative x- and y- coordinates, and LOWORD and HIWORD treat the coordinates as unsigned quantities.
1246 
1247 */
1248 
1249 version(linux) {
1250 	version = X11;
1251 	version(without_libnotify) {
1252 		// we cool
1253 	}
1254 	else
1255 		version = libnotify;
1256 }
1257 
1258 version(libnotify) {
1259 	pragma(lib, "dl");
1260 	import core.sys.posix.dlfcn;
1261 
1262 	void delegate()[int] libnotify_action_delegates;
1263 	int libnotify_action_delegates_count;
1264 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1265 		auto idx = cast(int) user_data;
1266 		if(auto dgptr = idx in libnotify_action_delegates) {
1267 			(*dgptr)();
1268 			libnotify_action_delegates.remove(idx);
1269 		}
1270 	}
1271 
1272 	struct C_DynamicLibrary {
1273 		void* handle;
1274 		this(string name) {
1275 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1276 			if(handle is null)
1277 				throw new Exception("dlopen");
1278 		}
1279 
1280 		void close() {
1281 			dlclose(handle);
1282 		}
1283 
1284 		~this() {
1285 			// close
1286 		}
1287 
1288 		// FIXME: this looks up by name every time....
1289 		template call(string func, Ret, Args...) {
1290 			extern(C) Ret function(Args) fptr;
1291 			typeof(fptr) call() {
1292 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1293 				return fptr;
1294 			}
1295 		}
1296 	}
1297 
1298 	C_DynamicLibrary* libnotify;
1299 }
1300 
1301 version(OSX) {
1302 	version(OSXCocoa) {}
1303 	else { version = X11; }
1304 }
1305 	//version = OSXCocoa; // this was written by KennyTM
1306 version(FreeBSD)
1307 	version = X11;
1308 version(Solaris)
1309 	version = X11;
1310 
1311 version(X11) {
1312 	version(without_xft) {}
1313 	else version=with_xft;
1314 }
1315 
1316 void featureNotImplemented()() {
1317 	version(allow_unimplemented_features)
1318 		throw new NotYetImplementedException();
1319 	else
1320 		static assert(0);
1321 }
1322 
1323 // these are so the static asserts don't trigger unless you want to
1324 // add support to it for an OS
1325 version(Windows)
1326 	version = with_timer;
1327 version(linux)
1328 	version = with_timer;
1329 version(OSXCocoa)
1330 	version = with_timer;
1331 
1332 version(with_timer)
1333 	enum bool SimpledisplayTimerAvailable = true;
1334 else
1335 	enum bool SimpledisplayTimerAvailable = false;
1336 
1337 /// If you have to get down and dirty with implementation details, this helps figure out if Windows is available you can `static if(UsingSimpledisplayWindows) ...` more reliably than `version()` because `version` is module-local.
1338 version(Windows)
1339 	enum bool UsingSimpledisplayWindows = true;
1340 else
1341 	enum bool UsingSimpledisplayWindows = false;
1342 
1343 /// If you have to get down and dirty with implementation details, this helps figure out if X is available you can `static if(UsingSimpledisplayX11) ...` more reliably than `version()` because `version` is module-local.
1344 version(X11)
1345 	enum bool UsingSimpledisplayX11 = true;
1346 else
1347 	enum bool UsingSimpledisplayX11 = false;
1348 
1349 /// If you have to get down and dirty with implementation details, this helps figure out if Cocoa is available you can `static if(UsingSimpledisplayCocoa) ...` more reliably than `version()` because `version` is module-local.
1350 version(OSXCocoa)
1351 	enum bool UsingSimpledisplayCocoa = true;
1352 else
1353 	enum bool UsingSimpledisplayCocoa = false;
1354 
1355 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1356 version(Windows)
1357 	enum multipleWindowsSupported = true;
1358 else version(X11)
1359 	enum multipleWindowsSupported = true;
1360 else version(OSXCocoa)
1361 	enum multipleWindowsSupported = true;
1362 else
1363 	static assert(0);
1364 
1365 version(without_opengl)
1366 	enum bool OpenGlEnabled = false;
1367 else
1368 	enum bool OpenGlEnabled = true;
1369 
1370 /++
1371 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1372 	If you mix this in above your `main` function, you no longer need to use the linker
1373 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1374 
1375 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1376 
1377 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1378 	stderr writeln. It will fail and throw an exception.
1379 
1380 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1381 
1382 	History:
1383 		Added November 24, 2021 (dub v10.4)
1384 +/
1385 mixin template EnableWindowsSubsystem() {
1386 	version(Windows)
1387 	version(CRuntime_Microsoft) {
1388 		pragma(linkerDirective, "/subsystem:windows");
1389 		version(LDC)
1390 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1391 		else
1392 			pragma(linkerDirective, "/entry:mainCRTStartup");
1393 	}
1394 }
1395 
1396 
1397 /++
1398 	After selecting a type from [WindowTypes], you may further customize
1399 	its behavior by setting one or more of these flags.
1400 
1401 
1402 	The different window types have different meanings of `normal`. If the
1403 	window type already is a good match for what you want to do, you should
1404 	just use [WindowFlags.normal], the default, which will do the right thing
1405 	for your users.
1406 
1407 	The window flags will not always be honored by the operating system
1408 	and window managers; they are hints, not commands.
1409 +/
1410 enum WindowFlags : int {
1411 	normal = 0, ///
1412 	skipTaskbar = 1, ///
1413 	alwaysOnTop = 2, ///
1414 	alwaysOnBottom = 4, ///
1415 	cannotBeActivated = 8, ///
1416 	alwaysRequestMouseMotionEvents = 16, /// By default, simpledisplay will attempt to optimize mouse motion event reporting when it detects a remote connection, causing them to only be issued if input is grabbed (see: [SimpleWindow.grabInput]). This means doing hover effects and mouse game control on a remote X connection may not work right. Include this flag to override this optimization and always request the motion events. However btw, if you are doing mouse game control, you probably want to grab input anyway, and hover events are usually expendable! So think before you use this flag.
1417 	extraComposite = 32, /// On windows this will make this a layered windows (not supported for child windows before windows 8) to support transparency and improve animation performance.
1418 	/++
1419 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1420 		it is still a top-level window. This should NOT be set separately for most window types.
1421 
1422 		A transient window will not keep the application open if its main window closes.
1423 
1424 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1425 
1426 
1427 		From the ICCM:
1428 
1429 		$(BLOCKQUOTE
1430 			It is important not to confuse WM_TRANSIENT_FOR with override-redirect. WM_TRANSIENT_FOR should be used in those cases where the pointer is not grabbed while the window is mapped (in other words, if other windows are allowed to be active while the transient is up). If other windows must be prevented from processing input (for example, when implementing pop-up menus), use override-redirect and grab the pointer while the window is mapped.
1431 
1432 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1433 		)
1434 
1435 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1436 
1437 		History:
1438 			Added February 23, 2021 but not yet stabilized.
1439 	+/
1440 	transient = 64,
1441 	/++
1442 		This indicates that the window manages its own platform-specific child window input focus. You must use a delegate, [SimpleWindow.setRequestedInputFocus], to set the input when requested. This delegate returns the handle to the window that should receive the focus.
1443 
1444 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1445 
1446 		History:
1447 			Added April 1, 2022
1448 	+/
1449 	managesChildWindowFocus = 128,
1450 
1451 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1452 }
1453 
1454 /++
1455 	When creating a window, you can pass a type to SimpleWindow's constructor,
1456 	then further customize the window by changing `WindowFlags`.
1457 
1458 
1459 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1460 	use. The others are there to build a foundation for a higher level GUI toolkit,
1461 	but are themselves not as high level as you might think from their names.
1462 
1463 	This list is based on the EMWH spec for X11.
1464 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1465 +/
1466 enum WindowTypes : int {
1467 	/// An ordinary application window.
1468 	normal,
1469 	/// A generic window without a title bar or border. You can draw on the entire area of the screen it takes up and use it as you wish. Remember that users don't really expect these though, so don't use it where a window of any other type is appropriate.
1470 	undecorated,
1471 	/// A window that doesn't actually display on screen. You can use it for cases where you need a dummy window handle to communicate with or something.
1472 	eventOnly,
1473 	/// A drop down menu, such as from a menu bar
1474 	dropdownMenu,
1475 	/// A popup menu, such as from a right click
1476 	popupMenu,
1477 	/// A popup bubble notification
1478 	notification,
1479 	/*
1480 	menu, /// a tearable menu bar
1481 	splashScreen, /// a loading splash screen for your application
1482 	tooltip, /// A tiny window showing temporary help text or something.
1483 	comboBoxDropdown,
1484 	dialog,
1485 	toolbar
1486 	*/
1487 	/// a child nested inside the parent. You must pass a parent window to the ctor
1488 	nestedChild,
1489 
1490 	/++
1491 		The type you get when you pass in an existing browser handle, which means most
1492 		of simpledisplay's fancy things will not be done since they were never set up.
1493 
1494 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1495 		failure; you should use the existing handle constructor.
1496 
1497 		History:
1498 			Added November 17, 2022 (previously it would have type `normal`)
1499 	+/
1500 	minimallyWrapped
1501 }
1502 
1503 
1504 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1505 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1506 private __gshared char* sdpyWindowClassStr = null;
1507 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1508 
1509 /**
1510 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1511 	You may want to change context version if you want to use advanced shaders or
1512 	other modern OpenGL techinques. This setting doesn't affect already created
1513 	windows. You may use version 2.1 as your default, which should be supported
1514 	by any box since 2006, so seems to be a reasonable choice.
1515 
1516 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1517 	old context creation code without any version specified. This is the safest
1518 	way to init OpenGL, but it may not give you access to advanced features.
1519 
1520 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1521 */
1522 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1523 
1524 /**
1525 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1526 	pipeline functions, and without "compatible" mode you won't be able to use
1527 	your old non-shader-based code with such contexts. By default SimpleDisplay
1528 	creates compatible context, so you can gradually upgrade your OpenGL code if
1529 	you want to (or leave it as is, as it should "just work").
1530 */
1531 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1532 
1533 /**
1534 	Set to `true` to allow creating OpenGL context with lower version than requested
1535 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1536 	`openGLContextFallbackActivated()` will return `true`.
1537 	*/
1538 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1539 
1540 /**
1541 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1542 	*/
1543 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1544 
1545 /++
1546 	History:
1547 		Added April 24, 2023  (dub v11.0)
1548 +/
1549 version(without_opengl) {} else
1550 auto openGLCurrentContext() {
1551 	version(Windows)
1552 		return wglGetCurrentContext();
1553 	else
1554 		return glXGetCurrentContext();
1555 }
1556 
1557 
1558 /**
1559 	Set window class name for all following `new SimpleWindow()` calls.
1560 
1561 	WARNING! For Windows, you should set your class name before creating any
1562 	window, and NEVER change it after that!
1563 */
1564 void sdpyWindowClass (const(char)[] v) {
1565 	import core.stdc.stdlib : realloc;
1566 	if (v.length == 0) v = "SimpleDisplayWindow";
1567 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1568 	if (sdpyWindowClassStr is null) return; // oops
1569 	sdpyWindowClassStr[0..v.length+1] = 0;
1570 	sdpyWindowClassStr[0..v.length] = v[];
1571 }
1572 
1573 /**
1574 	Get current window class name.
1575 */
1576 string sdpyWindowClass () {
1577 	if (sdpyWindowClassStr is null) return null;
1578 	foreach (immutable idx; 0..size_t.max-1) {
1579 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1580 	}
1581 	return null;
1582 }
1583 
1584 /++
1585 	Returns the logical DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. This isn't necessarily related to the physical side of the screen; it is associated with a user-defined scaling factor.
1586 
1587 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1588 +/
1589 float[2] getDpi() {
1590 	float[2] dpi;
1591 	version(Windows) {
1592 		HDC screen = GetDC(null);
1593 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1594 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1595 	} else version(X11) {
1596 		auto display = XDisplayConnection.get;
1597 		auto screen = DefaultScreen(display);
1598 
1599 		void fallback() {
1600 			/+
1601 			// 25.4 millimeters in an inch...
1602 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1603 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1604 			+/
1605 
1606 			// the physical size isn't actually as important as the logical size since this is
1607 			// all about scaling really
1608 			dpi[0] = 96;
1609 			dpi[1] = 96;
1610 		}
1611 
1612 		auto xft = getXftDpi();
1613 		if(xft is float.init)
1614 			fallback();
1615 		else {
1616 			dpi[0] = xft;
1617 			dpi[1] = xft;
1618 		}
1619 	}
1620 
1621 	return dpi;
1622 }
1623 
1624 version(X11)
1625 float getXftDpi() {
1626 	auto display = XDisplayConnection.get;
1627 
1628 	char* resourceString = XResourceManagerString(display);
1629 	XrmInitialize();
1630 
1631 	if (resourceString) {
1632 		auto db = XrmGetStringDatabase(resourceString);
1633 		XrmValue value;
1634 		char* type;
1635 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1636 			if (value.addr) {
1637 				import core.stdc.stdlib;
1638 				return atof(cast(char*) value.addr);
1639 			}
1640 		}
1641 	}
1642 
1643 	return float.init;
1644 }
1645 
1646 /++
1647 	Implementation used by [SimpleWindow.takeScreenshot].
1648 
1649 	Params:
1650 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1651 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1652 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1653 		x = the x-offset of the image to capture, from the left.
1654 		y = the y-offset of the image to capture, from the top.
1655 
1656 	History:
1657 		Added on March 14, 2021
1658 
1659 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1660 
1661 +/
1662 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1663 	TrueColorImage got;
1664 	version(X11) {
1665 		auto display = XDisplayConnection.get;
1666 		if(handle == 0)
1667 			handle = RootWindow(display, DefaultScreen(display));
1668 
1669 		if(width == 0 || height == 0) {
1670 			Window root;
1671 			int xpos, ypos;
1672 			uint widthret, heightret, borderret, depthret;
1673 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1674 
1675 			if(width == 0)
1676 				width = widthret;
1677 			if(height == 0)
1678 				height = heightret;
1679 		}
1680 
1681 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1682 
1683 		// https://github.com/adamdruppe/arsd/issues/98
1684 
1685 		auto i = new Image(image);
1686 		got = i.toTrueColorImage();
1687 
1688 		XDestroyImage(image);
1689 	} else version(Windows) {
1690 		auto hdc = GetDC(handle);
1691 		scope(exit) ReleaseDC(handle, hdc);
1692 
1693 		if(width == 0 || height == 0) {
1694 			BITMAP bmHeader;
1695 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1696 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1697 			if(width == 0)
1698 				width = bmHeader.bmWidth;
1699 			if(height == 0)
1700 				height = bmHeader.bmHeight;
1701 		}
1702 
1703 		auto i = new Image(width, height);
1704 		HDC hdcMem = CreateCompatibleDC(hdc);
1705 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1706 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1707 		SelectObject(hdcMem, hbmOld);
1708 		DeleteDC(hdcMem);
1709 
1710 		got = i.toTrueColorImage();
1711 	} else featureNotImplemented();
1712 
1713 	return got;
1714 }
1715 
1716 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1717 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1718 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1719 
1720 version(Windows)
1721 shared static this() {
1722 	auto lib = LoadLibrary("User32.dll");
1723 	if(lib is null)
1724 		return;
1725 	//scope(exit)
1726 		//FreeLibrary(lib);
1727 
1728 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1729 
1730 	if(SetProcessDpiAwarenessContext is null)
1731 		return;
1732 
1733 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1734 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1735 		//writeln(GetLastError());
1736 	}
1737 
1738 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1739 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1740 }
1741 
1742 /++
1743 	Blocking mode for event loop calls associated with a window instance.
1744 
1745 	History:
1746 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1747 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1748 		is, all would block until the application quit.
1749 
1750 		That behavior can still be achieved here with `untilApplicationQuits`,
1751 		or explicitly calling the top-level `EventLoop.get.run` function.
1752 +/
1753 enum BlockingMode {
1754 	/++
1755 		The event loop call will block until the whole application is ready
1756 		to quit if it is the only one running, but if it is nested inside
1757 		another one, it will only block until the window you're calling it on
1758 		closes.
1759 	+/
1760 	automatic             = 0x00,
1761 	/++
1762 		The event loop call will only return when the whole application
1763 		is ready to quit. This usually means all windows have been closed.
1764 
1765 		This is appropriate for your main application event loop.
1766 	+/
1767 	untilApplicationQuits = 0x01,
1768 	/++
1769 		The event loop will return when the window you're calling it on
1770 		closes. If there are other windows still open, they may be destroyed
1771 		unless you have another event loop running later.
1772 
1773 		This might be appropriate for a modal dialog box loop. Remember that
1774 		other windows are still processing input though, so you can end up
1775 		with a lengthy call stack if this happens in a loop, similar to a
1776 		recursive function (well, it literally is a recursive function, just
1777 		not an obvious looking one).
1778 	+/
1779 	untilWindowCloses     = 0x02,
1780 	/++
1781 		If an event loop is already running, this call will immediately
1782 		return, allowing the existing loop to handle it. If not, this call
1783 		will block until the condition you bitwise-or into the flag.
1784 
1785 		The default is to block until the application quits, same as with
1786 		the `automatic` setting (since if it were nested, which triggers until
1787 		window closes in automatic, this flag would instead not block at all),
1788 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1789 		it will only nest until the window closes. You might want that if you are
1790 		going to open two windows simultaneously and want closing just one of them
1791 		to trigger the event loop return.
1792 	+/
1793 	onlyIfNotNested       = 0x10,
1794 }
1795 
1796 /++
1797 	The flagship window class.
1798 
1799 
1800 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1801 	out of more advanced or complex features of the underlying windowing system.
1802 
1803 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1804 	and get a suitable window to work with.
1805 
1806 	From there, you can opt into additional features, like custom resizability and OpenGL support
1807 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1808 	and customization flags with the final two constructor arguments.
1809 
1810 	If none of that works for you, you can also create a window using native function calls, then
1811 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1812 	though, if you do this, managing the window is still your own responsibility! Notably, you
1813 	will need to destroy it yourself.
1814 +/
1815 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1816 
1817 	/++
1818 		Copies the window's current state into a [TrueColorImage].
1819 
1820 		Be warned: this can be a very slow operation
1821 
1822 		History:
1823 			Actually implemented on March 14, 2021
1824 	+/
1825 	TrueColorImage takeScreenshot() {
1826 		version(Windows)
1827 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1828 		else version(OSXCocoa)
1829 			throw new NotYetImplementedException();
1830 		else
1831 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
1832 	}
1833 
1834 	/++
1835 		Returns the actual logical DPI for the window on its current display monitor. If the window
1836 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1837 
1838 		Please note this function may return zero if it doesn't know the answer!
1839 
1840 
1841 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1842 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1843 
1844 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1845 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1846 		window primarily resides on by checking the center point of the window against the monitor map.
1847 
1848 		Returns:
1849 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1850 			assumes the X and Y dpi are the same.
1851 
1852 		History:
1853 			Added November 26, 2021 (dub v10.4)
1854 
1855 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1856 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1857 			that.
1858 
1859 		Bugs:
1860 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1861 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1862 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1863 			and 1.5 on the secondary monitor.
1864 
1865 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1866 			is a historical misnomer - the real thing of interest is the scale factor and due to
1867 			compatibility concerns the scale would modify dpi values to trick applications. But since
1868 			that's the terminology common out there, I used it too.
1869 
1870 		See_Also:
1871 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1872 			as this since the window many be on a different monitor, but it is a reasonable fallback
1873 			to use if `actualDpi` returns 0.
1874 
1875 			[onDpiChanged] is changed when `actualDpi` has changed.
1876 	+/
1877 	int actualDpi() {
1878 		version(X11) bool useFallbackDpi = false;
1879 		if(!actualDpiLoadAttempted) {
1880 			// FIXME: do the actual monitor we are on
1881 			// and on X this is a good chance to load the monitor map.
1882 			version(Windows) {
1883 				if(GetDpiForWindow)
1884 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1885 			} else version(X11) {
1886 				if(!xRandrInfoLoadAttemped) {
1887 					xRandrInfoLoadAttemped = true;
1888 					if(!XRandrLibrary.attempted) {
1889 						XRandrLibrary.loadDynamicLibrary();
1890 					}
1891 
1892 					if(XRandrLibrary.loadSuccessful) {
1893 						auto display = XDisplayConnection.get;
1894 						int scratch;
1895 						int major, minor;
1896 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1897 							goto fallback;
1898 
1899 						XRRQueryVersion(display, &major, &minor);
1900 						if(major <= 1 && minor < 5)
1901 							goto fallback;
1902 
1903 						int count;
1904 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1905 						if(monitors is null)
1906 							goto fallback;
1907 						scope(exit) XRRFreeMonitors(monitors);
1908 
1909 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1910 						MonitorInfo.info.assumeSafeAppend();
1911 						foreach(idx, monitor; monitors[0 .. count]) {
1912 							MonitorInfo.info ~= MonitorInfo(
1913 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1914 								Size(monitor.mwidth, monitor.mheight),
1915 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1916 							);
1917 
1918 							/+
1919 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1920 							// unknown physical size, just guess 96 to avoid divide by zero
1921 							MonitorInfo.info ~= MonitorInfo(
1922 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1923 								Size(monitor.mwidth, monitor.mheight),
1924 								96
1925 							);
1926 							else
1927 							// and actual thing
1928 							MonitorInfo.info ~= MonitorInfo(
1929 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1930 								Size(monitor.mwidth, monitor.mheight),
1931 								minInternal(
1932 									// millimeter to int then rounding up.
1933 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1934 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1935 								)
1936 							);
1937 							+/
1938 						}
1939 					// writeln("Here", MonitorInfo.info);
1940 					}
1941 				}
1942 
1943 				if(XRandrLibrary.loadSuccessful) {
1944 					updateActualDpi(true);
1945 					// writeln("updated");
1946 
1947 					if(!requestedInput) {
1948 						// this is what requests live updates should the configuration change
1949 						// each time you select input, it sends an initial event, so very important
1950 						// to not get into a loop of selecting input, getting event, updating data,
1951 						// and reselecting input...
1952 						requestedInput = true;
1953 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1954 						// writeln("requested input");
1955 					}
1956 				} else {
1957 					fallback:
1958 					// make sure we disable events that aren't coming
1959 					xrrEventBase = -1;
1960 					// best guess... respect the custom scaling user command to some extent at least though
1961 					useFallbackDpi = true;
1962 				}
1963 			} else version(OSXCocoa) {
1964 				actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME
1965 			}
1966 			actualDpiLoadAttempted = true;
1967 		} else version(X11) if(MonitorInfo.info.length == 0) {
1968 			useFallbackDpi = true;
1969 		}
1970 
1971 		version(X11)
1972 		if(useFallbackDpi)
1973 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1974 
1975 		return actualDpi_;
1976 	}
1977 
1978 	private int actualDpi_;
1979 	private bool actualDpiLoadAttempted;
1980 
1981 	version(X11) private {
1982 		bool requestedInput;
1983 		static bool xRandrInfoLoadAttemped;
1984 		struct MonitorInfo {
1985 			Rectangle position;
1986 			Size size;
1987 			int dpi;
1988 
1989 			static MonitorInfo[] info;
1990 		}
1991 		bool screenPositionKnown;
1992 		int screenPositionX;
1993 		int screenPositionY;
1994 		void updateActualDpi(bool loadingNow = false) {
1995 			if(!loadingNow && !actualDpiLoadAttempted)
1996 				actualDpi(); // just to make it do the load
1997 			foreach(idx, m; MonitorInfo.info) {
1998 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1999 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
2000 					actualDpi_ = m.dpi;
2001 					// writeln("monitor ", idx);
2002 					if(changed && onDpiChanged)
2003 						onDpiChanged();
2004 					break;
2005 				}
2006 			}
2007 		}
2008 	}
2009 
2010 	/++
2011 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2012 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2013 
2014 		History:
2015 			Added November 26, 2021 (dub v10.4)
2016 
2017 		See_Also:
2018 			[actualDpi]
2019 	+/
2020 	void delegate() onDpiChanged;
2021 
2022 	version(X11) {
2023 		void recreateAfterDisconnect() {
2024 			if(!stateDiscarded) return;
2025 
2026 			if(_parent !is null && _parent.stateDiscarded)
2027 				_parent.recreateAfterDisconnect();
2028 
2029 			bool wasHidden = hidden;
2030 
2031 			activeScreenPainter = null; // should already be done but just to confirm
2032 
2033 			actualDpi_ = 0;
2034 			actualDpiLoadAttempted = false;
2035 			xRandrInfoLoadAttemped = false;
2036 
2037 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2038 
2039 			if(auto dh = dropHandler) {
2040 				dropHandler = null;
2041 				enableDragAndDrop(this, dh);
2042 			}
2043 
2044 			if(recreateAdditionalConnectionState)
2045 				recreateAdditionalConnectionState();
2046 
2047 			hidden = wasHidden;
2048 			stateDiscarded = false;
2049 		}
2050 
2051 		bool stateDiscarded;
2052 		void discardConnectionState() {
2053 			if(XDisplayConnection.display)
2054 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2055 			if(discardAdditionalConnectionState)
2056 				discardAdditionalConnectionState();
2057 			stateDiscarded = true;
2058 		}
2059 
2060 		void delegate() discardAdditionalConnectionState;
2061 		void delegate() recreateAdditionalConnectionState;
2062 
2063 	}
2064 
2065 	private DropHandler dropHandler;
2066 
2067 	SimpleWindow _parent;
2068 	bool beingOpenKeepsAppOpen = true;
2069 	/++
2070 		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.
2071 
2072 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2073 
2074 		Params:
2075 
2076 		width = the width of the window's client area, in pixels
2077 		height = the height of the window's client area, in pixels
2078 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2079 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2080 		resizable = [Resizability] has three options:
2081 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2082 			$(P `fixedSize` will not allow the user to resize the window.)
2083 			$(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.)
2084 		windowType = The type of window you want to make.
2085 		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.
2086 		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".
2087 	+/
2088 	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) {
2089 		claimGuiThread();
2090 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2091 		this._width = this._virtualWidth = width;
2092 		this._height = this._virtualHeight = height;
2093 		this.openglMode = opengl;
2094 		version(X11) {
2095 			// auto scale not implemented except with opengl and even there it is kinda weird
2096 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2097 				resizable = Resizability.fixedSize;
2098 		}
2099 		this.resizability = resizable;
2100 		this.windowType = windowType;
2101 		this.customizationFlags = customizationFlags;
2102 		this._title = (title is null ? "D Application" : title);
2103 		this._parent = parent;
2104 		impl.createWindow(width, height, this._title, opengl, parent);
2105 
2106 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2107 			beingOpenKeepsAppOpen = false;
2108 	}
2109 
2110 	/// ditto
2111 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2112 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2113 	}
2114 
2115 	/// Same as above, except using the `Size` struct instead of separate width and height.
2116 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2117 		this(size.width, size.height, title, opengl, resizable);
2118 	}
2119 
2120 	/// ditto
2121 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2122 		this(size, title, opengl, resizable);
2123 	}
2124 
2125 
2126 	/++
2127 		Creates a window based on the given [Image]. It's client area
2128 		width and height is equal to the image. (A window's client area
2129 		is the drawable space inside; it excludes the title bar, etc.)
2130 
2131 		Windows based on images will not be resizable and do not use OpenGL.
2132 
2133 		It will draw the image in upon creation, but this will be overwritten
2134 		upon any draws, including the initial window visible event.
2135 
2136 		You probably do not want to use this and it may be removed from
2137 		the library eventually, or I might change it to be a "permanent"
2138 		background image; one that is automatically drawn on it before any
2139 		other drawing event. idk.
2140 	+/
2141 	this(Image image, string title = null) {
2142 		this(image.width, image.height, title);
2143 		this.image = image;
2144 	}
2145 
2146 	/++
2147 		Wraps a native window handle with very little additional processing - notably no destruction
2148 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2149 		windows created through the low level API (so you can use platform-specific options and
2150 		other details SimpleWindow does not expose) available to the event loop wrappers.
2151 	+/
2152 	this(NativeWindowHandle nativeWindow) {
2153 		windowType = WindowTypes.minimallyWrapped;
2154 		version(Windows)
2155 			impl.hwnd = nativeWindow;
2156 		else version(X11) {
2157 			impl.window = nativeWindow;
2158 			if(nativeWindow)
2159 				display = XDisplayConnection.get(); // get initial display to not segfault
2160 		} else version(OSXCocoa) {
2161 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2162 		} else featureNotImplemented();
2163 		// FIXME: set the size correctly
2164 		_width = 1;
2165 		_height = 1;
2166 		if(nativeWindow)
2167 			nativeMapping[cast(void*) nativeWindow] = this;
2168 
2169 		beingOpenKeepsAppOpen = false;
2170 
2171 		if(nativeWindow)
2172 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2173 		_suppressDestruction = true; // so it doesn't try to close
2174 	}
2175 
2176 	/++
2177 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2178 		The delegate will be called when the window manager asks you to take focus.
2179 
2180 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2181 
2182 		History:
2183 			Added April 1, 2022 (dub v10.8)
2184 	+/
2185 	SimpleWindow delegate() setRequestedInputFocus;
2186 
2187 	/// Experimental, do not use yet
2188 	/++
2189 		Grabs exclusive input from the user until you release it with
2190 		[releaseInputGrab].
2191 
2192 
2193 		Note: it is extremely rude to do this without good reason.
2194 		Reasons may include doing some kind of mouse drag operation
2195 		or popping up a temporary menu that should get events and will
2196 		be dismissed at ease by the user clicking away.
2197 
2198 		Params:
2199 			keyboard = do you want to grab keyboard input?
2200 			mouse = grab mouse input?
2201 			confine = confine the mouse cursor to inside this window?
2202 
2203 		History:
2204 			Prior to March 11, 2021, grabbing the keyboard would always also
2205 			set the X input focus. Now, it only focuses if it is a non-transient
2206 			window and otherwise manages the input direction internally.
2207 
2208 			This means spurious focus/blur events will no longer be sent and the
2209 			application will not steal focus from other applications (which the
2210 			window manager may have rejected anyway).
2211 	+/
2212 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2213 		static if(UsingSimpledisplayX11) {
2214 			XSync(XDisplayConnection.get, 0);
2215 			if(keyboard) {
2216 				if(isTransient && _parent) {
2217 					/*
2218 					FIXME:
2219 						setting the keyboard focus is not actually that helpful, what I more likely want
2220 						is the events from the parent window to be sent over here if we're transient.
2221 					*/
2222 
2223 					_parent.inputProxy = this;
2224 				} else {
2225 
2226 					SimpleWindow setTo;
2227 					if(setRequestedInputFocus !is null)
2228 						setTo = setRequestedInputFocus();
2229 					if(setTo is null)
2230 						setTo = this;
2231 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2232 				}
2233 			}
2234 			if(mouse) {
2235 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2236 				EventMask.PointerMotionMask // FIXME: not efficient
2237 				| EventMask.ButtonPressMask
2238 				| EventMask.ButtonReleaseMask
2239 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2240 				)
2241 			{
2242 				XSync(XDisplayConnection.get, 0);
2243 				import core.stdc.stdio;
2244 				printf("Grab input failed %d\n", res);
2245 				//throw new Exception("Grab input failed");
2246 			} else {
2247 				// cool
2248 			}
2249 			}
2250 
2251 		} else version(Windows) {
2252 			// FIXME: keyboard?
2253 			SetCapture(impl.hwnd);
2254 			if(confine) {
2255 				RECT rcClip;
2256 				//RECT rcOldClip;
2257 				//GetClipCursor(&rcOldClip);
2258 				GetWindowRect(hwnd, &rcClip);
2259 				ClipCursor(&rcClip);
2260 			}
2261 		} else version(OSXCocoa) {
2262 			// throw new NotYetImplementedException();
2263 		} else static assert(0);
2264 	}
2265 
2266 	private Point imePopupLocation = Point(0, 0);
2267 
2268 	/++
2269 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2270 
2271 		Bugs:
2272 			Not implemented outside X11.
2273 	+/
2274 	void setIMEPopupLocation(Point location) {
2275 		static if(UsingSimpledisplayX11) {
2276 			imePopupLocation = location;
2277 			updateIMEPopupLocation();
2278 		} else {
2279 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2280 			// throw new NotYetImplementedException();
2281 		}
2282 	}
2283 
2284 	/// ditto
2285 	void setIMEPopupLocation(int x, int y) {
2286 		return setIMEPopupLocation(Point(x, y));
2287 	}
2288 
2289 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2290 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2291 	// receives a ConfigureNotify event
2292 	private void updateIMEPopupLocation() {
2293 		static if(UsingSimpledisplayX11) {
2294 			if (xic is null) {
2295 				return;
2296 			}
2297 
2298 			XPoint nspot;
2299 			nspot.x = cast(short) imePopupLocation.x;
2300 			nspot.y = cast(short) imePopupLocation.y;
2301 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2302 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2303 			XFree(preeditAttr);
2304 		}
2305 	}
2306 
2307 	private bool imeFocused = true;
2308 
2309 	/++
2310 		Tells the IME whether or not an input field is currently focused in the window.
2311 
2312 		Bugs:
2313 			Not implemented outside X11.
2314 	+/
2315 	void setIMEFocused(bool value) {
2316 		imeFocused = value;
2317 		updateIMEFocused();
2318 	}
2319 
2320 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2321 	private void updateIMEFocused() {
2322 		static if(UsingSimpledisplayX11) {
2323 			if (xic is null) {
2324 				return;
2325 			}
2326 
2327 			if (focused && imeFocused) {
2328 				XSetICFocus(xic);
2329 			} else {
2330 				XUnsetICFocus(xic);
2331 			}
2332 		}
2333 	}
2334 
2335 	/++
2336 		Returns the native window.
2337 
2338 		History:
2339 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2340 			to access it through the `impl` member (which is semi-supported
2341 			but platform specific and here it is simple enough to offer an accessor).
2342 
2343 		Bugs:
2344 			Not implemented outside Windows or X11.
2345 	+/
2346 	NativeWindowHandle nativeWindowHandle() {
2347 		version(X11)
2348 			return impl.window;
2349 		else version(Windows)
2350 			return impl.hwnd;
2351 		else
2352 			throw new NotYetImplementedException();
2353 	}
2354 
2355 	private bool isTransient() {
2356 		with(WindowTypes)
2357 		final switch(windowType) {
2358 			case normal, undecorated, eventOnly:
2359 			case nestedChild, minimallyWrapped:
2360 				return (customizationFlags & WindowFlags.transient) ? true : false;
2361 			case dropdownMenu, popupMenu, notification:
2362 				return true;
2363 		}
2364 	}
2365 
2366 	private SimpleWindow inputProxy;
2367 
2368 	/++
2369 		Releases the grab acquired by [grabInput].
2370 	+/
2371 	void releaseInputGrab() {
2372 		static if(UsingSimpledisplayX11) {
2373 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2374 			if(_parent)
2375 				_parent.inputProxy = null;
2376 		} else version(Windows) {
2377 			ReleaseCapture();
2378 			ClipCursor(null);
2379 		} else version(OSXCocoa) {
2380 			// throw new NotYetImplementedException();
2381 		} else static assert(0);
2382 	}
2383 
2384 	/++
2385 		Sets the input focus to this window.
2386 
2387 		You shouldn't call this very often - please let the user control the input focus.
2388 	+/
2389 	void focus() {
2390 		static if(UsingSimpledisplayX11) {
2391 			SimpleWindow setTo;
2392 			if(setRequestedInputFocus !is null)
2393 				setTo = setRequestedInputFocus();
2394 			if(setTo is null)
2395 				setTo = this;
2396 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2397 		} else version(Windows) {
2398 			SetFocus(this.impl.hwnd);
2399 		} else version(OSXCocoa) {
2400 			throw new NotYetImplementedException();
2401 		} else static assert(0);
2402 	}
2403 
2404 	/++
2405 		Requests attention from the user for this window.
2406 
2407 
2408 		The typical result of this function is to change the color
2409 		of the taskbar icon, though it may be tweaked on specific
2410 		platforms.
2411 
2412 		It is meant to unobtrusively tell the user that something
2413 		relevant to them happened in the background and they should
2414 		check the window when they get a chance. Upon receiving the
2415 		keyboard focus, the window will automatically return to its
2416 		natural state.
2417 
2418 		If the window already has the keyboard focus, this function
2419 		may do nothing, because the user is presumed to already be
2420 		giving the window attention.
2421 
2422 		Implementation_note:
2423 
2424 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2425 		atom on X11 and the FlashWindow function on Windows.
2426 	+/
2427 	void requestAttention() {
2428 		if(_focused)
2429 			return;
2430 
2431 		version(Windows) {
2432 			FLASHWINFO info;
2433 			info.cbSize = info.sizeof;
2434 			info.hwnd = impl.hwnd;
2435 			info.dwFlags = FLASHW_TRAY;
2436 			info.uCount = 1;
2437 
2438 			FlashWindowEx(&info);
2439 
2440 		} else version(X11) {
2441 			demandingAttention = true;
2442 			demandAttention(this, true);
2443 		} else version(OSXCocoa) {
2444 			throw new NotYetImplementedException();
2445 		} else static assert(0);
2446 	}
2447 
2448 	private bool _focused;
2449 
2450 	version(X11) private bool demandingAttention;
2451 
2452 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2453 	/// You'll have to call `close()` manually if you set this delegate.
2454 	void delegate () closeQuery;
2455 
2456 	/// This will be called when window visibility was changed.
2457 	void delegate (bool becomesVisible) visibilityChanged;
2458 
2459 	/// This will be called when window becomes visible for the first time.
2460 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2461 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2462 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2463 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2464 	private bool _visibleForTheFirstTimeCalled;
2465 	void delegate () visibleForTheFirstTime;
2466 
2467 	/// Returns true if the window has been closed.
2468 	final @property bool closed() { return _closed; }
2469 
2470 	private final @property bool notClosed() { return !_closed; }
2471 
2472 	/// Returns true if the window is focused.
2473 	final @property bool focused() { return _focused; }
2474 
2475 	private bool _visible;
2476 	/// Returns true if the window is visible (mapped).
2477 	final @property bool visible() { return _visible; }
2478 
2479 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2480 	void close() {
2481 		if (!_closed) {
2482 			runInGuiThread( {
2483 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2484 				if (onClosing !is null) onClosing();
2485 				impl.closeWindow();
2486 				_closed = true;
2487 			} );
2488 		}
2489 	}
2490 
2491 	/++
2492 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2493 
2494 		History:
2495 			Overload added on March 7, 2021.
2496 	+/
2497 	void close() shared {
2498 		(cast() this).close();
2499 	}
2500 
2501 	/++
2502 
2503 	+/
2504 	void maximize() {
2505 		version(Windows)
2506 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2507 		else version(X11) {
2508 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2509 
2510 			// also note _NET_WM_STATE_FULLSCREEN
2511 		}
2512 
2513 	}
2514 
2515 	private bool _fullscreen;
2516 	version(Windows)
2517 	private WINDOWPLACEMENT g_wpPrev;
2518 
2519 	/// not fully implemented but planned for a future release
2520 	void fullscreen(bool yes) {
2521 		version(Windows) {
2522 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2523 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2524 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2525 				MONITORINFO mi;
2526 				mi.cbSize = MONITORINFO.sizeof;
2527 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2528 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2529 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2530 					SetWindowLong(hwnd, GWL_STYLE,
2531 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2532 					SetWindowPos(hwnd, HWND_TOP,
2533 						     mi.rcMonitor.left, mi.rcMonitor.top,
2534 						     mi.rcMonitor.right - mi.rcMonitor.left,
2535 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2536 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2537 				}
2538 			} else {
2539 				SetWindowLong(hwnd, GWL_STYLE,
2540 					      dwStyle | WS_OVERLAPPEDWINDOW);
2541 				SetWindowPlacement(hwnd, &g_wpPrev);
2542 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2543 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2544 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2545 			}
2546 
2547 		} else version(X11) {
2548 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2549 		}
2550 
2551 		_fullscreen = yes;
2552 
2553 	}
2554 
2555 	bool fullscreen() {
2556 		return _fullscreen;
2557 	}
2558 
2559 	/++
2560 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2561 
2562 	+/
2563 	void minimize() {
2564 		version(Windows)
2565 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2566 		//else version(X11)
2567 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2568 	}
2569 
2570 	/// Alias for `hidden = false`
2571 	void show() {
2572 		hidden = false;
2573 	}
2574 
2575 	/// Alias for `hidden = true`
2576 	void hide() {
2577 		hidden = true;
2578 	}
2579 
2580 	/// Hide cursor when it enters the window.
2581 	void hideCursor() {
2582 		version(OSXCocoa) throw new NotYetImplementedException(); else
2583 		if (!_closed) impl.hideCursor();
2584 	}
2585 
2586 	/// Don't hide cursor when it enters the window.
2587 	void showCursor() {
2588 		version(OSXCocoa) throw new NotYetImplementedException(); else
2589 		if (!_closed) impl.showCursor();
2590 	}
2591 
2592 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2593 	 *
2594 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2595 	 * control. Try to think for other approaches before using this function.
2596 	 *
2597 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2598 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2599 	 *       receive "mouse moved here" event.
2600 	 */
2601 	bool warpMouse (int x, int y) {
2602 		version(X11) {
2603 			if (!_closed) { impl.warpMouse(x, y); return true; }
2604 		} else version(Windows) {
2605 			if (!_closed) {
2606 				POINT point;
2607 				point.x = x;
2608 				point.y = y;
2609 				if(ClientToScreen(impl.hwnd, &point)) {
2610 					SetCursorPos(point.x, point.y);
2611 					return true;
2612 				}
2613 			}
2614 		}
2615 		return false;
2616 	}
2617 
2618 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2619 	void sendDummyEvent () {
2620 		version(X11) {
2621 			if (!_closed) { impl.sendDummyEvent(); }
2622 		}
2623 	}
2624 
2625 	/// Set window minimal size.
2626 	void setMinSize (int minwidth, int minheight) {
2627 		version(OSXCocoa) throw new NotYetImplementedException(); else
2628 		if (!_closed) impl.setMinSize(minwidth, minheight);
2629 	}
2630 
2631 	/// Set window maximal size.
2632 	void setMaxSize (int maxwidth, int maxheight) {
2633 		version(OSXCocoa) throw new NotYetImplementedException(); else
2634 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2635 	}
2636 
2637 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2638 	/// Currently only supported on X11.
2639 	void setResizeGranularity (int granx, int grany) {
2640 		version(OSXCocoa) throw new NotYetImplementedException(); else
2641 		if (!_closed) impl.setResizeGranularity(granx, grany);
2642 	}
2643 
2644 	/// Move window.
2645 	void move(int x, int y) {
2646 		version(OSXCocoa) throw new NotYetImplementedException(); else
2647 		if (!_closed) impl.move(x, y);
2648 	}
2649 
2650 	/// ditto
2651 	void move(Point p) {
2652 		version(OSXCocoa) throw new NotYetImplementedException(); else
2653 		if (!_closed) impl.move(p.x, p.y);
2654 	}
2655 
2656 	/++
2657 		Resize window.
2658 
2659 		Note that the width and height of the window are NOT instantly
2660 		updated - it waits for the window manager to approve the resize
2661 		request, which means you must return to the event loop before the
2662 		width and height are actually changed.
2663 	+/
2664 	void resize(int w, int h) {
2665 		if(!_closed && _fullscreen) fullscreen = false;
2666 		version(OSXCocoa) throw new NotYetImplementedException(); else
2667 		if (!_closed) impl.resize(w, h);
2668 	}
2669 
2670 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2671 	void moveResize (int x, int y, int w, int h) {
2672 		if(!_closed && _fullscreen) fullscreen = false;
2673 		version(OSXCocoa) throw new NotYetImplementedException(); else
2674 		if (!_closed) impl.moveResize(x, y, w, h);
2675 	}
2676 
2677 	private bool _hidden;
2678 
2679 	/// Returns true if the window is hidden.
2680 	final @property bool hidden() {
2681 		return _hidden;
2682 	}
2683 
2684 	/// Shows or hides the window based on the bool argument.
2685 	final @property void hidden(bool b) {
2686 		_hidden = b;
2687 		version(Windows) {
2688 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2689 		} else version(X11) {
2690 			if(b)
2691 				//XUnmapWindow(impl.display, impl.window);
2692 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2693 			else
2694 				XMapWindow(impl.display, impl.window);
2695 		} else version(OSXCocoa) {
2696 			// throw new NotYetImplementedException();
2697 		} else static assert(0);
2698 	}
2699 
2700 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2701 	void opacity(double opacity) @property
2702 	in {
2703 		assert(opacity >= 0 && opacity <= 1);
2704 	} do {
2705 		version (Windows) {
2706 			impl.setOpacity(cast(ubyte)(255 * opacity));
2707 		} else version (X11) {
2708 			impl.setOpacity(cast(uint)(uint.max * opacity));
2709 		} else throw new NotYetImplementedException();
2710 	}
2711 
2712 	/++
2713 		Sets your event handlers, without entering the event loop. Useful if you
2714 		have multiple windows - set the handlers on each window, then only do
2715 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2716 
2717 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2718 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2719 		delegate signatures.
2720 	+/
2721 	void setEventHandlers(T...)(T eventHandlers) {
2722 		// FIXME: add more events
2723 		foreach(handler; eventHandlers) {
2724 			static if(__traits(compiles, handleKeyEvent = handler)) {
2725 				handleKeyEvent = handler;
2726 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2727 				handleCharEvent = handler;
2728 			} else static if(__traits(compiles, handlePulse = handler)) {
2729 				handlePulse = handler;
2730 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2731 				handleMouseEvent = handler;
2732 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2733 		}
2734 	}
2735 
2736 	/++
2737 		The event loop automatically returns when the window is closed
2738 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2739 		pulse timer is created. The event loop will block until an event
2740 		arrives or the pulse timer goes off.
2741 
2742 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2743 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2744 		[handleMouseEvent], based on the signature of delegates you provide.
2745 
2746 		Give one with no parameters to set a timer pulse handler. Give one that
2747 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2748 		and one that takes `dchar` for a char event handler. You can use as many
2749 		or as few handlers as you need for your application.
2750 
2751 		Bugs:
2752 
2753 		$(PITFALL
2754 			You should always have one event loop live for your application.
2755 			If you make two windows in sequence, the second call to eventLoop
2756 			might fail:
2757 
2758 			---
2759 			// don't do this!
2760 			auto window = new SimpleWindow();
2761 			window.eventLoop(0);
2762 
2763 			auto window2 = new SimpleWindow();
2764 			window2.eventLoop(0); // problematic! might crash
2765 			---
2766 
2767 			simpledisplay's current implementation assumes that final cleanup is
2768 			done when the event loop refcount reaches zero. So after the first
2769 			eventLoop returns, when there isn't already another one active, it assumes
2770 			the program will exit soon and cleans up.
2771 
2772 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2773 			it eventually, but in the mean time, there's an easy solution:
2774 
2775 			---
2776 			// do this
2777 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2778 
2779 			auto window = new SimpleWindow();
2780 			window.eventLoop(0);
2781 
2782 			auto window2 = new SimpleWindow();
2783 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2784 			---
2785 
2786 			By adding a top-level reference to the event loop, it ensures the final cleanup
2787 			is not performed until it goes out of scope too, letting the individual window loops
2788 			work without trouble despite the bug.
2789 		)
2790 
2791 		History:
2792 			The overload without `pulseTimeout` was added on December 8, 2021.
2793 
2794 			On December 9, 2021, the default blocking mode (which is now configurable
2795 			because [eventLoopWithBlockingMode] was added) switched from
2796 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2797 			should almost never be noticeable to you since the typical simpledisplay
2798 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2799 
2800 		See_Also:
2801 			[eventLoopWithBlockingMode]
2802 	+/
2803 	final int eventLoop(T...)(
2804 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2805 		T eventHandlers) /// delegate list like std.concurrency.receive
2806 	{
2807 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2808 	}
2809 
2810 	/// ditto
2811 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2812 	{
2813 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2814 	}
2815 
2816 	/++
2817 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2818 
2819 		History:
2820 			Added December 8, 2021 (dub v10.5)
2821 
2822 			Previously, this implementation was right inside [eventLoop], but when I wanted
2823 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2824 			just renamed it instead of adding as an overload. Besides, the new name makes it
2825 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2826 
2827 		See_Also:
2828 			[SimpleWindow.eventLoop], [EventLoop]
2829 
2830 		Bugs:
2831 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2832 	+/
2833 	final int eventLoopWithBlockingMode(T...)(
2834 		BlockingMode blockingMode, /// when you want this function to block until
2835 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2836 		T eventHandlers) /// delegate list like std.concurrency.receive
2837 	{
2838 		setEventHandlers(eventHandlers);
2839 
2840 		version(with_eventloop) {
2841 			// delegates event loop to my other module
2842 			version(X11)
2843 				XFlush(display);
2844 
2845 			import arsd.eventloop;
2846 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2847 			scope(exit) clearInterval(handle);
2848 
2849 			loop();
2850 			return 0;
2851 		} else version(OSXCocoa) {
2852 			// FIXME
2853 			if (handlePulse !is null && pulseTimeout != 0) {
2854 				timer = NSTimer.schedule(pulseTimeout*1e-3,
2855 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
2856 					null, true);
2857 			}
2858 
2859 			view.setNeedsDisplay(true);
2860 
2861 			NSApp.run();
2862             		return 0;
2863         	} else {
2864 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2865 
2866 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2867 				return 0;
2868 
2869 			return el.run(
2870 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2871 					null :
2872 					&this.notClosed
2873 			);
2874 		}
2875 	}
2876 
2877 	/++
2878 		This lets you draw on the window (or its backing buffer) using basic
2879 		2D primitives.
2880 
2881 		Be sure to call this in a limited scope because your changes will not
2882 		actually appear on the window until ScreenPainter's destructor runs.
2883 
2884 		Returns: an instance of [ScreenPainter], which has the drawing methods
2885 		on it to draw on this window.
2886 
2887 		Params:
2888 			manualInvalidations = if you set this to true, you will need to
2889 			set the invalid rectangle on the painter yourself. If false, it
2890 			assumes the whole window has been redrawn each time you draw.
2891 
2892 			Only invalidated rectangles are blitted back to the window when
2893 			the destructor runs. Doing this yourself can reduce flickering
2894 			of child windows.
2895 
2896 		History:
2897 			The `manualInvalidations` parameter overload was added on
2898 			December 30, 2021 (dub v10.5)
2899 	+/
2900 	ScreenPainter draw() {
2901 		return draw(false);
2902 	}
2903 	/// ditto
2904 	ScreenPainter draw(bool manualInvalidations) {
2905 		return impl.getPainter(manualInvalidations);
2906 	}
2907 
2908 	// This is here to implement the interface we use for various native handlers.
2909 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2910 
2911 	// maps native window handles to SimpleWindow instances, if there are any
2912 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2913 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
2914 	version(OSXCocoa)
2915 	public __gshared SimpleWindow[void*] nativeMapping;
2916 	else
2917 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2918 
2919 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
2920 	private int _virtualWidth;
2921 	private int _virtualHeight;
2922 
2923 	/// Width of the window's drawable client area, in pixels.
2924 	@scriptable
2925 	final @property int width() const pure nothrow @safe @nogc {
2926 		if(resizability == Resizability.automaticallyScaleIfPossible)
2927 			return _virtualWidth;
2928 		else
2929 			return _width;
2930 	}
2931 
2932 	/// Height of the window's drawable client area, in pixels.
2933 	@scriptable
2934 	final @property int height() const pure nothrow @safe @nogc {
2935 		if(resizability == Resizability.automaticallyScaleIfPossible)
2936 			return _virtualHeight;
2937 		else
2938 			return _height;
2939 	}
2940 
2941 	/++
2942 		Returns the actual size of the window, bypassing the logical
2943 		illusions of [Resizability.automaticallyScaleIfPossible].
2944 
2945 		History:
2946 			Added November 11, 2022 (dub v10.10)
2947 	+/
2948 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
2949 		return Size(_width, _height);
2950 	}
2951 
2952 
2953 	private int _width;
2954 	private int _height;
2955 
2956 	// HACK: making the best of some copy constructor woes with refcounting
2957 	private ScreenPainterImplementation* activeScreenPainter_;
2958 
2959 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2960 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2961 
2962 	private OpenGlOptions openglMode;
2963 	private Resizability resizability;
2964 	private WindowTypes windowType;
2965 	private int customizationFlags;
2966 
2967 	/// `true` if OpenGL was initialized for this window.
2968 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2969 		version(without_opengl)
2970 			return false;
2971 		else
2972 			return (openglMode == OpenGlOptions.yes);
2973 	}
2974 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2975 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2976 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2977 
2978 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2979 	/// to call this, as it's not recommended to share window between threads.
2980 	void mtLock () {
2981 		version(X11) {
2982 			XLockDisplay(this.display);
2983 		}
2984 	}
2985 
2986 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2987 	/// to call this, as it's not recommended to share window between threads.
2988 	void mtUnlock () {
2989 		version(X11) {
2990 			XUnlockDisplay(this.display);
2991 		}
2992 	}
2993 
2994 	/// Emit a beep to get user's attention.
2995 	void beep () {
2996 		version(X11) {
2997 			XBell(this.display, 100);
2998 		} else version(Windows) {
2999 			MessageBeep(0xFFFFFFFF);
3000 		}
3001 	}
3002 
3003 
3004 
3005 	version(without_opengl) {} else {
3006 
3007 		/// 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`.
3008 		void delegate() redrawOpenGlScene;
3009 
3010 		/// This will allow you to change OpenGL vsync state.
3011 		final @property void vsync (bool wait) {
3012 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3013 		  version(X11) {
3014 		    setAsCurrentOpenGlContext();
3015 		    glxSetVSync(display, impl.window, wait);
3016 		  } else version(Windows) {
3017 		    setAsCurrentOpenGlContext();
3018                     wglSetVSync(wait);
3019 		  }
3020 		}
3021 
3022 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3023 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3024 		/// enough without waiting 'em to finish their frame business.
3025 		bool useGLFinish = true;
3026 
3027 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3028 		/// 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.
3029 		void redrawOpenGlSceneNow() {
3030 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3031 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3032 			if(redrawOpenGlScene is null)
3033 				return;
3034 
3035 			this.mtLock();
3036 			scope(exit) this.mtUnlock();
3037 
3038 			this.setAsCurrentOpenGlContext();
3039 
3040 			redrawOpenGlScene();
3041 
3042 			this.swapOpenGlBuffers();
3043 			// 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.
3044 			if (useGLFinish) glFinish();
3045 		}
3046 
3047 		private bool redrawOpenGlSceneSoonSet = false;
3048 		private static class RedrawOpenGlSceneEvent {
3049 			SimpleWindow w;
3050 			this(SimpleWindow w) { this.w = w; }
3051 		}
3052 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3053 		/++
3054 			Queues an opengl redraw as soon as the other pending events are cleared.
3055 		+/
3056 		void redrawOpenGlSceneSoon() {
3057 			if(redrawOpenGlScene is null)
3058 				return;
3059 
3060 			if(!redrawOpenGlSceneSoonSet) {
3061 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3062 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3063 				redrawOpenGlSceneSoonSet = true;
3064 			}
3065 			this.postEvent(redrawOpenGlSceneEvent, true);
3066 		}
3067 
3068 
3069 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3070 		void setAsCurrentOpenGlContext() {
3071 			assert(openglMode == OpenGlOptions.yes);
3072 			version(X11) {
3073 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3074 					throw new Exception("glXMakeCurrent");
3075 			} else version(Windows) {
3076 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3077 				if (!wglMakeCurrent(ghDC, ghRC))
3078 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3079 			}
3080 		}
3081 
3082 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3083 		/// This doesn't throw, returning success flag instead.
3084 		bool setAsCurrentOpenGlContextNT() nothrow {
3085 			assert(openglMode == OpenGlOptions.yes);
3086 			version(X11) {
3087 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3088 			} else version(Windows) {
3089 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3090 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3091 			}
3092 		}
3093 
3094 		/// 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.
3095 		/// This doesn't throw, returning success flag instead.
3096 		bool releaseCurrentOpenGlContext() nothrow {
3097 			assert(openglMode == OpenGlOptions.yes);
3098 			version(X11) {
3099 				return (glXMakeCurrent(display, 0, null) != 0);
3100 			} else version(Windows) {
3101 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3102 				return wglMakeCurrent(ghDC, null) ? true : false;
3103 			}
3104 		}
3105 
3106 		/++
3107 			simpledisplay always uses double buffering, usually automatically. This
3108 			manually swaps the OpenGL buffers.
3109 
3110 
3111 			You should not need to call this yourself because simpledisplay will do it
3112 			for you after calling your `redrawOpenGlScene`.
3113 
3114 			Remember that this may throw an exception, which you can catch in a multithreaded
3115 			application to keep your thread from dying from an unhandled exception.
3116 		+/
3117 		void swapOpenGlBuffers() {
3118 			assert(openglMode == OpenGlOptions.yes);
3119 			version(X11) {
3120 				if (!this._visible) return; // no need to do this if window is invisible
3121 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3122 				glXSwapBuffers(display, impl.window);
3123 			} else version(Windows) {
3124 				SwapBuffers(ghDC);
3125 			}
3126 		}
3127 	}
3128 
3129 	/++
3130 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3131 
3132 
3133 		---
3134 			auto window = new SimpleWindow(100, 100, "First title");
3135 			window.title = "A new title";
3136 		---
3137 
3138 		You may call this function at any time.
3139 	+/
3140 	@property void title(string title) {
3141 		_title = title;
3142 		version(OSXCocoa) throw new NotYetImplementedException(); else
3143 		impl.setTitle(title);
3144 	}
3145 
3146 	private string _title;
3147 
3148 	/// Gets the title
3149 	@property string title() {
3150 		if(_title is null)
3151 			_title = getRealTitle();
3152 		return _title;
3153 	}
3154 
3155 	/++
3156 		Get the title as set by the window manager.
3157 		May not match what you attempted to set.
3158 	+/
3159 	string getRealTitle() {
3160 		static if(is(typeof(impl.getTitle())))
3161 			return impl.getTitle();
3162 		else
3163 			return null;
3164 	}
3165 
3166 	// don't use this generally it is not yet really released
3167 	version(X11)
3168 	@property Image secret_icon() {
3169 		return secret_icon_inner;
3170 	}
3171 	private Image secret_icon_inner;
3172 
3173 
3174 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3175 	@property void icon(MemoryImage icon) {
3176 		if(icon is null)
3177 			return;
3178 		auto tci = icon.getAsTrueColorImage();
3179 		version(Windows) {
3180 			winIcon = new WindowsIcon(icon);
3181 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3182 		} else version(X11) {
3183 			secret_icon_inner = Image.fromMemoryImage(icon);
3184 			// FIXME: ensure this is correct
3185 			auto display = XDisplayConnection.get;
3186 			arch_ulong[] buffer;
3187 			buffer ~= icon.width;
3188 			buffer ~= icon.height;
3189 			foreach(c; tci.imageData.colors) {
3190 				arch_ulong b;
3191 				b |= c.a << 24;
3192 				b |= c.r << 16;
3193 				b |= c.g << 8;
3194 				b |= c.b;
3195 				buffer ~= b;
3196 			}
3197 
3198 			XChangeProperty(
3199 				display,
3200 				impl.window,
3201 				GetAtom!("_NET_WM_ICON", true)(display),
3202 				GetAtom!"CARDINAL"(display),
3203 				32 /* bits */,
3204 				0 /*PropModeReplace*/,
3205 				buffer.ptr,
3206 				cast(int) buffer.length);
3207 		} else version(OSXCocoa) {
3208 			throw new NotYetImplementedException();
3209 		} else static assert(0);
3210 	}
3211 
3212 	version(Windows)
3213 		private WindowsIcon winIcon;
3214 
3215 	bool _suppressDestruction;
3216 
3217 	~this() {
3218 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3219 		if(_suppressDestruction)
3220 			return;
3221 		impl.dispose();
3222 	}
3223 
3224 	private bool _closed;
3225 
3226 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3227 	/*
3228 	ScreenPainter drawTransiently() {
3229 		return impl.getPainter();
3230 	}
3231 	*/
3232 
3233 	/// Draws an image on the window. This is meant to provide quick look
3234 	/// of a static image generated elsewhere.
3235 	@property void image(Image i) {
3236 	/+
3237 		version(Windows) {
3238 			BITMAP bm;
3239 			HDC hdc = GetDC(hwnd);
3240 			HDC hdcMem = CreateCompatibleDC(hdc);
3241 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3242 
3243 			GetObject(i.handle, bm.sizeof, &bm);
3244 
3245 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3246 
3247 			SelectObject(hdcMem, hbmOld);
3248 			DeleteDC(hdcMem);
3249 			ReleaseDC(hwnd, hdc);
3250 
3251 			/*
3252 			RECT r;
3253 			r.right = i.width;
3254 			r.bottom = i.height;
3255 			InvalidateRect(hwnd, &r, false);
3256 			*/
3257 		} else
3258 		version(X11) {
3259 			if(!destroyed) {
3260 				if(i.usingXshm)
3261 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3262 				else
3263 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3264 			}
3265 		} else
3266 		version(OSXCocoa) {
3267 			draw().drawImage(Point(0, 0), i);
3268 			setNeedsDisplay(view, true);
3269 		} else static assert(0);
3270 	+/
3271 		auto painter = this.draw;
3272 		painter.drawImage(Point(0, 0), i);
3273 	}
3274 
3275 	/++
3276 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3277 
3278 		---
3279 		window.cursor = GenericCursor.Help;
3280 		// now the window mouse cursor is set to a generic help
3281 		---
3282 
3283 	+/
3284 	@property void cursor(MouseCursor cursor) {
3285 		version(OSXCocoa)
3286 			{} // featureNotImplemented();
3287 		else
3288 		if(this.impl.curHidden <= 0) {
3289 			static if(UsingSimpledisplayX11) {
3290 				auto ch = cursor.cursorHandle;
3291 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3292 			} else version(Windows) {
3293 				auto ch = cursor.cursorHandle;
3294 				impl.currentCursor = ch;
3295 				SetCursor(ch); // redraw without waiting for mouse movement to update
3296 			} else featureNotImplemented();
3297 		}
3298 
3299 	}
3300 
3301 	/// What follows are the event handlers. These are set automatically
3302 	/// by the eventLoop function, but are still public so you can change
3303 	/// them later. wasPressed == true means key down. false == key up.
3304 
3305 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3306 	void delegate(KeyEvent ke) handleKeyEvent;
3307 
3308 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3309 	void delegate(dchar c) handleCharEvent;
3310 
3311 	/// Handles a timer pulse. Settable through setEventHandlers.
3312 	void delegate() handlePulse;
3313 
3314 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3315 	void delegate(bool) onFocusChange;
3316 
3317 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3318 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3319 	void delegate() onClosing;
3320 
3321 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3322 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3323 	 * last minute cleanup. */
3324 	void delegate() onDestroyed;
3325 
3326 	static if (UsingSimpledisplayX11)
3327 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3328 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3329 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3330 	 *
3331 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3332 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3333 
3334 	//version(Windows)
3335 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3336 
3337 	private {
3338 		int lastMouseX = int.min;
3339 		int lastMouseY = int.min;
3340 		void mdx(ref MouseEvent ev) {
3341 			if(lastMouseX == int.min || lastMouseY == int.min) {
3342 				ev.dx = 0;
3343 				ev.dy = 0;
3344 			} else {
3345 				ev.dx = ev.x - lastMouseX;
3346 				ev.dy = ev.y - lastMouseY;
3347 			}
3348 
3349 			lastMouseX = ev.x;
3350 			lastMouseY = ev.y;
3351 		}
3352 	}
3353 
3354 	/// Mouse event handler. Settable through setEventHandlers.
3355 	void delegate(MouseEvent) handleMouseEvent;
3356 
3357 	/// use to redraw child widgets if you use system apis to add stuff
3358 	void delegate() paintingFinished;
3359 
3360 	void delegate() paintingFinishedDg() {
3361 		return paintingFinished;
3362 	}
3363 
3364 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3365 	/// for this to ever happen.
3366 	void delegate(int width, int height) windowResized;
3367 
3368 	/++
3369 		Platform specific - handle any native message this window gets.
3370 
3371 		Note: this is called *in addition to* other event handlers, unless you either:
3372 
3373 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3374 
3375 		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.
3376 
3377 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3378 
3379 		On X, it takes the form of `int delegate(XEvent)`.
3380 
3381 		History:
3382 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3383 
3384 			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.
3385 	+/
3386 	NativeEventHandler handleNativeEvent_;
3387 
3388 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3389 		return handleNativeEvent_;
3390 	}
3391 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3392 		handleNativeEvent_ = neh;
3393 	}
3394 
3395 	version(Windows)
3396 	// compatibility shim with the old deprecated way
3397 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3398 	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) {
3399 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3400 			auto ret = dg(h, m, w, l);
3401 			if(ret == 0)
3402 				r = 1;
3403 			return ret;
3404 		};
3405 	}
3406 
3407 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3408 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3409 	/// this instead and it will work the same way.
3410 	__gshared NativeEventHandler handleNativeGlobalEvent;
3411 
3412 //  private:
3413 	/// The native implementation is available, but you shouldn't use it unless you are
3414 	/// familiar with the underlying operating system, don't mind depending on it, and
3415 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3416 	/// do what you need to do with handleNativeEvent instead.
3417 	///
3418 	/// This is likely to eventually change to be just a struct holding platform-specific
3419 	/// handles instead of a template mixin at some point because I'm not happy with the
3420 	/// code duplication here (ironically).
3421 	mixin NativeSimpleWindowImplementation!() impl;
3422 
3423 	/**
3424 		This is in-process one-way (from anything to window) event sending mechanics.
3425 		It is thread-safe, so it can be used in multi-threaded applications to send,
3426 		for example, "wake up and repaint" events when thread completed some operation.
3427 		This will allow to avoid using timer pulse to check events with synchronization,
3428 		'cause event handler will be called in UI thread. You can stop guessing which
3429 		pulse frequency will be enough for your app.
3430 		Note that events handlers may be called in arbitrary order, i.e. last registered
3431 		handler can be called first, and vice versa.
3432 	*/
3433 public:
3434 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3435 	 * "spamming" window with events it can't cope with.
3436 	 * It is safe to call this from non-UI threads.
3437 	 */
3438 	@property bool eventQueueEmpty() () {
3439 		synchronized(this) {
3440 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3441 		}
3442 		return true;
3443 	}
3444 
3445 	/** Does our custom event queue contains at least one with the given type?
3446 	 * Can be used in simple cases to prevent "spamming" window with events
3447 	 * it can't cope with.
3448 	 * It is safe to call this from non-UI threads.
3449 	 */
3450 	@property bool eventQueued(ET:Object) () {
3451 		synchronized(this) {
3452 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3453 				if (!o.doProcess) {
3454 					if (cast(ET)(o.evt)) return true;
3455 				}
3456 			}
3457 		}
3458 		return false;
3459 	}
3460 
3461 	/++
3462 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3463 
3464 		History:
3465 			Added May 12, 2021
3466 	+/
3467 	void delegate(Exception e) nothrow eventUncaughtException;
3468 
3469 	/** Add listener for custom event. Can be used like this:
3470 	 *
3471 	 * ---------------------
3472 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3473 	 *   ...
3474 	 *   win.removeEventListener(eid);
3475 	 * ---------------------
3476 	 *
3477 	 * Returns: 0 on failure (should never happen, so ignore it)
3478 	 *
3479 	 * $(WARNING Don't use this method in object destructors!)
3480 	 *
3481 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3482 	 *           'cause if event handler id counter will overflow, you won't be able
3483 	 *           to register any more events.)
3484 	 */
3485 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3486 		if (dg is null) return 0; // ignore empty handlers
3487 		synchronized(this) {
3488 			//FIXME: abort on overflow?
3489 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3490 			EventHandlerEntry e;
3491 			e.dg = delegate (Object o) {
3492 				if (auto co = cast(ET)o) {
3493 					try {
3494 						dg(co);
3495 					} catch (Exception e) {
3496 						// sorry!
3497 						if(eventUncaughtException)
3498 							eventUncaughtException(e);
3499 					}
3500 					return true;
3501 				}
3502 				return false;
3503 			};
3504 			e.id = lastUsedHandlerId;
3505 			auto optr = eventHandlers.ptr;
3506 			eventHandlers ~= e;
3507 			if (eventHandlers.ptr !is optr) {
3508 				import core.memory : GC;
3509 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3510 			}
3511 			return lastUsedHandlerId;
3512 		}
3513 	}
3514 
3515 	/// Remove event listener. It is safe to pass invalid event id here.
3516 	/// $(WARNING Don't use this method in object destructors!)
3517 	void removeEventListener() (uint id) {
3518 		if (id == 0 || id > lastUsedHandlerId) return;
3519 		synchronized(this) {
3520 			foreach (immutable idx; 0..eventHandlers.length) {
3521 				if (eventHandlers[idx].id == id) {
3522 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3523 					eventHandlers[$-1].dg = null;
3524 					eventHandlers.length -= 1;
3525 					eventHandlers.assumeSafeAppend;
3526 					return;
3527 				}
3528 			}
3529 		}
3530 	}
3531 
3532 	/// Post event to queue. It is safe to call this from non-UI threads.
3533 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3534 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3535 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3536 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3537 		if (this.closed) return false; // closed windows can't handle events
3538 
3539 		// remove all events of type `ET`
3540 		void removeAllET () {
3541 			uint eidx = 0, ec = eventQueueUsed;
3542 			auto eptr = eventQueue.ptr;
3543 			while (eidx < ec) {
3544 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3545 				if (cast(ET)eptr.evt !is null) {
3546 					// i found her!
3547 					if (inCustomEventProcessor) {
3548 						// if we're in custom event processing loop, processor will clear it for us
3549 						eptr.evt = null;
3550 						++eidx;
3551 						++eptr;
3552 					} else {
3553 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3554 						ec = --eventQueueUsed;
3555 						// clear last event (it is already copied)
3556 						eventQueue.ptr[ec].evt = null;
3557 					}
3558 				} else {
3559 					++eidx;
3560 					++eptr;
3561 				}
3562 			}
3563 		}
3564 
3565 		if (evt is null) {
3566 			if (replace) { synchronized(this) removeAllET(); }
3567 			// ignore empty events, they can't be handled anyway
3568 			return false;
3569 		}
3570 
3571 		// add events even if no event FD/event object created yet
3572 		synchronized(this) {
3573 			if (replace) removeAllET();
3574 			if (eventQueueUsed == uint.max) return false; // just in case
3575 			if (eventQueueUsed < eventQueue.length) {
3576 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3577 			} else {
3578 				if (eventQueue.capacity == eventQueue.length) {
3579 					// need to reallocate; do a trick to ensure that old array is cleared
3580 					auto oarr = eventQueue;
3581 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3582 					// just in case, do yet another check
3583 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3584 					import core.memory : GC;
3585 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3586 				} else {
3587 					auto optr = eventQueue.ptr;
3588 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3589 					assert(eventQueue.ptr is optr);
3590 				}
3591 				++eventQueueUsed;
3592 				assert(eventQueueUsed == eventQueue.length);
3593 			}
3594 			if (!eventWakeUp()) {
3595 				// can't wake up event processor, so there is no reason to keep the event
3596 				assert(eventQueueUsed > 0);
3597 				eventQueue[--eventQueueUsed].evt = null;
3598 				return false;
3599 			}
3600 			return true;
3601 		}
3602 	}
3603 
3604 	/// Post event to queue. It is safe to call this from non-UI threads.
3605 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3606 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3607 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3608 		return postTimeout!ET(evt, 0, replace);
3609 	}
3610 
3611 private:
3612 	private import core.time : MonoTime;
3613 
3614 	version(Posix) {
3615 		__gshared int customEventFDRead = -1;
3616 		__gshared int customEventFDWrite = -1;
3617 		__gshared int customSignalFD = -1;
3618 	} else version(Windows) {
3619 		__gshared HANDLE customEventH = null;
3620 	}
3621 
3622 	// wake up event processor
3623 	static bool eventWakeUp () {
3624 		version(X11) {
3625 			import core.sys.posix.unistd : write;
3626 			ulong n = 1;
3627 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3628 			return true;
3629 		} else version(Windows) {
3630 			if (customEventH !is null) SetEvent(customEventH);
3631 			return true;
3632 		} else version(OSXCocoa) {
3633 			if(globalAppDelegate)
3634 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3635 			return true;
3636 		} else {
3637 			// not implemented for other OSes
3638 			return false;
3639 		}
3640 	}
3641 
3642 	static struct QueuedEvent {
3643 		Object evt;
3644 		bool timed = false;
3645 		MonoTime hittime = MonoTime.zero;
3646 		bool doProcess = false; // process event at the current iteration (internal flag)
3647 
3648 		this (Object aevt, uint toutmsecs) {
3649 			evt = aevt;
3650 			if (toutmsecs > 0) {
3651 				import core.time : msecs;
3652 				timed = true;
3653 				hittime = MonoTime.currTime+toutmsecs.msecs;
3654 			}
3655 		}
3656 	}
3657 
3658 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3659 	static struct EventHandlerEntry {
3660 		CustomEventHandler dg;
3661 		uint id;
3662 	}
3663 
3664 	uint lastUsedHandlerId;
3665 	EventHandlerEntry[] eventHandlers;
3666 	QueuedEvent[] eventQueue = null;
3667 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3668 	bool inCustomEventProcessor = false; // required to properly remove events
3669 
3670 	// process queued events and call custom event handlers
3671 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3672 	void processCustomEvents () {
3673 		bool hasSomethingToDo = false;
3674 		uint ecount;
3675 		bool ocep;
3676 		synchronized(this) {
3677 			ocep = inCustomEventProcessor;
3678 			inCustomEventProcessor = true;
3679 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3680 			auto ctt = MonoTime.currTime;
3681 			bool hasEmpty = false;
3682 			// mark events to process (this is required for `eventQueued()`)
3683 			foreach (ref qe; eventQueue[0..ecount]) {
3684 				if (qe.evt is null) { hasEmpty = true; continue; }
3685 				if (qe.timed) {
3686 					qe.doProcess = (qe.hittime <= ctt);
3687 				} else {
3688 					qe.doProcess = true;
3689 				}
3690 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3691 			}
3692 			if (!hasSomethingToDo) {
3693 				// remove empty events
3694 				if (hasEmpty) {
3695 					uint eidx = 0, ec = eventQueueUsed;
3696 					auto eptr = eventQueue.ptr;
3697 					while (eidx < ec) {
3698 						if (eptr.evt is null) {
3699 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3700 							ec = --eventQueueUsed;
3701 							eventQueue.ptr[ec].evt = null; // make GC life easier
3702 						} else {
3703 							++eidx;
3704 							++eptr;
3705 						}
3706 					}
3707 				}
3708 				inCustomEventProcessor = ocep;
3709 				return;
3710 			}
3711 		}
3712 		// process marked events
3713 		uint efree = 0; // non-processed events will be put at this index
3714 		EventHandlerEntry[] eh;
3715 		Object evt;
3716 		foreach (immutable eidx; 0..ecount) {
3717 			synchronized(this) {
3718 				if (!eventQueue[eidx].doProcess) {
3719 					// skip this event
3720 					assert(efree <= eidx);
3721 					if (efree != eidx) {
3722 						// copy this event to queue start
3723 						eventQueue[efree] = eventQueue[eidx];
3724 						eventQueue[eidx].evt = null; // just in case
3725 					}
3726 					++efree;
3727 					continue;
3728 				}
3729 				evt = eventQueue[eidx].evt;
3730 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3731 				if (evt is null) continue; // just in case
3732 				// try all handlers; this can be slow, but meh...
3733 				eh = eventHandlers;
3734 			}
3735 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3736 			evt = null;
3737 			eh = null;
3738 		}
3739 		synchronized(this) {
3740 			// move all unprocessed events to queue top; efree holds first "free index"
3741 			foreach (immutable eidx; ecount..eventQueueUsed) {
3742 				assert(efree <= eidx);
3743 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3744 				++efree;
3745 			}
3746 			eventQueueUsed = efree;
3747 			// wake up event processor on next event loop iteration if we have more queued events
3748 			// also, remove empty events
3749 			bool awaken = false;
3750 			uint eidx = 0, ec = eventQueueUsed;
3751 			auto eptr = eventQueue.ptr;
3752 			while (eidx < ec) {
3753 				if (eptr.evt is null) {
3754 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3755 					ec = --eventQueueUsed;
3756 					eventQueue.ptr[ec].evt = null; // make GC life easier
3757 				} else {
3758 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3759 					++eidx;
3760 					++eptr;
3761 				}
3762 			}
3763 			inCustomEventProcessor = ocep;
3764 		}
3765 	}
3766 
3767 	// for all windows in nativeMapping
3768 	package static void processAllCustomEvents () {
3769 
3770 		cleanupQueue.process();
3771 
3772 		justCommunication.processCustomEvents();
3773 
3774 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3775 			if (sw is null || sw.closed) continue;
3776 			sw.processCustomEvents();
3777 		}
3778 
3779 		runPendingRunInGuiThreadDelegates();
3780 	}
3781 
3782 	// 0: infinite (i.e. no scheduled events in queue)
3783 	uint eventQueueTimeoutMSecs () {
3784 		synchronized(this) {
3785 			if (eventQueueUsed == 0) return 0;
3786 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3787 			uint res = int.max;
3788 			auto ctt = MonoTime.currTime;
3789 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3790 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3791 				if (qe.doProcess) continue; // just in case
3792 				if (!qe.timed) return 1; // minimal
3793 				if (qe.hittime <= ctt) return 1; // minimal
3794 				auto tms = (qe.hittime-ctt).total!"msecs";
3795 				if (tms < 1) tms = 1; // safety net
3796 				if (tms >= int.max) tms = int.max-1; // and another safety net
3797 				if (res > tms) res = cast(uint)tms;
3798 			}
3799 			return (res >= int.max ? 0 : res);
3800 		}
3801 	}
3802 
3803 	// for all windows in nativeMapping
3804 	static uint eventAllQueueTimeoutMSecs () {
3805 		uint res = uint.max;
3806 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3807 			if (sw is null || sw.closed) continue;
3808 			uint to = sw.eventQueueTimeoutMSecs();
3809 			if (to && to < res) {
3810 				res = to;
3811 				if (to == 1) break; // can't have less than this
3812 			}
3813 		}
3814 		return (res >= int.max ? 0 : res);
3815 	}
3816 
3817 	version(X11) {
3818 		ResizeEvent pendingResizeEvent;
3819 	}
3820 
3821 	/++
3822 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3823 
3824 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3825 		worth so you can disable it by setting this to `true`.
3826 
3827 		History:
3828 			Added November 13, 2022.
3829 	+/
3830 	public bool suppressAutoOpenglViewport = false;
3831 	private void updateOpenglViewportIfNeeded(int width, int height) {
3832 		if(suppressAutoOpenglViewport) return;
3833 
3834 		version(without_opengl) {} else
3835 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
3836 		// writeln(width, " ", height);
3837 			setAsCurrentOpenGlContextNT();
3838 			glViewport(0, 0, width, height);
3839 		}
3840 	}
3841 }
3842 
3843 version(OSXCocoa)
3844 	enum NSWindow NullWindow = null;
3845 else
3846 	enum NullWindow = NativeWindowHandle.init;
3847 
3848 /++
3849 	Magic pseudo-window for just posting events to a global queue.
3850 
3851 	Not entirely supported, I might delete it at any time.
3852 
3853 	Added Nov 5, 2021.
3854 +/
3855 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
3856 
3857 /* Drag and drop support { */
3858 version(X11) {
3859 
3860 } else version(Windows) {
3861 	import core.sys.windows.uuid;
3862 	import core.sys.windows.ole2;
3863 	import core.sys.windows.oleidl;
3864 	import core.sys.windows.objidl;
3865 	import core.sys.windows.wtypes;
3866 
3867 	pragma(lib, "ole32");
3868 	void initDnd() {
3869 		auto err = OleInitialize(null);
3870 		if(err != S_OK && err != S_FALSE)
3871 			throw new Exception("init");//err);
3872 	}
3873 }
3874 /* } End drag and drop support */
3875 
3876 
3877 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3878 /// See [GenericCursor].
3879 class MouseCursor {
3880 	int osId;
3881 	bool isStockCursor;
3882 	private this(int osId) {
3883 		this.osId = osId;
3884 		this.isStockCursor = true;
3885 	}
3886 
3887 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3888 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3889 
3890 	version(Windows) {
3891 		HCURSOR cursor_;
3892 		HCURSOR cursorHandle() {
3893 			if(cursor_ is null)
3894 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3895 			return cursor_;
3896 		}
3897 
3898 	} else static if(UsingSimpledisplayX11) {
3899 		Cursor cursor_ = None;
3900 		int xDisplaySequence;
3901 
3902 		Cursor cursorHandle() {
3903 			if(this.osId == None)
3904 				return None;
3905 
3906 			// we need to reload if we on a new X connection
3907 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3908 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3909 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3910 			}
3911 			return cursor_;
3912 		}
3913 	}
3914 }
3915 
3916 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3917 // https://tronche.com/gui/x/xlib/appendix/b/
3918 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3919 /// 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.
3920 enum GenericCursorType {
3921 	Default, /// The default arrow pointer.
3922 	Wait, /// A cursor indicating something is loading and the user must wait.
3923 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3924 	Help, /// A cursor indicating the user can get help about the pointer location.
3925 	Cross, /// A crosshair.
3926 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3927 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3928 	UpArrow, /// An arrow pointing straight up.
3929 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3930 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3931 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3932 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3933 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3934 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3935 
3936 }
3937 
3938 /*
3939 	X_plus == css cell == Windows ?
3940 */
3941 
3942 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3943 static struct GenericCursor {
3944 	static:
3945 	///
3946 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3947 		static MouseCursor mc;
3948 
3949 		auto type = __traits(getMember, GenericCursorType, str);
3950 
3951 		if(mc is null) {
3952 
3953 			version(Windows) {
3954 				int osId;
3955 				final switch(type) {
3956 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3957 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3958 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3959 					case GenericCursorType.Help: osId = IDC_HELP; break;
3960 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3961 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3962 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3963 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3964 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3965 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3966 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3967 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3968 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3969 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3970 				}
3971 			} else static if(UsingSimpledisplayX11) {
3972 				int osId;
3973 				final switch(type) {
3974 					case GenericCursorType.Default: osId = None; break;
3975 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3976 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3977 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3978 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3979 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3980 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3981 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3982 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3983 
3984 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3985 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3986 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3987 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3988 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3989 				}
3990 
3991 			} else {
3992 				int osId;
3993 				// featureNotImplemented();
3994 			}
3995 
3996 			mc = new MouseCursor(osId);
3997 		}
3998 		return mc;
3999 	}
4000 }
4001 
4002 
4003 /++
4004 	If you want to get more control over the event loop, you can use this.
4005 
4006 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4007 	to `EventLoop.get.run`.
4008 +/
4009 struct EventLoop {
4010 	@disable this();
4011 
4012 	/// Gets a reference to an existing event loop
4013 	static EventLoop get() {
4014 		return EventLoop(0, null);
4015 	}
4016 
4017 	static void quitApplication() {
4018 		EventLoop.get().exit();
4019 	}
4020 
4021 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4022 
4023 	/// Construct an application-global event loop for yourself
4024 	/// See_Also: [SimpleWindow.setEventHandlers]
4025 	this(long pulseTimeout, void delegate() handlePulse) {
4026 		synchronized(monitor) {
4027 			if(impl is null) {
4028 				claimGuiThread();
4029 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4030 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4031 			} else {
4032 				if(pulseTimeout) {
4033 					impl.pulseTimeout = pulseTimeout;
4034 					impl.handlePulse = handlePulse;
4035 				}
4036 			}
4037 			impl.refcount++;
4038 		}
4039 	}
4040 
4041 	~this() {
4042 		if(impl is null)
4043 			return;
4044 		impl.refcount--;
4045 		if(impl.refcount == 0) {
4046 			impl.dispose();
4047 			if(thisIsGuiThread)
4048 				guiThreadFinalize();
4049 		}
4050 
4051 	}
4052 
4053 	this(this) {
4054 		if(impl is null)
4055 			return;
4056 		impl.refcount++;
4057 	}
4058 
4059 	/// Runs the event loop until the whileCondition, if present, returns false
4060 	int run(bool delegate() whileCondition = null) {
4061 		assert(impl !is null);
4062 		impl.notExited = true;
4063 		return impl.run(whileCondition);
4064 	}
4065 
4066 	/// Exits the event loop
4067 	void exit() {
4068 		assert(impl !is null);
4069 		impl.notExited = false;
4070 	}
4071 
4072 	version(linux)
4073 	ref void delegate(int) signalHandler() {
4074 		assert(impl !is null);
4075 		return impl.signalHandler;
4076 	}
4077 
4078 	__gshared static EventLoopImpl* impl;
4079 }
4080 
4081 version(linux)
4082 	void delegate(int, int) globalHupHandler;
4083 
4084 version(Posix)
4085 	void makeNonBlocking(int fd) {
4086 		import fcntl = core.sys.posix.fcntl;
4087 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4088 		if(flags == -1)
4089 			throw new Exception("fcntl get");
4090 		flags |= fcntl.O_NONBLOCK;
4091 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4092 		if(s == -1)
4093 			throw new Exception("fcntl set");
4094 	}
4095 
4096 struct EventLoopImpl {
4097 	int refcount;
4098 
4099 	bool notExited = true;
4100 
4101 	version(linux) {
4102 		static import ep = core.sys.linux.epoll;
4103 		static import unix = core.sys.posix.unistd;
4104 		static import err = core.stdc.errno;
4105 		import core.sys.linux.timerfd;
4106 
4107 		void delegate(int) signalHandler;
4108 	}
4109 
4110 	version(X11) {
4111 		int pulseFd = -1;
4112 		version(linux) ep.epoll_event[16] events = void;
4113 	} else version(Windows) {
4114 		Timer pulser;
4115 		HANDLE[] handles;
4116 	}
4117 
4118 
4119 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4120 	/// to call this, as it's not recommended to share window between threads.
4121 	void mtLock () {
4122 		version(X11) {
4123 			XLockDisplay(this.display);
4124 		}
4125 	}
4126 
4127 	version(X11)
4128 	auto display() { return XDisplayConnection.get; }
4129 
4130 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4131 	/// to call this, as it's not recommended to share window between threads.
4132 	void mtUnlock () {
4133 		version(X11) {
4134 			XUnlockDisplay(this.display);
4135 		}
4136 	}
4137 
4138 	version(with_eventloop)
4139 	void initialize(long pulseTimeout) {}
4140 	else
4141 	void initialize(long pulseTimeout) {
4142 		version(Windows) {
4143 			if(pulseTimeout && handlePulse !is null)
4144 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4145 
4146 			if (customEventH is null) {
4147 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4148 				if (customEventH !is null) {
4149 					handles ~= customEventH;
4150 				} else {
4151 					// this is something that should not be; better be safe than sorry
4152 					throw new Exception("can't create eventfd for custom event processing");
4153 				}
4154 			}
4155 
4156 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4157 		}
4158 
4159 		version(linux) {
4160 			prepareEventLoop();
4161 			{
4162 				auto display = XDisplayConnection.get;
4163 				// adding Xlib file
4164 				ep.epoll_event ev = void;
4165 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4166 				ev.events = ep.EPOLLIN;
4167 				ev.data.fd = display.fd;
4168 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4169 					throw new Exception("add x fd");// ~ to!string(epollFd));
4170 				displayFd = display.fd;
4171 			}
4172 
4173 			if(pulseTimeout && handlePulse !is null) {
4174 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4175 				if(pulseFd == -1)
4176 					throw new Exception("pulse timer create failed");
4177 
4178 				itimerspec value;
4179 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4180 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4181 
4182 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4183 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4184 
4185 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4186 					throw new Exception("couldn't make pulse timer");
4187 
4188 				ep.epoll_event ev = void;
4189 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4190 				ev.events = ep.EPOLLIN;
4191 				ev.data.fd = pulseFd;
4192 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4193 			}
4194 
4195 			// eventfd for custom events
4196 			if (customEventFDWrite == -1) {
4197 				customEventFDWrite = eventfd(0, 0);
4198 				customEventFDRead = customEventFDWrite;
4199 				if (customEventFDRead >= 0) {
4200 					ep.epoll_event ev = void;
4201 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4202 					ev.events = ep.EPOLLIN;
4203 					ev.data.fd = customEventFDRead;
4204 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4205 				} else {
4206 					// this is something that should not be; better be safe than sorry
4207 					throw new Exception("can't create eventfd for custom event processing");
4208 				}
4209 			}
4210 
4211 			if (customSignalFD == -1) {
4212 				import core.sys.linux.sys.signalfd;
4213 
4214 				sigset_t sigset;
4215 				auto err = sigemptyset(&sigset);
4216 				assert(!err);
4217 				err = sigaddset(&sigset, SIGINT);
4218 				assert(!err);
4219 				err = sigaddset(&sigset, SIGHUP);
4220 				assert(!err);
4221 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4222 				assert(!err);
4223 
4224 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4225 				assert(customSignalFD != -1);
4226 
4227 				ep.epoll_event ev = void;
4228 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4229 				ev.events = ep.EPOLLIN;
4230 				ev.data.fd = customSignalFD;
4231 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4232 			}
4233 		} else version(Posix) {
4234 			prepareEventLoop();
4235 			if (customEventFDRead == -1) {
4236 				int[2] bfr;
4237 				import core.sys.posix.unistd;
4238 				auto ret = pipe(bfr);
4239 				if(ret == -1) throw new Exception("pipe");
4240 				customEventFDRead = bfr[0];
4241 				customEventFDWrite = bfr[1];
4242 			}
4243 
4244 		}
4245 
4246 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4247 
4248 		version(linux) {
4249 			this.mtLock();
4250 			scope(exit) this.mtUnlock();
4251 			XPending(display); // no, really
4252 		}
4253 
4254 		disposed = false;
4255 	}
4256 
4257 	bool disposed = true;
4258 	version(X11)
4259 		int displayFd = -1;
4260 
4261 	version(with_eventloop)
4262 	void dispose() {}
4263 	else
4264 	void dispose() {
4265 		disposed = true;
4266 		version(X11) {
4267 			if(pulseFd != -1) {
4268 				import unix = core.sys.posix.unistd;
4269 				unix.close(pulseFd);
4270 				pulseFd = -1;
4271 			}
4272 
4273 				version(linux)
4274 				if(displayFd != -1) {
4275 					// 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
4276 					ep.epoll_event ev = void;
4277 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4278 					ev.events = ep.EPOLLIN;
4279 					ev.data.fd = displayFd;
4280 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4281 					displayFd = -1;
4282 				}
4283 
4284 		} else version(Windows) {
4285 			if(pulser !is null) {
4286 				pulser.destroy();
4287 				pulser = null;
4288 			}
4289 			if (customEventH !is null) {
4290 				CloseHandle(customEventH);
4291 				customEventH = null;
4292 			}
4293 		}
4294 	}
4295 
4296 	this(long pulseTimeout, void delegate() handlePulse) {
4297 		this.pulseTimeout = pulseTimeout;
4298 		this.handlePulse = handlePulse;
4299 		initialize(pulseTimeout);
4300 	}
4301 
4302 	private long pulseTimeout;
4303 	void delegate() handlePulse;
4304 
4305 	~this() {
4306 		dispose();
4307 	}
4308 
4309 	version(Posix)
4310 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4311 	version(Posix)
4312 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4313 	version(linux)
4314 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4315 	version(Windows)
4316 	ref auto customEventH() { return SimpleWindow.customEventH; }
4317 
4318 	version(with_eventloop) {
4319 		int loopHelper(bool delegate() whileCondition) {
4320 			// FIXME: whileCondition
4321 			import arsd.eventloop;
4322 			loop();
4323 			return 0;
4324 		}
4325 	} else
4326 	int loopHelper(bool delegate() whileCondition) {
4327 		version(X11) {
4328 			bool done = false;
4329 
4330 			XFlush(display);
4331 			insideXEventLoop = true;
4332 			scope(exit) insideXEventLoop = false;
4333 
4334 			version(linux) {
4335 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4336 					bool forceXPending = false;
4337 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4338 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4339 					{
4340 						this.mtLock();
4341 						scope(exit) this.mtUnlock();
4342 						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
4343 					}
4344 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4345 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4346 					if(nfds == -1) {
4347 						if(err.errno == err.EINTR) {
4348 							//if(forceXPending) goto xpending;
4349 							continue; // interrupted by signal, just try again
4350 						}
4351 						throw new Exception("epoll wait failure");
4352 					}
4353 					// writeln(nfds, " ", events[0].data.fd);
4354 
4355 					SimpleWindow.processAllCustomEvents(); // anyway
4356 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4357 					foreach(idx; 0 .. nfds) {
4358 						if(done) break;
4359 						auto fd = events[idx].data.fd;
4360 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4361 						auto flags = events[idx].events;
4362 						if(flags & ep.EPOLLIN) {
4363 							if (fd == customSignalFD) {
4364 								version(linux) {
4365 									import core.sys.linux.sys.signalfd;
4366 									import core.sys.posix.unistd : read;
4367 									signalfd_siginfo info;
4368 									read(customSignalFD, &info, info.sizeof);
4369 
4370 									auto sig = info.ssi_signo;
4371 
4372 									if(EventLoop.get.signalHandler !is null) {
4373 										EventLoop.get.signalHandler()(sig);
4374 									} else {
4375 										EventLoop.get.exit();
4376 									}
4377 								}
4378 							} else if(fd == display.fd) {
4379 								version(sdddd) { writeln("X EVENT PENDING!"); }
4380 								this.mtLock();
4381 								scope(exit) this.mtUnlock();
4382 								while(!done && XPending(display)) {
4383 									done = doXNextEvent(this.display);
4384 								}
4385 								forceXPending = false;
4386 							} else if(fd == pulseFd) {
4387 								long expirationCount;
4388 								// 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...
4389 
4390 								handlePulse();
4391 
4392 								// read just to clear the buffer so poll doesn't trigger again
4393 								// BTW I read AFTER the pulse because if the pulse handler takes
4394 								// a lot of time to execute, we don't want the app to get stuck
4395 								// in a loop of timer hits without a chance to do anything else
4396 								//
4397 								// IOW handlePulse happens at most once per pulse interval.
4398 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4399 								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
4400 							} else if (fd == customEventFDRead) {
4401 								// we have some custom events; process 'em
4402 								import core.sys.posix.unistd : read;
4403 								ulong n;
4404 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4405 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4406 								//SimpleWindow.processAllCustomEvents();
4407 
4408 								forceXPending = true;
4409 							} else {
4410 								// some other timer
4411 								version(sdddd) { writeln("unknown fd: ", fd); }
4412 
4413 								if(Timer* t = fd in Timer.mapping)
4414 									(*t).trigger();
4415 
4416 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4417 									(*pfr).ready(flags);
4418 
4419 								// we don't know what the user did in this timer, so we need to assume that
4420 								// there's X data to be flushed and potentially processed
4421 								forceXPending = true;
4422 
4423 								// or i might add support for other FDs too
4424 								// but for now it is just timer
4425 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4426 							}
4427 						}
4428 						if(flags & ep.EPOLLHUP) {
4429 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4430 								(*pfr).hup(flags);
4431 							if(globalHupHandler)
4432 								globalHupHandler(fd, flags);
4433 						}
4434 						/+
4435 						} else {
4436 							// not interested in OUT, we are just reading here.
4437 							//
4438 							// error or hup might also be reported
4439 							// but it shouldn't here since we are only
4440 							// using a few types of FD and Xlib will report
4441 							// if it dies.
4442 							// so instead of thoughtfully handling it, I'll
4443 							// just throw. for now at least
4444 
4445 							throw new Exception("epoll did something else");
4446 						}
4447 						+/
4448 					}
4449 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4450 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4451 					xpending:
4452 					if (!done && forceXPending) {
4453 						this.mtLock();
4454 						scope(exit) this.mtUnlock();
4455 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4456 						while(!done && XPending(display)) {
4457 							done = doXNextEvent(this.display);
4458 						}
4459 					}
4460 				}
4461 			} else {
4462 				// Generic fallback: yes to simple pulse support,
4463 				// but NO timer support!
4464 
4465 				// FIXME: we could probably support the POSIX timer_create
4466 				// signal-based option, but I'm in no rush to write it since
4467 				// I prefer the fd-based functions.
4468 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4469 
4470 					import core.sys.posix.poll;
4471 
4472 					pollfd[] pfds;
4473 					pollfd[32] pfdsBuffer;
4474 					auto len = PosixFdReader.mapping.length + 2;
4475 					// FIXME: i should just reuse the buffer
4476 					if(len < pfdsBuffer.length)
4477 						pfds = pfdsBuffer[0 .. len];
4478 					else
4479 						pfds = new pollfd[](len);
4480 
4481 					pfds[0].fd = display.fd;
4482 					pfds[0].events = POLLIN;
4483 					pfds[0].revents = 0;
4484 
4485 					int slot = 1;
4486 
4487 					if(customEventFDRead != -1) {
4488 						pfds[slot].fd = customEventFDRead;
4489 						pfds[slot].events = POLLIN;
4490 						pfds[slot].revents = 0;
4491 
4492 						slot++;
4493 					}
4494 
4495 					foreach(fd, obj; PosixFdReader.mapping) {
4496 						if(!obj.enabled) continue;
4497 						pfds[slot].fd = fd;
4498 						pfds[slot].events = POLLIN;
4499 						pfds[slot].revents = 0;
4500 
4501 						slot++;
4502 					}
4503 
4504 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4505 					if(ret == -1) throw new Exception("poll");
4506 
4507 					if(ret == 0) {
4508 						// FIXME it may not necessarily time out if events keep coming
4509 						if(handlePulse !is null)
4510 							handlePulse();
4511 					} else {
4512 						foreach(s; 0 .. slot) {
4513 							if(pfds[s].revents == 0) continue;
4514 
4515 							if(pfds[s].fd == display.fd) {
4516 								while(!done && XPending(display)) {
4517 									this.mtLock();
4518 									scope(exit) this.mtUnlock();
4519 									done = doXNextEvent(this.display);
4520 								}
4521 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4522 
4523 								import core.sys.posix.unistd : read;
4524 								ulong n;
4525 								read(customEventFDRead, &n, n.sizeof);
4526 								SimpleWindow.processAllCustomEvents();
4527 							} else {
4528 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4529 								if(pfds[s].revents & POLLNVAL) {
4530 									obj.dispose();
4531 								} else {
4532 									obj.ready(pfds[s].revents);
4533 								}
4534 							}
4535 
4536 							ret--;
4537 							if(ret == 0) break;
4538 						}
4539 					}
4540 				}
4541 			}
4542 		}
4543 
4544 		version(Windows) {
4545 			int ret = -1;
4546 			MSG message;
4547 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4548 				eventLoopRound++;
4549 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4550 				auto waitResult = MsgWaitForMultipleObjectsEx(
4551 					cast(int) handles.length, handles.ptr,
4552 					(wto == 0 ? INFINITE : wto), /* timeout */
4553 					0x04FF, /* QS_ALLINPUT */
4554 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4555 
4556 				SimpleWindow.processAllCustomEvents(); // anyway
4557 				enum WAIT_OBJECT_0 = 0;
4558 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4559 					auto h = handles[waitResult - WAIT_OBJECT_0];
4560 					if(auto e = h in WindowsHandleReader.mapping) {
4561 						(*e).ready();
4562 					}
4563 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4564 					// message ready
4565 					int count;
4566 					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
4567 						ret = GetMessage(&message, null, 0, 0);
4568 						if(ret == -1)
4569 							throw new WindowsApiException("GetMessage", GetLastError());
4570 						TranslateMessage(&message);
4571 						DispatchMessage(&message);
4572 
4573 						count++;
4574 						if(count > 10)
4575 							break; // take the opportunity to catch up on other events
4576 
4577 						if(ret == 0) { // WM_QUIT
4578 							EventLoop.quitApplication();
4579 							break;
4580 						}
4581 					}
4582 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4583 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4584 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4585 					// timeout, should never happen since we aren't using it
4586 				} else if(waitResult == 0xFFFFFFFF) {
4587 						// failed
4588 						throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4589 				} else {
4590 					// idk....
4591 				}
4592 			}
4593 
4594 			// return message.wParam;
4595 			return 0;
4596 		} else {
4597 			return 0;
4598 		}
4599 	}
4600 
4601 	int run(bool delegate() whileCondition = null) {
4602 		if(disposed)
4603 			initialize(this.pulseTimeout);
4604 
4605 		version(X11) {
4606 			try {
4607 				return loopHelper(whileCondition);
4608 			} catch(XDisconnectException e) {
4609 				if(e.userRequested) {
4610 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4611 						item.discardConnectionState();
4612 					XCloseDisplay(XDisplayConnection.display);
4613 				}
4614 
4615 				XDisplayConnection.display = null;
4616 
4617 				this.dispose();
4618 
4619 				throw e;
4620 			}
4621 		} else {
4622 			return loopHelper(whileCondition);
4623 		}
4624 	}
4625 }
4626 
4627 
4628 /++
4629 	Provides an icon on the system notification area (also known as the system tray).
4630 
4631 
4632 	If a notification area is not available with the NotificationIcon object is created,
4633 	it will silently succeed and simply attempt to create one when an area becomes available.
4634 
4635 
4636 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
4637 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
4638 	with true color was added at that time. I was just too lazy to write the fallback.
4639 
4640 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
4641 	you use arsd 10.x when targeting Windows XP.
4642 +/
4643 version(OSXCocoa) {} else // NotYetImplementedException
4644 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4645 
4646 	version(X11) {
4647 		void recreateAfterDisconnect() {
4648 			stateDiscarded = false;
4649 			clippixmap = None;
4650 			throw new Exception("NOT IMPLEMENTED");
4651 		}
4652 
4653 		bool stateDiscarded;
4654 		void discardConnectionState() {
4655 			stateDiscarded = true;
4656 		}
4657 	}
4658 
4659 
4660 	version(X11) {
4661 		Image img;
4662 
4663 		NativeEventHandler getNativeEventHandler() {
4664 			return delegate int(XEvent e) {
4665 				switch(e.type) {
4666 					case EventType.Expose:
4667 					//case EventType.VisibilityNotify:
4668 						redraw();
4669 					break;
4670 					case EventType.ClientMessage:
4671 						version(sddddd) {
4672 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4673 						writeln("\t", e.xclient.format);
4674 						writeln("\t", e.xclient.data.l);
4675 						}
4676 					break;
4677 					case EventType.ButtonPress:
4678 						auto event = e.xbutton;
4679 						if (onClick !is null || onClickEx !is null) {
4680 							MouseButton mb = cast(MouseButton)0;
4681 							switch (event.button) {
4682 								case 1: mb = MouseButton.left; break; // left
4683 								case 2: mb = MouseButton.middle; break; // middle
4684 								case 3: mb = MouseButton.right; break; // right
4685 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4686 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4687 								case 6: break; // scroll left...
4688 								case 7: break; // scroll right...
4689 								case 8: mb = MouseButton.backButton; break;
4690 								case 9: mb = MouseButton.forwardButton; break;
4691 								default:
4692 							}
4693 							if (mb) {
4694 								try { onClick()(mb); } catch (Exception) {}
4695 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4696 							}
4697 						}
4698 					break;
4699 					case EventType.EnterNotify:
4700 						if (onEnter !is null) {
4701 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4702 						}
4703 						break;
4704 					case EventType.LeaveNotify:
4705 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4706 						break;
4707 					case EventType.DestroyNotify:
4708 						active = false;
4709 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4710 					break;
4711 					case EventType.ConfigureNotify:
4712 						auto event = e.xconfigure;
4713 						this.width = event.width;
4714 						this.height = event.height;
4715 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4716 						redraw();
4717 					break;
4718 					default: return 1;
4719 				}
4720 				return 1;
4721 			};
4722 		}
4723 
4724 		/* private */ void hideBalloon() {
4725 			balloon.close();
4726 			version(with_timer)
4727 				timer.destroy();
4728 			balloon = null;
4729 			version(with_timer)
4730 				timer = null;
4731 		}
4732 
4733 		void redraw() {
4734 			if (!active) return;
4735 
4736 			auto display = XDisplayConnection.get;
4737 			GC gc;
4738 
4739 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
4740 
4741 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
4742 				Visual *visual;
4743 				XVisualInfo vis_info;
4744 				XSetWindowAttributes win_attr;
4745 				c_ulong win_mask;
4746 
4747 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
4748 					assert(0);
4749 					// return 1;
4750 				}
4751 
4752 				visual = vis_info.visual;
4753 
4754 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
4755 				win_attr.background_pixel = 0;
4756 				win_attr.border_pixel = 0;
4757 
4758 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
4759 
4760 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
4761 
4762 				return 0;
4763 			}
4764 
4765 			if(useAlpha)
4766 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
4767 			else
4768 				gc = DefaultGC(display, DefaultScreen(display));
4769 
4770 			XClearWindow(display, nativeHandle);
4771 
4772 			if(!useAlpha && img !is null)
4773 				XSetClipMask(display, gc, clippixmap);
4774 
4775 			/+
4776 			XSetForeground(display, gc,
4777 				cast(uint) 0 << 16 |
4778 				cast(uint) 0 << 8 |
4779 				cast(uint) 0);
4780 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4781 			+/
4782 
4783 			if (img is null) {
4784 				XSetForeground(display, gc,
4785 					cast(uint) 0 << 16 |
4786 					cast(uint) 127 << 8 |
4787 					cast(uint) 0);
4788 				XFillArc(display, nativeHandle,
4789 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4790 			} else {
4791 				int dx = 0;
4792 				int dy = 0;
4793 				if(width > img.width)
4794 					dx = (width - img.width) / 2;
4795 				if(height > img.height)
4796 					dy = (height - img.height) / 2;
4797 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
4798 				XSetClipOrigin(display, gc, dx, dy);
4799 
4800 				int max(int a, int b) {
4801 					if(a > b) return a; else return b;
4802 				}
4803 
4804 				if (img.usingXshm)
4805 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
4806 				else
4807 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
4808 			}
4809 			XSetClipMask(display, gc, None);
4810 			flushGui();
4811 		}
4812 
4813 		static Window getTrayOwner() {
4814 			auto display = XDisplayConnection.get;
4815 			auto i = cast(int) DefaultScreen(display);
4816 			if(i < 10 && i >= 0) {
4817 				static Atom atom;
4818 				if(atom == None)
4819 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4820 				return XGetSelectionOwner(display, atom);
4821 			}
4822 			return None;
4823 		}
4824 
4825 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4826 			auto to = getTrayOwner();
4827 			auto display = XDisplayConnection.get;
4828 			XEvent ev;
4829 			ev.xclient.type = EventType.ClientMessage;
4830 			ev.xclient.window = to;
4831 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4832 			ev.xclient.format = 32;
4833 			ev.xclient.data.l[0] = CurrentTime;
4834 			ev.xclient.data.l[1] = message;
4835 			ev.xclient.data.l[2] = d1;
4836 			ev.xclient.data.l[3] = d2;
4837 			ev.xclient.data.l[4] = d3;
4838 
4839 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4840 		}
4841 
4842 		private static NotificationAreaIcon[] activeIcons;
4843 
4844 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4845 		private void newManager() {
4846 			close();
4847 			createXWin();
4848 
4849 			if(this.clippixmap)
4850 				XFreePixmap(XDisplayConnection.get, clippixmap);
4851 			if(this.originalMemoryImage)
4852 				this.icon = this.originalMemoryImage;
4853 			else if(this.img)
4854 				this.icon = this.img;
4855 		}
4856 
4857 		private bool useAlpha = false;
4858 
4859 		private void createXWin () {
4860 			// create window
4861 			auto display = XDisplayConnection.get;
4862 
4863 			// to check for MANAGER on root window to catch new/changed tray owners
4864 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4865 			// so if a thing does appear, we can handle it
4866 			foreach(ai; activeIcons)
4867 				if(ai is this)
4868 					goto alreadythere;
4869 			activeIcons ~= this;
4870 			alreadythere:
4871 
4872 			// and check for an existing tray
4873 			auto trayOwner = getTrayOwner();
4874 			if(trayOwner == None)
4875 				return;
4876 				//throw new Exception("No notification area found");
4877 
4878 			Visual* v = cast(Visual*) CopyFromParent;
4879 
4880 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
4881 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
4882 			// a resize event later.
4883 			width = 22;
4884 			height = 22;
4885 
4886 			// if they system gave us a 32 bit visual we need to switch to it too
4887 			int depth = 24;
4888 
4889 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4890 			if(visualProp !is null) {
4891 				c_ulong[] info = cast(c_ulong[]) visualProp;
4892 				if(info.length == 1) {
4893 					auto vid = info[0];
4894 					int returned;
4895 					XVisualInfo t;
4896 					t.visualid = vid;
4897 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4898 					if(got !is null) {
4899 						if(returned == 1) {
4900 							v = got.visual;
4901 							depth = got.depth;
4902 							// writeln("using special visual ", got.depth);
4903 							// writeln(depth);
4904 						}
4905 						XFree(got);
4906 					}
4907 				}
4908 			}
4909 
4910 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
4911 			XSetWindowAttributes attr;
4912 			attr.background_pixel = 0;
4913 			attr.border_pixel = 0;
4914 			attr.override_redirect = 0;
4915 			if(v !is cast(Visual*) CopyFromParent) {
4916 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
4917 				CWFlags |= CWColormap;
4918 				if(depth == 32)
4919 					useAlpha = true;
4920 				else
4921 					goto plain;
4922 			} else {
4923 				plain:
4924 				attr.background_pixmap = 1 /* ParentRelative */;
4925 				CWFlags |= CWBackPixmap;
4926 			}
4927 
4928 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
4929 
4930 			assert(nativeWindow);
4931 
4932 			if(!useAlpha)
4933 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4934 
4935 			nativeHandle = nativeWindow;
4936 
4937 			///+
4938 			arch_ulong[2] info;
4939 			info[0] = 0;
4940 			info[1] = 1;
4941 
4942 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4943 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4944 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4945 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4946 
4947 			XChangeProperty(
4948 				display,
4949 				nativeWindow,
4950 				GetAtom!("_XEMBED_INFO", true)(display),
4951 				GetAtom!("_XEMBED_INFO", true)(display),
4952 				32 /* bits */,
4953 				0 /*PropModeReplace*/,
4954 				info.ptr,
4955 				2);
4956 
4957 			import core.sys.posix.unistd;
4958 			arch_ulong pid = getpid();
4959 
4960 			XChangeProperty(
4961 				display,
4962 				nativeWindow,
4963 				GetAtom!("_NET_WM_PID", true)(display),
4964 				XA_CARDINAL,
4965 				32 /* bits */,
4966 				0 /*PropModeReplace*/,
4967 				&pid,
4968 				1);
4969 
4970 			updateNetWmIcon();
4971 
4972 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4973 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4974 				XClassHint klass;
4975 				XWMHints wh;
4976 				XSizeHints size;
4977 				klass.res_name = sdpyWindowClassStr;
4978 				klass.res_class = sdpyWindowClassStr;
4979 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4980 			}
4981 
4982 				// believe it or not, THIS is what xfce needed for the 9999 issue
4983 				XSizeHints sh;
4984 				c_long spr;
4985 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4986 				sh.flags |= PMaxSize | PMinSize;
4987 				// FIXME maybe nicer resizing
4988 				sh.min_width = 16;
4989 				sh.min_height = 16;
4990 				sh.max_width = 22;
4991 				sh.max_height = 22;
4992 				XSetWMNormalHints(display, nativeWindow, &sh);
4993 
4994 
4995 			//+/
4996 
4997 
4998 			XSelectInput(display, nativeWindow,
4999 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
5000 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
5001 
5002 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5003 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5004 
5005 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5006 			active = true;
5007 		}
5008 
5009 		void updateNetWmIcon() {
5010 			if(img is null) return;
5011 			auto display = XDisplayConnection.get;
5012 			// FIXME: ensure this is correct
5013 			arch_ulong[] buffer;
5014 			auto imgMi = img.toTrueColorImage;
5015 			buffer ~= imgMi.width;
5016 			buffer ~= imgMi.height;
5017 			foreach(c; imgMi.imageData.colors) {
5018 				arch_ulong b;
5019 				b |= c.a << 24;
5020 				b |= c.r << 16;
5021 				b |= c.g << 8;
5022 				b |= c.b;
5023 				buffer ~= b;
5024 			}
5025 
5026 			XChangeProperty(
5027 				display,
5028 				nativeHandle,
5029 				GetAtom!"_NET_WM_ICON"(display),
5030 				GetAtom!"CARDINAL"(display),
5031 				32 /* bits */,
5032 				0 /*PropModeReplace*/,
5033 				buffer.ptr,
5034 				cast(int) buffer.length);
5035 		}
5036 
5037 
5038 
5039 		private SimpleWindow balloon;
5040 		version(with_timer)
5041 		private Timer timer;
5042 
5043 		private Window nativeHandle;
5044 		private Pixmap clippixmap = None;
5045 		private int width = 16;
5046 		private int height = 16;
5047 		private bool active = false;
5048 
5049 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5050 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5051 		void delegate () onLeave; /// X11 only.
5052 
5053 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5054 
5055 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5056 		void getWindowRect (out int x, out int y, out int width, out int height) {
5057 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5058 			Window dummyw;
5059 			auto dpy = XDisplayConnection.get;
5060 			//XWindowAttributes xwa;
5061 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5062 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5063 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5064 			width = this.width;
5065 			height = this.height;
5066 		}
5067 	}
5068 
5069 	/+
5070 		What I actually want from this:
5071 
5072 		* set / change: icon, tooltip
5073 		* handle: mouse click, right click
5074 		* show: notification bubble.
5075 	+/
5076 
5077 	version(Windows) {
5078 		WindowsIcon win32Icon;
5079 		HWND hwnd;
5080 
5081 		NOTIFYICONDATAW data;
5082 
5083 		NativeEventHandler getNativeEventHandler() {
5084 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5085 				if(msg == WM_USER) {
5086 					auto event = LOWORD(lParam);
5087 					auto iconId = HIWORD(lParam);
5088 					//auto x = GET_X_LPARAM(wParam);
5089 					//auto y = GET_Y_LPARAM(wParam);
5090 					switch(event) {
5091 						case WM_LBUTTONDOWN:
5092 							onClick()(MouseButton.left);
5093 						break;
5094 						case WM_RBUTTONDOWN:
5095 							onClick()(MouseButton.right);
5096 						break;
5097 						case WM_MBUTTONDOWN:
5098 							onClick()(MouseButton.middle);
5099 						break;
5100 						case WM_MOUSEMOVE:
5101 							// sent, we could use it.
5102 						break;
5103 						case WM_MOUSEWHEEL:
5104 							// NOT SENT
5105 						break;
5106 						//case NIN_KEYSELECT:
5107 						//case NIN_SELECT:
5108 						//break;
5109 						default: {}
5110 					}
5111 				}
5112 				return 0;
5113 			};
5114 		}
5115 
5116 		enum NIF_SHOWTIP = 0x00000080;
5117 
5118 		private static struct NOTIFYICONDATAW {
5119 			DWORD cbSize;
5120 			HWND  hWnd;
5121 			UINT  uID;
5122 			UINT  uFlags;
5123 			UINT  uCallbackMessage;
5124 			HICON hIcon;
5125 			WCHAR[128] szTip;
5126 			DWORD dwState;
5127 			DWORD dwStateMask;
5128 			WCHAR[256] szInfo;
5129 			union {
5130 				UINT uTimeout;
5131 				UINT uVersion;
5132 			}
5133 			WCHAR[64] szInfoTitle;
5134 			DWORD dwInfoFlags;
5135 			GUID  guidItem;
5136 			HICON hBalloonIcon;
5137 		}
5138 
5139 	}
5140 
5141 	/++
5142 		Note that on Windows, only left, right, and middle buttons are sent.
5143 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5144 		program is meant to be used on Windows too.
5145 	+/
5146 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5147 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5148 		// but on X, we need an Image, so its canonical ctor is there. They should
5149 		// forward to each other though.
5150 		version(X11) {
5151 			this.name = name;
5152 			this.onClick = onClick;
5153 			createXWin();
5154 			this.icon = icon;
5155 		} else version(Windows) {
5156 			this.onClick = onClick;
5157 			this.win32Icon = new WindowsIcon(icon);
5158 
5159 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5160 
5161 			static bool registered = false;
5162 			if(!registered) {
5163 				WNDCLASSEX wc;
5164 				wc.cbSize = wc.sizeof;
5165 				wc.hInstance = hInstance;
5166 				wc.lpfnWndProc = &WndProc;
5167 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5168 				if(!RegisterClassExW(&wc))
5169 					throw new WindowsApiException("RegisterClass", GetLastError());
5170 				registered = true;
5171 			}
5172 
5173 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5174 			if(hwnd is null)
5175 				throw new WindowsApiException("CreateWindow", GetLastError());
5176 
5177 			data.cbSize = data.sizeof;
5178 			data.hWnd = hwnd;
5179 			data.uID = cast(uint) cast(void*) this;
5180 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5181 				// NIF_INFO means show balloon
5182 			data.uCallbackMessage = WM_USER;
5183 			data.hIcon = this.win32Icon.hIcon;
5184 			data.szTip = ""; // FIXME
5185 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5186 			data.dwStateMask = NIS_HIDDEN; // windows vista
5187 
5188 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5189 
5190 
5191 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5192 
5193 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5194 		} else version(OSXCocoa) {
5195 			throw new NotYetImplementedException();
5196 		} else static assert(0);
5197 	}
5198 
5199 	/// ditto
5200 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5201 		version(X11) {
5202 			this.onClick = onClick;
5203 			this.name = name;
5204 			createXWin();
5205 			this.icon = icon;
5206 		} else version(Windows) {
5207 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5208 		} else version(OSXCocoa) {
5209 			throw new NotYetImplementedException();
5210 		} else static assert(0);
5211 	}
5212 
5213 	version(X11) {
5214 		/++
5215 			X-specific extension (for now at least)
5216 		+/
5217 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5218 			this.onClickEx = onClickEx;
5219 			createXWin();
5220 			if (icon !is null) this.icon = icon;
5221 		}
5222 
5223 		/// ditto
5224 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5225 			this.onClickEx = onClickEx;
5226 			createXWin();
5227 			this.icon = icon;
5228 		}
5229 	}
5230 
5231 	private void delegate (MouseButton button) onClick_;
5232 
5233 	///
5234 	@property final void delegate(MouseButton) onClick() {
5235 		if(onClick_ is null)
5236 			onClick_ = delegate void(MouseButton) {};
5237 		return onClick_;
5238 	}
5239 
5240 	/// ditto
5241 	@property final void onClick(void delegate(MouseButton) handler) {
5242 		// I made this a property setter so we can wrap smaller arg
5243 		// delegates and just forward all to onClickEx or something.
5244 		onClick_ = handler;
5245 	}
5246 
5247 
5248 	string name_;
5249 	@property void name(string n) {
5250 		name_ = n;
5251 	}
5252 
5253 	@property string name() {
5254 		return name_;
5255 	}
5256 
5257 	private MemoryImage originalMemoryImage;
5258 
5259 	///
5260 	@property void icon(MemoryImage i) {
5261 		version(X11) {
5262 			this.originalMemoryImage = i;
5263 			if (!active) return;
5264 			if (i !is null) {
5265 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5266 				if(!useAlpha)
5267 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5268 				// writeln("using pixmap ", clippixmap);
5269 				updateNetWmIcon();
5270 				redraw();
5271 			} else {
5272 				if (this.img !is null) {
5273 					this.img = null;
5274 					redraw();
5275 				}
5276 			}
5277 		} else version(Windows) {
5278 			this.win32Icon = new WindowsIcon(i);
5279 
5280 			data.uFlags = NIF_ICON;
5281 			data.hIcon = this.win32Icon.hIcon;
5282 
5283 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5284 		} else version(OSXCocoa) {
5285 			throw new NotYetImplementedException();
5286 		} else static assert(0);
5287 	}
5288 
5289 	/// ditto
5290 	@property void icon (Image i) {
5291 		version(X11) {
5292 			if (!active) return;
5293 			if (i !is img) {
5294 				originalMemoryImage = null;
5295 				img = i;
5296 				redraw();
5297 			}
5298 		} else version(Windows) {
5299 			this.icon(i is null ? null : i.toTrueColorImage());
5300 		} else version(OSXCocoa) {
5301 			throw new NotYetImplementedException();
5302 		} else static assert(0);
5303 	}
5304 
5305 	/++
5306 		Shows a balloon notification. You can only show one balloon at a time, if you call
5307 		it twice while one is already up, the first balloon will be replaced.
5308 
5309 
5310 		The user is free to block notifications and they will automatically disappear after
5311 		a timeout period.
5312 
5313 		Params:
5314 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5315 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5316 			icon = the icon to display with the notification. If null, it uses your existing icon.
5317 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5318 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5319 	+/
5320 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5321 		bool useCustom = true;
5322 		version(libnotify) {
5323 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5324 			try {
5325 				if(!active) return;
5326 
5327 				if(libnotify is null) {
5328 					libnotify = new C_DynamicLibrary("libnotify.so");
5329 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5330 				}
5331 
5332 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5333 
5334 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5335 
5336 				if(onclick) {
5337 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5338 					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);
5339 					libnotify_action_delegates_count++;
5340 				}
5341 
5342 				// FIXME icon
5343 
5344 				// set hint image-data
5345 				// set default action for onclick
5346 
5347 				void* error;
5348 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5349 
5350 				useCustom = false;
5351 			} catch(Exception e) {
5352 
5353 			}
5354 		}
5355 
5356 		version(X11) {
5357 		if(useCustom) {
5358 			if(!active) return;
5359 			if(balloon) {
5360 				hideBalloon();
5361 			}
5362 			// I know there are two specs for this, but one is never
5363 			// implemented by any window manager I have ever seen, and
5364 			// the other is a bloated mess and too complicated for simpledisplay...
5365 			// so doing my own little window instead.
5366 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5367 
5368 			int x, y, width, height;
5369 			getWindowRect(x, y, width, height);
5370 
5371 			int bx = x - balloon.width;
5372 			int by = y - balloon.height;
5373 			if(bx < 0)
5374 				bx = x + width + balloon.width;
5375 			if(by < 0)
5376 				by = y + height;
5377 
5378 			// just in case, make sure it is actually on scren
5379 			if(bx < 0)
5380 				bx = 0;
5381 			if(by < 0)
5382 				by = 0;
5383 
5384 			balloon.move(bx, by);
5385 			auto painter = balloon.draw();
5386 			painter.fillColor = Color(220, 220, 220);
5387 			painter.outlineColor = Color.black;
5388 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5389 			auto iconWidth = icon is null ? 0 : icon.width;
5390 			if(icon)
5391 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5392 			iconWidth += 6; // margin around the icon
5393 
5394 			// draw a close button
5395 			painter.outlineColor = Color(44, 44, 44);
5396 			painter.fillColor = Color(255, 255, 255);
5397 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5398 			painter.pen = Pen(Color.black, 3);
5399 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5400 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5401 			painter.pen = Pen(Color.black, 1);
5402 			painter.fillColor = Color(220, 220, 220);
5403 
5404 			// Draw the title and message
5405 			painter.drawText(Point(4 + iconWidth, 4), title);
5406 			painter.drawLine(
5407 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5408 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5409 			);
5410 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5411 
5412 			balloon.setEventHandlers(
5413 				(MouseEvent ev) {
5414 					if(ev.type == MouseEventType.buttonPressed) {
5415 						if(ev.x > balloon.width - 16 && ev.y < 16)
5416 							hideBalloon();
5417 						else if(onclick)
5418 							onclick();
5419 					}
5420 				}
5421 			);
5422 			balloon.show();
5423 
5424 			version(with_timer)
5425 			timer = new Timer(timeout, &hideBalloon);
5426 			else {} // FIXME
5427 		}
5428 		} else version(Windows) {
5429 			enum NIF_INFO = 0x00000010;
5430 
5431 			data.uFlags = NIF_INFO;
5432 
5433 			// FIXME: go back to the last valid unicode code point
5434 			if(title.length > 40)
5435 				title = title[0 .. 40];
5436 			if(message.length > 220)
5437 				message = message[0 .. 220];
5438 
5439 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5440 			enum NIIF_LARGE_ICON  = 0x00000020;
5441 			enum NIIF_NOSOUND = 0x00000010;
5442 			enum NIIF_USER = 0x00000004;
5443 			enum NIIF_ERROR = 0x00000003;
5444 			enum NIIF_WARNING = 0x00000002;
5445 			enum NIIF_INFO = 0x00000001;
5446 			enum NIIF_NONE = 0;
5447 
5448 			WCharzBuffer t = WCharzBuffer(title);
5449 			WCharzBuffer m = WCharzBuffer(message);
5450 
5451 			t.copyInto(data.szInfoTitle);
5452 			m.copyInto(data.szInfo);
5453 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5454 
5455 			if(icon !is null) {
5456 				auto i = new WindowsIcon(icon);
5457 				data.hBalloonIcon = i.hIcon;
5458 				data.dwInfoFlags |= NIIF_USER;
5459 			}
5460 
5461 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5462 		} else version(OSXCocoa) {
5463 			throw new NotYetImplementedException();
5464 		} else static assert(0);
5465 	}
5466 
5467 	///
5468 	//version(Windows)
5469 	void show() {
5470 		version(X11) {
5471 			if(!hidden)
5472 				return;
5473 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5474 			hidden = false;
5475 		} else version(Windows) {
5476 			data.uFlags = NIF_STATE;
5477 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5478 			data.dwStateMask = NIS_HIDDEN; // windows vista
5479 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5480 		} else version(OSXCocoa) {
5481 			throw new NotYetImplementedException();
5482 		} else static assert(0);
5483 	}
5484 
5485 	version(X11)
5486 		bool hidden = false;
5487 
5488 	///
5489 	//version(Windows)
5490 	void hide() {
5491 		version(X11) {
5492 			if(hidden)
5493 				return;
5494 			hidden = true;
5495 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5496 		} else version(Windows) {
5497 			data.uFlags = NIF_STATE;
5498 			data.dwState = NIS_HIDDEN; // windows vista
5499 			data.dwStateMask = NIS_HIDDEN; // windows vista
5500 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5501 		} else version(OSXCocoa) {
5502 			throw new NotYetImplementedException();
5503 		} else static assert(0);
5504 	}
5505 
5506 	///
5507 	void close () {
5508 		version(X11) {
5509 			if (active) {
5510 				active = false; // event handler will set this too, but meh
5511 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5512 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5513 				flushGui();
5514 			}
5515 		} else version(Windows) {
5516 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5517 		} else version(OSXCocoa) {
5518 			throw new NotYetImplementedException();
5519 		} else static assert(0);
5520 	}
5521 
5522 	~this() {
5523 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5524 		version(X11)
5525 			if(clippixmap != None)
5526 				XFreePixmap(XDisplayConnection.get, clippixmap);
5527 		close();
5528 	}
5529 }
5530 
5531 version(X11)
5532 /// Call `XFreePixmap` on the return value.
5533 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5534 	char[] data = new char[](i.width * i.height / 8 + 2);
5535 	data[] = 0;
5536 
5537 	int bitOffset = 0;
5538 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5539 		ubyte v = c.a > 128 ? 1 : 0;
5540 		data[bitOffset / 8] |= v << (bitOffset%8);
5541 		bitOffset++;
5542 	}
5543 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5544 	return handle;
5545 }
5546 
5547 
5548 // basic functions to make timers
5549 /**
5550 	A timer that will trigger your function on a given interval.
5551 
5552 
5553 	You create a timer with an interval and a callback. It will continue
5554 	to fire on the interval until it is destroyed.
5555 
5556 	There are currently no one-off timers (instead, just create one and
5557 	destroy it when it is triggered) nor are there pause/resume functions -
5558 	the timer must again be destroyed and recreated if you want to pause it.
5559 
5560 	---
5561 	auto timer = new Timer(50, { it happened!; });
5562 	timer.destroy();
5563 	---
5564 
5565 	Timers can only be expected to fire when the event loop is running and only
5566 	once per iteration through the event loop.
5567 
5568 	History:
5569 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5570 		slow could lock up the event loop. It now guarantees other things will
5571 		get a chance to run between timer calls, even if that means not keeping up
5572 		with the requested interval.
5573 */
5574 version(with_timer) {
5575 class Timer {
5576 // FIXME: needs pause and unpause
5577 	// FIXME: I might add overloads for ones that take a count of
5578 	// how many elapsed since last time (on Windows, it will divide
5579 	// the ticks thing given, on Linux it is just available) and
5580 	// maybe one that takes an instance of the Timer itself too
5581 	/// Create a timer with a callback when it triggers.
5582 	this(int intervalInMilliseconds, void delegate() onPulse) {
5583 		assert(onPulse !is null);
5584 
5585 		this.intervalInMilliseconds = intervalInMilliseconds;
5586 		this.onPulse = onPulse;
5587 
5588 		version(Windows) {
5589 			/*
5590 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5591 			if(handle == 0)
5592 				throw new WindowsApiException("SetTimer", GetLastError());
5593 			*/
5594 
5595 			// thanks to Archival 998 for the WaitableTimer blocks
5596 			handle = CreateWaitableTimer(null, false, null);
5597 			long initialTime = -intervalInMilliseconds;
5598 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5599 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5600 
5601 			mapping[handle] = this;
5602 
5603 		} else version(linux) {
5604 			static import ep = core.sys.linux.epoll;
5605 
5606 			import core.sys.linux.timerfd;
5607 
5608 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5609 			if(fd == -1)
5610 				throw new Exception("timer create failed");
5611 
5612 			mapping[fd] = this;
5613 
5614 			itimerspec value = makeItimerspec(intervalInMilliseconds);
5615 
5616 			if(timerfd_settime(fd, 0, &value, null) == -1)
5617 				throw new Exception("couldn't make pulse timer");
5618 
5619 			version(with_eventloop) {
5620 				import arsd.eventloop;
5621 				addFileEventListeners(fd, &trigger, null, null);
5622 			} else {
5623 				prepareEventLoop();
5624 
5625 				ep.epoll_event ev = void;
5626 				ev.events = ep.EPOLLIN;
5627 				ev.data.fd = fd;
5628 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5629 			}
5630 		} else featureNotImplemented();
5631 	}
5632 
5633 	private int intervalInMilliseconds;
5634 
5635 	// just cuz I sometimes call it this.
5636 	alias dispose = destroy;
5637 
5638 	/// Stop and destroy the timer object.
5639 	void destroy() {
5640 		version(Windows) {
5641 			staticDestroy(handle);
5642 			handle = null;
5643 		} else version(linux) {
5644 			staticDestroy(fd);
5645 			fd = -1;
5646 		} else featureNotImplemented();
5647 	}
5648 
5649 	version(Windows)
5650 	static void staticDestroy(HANDLE handle) {
5651 		if(handle) {
5652 			// KillTimer(null, handle);
5653 			CancelWaitableTimer(cast(void*)handle);
5654 			mapping.remove(handle);
5655 			CloseHandle(handle);
5656 		}
5657 	}
5658 	else version(linux)
5659 	static void staticDestroy(int fd) {
5660 		if(fd != -1) {
5661 			import unix = core.sys.posix.unistd;
5662 			static import ep = core.sys.linux.epoll;
5663 
5664 			version(with_eventloop) {
5665 				import arsd.eventloop;
5666 				removeFileEventListeners(fd);
5667 			} else {
5668 				ep.epoll_event ev = void;
5669 				ev.events = ep.EPOLLIN;
5670 				ev.data.fd = fd;
5671 
5672 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5673 			}
5674 			unix.close(fd);
5675 			mapping.remove(fd);
5676 		}
5677 	}
5678 
5679 	~this() {
5680 		version(Windows) { if(handle)
5681 			cleanupQueue.queue!staticDestroy(handle);
5682 		} else version(linux) { if(fd != -1)
5683 			cleanupQueue.queue!staticDestroy(fd);
5684 		}
5685 	}
5686 
5687 	void changeTime(int intervalInMilliseconds)
5688 	{
5689 		this.intervalInMilliseconds = intervalInMilliseconds;
5690 		version(Windows)
5691 		{
5692 			if(handle)
5693 			{
5694 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5695 				long initialTime = -intervalInMilliseconds;
5696 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5697 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
5698 			}
5699 		} else version(linux) {
5700 			import core.sys.linux.timerfd;
5701 
5702 			itimerspec value = makeItimerspec(intervalInMilliseconds);
5703 			if(timerfd_settime(fd, 0, &value, null) == -1) {
5704 				throw new Exception("couldn't change pulse timer");
5705 			}
5706 		} else {
5707 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
5708 		}
5709 	}
5710 
5711 
5712 	private:
5713 
5714 	void delegate() onPulse;
5715 
5716 	int lastEventLoopRoundTriggered;
5717 
5718 	version(linux) {
5719 		static auto makeItimerspec(int intervalInMilliseconds) {
5720 			import core.sys.linux.timerfd;
5721 
5722 			itimerspec value;
5723 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5724 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5725 
5726 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5727 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5728 
5729 			return value;
5730 		}
5731 	}
5732 
5733 	void trigger() {
5734 		version(linux) {
5735 			import unix = core.sys.posix.unistd;
5736 			long val;
5737 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5738 		} else version(Windows) {
5739 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5740 				return; // never try to actually run faster than the event loop
5741 			lastEventLoopRoundTriggered = eventLoopRound;
5742 		} else featureNotImplemented();
5743 
5744 		onPulse();
5745 	}
5746 
5747 	version(Windows)
5748 	void rearm() {
5749 
5750 	}
5751 
5752 	version(Windows)
5753 		extern(Windows)
5754 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5755 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5756 			if(Timer* t = timer in mapping) {
5757 				try
5758 				(*t).trigger();
5759 				catch(Exception e) { sdpy_abort(e); assert(0); }
5760 			}
5761 		}
5762 
5763 	version(Windows) {
5764 		//UINT_PTR handle;
5765 		//static Timer[UINT_PTR] mapping;
5766 		HANDLE handle;
5767 		__gshared Timer[HANDLE] mapping;
5768 	} else version(linux) {
5769 		int fd = -1;
5770 		__gshared Timer[int] mapping;
5771 	} else version(OSXCocoa) {
5772 	} else static assert(0, "timer not supported");
5773 }
5774 }
5775 
5776 version(Windows)
5777 private int eventLoopRound;
5778 
5779 version(Windows)
5780 /// 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
5781 class WindowsHandleReader {
5782 	///
5783 	this(void delegate() onReady, HANDLE handle) {
5784 		this.onReady = onReady;
5785 		this.handle = handle;
5786 
5787 		mapping[handle] = this;
5788 
5789 		enable();
5790 	}
5791 
5792 	///
5793 	void enable() {
5794 		auto el = EventLoop.get().impl;
5795 		el.handles ~= handle;
5796 	}
5797 
5798 	///
5799 	void disable() {
5800 		auto el = EventLoop.get().impl;
5801 		for(int i = 0; i < el.handles.length; i++) {
5802 			if(el.handles[i] is handle) {
5803 				el.handles[i] = el.handles[$-1];
5804 				el.handles = el.handles[0 .. $-1];
5805 				return;
5806 			}
5807 		}
5808 	}
5809 
5810 	void dispose() {
5811 		disable();
5812 		if(handle)
5813 			mapping.remove(handle);
5814 		handle = null;
5815 	}
5816 
5817 	void ready() {
5818 		if(onReady)
5819 			onReady();
5820 	}
5821 
5822 	HANDLE handle;
5823 	void delegate() onReady;
5824 
5825 	__gshared WindowsHandleReader[HANDLE] mapping;
5826 }
5827 
5828 version(Posix)
5829 /// Lets you add files to the event loop for reading. Use at your own risk.
5830 class PosixFdReader {
5831 	///
5832 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5833 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5834 	}
5835 
5836 	///
5837 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5838 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5839 	}
5840 
5841 	///
5842 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5843 		this.onReady = onReady;
5844 		this.fd = fd;
5845 		this.captureWrites = captureWrites;
5846 		this.captureReads = captureReads;
5847 
5848 		mapping[fd] = this;
5849 
5850 		version(with_eventloop) {
5851 			import arsd.eventloop;
5852 			addFileEventListeners(fd, &readyel);
5853 		} else {
5854 			enable();
5855 		}
5856 	}
5857 
5858 	bool captureReads;
5859 	bool captureWrites;
5860 
5861 	version(with_eventloop) {} else
5862 	///
5863 	void enable() {
5864 		prepareEventLoop();
5865 
5866 		enabled = true;
5867 
5868 		version(linux) {
5869 			static import ep = core.sys.linux.epoll;
5870 			ep.epoll_event ev = void;
5871 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5872 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5873 			ev.data.fd = fd;
5874 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5875 		} else {
5876 
5877 		}
5878 	}
5879 
5880 	version(with_eventloop) {} else
5881 	///
5882 	void disable() {
5883 		prepareEventLoop();
5884 
5885 		enabled = false;
5886 
5887 		version(linux) {
5888 			static import ep = core.sys.linux.epoll;
5889 			ep.epoll_event ev = void;
5890 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5891 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5892 			ev.data.fd = fd;
5893 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5894 		}
5895 	}
5896 
5897 	version(with_eventloop) {} else
5898 	///
5899 	void dispose() {
5900 		if(enabled)
5901 			disable();
5902 		if(fd != -1)
5903 			mapping.remove(fd);
5904 		fd = -1;
5905 	}
5906 
5907 	void delegate(int, bool, bool) onReady;
5908 
5909 	version(with_eventloop)
5910 	void readyel() {
5911 		onReady(fd, true, true);
5912 	}
5913 
5914 	void ready(uint flags) {
5915 		version(linux) {
5916 			static import ep = core.sys.linux.epoll;
5917 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5918 		} else {
5919 			import core.sys.posix.poll;
5920 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5921 		}
5922 	}
5923 
5924 	void hup(uint flags) {
5925 		if(onHup)
5926 			onHup();
5927 	}
5928 
5929 	void delegate() onHup;
5930 
5931 	int fd = -1;
5932 	private bool enabled;
5933 	__gshared PosixFdReader[int] mapping;
5934 }
5935 
5936 // basic functions to access the clipboard
5937 /+
5938 
5939 
5940 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5941 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5942 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5943 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5944 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5945 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5946 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5947 
5948 +/
5949 
5950 /++
5951 	this does a delegate because it is actually an async call on X...
5952 	the receiver may never be called if the clipboard is empty or unavailable
5953 	gets plain text from the clipboard.
5954 +/
5955 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5956 	version(Windows) {
5957 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5958 		if(OpenClipboard(hwndOwner) == 0)
5959 			throw new WindowsApiException("OpenClipboard", GetLastError());
5960 		scope(exit)
5961 			CloseClipboard();
5962 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5963 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5964 
5965 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5966 				scope(exit)
5967 					GlobalUnlock(dataHandle);
5968 
5969 				// FIXME: CR/LF conversions
5970 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5971 				int len = 0;
5972 				auto d = data;
5973 				while(*d) {
5974 					d++;
5975 					len++;
5976 				}
5977 				string s;
5978 				s.reserve(len);
5979 				foreach(dchar ch; data[0 .. len]) {
5980 					s ~= ch;
5981 				}
5982 				receiver(s);
5983 			}
5984 		}
5985 	} else version(X11) {
5986 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5987 	} else version(OSXCocoa) {
5988 		throw new NotYetImplementedException();
5989 	} else static assert(0);
5990 }
5991 
5992 // FIXME: a clipboard listener might be cool btw
5993 
5994 /++
5995 	this does a delegate because it is actually an async call on X...
5996 	the receiver may never be called if the clipboard is empty or unavailable
5997 	gets image from the clipboard.
5998 
5999 	templated because it introduces an optional dependency on arsd.bmp
6000 +/
6001 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6002 	version(Windows) {
6003 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6004 		if(OpenClipboard(hwndOwner) == 0)
6005 			throw new WindowsApiException("OpenClipboard", GetLastError());
6006 		scope(exit)
6007 			CloseClipboard();
6008 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6009 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6010 				scope(exit)
6011 					GlobalUnlock(dataHandle);
6012 
6013 				auto len = GlobalSize(dataHandle);
6014 
6015 				import arsd.bmp;
6016 				auto img = readBmp(data[0 .. len], false);
6017 				receiver(img);
6018 			}
6019 		}
6020 	} else version(X11) {
6021 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6022 	} else version(OSXCocoa) {
6023 		throw new NotYetImplementedException();
6024 	} else static assert(0);
6025 }
6026 
6027 /// Copies some text to the clipboard.
6028 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6029 	assert(clipboardOwner !is null);
6030 	version(Windows) {
6031 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6032 			throw new WindowsApiException("OpenClipboard", GetLastError());
6033 		scope(exit)
6034 			CloseClipboard();
6035 		EmptyClipboard();
6036 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6037 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6038 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6039 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6040 			auto slice = data[0 .. sz];
6041 			scope(failure)
6042 				GlobalUnlock(handle);
6043 
6044 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6045 
6046 			GlobalUnlock(handle);
6047 			SetClipboardData(CF_UNICODETEXT, handle);
6048 		}
6049 	} else version(X11) {
6050 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6051 	} else version(OSXCocoa) {
6052 		throw new NotYetImplementedException();
6053 	} else static assert(0);
6054 }
6055 
6056 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6057 	assert(clipboardOwner !is null);
6058 	version(Windows) {
6059 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6060 			throw new WindowsApiException("OpenClipboard", GetLastError());
6061 		scope(exit)
6062 			CloseClipboard();
6063 		EmptyClipboard();
6064 
6065 
6066 		import arsd.bmp;
6067 		ubyte[] mdata;
6068 		mdata.reserve(img.width * img.height);
6069 		void sink(ubyte b) {
6070 			mdata ~= b;
6071 		}
6072 		writeBmpIndirect(img, &sink, false);
6073 
6074 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6075 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6076 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6077 			auto slice = data[0 .. mdata.length];
6078 			scope(failure)
6079 				GlobalUnlock(handle);
6080 
6081 			slice[] = mdata[];
6082 
6083 			GlobalUnlock(handle);
6084 			SetClipboardData(CF_DIB, handle);
6085 		}
6086 	} else version(X11) {
6087 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6088 			mixin X11SetSelectionHandler_Basics;
6089 			private const(ubyte)[] mdata;
6090 			private const(ubyte)[] mdata_original;
6091 			this(MemoryImage img) {
6092 				import arsd.bmp;
6093 
6094 				mdata.reserve(img.width * img.height);
6095 				void sink(ubyte b) {
6096 					mdata ~= b;
6097 				}
6098 				writeBmpIndirect(img, &sink, true);
6099 
6100 				mdata_original = mdata;
6101 			}
6102 
6103 			Atom[] availableFormats() {
6104 				auto display = XDisplayConnection.get;
6105 				return [
6106 					GetAtom!"image/bmp"(display),
6107 					GetAtom!"TARGETS"(display)
6108 				];
6109 			}
6110 
6111 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6112 				if(mdata.length < data.length) {
6113 					data[0 .. mdata.length] = mdata[];
6114 					auto ret = data[0 .. mdata.length];
6115 					mdata = mdata[$..$];
6116 					return ret;
6117 				} else {
6118 					data[] = mdata[0 .. data.length];
6119 					mdata = mdata[data.length .. $];
6120 					return data[];
6121 				}
6122 			}
6123 
6124 			void done() {
6125 				mdata = mdata_original;
6126 			}
6127 		}
6128 
6129 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6130 	} else version(OSXCocoa) {
6131 		throw new NotYetImplementedException();
6132 	} else static assert(0);
6133 }
6134 
6135 
6136 version(X11) {
6137 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6138 
6139 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6140 
6141 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6142 	/// Platform-specific for X11.
6143 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6144 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6145 		__gshared static Atom a;
6146 		if(!a) {
6147 			a = XInternAtom(display, name, !create);
6148 			// FIXME: might need to synchronize this and attach it to the actual object
6149 			interredAtoms ~= &a;
6150 		}
6151 		if(a == None)
6152 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6153 		return a;
6154 	}
6155 
6156 	/// Platform-specific for X11 - gets atom names as a string.
6157 	string getAtomName(Atom atom, Display* display) {
6158 		auto got = XGetAtomName(display, atom);
6159 		scope(exit) XFree(got);
6160 		import core.stdc.string;
6161 		string s = got[0 .. strlen(got)].idup;
6162 		return s;
6163 	}
6164 
6165 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6166 	void setPrimarySelection(SimpleWindow window, string text) {
6167 		setX11Selection!"PRIMARY"(window, text);
6168 	}
6169 
6170 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6171 	void setSecondarySelection(SimpleWindow window, string text) {
6172 		setX11Selection!"SECONDARY"(window, text);
6173 	}
6174 
6175 	interface X11SetSelectionHandler {
6176 		// should include TARGETS right now
6177 		Atom[] availableFormats();
6178 		// Return the slice of data you filled, empty slice if done.
6179 		// this is to support the incremental thing
6180 		ubyte[] getData(Atom format, return scope ubyte[] data);
6181 
6182 		void done();
6183 
6184 		void handleRequest(XEvent);
6185 
6186 		bool matchesIncr(Window, Atom);
6187 		void sendMoreIncr(XPropertyEvent*);
6188 	}
6189 
6190 	mixin template X11SetSelectionHandler_Basics() {
6191 		Window incrWindow;
6192 		Atom incrAtom;
6193 		Atom selectionAtom;
6194 		Atom formatAtom;
6195 		ubyte[] toSend;
6196 		bool matchesIncr(Window w, Atom a) {
6197 			return incrAtom && incrAtom == a && w == incrWindow;
6198 		}
6199 		void sendMoreIncr(XPropertyEvent* event) {
6200 			auto display = XDisplayConnection.get;
6201 
6202 			XChangeProperty (display,
6203 				incrWindow,
6204 				incrAtom,
6205 				formatAtom,
6206 				8 /* bits */, PropModeReplace,
6207 				toSend.ptr, cast(int) toSend.length);
6208 
6209 			if(toSend.length != 0) {
6210 				toSend = this.getData(formatAtom, toSend[]);
6211 			} else {
6212 				this.done();
6213 				incrWindow = None;
6214 				incrAtom = None;
6215 				selectionAtom = None;
6216 				formatAtom = None;
6217 				toSend = null;
6218 			}
6219 		}
6220 		void handleRequest(XEvent ev) {
6221 
6222 			auto display = XDisplayConnection.get;
6223 
6224 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6225 			XSelectionEvent selectionEvent;
6226 			selectionEvent.type = EventType.SelectionNotify;
6227 			selectionEvent.display = event.display;
6228 			selectionEvent.requestor = event.requestor;
6229 			selectionEvent.selection = event.selection;
6230 			selectionEvent.time = event.time;
6231 			selectionEvent.target = event.target;
6232 
6233 			bool supportedType() {
6234 				foreach(t; this.availableFormats())
6235 					if(t == event.target)
6236 						return true;
6237 				return false;
6238 			}
6239 
6240 			if(event.property == None) {
6241 				selectionEvent.property = event.target;
6242 
6243 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6244 				XFlush(display);
6245 			} if(event.target == GetAtom!"TARGETS"(display)) {
6246 				/* respond with the supported types */
6247 				auto tlist = this.availableFormats();
6248 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6249 				selectionEvent.property = event.property;
6250 
6251 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6252 				XFlush(display);
6253 			} else if(supportedType()) {
6254 				auto buffer = new ubyte[](1024 * 64);
6255 				auto toSend = this.getData(event.target, buffer[]);
6256 
6257 				if(toSend.length < 32 * 1024) {
6258 					// small enough to send directly...
6259 					selectionEvent.property = event.property;
6260 					XChangeProperty (display,
6261 						selectionEvent.requestor,
6262 						selectionEvent.property,
6263 						event.target,
6264 						8 /* bits */, 0 /* PropModeReplace */,
6265 						toSend.ptr, cast(int) toSend.length);
6266 
6267 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6268 					XFlush(display);
6269 				} else {
6270 					// large, let's send incrementally
6271 					arch_ulong l = toSend.length;
6272 
6273 					// if I wanted other events from this window don't want to clear that out....
6274 					XWindowAttributes xwa;
6275 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6276 
6277 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6278 
6279 					incrWindow = event.requestor;
6280 					incrAtom = event.property;
6281 					formatAtom = event.target;
6282 					selectionAtom = event.selection;
6283 					this.toSend = toSend;
6284 
6285 					selectionEvent.property = event.property;
6286 					XChangeProperty (display,
6287 						selectionEvent.requestor,
6288 						selectionEvent.property,
6289 						GetAtom!"INCR"(display),
6290 						32 /* bits */, PropModeReplace,
6291 						&l, 1);
6292 
6293 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6294 					XFlush(display);
6295 				}
6296 				//if(after)
6297 					//after();
6298 			} else {
6299 				debug(sdpy_clip) {
6300 					writeln("Unsupported data ", getAtomName(event.target, display));
6301 				}
6302 				selectionEvent.property = None; // I don't know how to handle this type...
6303 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6304 				XFlush(display);
6305 			}
6306 		}
6307 	}
6308 
6309 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6310 		mixin X11SetSelectionHandler_Basics;
6311 		private const(ubyte)[] text;
6312 		private const(ubyte)[] text_original;
6313 		this(string text) {
6314 			this.text = cast(const ubyte[]) text;
6315 			this.text_original = this.text;
6316 		}
6317 		Atom[] availableFormats() {
6318 			auto display = XDisplayConnection.get;
6319 			return [
6320 				GetAtom!"UTF8_STRING"(display),
6321 				GetAtom!"text/plain"(display),
6322 				XA_STRING,
6323 				GetAtom!"TARGETS"(display)
6324 			];
6325 		}
6326 
6327 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6328 			if(text.length < data.length) {
6329 				data[0 .. text.length] = text[];
6330 				return data[0 .. text.length];
6331 			} else {
6332 				data[] = text[0 .. data.length];
6333 				text = text[data.length .. $];
6334 				return data[];
6335 			}
6336 		}
6337 
6338 		void done() {
6339 			text = text_original;
6340 		}
6341 	}
6342 
6343 	/// 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?!)
6344 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6345 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6346 	}
6347 
6348 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6349 		assert(window !is null);
6350 
6351 		auto display = XDisplayConnection.get();
6352 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6353 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6354 		else Atom a = GetAtom!atomName(display);
6355 
6356 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6357 
6358 		window.impl.setSelectionHandlers[a] = data;
6359 	}
6360 
6361 	///
6362 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6363 		getX11Selection!"PRIMARY"(window, handler);
6364 	}
6365 
6366 	// added July 28, 2020
6367 	// undocumented as experimental tho
6368 	interface X11GetSelectionHandler {
6369 		void handleData(Atom target, in ubyte[] data);
6370 		Atom findBestFormat(Atom[] answer);
6371 
6372 		void prepareIncremental(Window, Atom);
6373 		bool matchesIncr(Window, Atom);
6374 		void handleIncrData(Atom, in ubyte[] data);
6375 	}
6376 
6377 	mixin template X11GetSelectionHandler_Basics() {
6378 		Window incrWindow;
6379 		Atom incrAtom;
6380 
6381 		void prepareIncremental(Window w, Atom a) {
6382 			incrWindow = w;
6383 			incrAtom = a;
6384 		}
6385 		bool matchesIncr(Window w, Atom a) {
6386 			return incrWindow == w && incrAtom == a;
6387 		}
6388 
6389 		Atom incrFormatAtom;
6390 		ubyte[] incrData;
6391 		void handleIncrData(Atom format, in ubyte[] data) {
6392 			incrFormatAtom = format;
6393 
6394 			if(data.length)
6395 				incrData ~= data;
6396 			else
6397 				handleData(incrFormatAtom, incrData);
6398 
6399 		}
6400 	}
6401 
6402 	///
6403 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6404 		assert(window !is null);
6405 
6406 		auto display = XDisplayConnection.get();
6407 		auto atom = GetAtom!atomName(display);
6408 
6409 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6410 			this(void delegate(in char[]) handler) {
6411 				this.handler = handler;
6412 			}
6413 
6414 			mixin X11GetSelectionHandler_Basics;
6415 
6416 			void delegate(in char[]) handler;
6417 
6418 			void handleData(Atom target, in ubyte[] data) {
6419 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6420 					handler(cast(const char[]) data);
6421 			}
6422 
6423 			Atom findBestFormat(Atom[] answer) {
6424 				Atom best = None;
6425 				foreach(option; answer) {
6426 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6427 						best = option;
6428 						break;
6429 					} else if(option == XA_STRING) {
6430 						best = option;
6431 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6432 						best = option;
6433 					}
6434 				}
6435 				return best;
6436 			}
6437 		}
6438 
6439 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6440 
6441 		auto target = GetAtom!"TARGETS"(display);
6442 
6443 		// SDD_DATA is "simpledisplay.d data"
6444 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6445 	}
6446 
6447 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6448 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6449 		assert(window !is null);
6450 
6451 		auto display = XDisplayConnection.get();
6452 		auto atom = GetAtom!atomName(display);
6453 
6454 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6455 			this(void delegate(MemoryImage) handler) {
6456 				this.handler = handler;
6457 			}
6458 
6459 			mixin X11GetSelectionHandler_Basics;
6460 
6461 			void delegate(MemoryImage) handler;
6462 
6463 			void handleData(Atom target, in ubyte[] data) {
6464 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6465 					import arsd.bmp;
6466 					handler(readBmp(data));
6467 				}
6468 			}
6469 
6470 			Atom findBestFormat(Atom[] answer) {
6471 				Atom best = None;
6472 				foreach(option; answer) {
6473 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6474 						best = option;
6475 					}
6476 				}
6477 				return best;
6478 			}
6479 
6480 		}
6481 
6482 
6483 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6484 
6485 		auto target = GetAtom!"TARGETS"(display);
6486 
6487 		// SDD_DATA is "simpledisplay.d data"
6488 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6489 	}
6490 
6491 
6492 	///
6493 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6494 		Atom actualType;
6495 		int actualFormat;
6496 		arch_ulong actualItems;
6497 		arch_ulong bytesRemaining;
6498 		void* data;
6499 
6500 		auto display = XDisplayConnection.get();
6501 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6502 			if(actualFormat == 0)
6503 				return null;
6504 			else {
6505 				int byteLength;
6506 				if(actualFormat == 32) {
6507 					// 32 means it is a C long... which is variable length
6508 					actualFormat = cast(int) arch_long.sizeof * 8;
6509 				}
6510 
6511 				// then it is just a bit count
6512 				byteLength = cast(int) (actualItems * actualFormat / 8);
6513 
6514 				auto d = new ubyte[](byteLength);
6515 				d[] = cast(ubyte[]) data[0 .. byteLength];
6516 				XFree(data);
6517 				return d;
6518 			}
6519 		}
6520 		return null;
6521 	}
6522 
6523 	/* defined in the systray spec */
6524 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6525 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6526 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6527 
6528 
6529 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6530 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6531 	public class GlobalHotkey {
6532 		KeyEvent key;
6533 		void delegate () handler;
6534 
6535 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6536 
6537 		/// Create from initialzed KeyEvent object
6538 		this (KeyEvent akey, void delegate () ahandler=null) {
6539 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6540 			key = akey;
6541 			handler = ahandler;
6542 		}
6543 
6544 		/// Create from emacs-like key name ("C-M-Y", etc.)
6545 		this (const(char)[] akey, void delegate () ahandler=null) {
6546 			key = KeyEvent.parse(akey);
6547 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6548 			handler = ahandler;
6549 		}
6550 
6551 	}
6552 
6553 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6554 		//conwriteln("failed to grab key");
6555 		GlobalHotkeyManager.ghfailed = true;
6556 		return 0;
6557 	}
6558 
6559 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6560 		Image.impl.xshmfailed = true;
6561 		return 0;
6562 	}
6563 
6564 	private __gshared int errorHappened;
6565 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6566 		import core.stdc.stdio;
6567 		char[265] buffer;
6568 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6569 		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);
6570 		errorHappened = true;
6571 		return 0;
6572 	}
6573 
6574 	/++
6575 		Global hotkey manager. It contains static methods to manage global hotkeys.
6576 
6577 		---
6578 		 try {
6579 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6580 		} catch (Exception e) {
6581 			conwriteln("ERROR registering hotkey!");
6582 		}
6583 		EventLoop.get.run();
6584 		---
6585 
6586 		The key strings are based on Emacs. In practical terms,
6587 		`M` means `alt` and `H` means the Windows logo key. `C`
6588 		is `ctrl`.
6589 
6590 		$(WARNING
6591 			This is X-specific right now. If you are on
6592 			Windows, try [registerHotKey] instead.
6593 
6594 			We will probably merge these into a single
6595 			interface later.
6596 		)
6597 	+/
6598 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6599 		version(X11) {
6600 			void recreateAfterDisconnect() {
6601 				throw new Exception("NOT IMPLEMENTED");
6602 			}
6603 			void discardConnectionState() {
6604 				throw new Exception("NOT IMPLEMENTED");
6605 			}
6606 		}
6607 
6608 		private static immutable uint[8] masklist = [ 0,
6609 			KeyOrButtonMask.LockMask,
6610 			KeyOrButtonMask.Mod2Mask,
6611 			KeyOrButtonMask.Mod3Mask,
6612 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6613 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6614 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6615 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6616 		];
6617 		private __gshared GlobalHotkeyManager ghmanager;
6618 		private __gshared bool ghfailed = false;
6619 
6620 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6621 			if (modmask == 0) return false;
6622 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6623 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6624 			return true;
6625 		}
6626 
6627 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6628 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6629 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6630 			return modmask;
6631 		}
6632 
6633 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6634 			uint keycode = cast(uint)ke.key;
6635 			auto dpy = XDisplayConnection.get;
6636 			return XKeysymToKeycode(dpy, keycode);
6637 		}
6638 
6639 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6640 
6641 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6642 
6643 		NativeEventHandler getNativeEventHandler () {
6644 			return delegate int (XEvent e) {
6645 				if (e.type != EventType.KeyPress) return 1;
6646 				auto kev = cast(const(XKeyEvent)*)&e;
6647 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6648 				if (auto ghkp = hash in globalHotkeyList) {
6649 					try {
6650 						ghkp.doHandle();
6651 					} catch (Exception e) {
6652 						import core.stdc.stdio : stderr, fprintf;
6653 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6654 					}
6655 				}
6656 				return 1;
6657 			};
6658 		}
6659 
6660 		private this () {
6661 			auto dpy = XDisplayConnection.get;
6662 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6663 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6664 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6665 		}
6666 
6667 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6668 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6669 		static void register (GlobalHotkey gh) {
6670 			if (gh is null) return;
6671 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6672 
6673 			auto dpy = XDisplayConnection.get;
6674 			immutable keycode = keyEvent2KeyCode(gh.key);
6675 
6676 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6677 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6678 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6679 			XSync(dpy, 0/*False*/);
6680 
6681 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6682 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6683 			ghfailed = false;
6684 			foreach (immutable uint ormask; masklist[]) {
6685 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6686 			}
6687 			XSync(dpy, 0/*False*/);
6688 			XSetErrorHandler(savedErrorHandler);
6689 
6690 			if (ghfailed) {
6691 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6692 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6693 				XSync(dpy, 0/*False*/);
6694 				XSetErrorHandler(savedErrorHandler);
6695 				throw new Exception("cannot register global hotkey");
6696 			}
6697 
6698 			globalHotkeyList[hash] = gh;
6699 		}
6700 
6701 		/// Ditto
6702 		static void register (const(char)[] akey, void delegate () ahandler) {
6703 			register(new GlobalHotkey(akey, ahandler));
6704 		}
6705 
6706 		private static void removeByHash (ulong hash) {
6707 			if (auto ghp = hash in globalHotkeyList) {
6708 				auto dpy = XDisplayConnection.get;
6709 				immutable keycode = keyEvent2KeyCode(ghp.key);
6710 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6711 				XSync(dpy, 0/*False*/);
6712 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6713 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6714 				XSync(dpy, 0/*False*/);
6715 				XSetErrorHandler(savedErrorHandler);
6716 				globalHotkeyList.remove(hash);
6717 			}
6718 		}
6719 
6720 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6721 		/// It is safe to unregister unknown or invalid hotkey.
6722 		static void unregister (GlobalHotkey gh) {
6723 			//TODO: add second AA for faster search? prolly doesn't worth it.
6724 			if (gh is null) return;
6725 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6726 				if (kv.value is gh) {
6727 					removeByHash(kv.key);
6728 					return;
6729 				}
6730 			}
6731 		}
6732 
6733 		/// Ditto.
6734 		static void unregister (const(char)[] key) {
6735 			auto kev = KeyEvent.parse(key);
6736 			immutable keycode = keyEvent2KeyCode(kev);
6737 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6738 		}
6739 	}
6740 }
6741 
6742 version(Windows) {
6743 	/++
6744 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6745 
6746 		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).
6747 	+/
6748 	void sendSyntheticInput(wstring s) {
6749 			INPUT[] inputs;
6750 			inputs.reserve(s.length * 2);
6751 
6752 			foreach(wchar c; s) {
6753 				INPUT input;
6754 				input.type = INPUT_KEYBOARD;
6755 				input.ki.wScan = c;
6756 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6757 				inputs ~= input;
6758 
6759 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6760 				inputs ~= input;
6761 			}
6762 
6763 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6764 				throw new WindowsApiException("SendInput", GetLastError());
6765 			}
6766 
6767 	}
6768 
6769 
6770 	// global hotkey helper function
6771 
6772 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6773 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6774 		__gshared int hotkeyId = 0;
6775 		int id = ++hotkeyId;
6776 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6777 			throw new Exception("RegisterHotKey");
6778 
6779 		__gshared void delegate()[WPARAM][HWND] handlers;
6780 
6781 		handlers[window.impl.hwnd][id] = handler;
6782 
6783 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6784 
6785 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6786 			switch(msg) {
6787 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6788 				case WM_HOTKEY:
6789 					if(auto list = hwnd in handlers) {
6790 						if(auto h = wParam in *list) {
6791 							(*h)();
6792 							return 0;
6793 						}
6794 					}
6795 				goto default;
6796 				default:
6797 			}
6798 			if(oldHandler)
6799 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6800 			return 1; // pass it on
6801 		};
6802 
6803 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6804 			oldHandler = window.handleNativeEvent;
6805 			window.handleNativeEvent = nativeEventHandler;
6806 		}
6807 
6808 		return id;
6809 	}
6810 
6811 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6812 	void unregisterHotKey(SimpleWindow window, int id) {
6813 		if(!UnregisterHotKey(window.impl.hwnd, id))
6814 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
6815 	}
6816 }
6817 
6818 version (X11) {
6819 	pragma(lib, "dl");
6820 	import core.sys.posix.dlfcn;
6821 }
6822 
6823 /++
6824 	Allows for sending synthetic input to the X server via the Xtst
6825 	extension or on Windows using SendInput.
6826 
6827 	Please remember user input is meant to be user - don't use this
6828 	if you have some other alternative!
6829 
6830 	History:
6831 		Added May 17, 2020 with the X implementation.
6832 
6833 		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.)
6834 	Bugs:
6835 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6836 +/
6837 struct SyntheticInput {
6838 	@disable this();
6839 
6840 	private int* refcount;
6841 
6842 	version(X11) {
6843 		private void* lib;
6844 
6845 		private extern(C) {
6846 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6847 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6848 		}
6849 	}
6850 
6851 	/// The dummy param must be 0.
6852 	this(int dummy) {
6853 		version(X11) {
6854 			lib = dlopen("libXtst.so", RTLD_NOW);
6855 			if(lib is null)
6856 				throw new Exception("cannot load xtest lib extension");
6857 			scope(failure)
6858 				dlclose(lib);
6859 
6860 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6861 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6862 
6863 			if(XTestFakeKeyEvent is null)
6864 				throw new Exception("No XTestFakeKeyEvent");
6865 			if(XTestFakeButtonEvent is null)
6866 				throw new Exception("No XTestFakeButtonEvent");
6867 		}
6868 
6869 		refcount = new int;
6870 		*refcount = 1;
6871 	}
6872 
6873 	this(this) {
6874 		if(refcount)
6875 			*refcount += 1;
6876 	}
6877 
6878 	~this() {
6879 		if(refcount) {
6880 			*refcount -= 1;
6881 			if(*refcount == 0)
6882 				// I commented this because if I close the lib before
6883 				// XCloseDisplay, it is liable to segfault... so just
6884 				// gonna keep it loaded if it is loaded, no big deal
6885 				// anyway.
6886 				{} // dlclose(lib);
6887 		}
6888 	}
6889 
6890 	/++
6891 		Simulates typing a string into the keyboard.
6892 
6893 		Bugs:
6894 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6895 
6896 			Not implemented except on Windows and X11.
6897 	+/
6898 	void sendSyntheticInput(string s) {
6899 		version(Windows) {
6900 			INPUT[] inputs;
6901 			inputs.reserve(s.length * 2);
6902 
6903 			auto ei = GetMessageExtraInfo();
6904 
6905 			foreach(wchar c; s) {
6906 				INPUT input;
6907 				input.type = INPUT_KEYBOARD;
6908 				input.ki.wScan = c;
6909 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6910 				input.ki.dwExtraInfo = ei;
6911 				inputs ~= input;
6912 
6913 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6914 				inputs ~= input;
6915 			}
6916 
6917 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6918 				throw new WindowsApiException("SendInput", GetLastError());
6919 			}
6920 		} else version(X11) {
6921 			int delay = 0;
6922 			foreach(ch; s) {
6923 				pressKey(cast(Key) ch, true, delay);
6924 				pressKey(cast(Key) ch, false, delay);
6925 				delay += 5;
6926 			}
6927 		} else throw new NotYetImplementedException();
6928 	}
6929 
6930 	/++
6931 		Sends a fake press or release key event.
6932 
6933 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6934 
6935 		Bugs:
6936 			The `delay` parameter is not implemented yet on Windows.
6937 
6938 			Not implemented except on Windows and X11.
6939 	+/
6940 	void pressKey(Key key, bool pressed, int delay = 0) {
6941 		version(Windows) {
6942 			INPUT input;
6943 			input.type = INPUT_KEYBOARD;
6944 			input.ki.wVk = cast(ushort) key;
6945 
6946 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6947 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6948 
6949 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6950 				throw new WindowsApiException("SendInput", GetLastError());
6951 			}
6952 		} else version(X11) {
6953 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6954 		} else throw new NotYetImplementedException();
6955 	}
6956 
6957 	/++
6958 		Sends a fake mouse button press or release event.
6959 
6960 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6961 
6962 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6963 
6964 		Bugs:
6965 			The `delay` parameter is not implemented yet on Windows.
6966 
6967 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6968 
6969 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6970 	+/
6971 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6972 		version(Windows) {
6973 			INPUT input;
6974 			input.type = INPUT_MOUSE;
6975 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6976 
6977 			// input.mi.mouseData for a wheel event
6978 
6979 			switch(button) {
6980 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6981 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6982 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6983 				case MouseButton.wheelUp:
6984 				case MouseButton.wheelDown:
6985 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6986 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6987 				break;
6988 				case MouseButton.backButton: throw new NotYetImplementedException();
6989 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6990 				default:
6991 			}
6992 
6993 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6994 				throw new WindowsApiException("SendInput", GetLastError());
6995 			}
6996 		} else version(X11) {
6997 			int btn;
6998 
6999 			switch(button) {
7000 				case MouseButton.left: btn = 1; break;
7001 				case MouseButton.middle: btn = 2; break;
7002 				case MouseButton.right: btn = 3; break;
7003 				case MouseButton.wheelUp: btn = 4; break;
7004 				case MouseButton.wheelDown: btn = 5; break;
7005 				case MouseButton.backButton: btn = 8; break;
7006 				case MouseButton.forwardButton: btn = 9; break;
7007 				default:
7008 			}
7009 
7010 			assert(btn);
7011 
7012 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7013 		} else throw new NotYetImplementedException();
7014 	}
7015 
7016 	///
7017 	static void moveMouseArrowBy(int dx, int dy) {
7018 		version(Windows) {
7019 			INPUT input;
7020 			input.type = INPUT_MOUSE;
7021 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7022 			input.mi.dx = dx;
7023 			input.mi.dy = dy;
7024 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7025 
7026 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7027 				throw new WindowsApiException("SendInput", GetLastError());
7028 			}
7029 		} else version(X11) {
7030 			auto disp = XDisplayConnection.get();
7031 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7032 			XFlush(disp);
7033 		} else throw new NotYetImplementedException();
7034 	}
7035 
7036 	///
7037 	static void moveMouseArrowTo(int x, int y) {
7038 		version(Windows) {
7039 			INPUT input;
7040 			input.type = INPUT_MOUSE;
7041 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7042 			input.mi.dx = x;
7043 			input.mi.dy = y;
7044 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7045 
7046 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7047 				throw new WindowsApiException("SendInput", GetLastError());
7048 			}
7049 		} else version(X11) {
7050 			auto disp = XDisplayConnection.get();
7051 			auto root = RootWindow(disp, DefaultScreen(disp));
7052 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7053 			XFlush(disp);
7054 		} else throw new NotYetImplementedException();
7055 	}
7056 }
7057 
7058 
7059 
7060 /++
7061 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7062 
7063 	See_Also:
7064 	$(LIST
7065 		*[ScreenPainter]
7066 		*[ScreenPainter.rasterOp]
7067 	)
7068 +/
7069 enum RasterOp {
7070 	normal, /// Replaces the pixel.
7071 	xor, /// Uses bitwise xor to draw.
7072 }
7073 
7074 // being phobos-free keeps the size WAY down
7075 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7076 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7077 package(arsd) const(wchar)* toWStringz(string s) {
7078 	wstring r;
7079 	foreach(dchar c; s)
7080 		r ~= c;
7081 	r ~= '\0';
7082 	return r.ptr;
7083 }
7084 private string[] split(in void[] a, char c) {
7085 		string[] ret;
7086 		size_t previous = 0;
7087 		foreach(i, char ch; cast(ubyte[]) a) {
7088 			if(ch == c) {
7089 				ret ~= cast(string) a[previous .. i];
7090 				previous = i + 1;
7091 			}
7092 		}
7093 		if(previous != a.length)
7094 			ret ~= cast(string) a[previous .. $];
7095 		return ret;
7096 	}
7097 
7098 version(without_opengl) {
7099 	enum OpenGlOptions {
7100 		no,
7101 	}
7102 } else {
7103 	/++
7104 		Determines if you want an OpenGL context created on the new window.
7105 
7106 
7107 		See more: [#topics-3d|in the 3d topic].
7108 
7109 		---
7110 		import arsd.simpledisplay;
7111 		void main() {
7112 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7113 
7114 			// Set up the matrix
7115 			window.setAsCurrentOpenGlContext(); // make this window active
7116 
7117 			// This is called on each frame, we will draw our scene
7118 			window.redrawOpenGlScene = delegate() {
7119 
7120 			};
7121 
7122 			window.eventLoop(0);
7123 		}
7124 		---
7125 	+/
7126 	enum OpenGlOptions {
7127 		no, /// No OpenGL context is created
7128 		yes, /// Yes, create an OpenGL context
7129 	}
7130 
7131 	version(X11) {
7132 		static if (!SdpyIsUsingIVGLBinds) {
7133 
7134 
7135 			struct __GLXFBConfigRec {}
7136 			alias GLXFBConfig = __GLXFBConfigRec*;
7137 
7138 			//pragma(lib, "GL");
7139 			//pragma(lib, "GLU");
7140 			interface GLX {
7141 			extern(C) nothrow @nogc {
7142 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7143 						const int *attrib_list);
7144 
7145 				 void glXCopyContext(Display *dpy, GLXContext src,
7146 						GLXContext dst, arch_ulong mask);
7147 
7148 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7149 						GLXContext share_list, Bool direct);
7150 
7151 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7152 						Pixmap pixmap);
7153 
7154 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7155 
7156 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7157 
7158 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7159 						int attrib, int *value);
7160 
7161 				 GLXContext glXGetCurrentContext();
7162 
7163 				 GLXDrawable glXGetCurrentDrawable();
7164 
7165 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7166 
7167 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7168 						GLXContext ctx);
7169 
7170 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7171 
7172 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7173 
7174 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7175 
7176 				 void glXUseXFont(Font font, int first, int count, int list_base);
7177 
7178 				 void glXWaitGL();
7179 
7180 				 void glXWaitX();
7181 
7182 
7183 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7184 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7185 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7186 
7187 				char* glXQueryExtensionsString (Display*, int);
7188 				void* glXGetProcAddress (const(char)*);
7189 
7190 			}
7191 			}
7192 
7193 			version(OSX)
7194 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7195 			else
7196 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7197 			shared static this() {
7198 				glx.loadDynamicLibrary();
7199 			}
7200 
7201 			alias glbindGetProcAddress = glXGetProcAddress;
7202 		}
7203 	} else version(Windows) {
7204 		/* it is done below by interface GL */
7205 	} else
7206 		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.");
7207 }
7208 
7209 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7210 alias Resizablity = Resizability;
7211 
7212 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7213 enum Resizability {
7214 	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.
7215 	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.
7216 	/++
7217 		$(PITFALL
7218 			Planned for the future but not implemented.
7219 		)
7220 
7221 		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.
7222 
7223 		History:
7224 			Added November 11, 2022, but not yet implemented and may not be for some time.
7225 	+/
7226 	/*@__future*/ allowResizingMaintainingAspectRatio,
7227 	/++
7228 		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.
7229 
7230 		History:
7231 			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.
7232 
7233 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7234 	+/
7235 	automaticallyScaleIfPossible,
7236 }
7237 /// ditto
7238 alias Resizeability = Resizability;
7239 
7240 
7241 /++
7242 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7243 +/
7244 enum TextAlignment : uint {
7245 	Left = 0, ///
7246 	Center = 1, ///
7247 	Right = 2, ///
7248 
7249 	VerticalTop = 0, ///
7250 	VerticalCenter = 4, ///
7251 	VerticalBottom = 8, ///
7252 }
7253 
7254 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7255 alias Rectangle = arsd.color.Rectangle;
7256 
7257 
7258 /++
7259 	Keyboard press and release events.
7260 +/
7261 struct KeyEvent {
7262 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7263 	Key key;
7264 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7265 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7266 
7267 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7268 
7269 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7270 
7271 	SimpleWindow window; /// associated Window
7272 
7273 	/++
7274 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7275 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7276 		to predict if char events are actually coming..
7277 
7278 		Only available on X systems since this information is not given ahead of time elsewhere.
7279 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7280 
7281 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7282 		and potential quirks I'd recommend avoiding it.
7283 
7284 		History:
7285 			Added April 26, 2021 (dub v9.5)
7286 	+/
7287 	version(X11)
7288 		dchar[] charsPossible;
7289 
7290 	// convert key event to simplified string representation a-la emacs
7291 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7292 		uint dpos = 0;
7293 		void put (const(char)[] s...) nothrow @trusted {
7294 			static if (growdest) {
7295 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7296 			} else {
7297 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7298 			}
7299 		}
7300 
7301 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7302 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7303 		}
7304 
7305 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7306 
7307 		// put modifiers
7308 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7309 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7310 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7311 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7312 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7313 
7314 		if (this.key) {
7315 			foreach (string kn; __traits(allMembers, Key)) {
7316 				if (this.key == __traits(getMember, Key, kn)) {
7317 					// HACK!
7318 					static if (kn == "N0") put("0");
7319 					else static if (kn == "N1") put("1");
7320 					else static if (kn == "N2") put("2");
7321 					else static if (kn == "N3") put("3");
7322 					else static if (kn == "N4") put("4");
7323 					else static if (kn == "N5") put("5");
7324 					else static if (kn == "N6") put("6");
7325 					else static if (kn == "N7") put("7");
7326 					else static if (kn == "N8") put("8");
7327 					else static if (kn == "N9") put("9");
7328 					else put(kn);
7329 					return dest[0..dpos];
7330 				}
7331 			}
7332 			put("Unknown");
7333 		} else {
7334 			if (dpos && dest[dpos-1] == '+') --dpos;
7335 		}
7336 		return dest[0..dpos];
7337 	}
7338 
7339 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7340 
7341 	/** Parse string into key name with modifiers. It accepts things like:
7342 	 *
7343 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7344 	 *
7345 	 * Ctrl+Win+1 -- windows style
7346 	 *
7347 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7348 	 *
7349 	 * Ctrl Win 1 -- and space
7350 	 *
7351 	 * and even "Win + 1 + Ctrl".
7352 	 */
7353 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7354 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7355 
7356 		// remove trailing spaces
7357 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7358 
7359 		// tokens delimited by blank, '+', or '-'
7360 		// null on eol
7361 		const(char)[] getToken () nothrow @trusted @nogc {
7362 			// remove leading spaces and delimiters
7363 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7364 			if (name.length == 0) return null; // oops, no more tokens
7365 			// get token
7366 			size_t epos = 0;
7367 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7368 			assert(epos > 0 && epos <= name.length);
7369 			auto res = name[0..epos];
7370 			name = name[epos..$];
7371 			return res;
7372 		}
7373 
7374 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7375 			if (s0.length != s1.length) return false;
7376 			foreach (immutable ci, char c0; s0) {
7377 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7378 				char c1 = s1[ci];
7379 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7380 				if (c0 != c1) return false;
7381 			}
7382 			return true;
7383 		}
7384 
7385 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7386 		if (updown !is null) *updown = -1;
7387 		KeyEvent res;
7388 		res.key = cast(Key)0; // just in case
7389 		const(char)[] tk, tkn; // last token
7390 		bool allowEmascStyle = true;
7391 		bool ignoreModifiers = false;
7392 		tokenloop: for (;;) {
7393 			tk = tkn;
7394 			tkn = getToken();
7395 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7396 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7397 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7398 			if (allowEmascStyle && tkn.length != 0) {
7399 				if (tk.length == 1) {
7400 					char mdc = tk[0];
7401 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7402 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7403 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7404 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7405 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7406 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7407 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7408 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7409 				}
7410 			}
7411 			allowEmascStyle = false;
7412 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7413 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7414 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7415 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7416 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7417 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7418 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7419 			if (tk.length == 0) continue;
7420 			// try key name
7421 			if (res.key == 0) {
7422 				// little hack
7423 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7424 					final switch (tk[0]) {
7425 						case '0': tk = "N0"; break;
7426 						case '1': tk = "N1"; break;
7427 						case '2': tk = "N2"; break;
7428 						case '3': tk = "N3"; break;
7429 						case '4': tk = "N4"; break;
7430 						case '5': tk = "N5"; break;
7431 						case '6': tk = "N6"; break;
7432 						case '7': tk = "N7"; break;
7433 						case '8': tk = "N8"; break;
7434 						case '9': tk = "N9"; break;
7435 					}
7436 				}
7437 				foreach (string kn; __traits(allMembers, Key)) {
7438 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7439 				}
7440 			}
7441 			// unknown or duplicate key name, get out of here
7442 			break;
7443 		}
7444 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7445 		return res; // something
7446 	}
7447 
7448 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7449 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7450 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7451 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7452 		}
7453 		bool ignoreMods;
7454 		int updown;
7455 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7456 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7457 		if (this.key != ke.key) {
7458 			// things like "ctrl+alt" are complicated
7459 			uint tkm = this.modifierState&modmask;
7460 			uint kkm = ke.modifierState&modmask;
7461 			Key tk = this.key;
7462 			// ke
7463 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7464 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7465 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7466 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7467 			// this
7468 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7469 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7470 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7471 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7472 			return (tk == ke.key && tkm == kkm);
7473 		}
7474 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7475 	}
7476 }
7477 
7478 /// Sets the application name.
7479 @property string ApplicationName(string name) {
7480 	return _applicationName = name;
7481 }
7482 
7483 string _applicationName;
7484 
7485 /// ditto
7486 @property string ApplicationName() {
7487 	if(_applicationName is null) {
7488 		import core.runtime;
7489 		return Runtime.args[0];
7490 	}
7491 	return _applicationName;
7492 }
7493 
7494 
7495 /// Type of a [MouseEvent].
7496 enum MouseEventType : int {
7497 	motion = 0, /// The mouse moved inside the window
7498 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7499 	buttonReleased = 2, /// A mouse button was released
7500 }
7501 
7502 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7503 /++
7504 	Listen for this on your event listeners if you are interested in mouse action.
7505 
7506 	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.
7507 
7508 	Examples:
7509 
7510 	This will draw boxes on the window with the mouse as you hold the left button.
7511 	---
7512 	import arsd.simpledisplay;
7513 
7514 	void main() {
7515 		auto window = new SimpleWindow();
7516 
7517 		window.eventLoop(0,
7518 			(MouseEvent ev) {
7519 				if(ev.modifierState & ModifierState.leftButtonDown) {
7520 					auto painter = window.draw();
7521 					painter.fillColor = Color.red;
7522 					painter.outlineColor = Color.black;
7523 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7524 				}
7525 			}
7526 		);
7527 	}
7528 	---
7529 +/
7530 struct MouseEvent {
7531 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7532 
7533 	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.
7534 	int y; /// Current Y position of the cursor when the event fired.
7535 
7536 	int dx; /// Change in X position since last report
7537 	int dy; /// Change in Y position since last report
7538 
7539 	MouseButton button; /// See [MouseButton]
7540 	int modifierState; /// See [ModifierState]
7541 
7542 	version(X11)
7543 		private Time timestamp;
7544 
7545 	/// Returns a linear representation of mouse button,
7546 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7547 	///
7548 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7549 	@property ubyte buttonLinear() const {
7550 		import core.bitop;
7551 		if(button == 0)
7552 			return 0;
7553 		return (bsf(button) + 1) & 0b1111;
7554 	}
7555 
7556 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7557 
7558 	SimpleWindow window; /// The window in which the event happened.
7559 
7560 	Point globalCoordinates() {
7561 		Point p;
7562 		if(window is null)
7563 			throw new Exception("wtf");
7564 		static if(UsingSimpledisplayX11) {
7565 			Window child;
7566 			XTranslateCoordinates(
7567 				XDisplayConnection.get,
7568 				window.impl.window,
7569 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7570 				x, y, &p.x, &p.y, &child);
7571 			return p;
7572 		} else version(Windows) {
7573 			POINT[1] points;
7574 			points[0].x = x;
7575 			points[0].y = y;
7576 			MapWindowPoints(
7577 				window.impl.hwnd,
7578 				null,
7579 				points.ptr,
7580 				points.length
7581 			);
7582 			p.x = points[0].x;
7583 			p.y = points[0].y;
7584 
7585 			return p;
7586 		} else version(OSXCocoa) {
7587 			throw new NotYetImplementedException();
7588 		} else static assert(0);
7589 	}
7590 
7591 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7592 
7593 	/**
7594 	can contain emacs-like modifier prefix
7595 	case-insensitive names:
7596 		lmbX/leftX
7597 		rmbX/rightX
7598 		mmbX/middleX
7599 		wheelX
7600 		motion (no prefix allowed)
7601 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7602 	*/
7603 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7604 		if (str.length == 0) return false; // just in case
7605 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7606 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7607 		auto anchor = str;
7608 		uint mods = 0; // uint.max == any
7609 		// interesting bits in kmod
7610 		uint kmodmask =
7611 			ModifierState.shift|
7612 			ModifierState.ctrl|
7613 			ModifierState.alt|
7614 			ModifierState.windows|
7615 			ModifierState.leftButtonDown|
7616 			ModifierState.middleButtonDown|
7617 			ModifierState.rightButtonDown|
7618 			0;
7619 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7620 		bool wasButtons = false;
7621 		while (str.length) {
7622 			if (str.ptr[0] <= ' ') {
7623 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7624 				continue;
7625 			}
7626 			// one-letter modifier?
7627 			if (str.length >= 2 && str.ptr[1] == '-') {
7628 				switch (str.ptr[0]) {
7629 					case '*': // "any" modifier (cannot be undone)
7630 						mods = mods.max;
7631 						break;
7632 					case 'C': case 'c': // emacs "ctrl"
7633 						if (mods != mods.max) mods |= ModifierState.ctrl;
7634 						break;
7635 					case 'M': case 'm': // emacs "meta"
7636 						if (mods != mods.max) mods |= ModifierState.alt;
7637 						break;
7638 					case 'S': case 's': // emacs "shift"
7639 						if (mods != mods.max) mods |= ModifierState.shift;
7640 						break;
7641 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7642 						if (mods != mods.max) mods |= ModifierState.windows;
7643 						break;
7644 					default:
7645 						return false; // unknown modifier
7646 				}
7647 				str = str[2..$];
7648 				continue;
7649 			}
7650 			// word
7651 			char[16] buf = void; // locased
7652 			auto wep = 0;
7653 			while (str.length) {
7654 				immutable char ch = str.ptr[0];
7655 				if (ch <= ' ' || ch == '-') break;
7656 				str = str[1..$];
7657 				if (wep > buf.length) return false; // too long
7658 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7659 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7660 				else return false; // invalid char
7661 			}
7662 			if (wep == 0) return false; // just in case
7663 			uint bnum;
7664 			enum UpDown { None = -1, Up, Down, Any }
7665 			auto updown = UpDown.None; // 0: up; 1: down
7666 			switch (buf[0..wep]) {
7667 				// left button
7668 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7669 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7670 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7671 				case "lmb": case "left": bnum = 0; break;
7672 				// middle button
7673 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7674 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7675 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7676 				case "mmb": case "middle": bnum = 1; break;
7677 				// right button
7678 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7679 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7680 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7681 				case "rmb": case "right": bnum = 2; break;
7682 				// wheel
7683 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7684 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7685 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7686 				case "wheel": bnum = 3; break;
7687 				// motion
7688 				case "motion": bnum = 7; break;
7689 				// unknown
7690 				default: return false;
7691 			}
7692 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7693 			// parse possible "-up" or "-down"
7694 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7695 				wep = 0;
7696 				foreach (immutable idx, immutable char ch; str[1..$]) {
7697 					if (ch <= ' ' || ch == '-') break;
7698 					assert(idx == wep); // for now; trick
7699 					if (wep > buf.length) { wep = 0; break; } // too long
7700 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7701 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7702 					else { wep = 0; break; } // invalid char
7703 				}
7704 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7705 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7706 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7707 				// remove parsed part
7708 				if (updown != UpDown.None) str = str[wep+1..$];
7709 			}
7710 			if (updown == UpDown.None) {
7711 				updown = UpDown.Down;
7712 			}
7713 			wasButtons = wasButtons || (bnum <= 2);
7714 			//assert(updown != UpDown.None);
7715 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7716 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7717 			if (lastButt != lastButt.max) {
7718 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7719 				if (mods != mods.max) {
7720 					uint butbit = 0;
7721 					final switch (lastButt&0x03) {
7722 						case 0: butbit = ModifierState.leftButtonDown; break;
7723 						case 1: butbit = ModifierState.middleButtonDown; break;
7724 						case 2: butbit = ModifierState.rightButtonDown; break;
7725 					}
7726 					     if (lastButt&Flag.Down) mods |= butbit;
7727 					else if (lastButt&Flag.Up) mods &= ~butbit;
7728 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7729 				}
7730 			}
7731 			// remember last button
7732 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7733 		}
7734 		// no button -- nothing to do
7735 		if (lastButt == lastButt.max) return false;
7736 		// done parsing, check if something's left
7737 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7738 		// remove action button from mask
7739 		if ((lastButt&0xff) < 3) {
7740 			final switch (lastButt&0x03) {
7741 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7742 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7743 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7744 			}
7745 		}
7746 		// special case: "Motion" means "ignore buttons"
7747 		if ((lastButt&0xff) == 7 && !wasButtons) {
7748 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7749 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7750 		}
7751 		uint kmod = event.modifierState&kmodmask;
7752 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7753 		// check modifier state
7754 		if (mods != mods.max) {
7755 			if (kmod != mods) return false;
7756 		}
7757 		// now check type
7758 		if ((lastButt&0xff) == 7) {
7759 			// motion
7760 			if (event.type != MouseEventType.motion) return false;
7761 		} else if ((lastButt&0xff) == 3) {
7762 			// wheel
7763 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7764 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7765 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7766 			return false;
7767 		} else {
7768 			// buttons
7769 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7770 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7771 			{
7772 				return false;
7773 			}
7774 			// button number
7775 			switch (lastButt&0x03) {
7776 				case 0: if (event.button != MouseButton.left) return false; break;
7777 				case 1: if (event.button != MouseButton.middle) return false; break;
7778 				case 2: if (event.button != MouseButton.right) return false; break;
7779 				default: return false;
7780 			}
7781 		}
7782 		return true;
7783 	}
7784 }
7785 
7786 version(arsd_mevent_strcmp_test) unittest {
7787 	MouseEvent event;
7788 	event.type = MouseEventType.buttonPressed;
7789 	event.button = MouseButton.left;
7790 	event.modifierState = ModifierState.ctrl;
7791 	assert(event == "C-LMB");
7792 	assert(event != "C-LMBUP");
7793 	assert(event != "C-LMB-UP");
7794 	assert(event != "C-S-LMB");
7795 	assert(event == "*-LMB");
7796 	assert(event != "*-LMB-UP");
7797 
7798 	event.type = MouseEventType.buttonReleased;
7799 	assert(event != "C-LMB");
7800 	assert(event == "C-LMBUP");
7801 	assert(event == "C-LMB-UP");
7802 	assert(event != "C-S-LMB");
7803 	assert(event != "*-LMB");
7804 	assert(event == "*-LMB-UP");
7805 
7806 	event.button = MouseButton.right;
7807 	event.modifierState |= ModifierState.shift;
7808 	event.type = MouseEventType.buttonPressed;
7809 	assert(event != "C-LMB");
7810 	assert(event != "C-LMBUP");
7811 	assert(event != "C-LMB-UP");
7812 	assert(event != "C-S-LMB");
7813 	assert(event != "*-LMB");
7814 	assert(event != "*-LMB-UP");
7815 
7816 	assert(event != "C-RMB");
7817 	assert(event != "C-RMBUP");
7818 	assert(event != "C-RMB-UP");
7819 	assert(event == "C-S-RMB");
7820 	assert(event == "*-RMB");
7821 	assert(event != "*-RMB-UP");
7822 }
7823 
7824 /// This gives a few more options to drawing lines and such
7825 struct Pen {
7826 	Color color; /// the foreground color
7827 	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.
7828 	Style style; /// See [Style]
7829 /+
7830 // From X.h
7831 
7832 #define LineSolid		0
7833 #define LineOnOffDash		1
7834 #define LineDoubleDash		2
7835        LineDou-        The full path of the line is drawn, but the
7836        bleDash         even dashes are filled differently from the
7837                        odd dashes (see fill-style) with CapButt
7838                        style used where even and odd dashes meet.
7839 
7840 
7841 
7842 /* capStyle */
7843 
7844 #define CapNotLast		0
7845 #define CapButt			1
7846 #define CapRound		2
7847 #define CapProjecting		3
7848 
7849 /* joinStyle */
7850 
7851 #define JoinMiter		0
7852 #define JoinRound		1
7853 #define JoinBevel		2
7854 
7855 /* fillStyle */
7856 
7857 #define FillSolid		0
7858 #define FillTiled		1
7859 #define FillStippled		2
7860 #define FillOpaqueStippled	3
7861 
7862 
7863 +/
7864 	/// Style of lines drawn
7865 	enum Style {
7866 		Solid, /// a solid line
7867 		Dashed, /// a dashed line
7868 		Dotted, /// a dotted line
7869 	}
7870 }
7871 
7872 
7873 /++
7874 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7875 
7876 
7877 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7878 
7879 	$(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.)
7880 
7881 	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.
7882 
7883 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7884 
7885 	$(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.
7886 
7887 	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!
7888 
7889 	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!)
7890 
7891 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7892 
7893 	---
7894 		auto image = new Image(256, 256);
7895 		scope(exit) destroy(image);
7896 	---
7897 
7898 	As long as you don't hold on to it outside the scope.
7899 
7900 	I might change it to be an owned pointer at some point in the future.
7901 
7902 	)
7903 
7904 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7905 	you can also often get a fair amount of speedup by getting the raw data format and
7906 	writing some custom code.
7907 
7908 	FIXME INSERT EXAMPLES HERE
7909 
7910 
7911 +/
7912 final class Image {
7913 	///
7914 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7915 		this.width = width;
7916 		this.height = height;
7917 		this.enableAlpha = enableAlpha;
7918 
7919 		impl.createImage(width, height, forcexshm, enableAlpha);
7920 	}
7921 
7922 	///
7923 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7924 		this(size.width, size.height, forcexshm, enableAlpha);
7925 	}
7926 
7927 	private bool suppressDestruction;
7928 
7929 	version(X11)
7930 	this(XImage* handle) {
7931 		this.handle = handle;
7932 		this.rawData = cast(ubyte*) handle.data;
7933 		this.width = handle.width;
7934 		this.height = handle.height;
7935 		this.enableAlpha = handle.depth == 32;
7936 		suppressDestruction = true;
7937 	}
7938 
7939 	~this() {
7940 		if(suppressDestruction) return;
7941 		impl.dispose();
7942 	}
7943 
7944 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7945 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7946 	pure const @system nothrow {
7947 		/*
7948 			To use these to draw a blue rectangle with size WxH at position X,Y...
7949 
7950 			// make certain that it will fit before we proceed
7951 			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!
7952 
7953 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7954 			// (though calculating them isn't really that expensive).
7955 			auto nextLineAdjustment = img.adjustmentForNextLine();
7956 			auto offR = img.redByteOffset();
7957 			auto offB = img.blueByteOffset();
7958 			auto offG = img.greenByteOffset();
7959 			auto bpp = img.bytesPerPixel();
7960 
7961 			auto data = img.getDataPointer();
7962 
7963 			// figure out the starting byte offset
7964 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7965 
7966 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7967 
7968 			// and now our drawing loop for the rectangle
7969 			foreach(y; 0 .. H) {
7970 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7971 				foreach(x; 0 .. W) {
7972 					// write our color
7973 					data[offR] = 0;
7974 					data[offG] = 0;
7975 					data[offB] = 255;
7976 
7977 					data += bpp; // moving to the next pixel is just an addition...
7978 				}
7979 				startOfLine += nextLineAdjustment;
7980 			}
7981 
7982 
7983 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7984 
7985 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7986 			can be made into a bitmask or something so we can write them as *uint...
7987 		*/
7988 
7989 		///
7990 		int offsetForTopLeftPixel() {
7991 			version(X11) {
7992 				return 0;
7993 			} else version(Windows) {
7994 				if(enableAlpha) {
7995 					return (width * 4) * (height - 1);
7996 				} else {
7997 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7998 				}
7999 			} else version(OSXCocoa) {
8000 				return 0 ; //throw new NotYetImplementedException();
8001 			} else static assert(0, "fill in this info for other OSes");
8002 		}
8003 
8004 		///
8005 		int offsetForPixel(int x, int y) {
8006 			version(X11) {
8007 				auto offset = (y * width + x) * 4;
8008 				return offset;
8009 			} else version(Windows) {
8010 				if(enableAlpha) {
8011 					auto itemsPerLine = width * 4;
8012 					// remember, bmps are upside down
8013 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8014 					return offset;
8015 				} else {
8016 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8017 					// remember, bmps are upside down
8018 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8019 					return offset;
8020 				}
8021 			} else version(OSXCocoa) {
8022 				return 0 ; //throw new NotYetImplementedException();
8023 			} else static assert(0, "fill in this info for other OSes");
8024 		}
8025 
8026 		///
8027 		int adjustmentForNextLine() {
8028 			version(X11) {
8029 				return width * 4;
8030 			} else version(Windows) {
8031 				// windows bmps are upside down, so the adjustment is actually negative
8032 				if(enableAlpha)
8033 					return - (cast(int) width * 4);
8034 				else
8035 					return -((cast(int) width * 3 + 3) / 4) * 4;
8036 			} else version(OSXCocoa) {
8037 				return 0 ; //throw new NotYetImplementedException();
8038 			} else static assert(0, "fill in this info for other OSes");
8039 		}
8040 
8041 		/// once you have the position of a pixel, use these to get to the proper color
8042 		int redByteOffset() {
8043 			version(X11) {
8044 				return 2;
8045 			} else version(Windows) {
8046 				return 2;
8047 			} else version(OSXCocoa) {
8048 				return 0 ; //throw new NotYetImplementedException();
8049 			} else static assert(0, "fill in this info for other OSes");
8050 		}
8051 
8052 		///
8053 		int greenByteOffset() {
8054 			version(X11) {
8055 				return 1;
8056 			} else version(Windows) {
8057 				return 1;
8058 			} else version(OSXCocoa) {
8059 				return 0 ; //throw new NotYetImplementedException();
8060 			} else static assert(0, "fill in this info for other OSes");
8061 		}
8062 
8063 		///
8064 		int blueByteOffset() {
8065 			version(X11) {
8066 				return 0;
8067 			} else version(Windows) {
8068 				return 0;
8069 			} else version(OSXCocoa) {
8070 				return 0 ; //throw new NotYetImplementedException();
8071 			} else static assert(0, "fill in this info for other OSes");
8072 		}
8073 
8074 		/// Only valid if [enableAlpha] is true
8075 		int alphaByteOffset() {
8076 			version(X11) {
8077 				return 3;
8078 			} else version(Windows) {
8079 				return 3;
8080 			} else version(OSXCocoa) {
8081 				return 3; //throw new NotYetImplementedException();
8082 			} else static assert(0, "fill in this info for other OSes");
8083 		}
8084 	}
8085 
8086 	///
8087 	final void putPixel(int x, int y, Color c) {
8088 		if(x < 0 || x >= width)
8089 			return;
8090 		if(y < 0 || y >= height)
8091 			return;
8092 
8093 		impl.setPixel(x, y, c);
8094 	}
8095 
8096 	///
8097 	final Color getPixel(int x, int y) {
8098 		if(x < 0 || x >= width)
8099 			return Color.transparent;
8100 		if(y < 0 || y >= height)
8101 			return Color.transparent;
8102 
8103 		version(OSXCocoa) throw new NotYetImplementedException(); else
8104 		return impl.getPixel(x, y);
8105 	}
8106 
8107 	///
8108 	final void opIndexAssign(Color c, int x, int y) {
8109 		putPixel(x, y, c);
8110 	}
8111 
8112 	///
8113 	TrueColorImage toTrueColorImage() {
8114 		auto tci = new TrueColorImage(width, height);
8115 		convertToRgbaBytes(tci.imageData.bytes);
8116 		return tci;
8117 	}
8118 
8119 	///
8120 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8121 		auto tci = i.getAsTrueColorImage();
8122 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8123 		static if(UsingSimpledisplayX11)
8124 			img.premultiply = premultiply;
8125 		img.setRgbaBytes(tci.imageData.bytes);
8126 		return img;
8127 	}
8128 
8129 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8130 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8131 	/// if you pass null, it will allocate a new one.
8132 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8133 		if(where is null)
8134 			where = new ubyte[this.width*this.height*4];
8135 		convertToRgbaBytes(where);
8136 		return where;
8137 	}
8138 
8139 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8140 	void setRgbaBytes(in ubyte[] from ) {
8141 		assert(from.length == this.width * this.height * 4);
8142 		setFromRgbaBytes(from);
8143 	}
8144 
8145 	// FIXME: make properly cross platform by getting rgba right
8146 
8147 	/// warning: this is not portable across platforms because the data format can change
8148 	ubyte* getDataPointer() {
8149 		return impl.rawData;
8150 	}
8151 
8152 	/// for use with getDataPointer
8153 	final int bytesPerLine() const pure @safe nothrow {
8154 		version(Windows)
8155 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8156 		else version(X11)
8157 			return 4 * width;
8158 		else version(OSXCocoa)
8159 			return 4 * width;
8160 		else static assert(0);
8161 	}
8162 
8163 	/// for use with getDataPointer
8164 	final int bytesPerPixel() const pure @safe nothrow {
8165 		version(Windows)
8166 			return enableAlpha ? 4 : 3;
8167 		else version(X11)
8168 			return 4;
8169 		else version(OSXCocoa)
8170 			return 4;
8171 		else static assert(0);
8172 	}
8173 
8174 	///
8175 	immutable int width;
8176 
8177 	///
8178 	immutable int height;
8179 
8180 	///
8181 	immutable bool enableAlpha;
8182     //private:
8183 	mixin NativeImageImplementation!() impl;
8184 }
8185 
8186 /++
8187 	A convenience function to pop up a window displaying the image.
8188 	If you pass a win, it will draw the image in it. Otherwise, it will
8189 	create a window with the size of the image and run its event loop, closing
8190 	when a key is pressed.
8191 
8192 	History:
8193 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8194 		always block until the application quit which could cause bizarre behavior
8195 		inside a more complex application. Now, the default is to block until
8196 		this window closes if it is the only event loop running, and otherwise,
8197 		not to block at all and just pop up the display window asynchronously.
8198 +/
8199 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8200 	if(win is null) {
8201 		win = new SimpleWindow(image);
8202 		{
8203 			auto p = win.draw;
8204 			p.drawImage(Point(0, 0), image);
8205 		}
8206 		win.eventLoopWithBlockingMode(
8207 			bm, 0,
8208 			(KeyEvent ev) {
8209 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8210 			} );
8211 	} else {
8212 		win.image = image;
8213 	}
8214 }
8215 
8216 enum FontWeight : int {
8217 	dontcare = 0,
8218 	thin = 100,
8219 	extralight = 200,
8220 	light = 300,
8221 	regular = 400,
8222 	medium = 500,
8223 	semibold = 600,
8224 	bold = 700,
8225 	extrabold = 800,
8226 	heavy = 900
8227 }
8228 
8229 /++
8230 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8231 
8232 	History:
8233 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8234 +/
8235 interface MeasurableFont {
8236 	/++
8237 		Returns true if it is a monospace font, meaning each of the
8238 		glyphs (at least the ascii characters) have matching width
8239 		and no kerning, so you can determine the display width of some
8240 		strings by simply multiplying the string width by [averageWidth].
8241 
8242 		(Please note that multiply doesn't $(I actually) work in general,
8243 		consider characters like tab and newline, but it does sometimes.)
8244 	+/
8245 	bool isMonospace();
8246 
8247 	/++
8248 		The average width of glyphs in the font, traditionally equal to the
8249 		width of the lowercase x. Can be used to estimate bounding boxes,
8250 		especially if the font [isMonospace].
8251 
8252 		Given in pixels.
8253 	+/
8254 	int averageWidth();
8255 	/++
8256 		The height of the bounding box of a line.
8257 	+/
8258 	int height();
8259 	/++
8260 		The maximum ascent of a glyph above the baseline.
8261 
8262 		Given in pixels.
8263 	+/
8264 	int ascent();
8265 	/++
8266 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8267 
8268 		Given in pixels.
8269 	+/
8270 	int descent();
8271 	/++
8272 		The display width of the given string, and if you provide a window, it will use it to
8273 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8274 
8275 		Given in pixels.
8276 	+/
8277 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8278 
8279 }
8280 
8281 // FIXME: i need a font cache and it needs to handle disconnects.
8282 
8283 /++
8284 	Represents a font loaded off the operating system or the X server.
8285 
8286 
8287 	While the api here is unified cross platform, the fonts are not necessarily
8288 	available, even across machines of the same platform, so be sure to always check
8289 	for null (using [isNull]) and have a fallback plan.
8290 
8291 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8292 
8293 	Worst case, a null font will automatically fall back to the default font loaded
8294 	for your system.
8295 +/
8296 class OperatingSystemFont : MeasurableFont {
8297 	// FIXME: when the X Connection is lost, these need to be invalidated!
8298 	// that means I need to store the original stuff again to reconstruct it too.
8299 
8300 	version(X11) {
8301 		XFontStruct* font;
8302 		XFontSet fontset;
8303 
8304 		version(with_xft) {
8305 			XftFont* xftFont;
8306 			bool isXft;
8307 		}
8308 	} else version(Windows) {
8309 		HFONT font;
8310 		int width_;
8311 		int height_;
8312 	} else version(OSXCocoa) {
8313 		NSFont font;
8314 	} else static assert(0);
8315 
8316 	/++
8317 		Constructs the class and immediately calls [load].
8318 	+/
8319 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8320 		load(name, size, weight, italic);
8321 	}
8322 
8323 	/++
8324 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8325 
8326 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8327 
8328 		History:
8329 			Added January 24, 2021.
8330 	+/
8331 	this() {
8332 		// this space intentionally left blank
8333 	}
8334 
8335 	/++
8336 		Constructs a copy of the given font object.
8337 
8338 		History:
8339 			Added January 7, 2023.
8340 	+/
8341 	this(OperatingSystemFont font) {
8342 		if(font is null || font.loadedInfo is LoadedInfo.init)
8343 			loadDefault();
8344 		else
8345 			load(font.loadedInfo.tupleof);
8346 	}
8347 
8348 	/++
8349 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8350 
8351 		History:
8352 			Added November 13, 2020.
8353 	+/
8354 	version(with_xft)
8355 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8356 		unload();
8357 
8358 		if(!XftLibrary.attempted) {
8359 			XftLibrary.loadDynamicLibrary();
8360 		}
8361 
8362 		if(!XftLibrary.loadSuccessful)
8363 			return false;
8364 
8365 		auto display = XDisplayConnection.get;
8366 
8367 		char[256] nameBuffer = void;
8368 		int nbp = 0;
8369 
8370 		void add(in char[] a) {
8371 			nameBuffer[nbp .. nbp + a.length] = a[];
8372 			nbp += a.length;
8373 		}
8374 		add(name);
8375 
8376 		if(size) {
8377 			add(":size=");
8378 			add(toInternal!string(size));
8379 		}
8380 		if(weight != FontWeight.dontcare) {
8381 			add(":weight=");
8382 			add(weightToString(weight));
8383 		}
8384 		if(italic)
8385 			add(":slant=100");
8386 
8387 		nameBuffer[nbp] = 0;
8388 
8389 		this.xftFont = XftFontOpenName(
8390 			display,
8391 			DefaultScreen(display),
8392 			nameBuffer.ptr
8393 		);
8394 
8395 		this.isXft = true;
8396 
8397 		if(xftFont !is null) {
8398 			isMonospace_ = stringWidth("x") == stringWidth("M");
8399 			ascent_ = xftFont.ascent;
8400 			descent_ = xftFont.descent;
8401 		}
8402 
8403 		return !isNull();
8404 	}
8405 
8406 	/++
8407 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8408 
8409 
8410 		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.
8411 
8412 		If `pattern` is null, it returns all available font families.
8413 
8414 		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.
8415 
8416 		The format of the pattern is platform-specific.
8417 
8418 		History:
8419 			Added May 1, 2021 (dub v9.5)
8420 	+/
8421 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8422 		version(Windows) {
8423 			auto hdc = GetDC(null);
8424 			scope(exit) ReleaseDC(null, hdc);
8425 			LOGFONT logfont;
8426 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8427 				auto localHandler = *(cast(typeof(handler)*) p);
8428 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8429 			}
8430 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8431 		} else version(X11) {
8432 			//import core.stdc.stdio;
8433 			bool done = false;
8434 			version(with_xft) {
8435 				if(!XftLibrary.attempted) {
8436 					XftLibrary.loadDynamicLibrary();
8437 				}
8438 
8439 				if(!XftLibrary.loadSuccessful)
8440 					goto skipXft;
8441 
8442 				if(!FontConfigLibrary.attempted)
8443 					FontConfigLibrary.loadDynamicLibrary();
8444 				if(!FontConfigLibrary.loadSuccessful)
8445 					goto skipXft;
8446 
8447 				{
8448 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8449 					if(got is null)
8450 						goto skipXft;
8451 					scope(exit) FcFontSetDestroy(got);
8452 
8453 					auto fontPatterns = got.fonts[0 .. got.nfont];
8454 					foreach(candidate; fontPatterns) {
8455 						char* where, whereStyle;
8456 
8457 						char* pmg = FcNameUnparse(candidate);
8458 
8459 						//FcPatternGetString(candidate, "family", 0, &where);
8460 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8461 						//if(where && whereStyle) {
8462 						if(pmg) {
8463 							if(!handler(pmg.sliceCString))
8464 								return;
8465 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8466 						}
8467 					}
8468 				}
8469 			}
8470 
8471 			skipXft:
8472 
8473 			if(pattern is null)
8474 				pattern = "*";
8475 
8476 			int count;
8477 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8478 			scope(exit) XFreeFontNames(coreFontsRaw);
8479 
8480 			auto coreFonts = coreFontsRaw[0 .. count];
8481 
8482 			foreach(font; coreFonts) {
8483 				char[128] tmp;
8484 				tmp[0 ..5] = "core:";
8485 				auto cf = font.sliceCString;
8486 				if(5 + cf.length > tmp.length)
8487 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8488 				tmp[5 .. 5 + cf.length] = cf;
8489 				if(!handler(tmp[0 .. 5 + cf.length]))
8490 					return;
8491 			}
8492 		}
8493 	}
8494 
8495 	/++
8496 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8497 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8498 
8499 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8500 		underlying system doesn't support returning the raw bytes.
8501 
8502 		History:
8503 			Added September 10, 2021 (dub v10.3)
8504 	+/
8505 	ubyte[] getTtfBytes() {
8506 		if(isNull)
8507 			return null;
8508 
8509 		version(Windows) {
8510 			auto dc = GetDC(null);
8511 			auto orig = SelectObject(dc, font);
8512 
8513 			scope(exit) {
8514 				SelectObject(dc, orig);
8515 				ReleaseDC(null, dc);
8516 			}
8517 
8518 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8519 			if(res == GDI_ERROR)
8520 				return null;
8521 
8522 			ubyte[] buffer = new ubyte[](res);
8523 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8524 			if(res == GDI_ERROR)
8525 				return null; // wtf really tbh
8526 
8527 			return buffer;
8528 		} else version(with_xft) {
8529 			if(isXft && xftFont) {
8530 				if(!FontConfigLibrary.attempted)
8531 					FontConfigLibrary.loadDynamicLibrary();
8532 				if(!FontConfigLibrary.loadSuccessful)
8533 					return null;
8534 
8535 				char* file;
8536 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8537 					if (file !is null && file[0]) {
8538 						import core.stdc.stdio;
8539 						auto fp = fopen(file, "rb");
8540 						if(fp is null)
8541 							return null;
8542 						scope(exit)
8543 							fclose(fp);
8544 						fseek(fp, 0, SEEK_END);
8545 						ubyte[] buffer = new ubyte[](ftell(fp));
8546 						fseek(fp, 0, SEEK_SET);
8547 
8548 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8549 						if(got != buffer.length)
8550 							return null;
8551 
8552 						return buffer;
8553 					}
8554 				}
8555 			}
8556 			return null;
8557 		} else throw new NotYetImplementedException();
8558 	}
8559 
8560 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8561 
8562 	private string weightToString(FontWeight weight) {
8563 		with(FontWeight)
8564 		final switch(weight) {
8565 			case dontcare: return "*";
8566 			case thin: return "extralight";
8567 			case extralight: return "extralight";
8568 			case light: return "light";
8569 			case regular: return "regular";
8570 			case medium: return "medium";
8571 			case semibold: return "demibold";
8572 			case bold: return "bold";
8573 			case extrabold: return "demibold";
8574 			case heavy: return "black";
8575 		}
8576 	}
8577 
8578 	/++
8579 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8580 
8581 		History:
8582 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8583 	+/
8584 	version(X11)
8585 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8586 		unload();
8587 
8588 		string xfontstr;
8589 
8590 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8591 			// this is kinda a disgusting hack but if the user sends an exact
8592 			// string I'd like to honor it...
8593 			xfontstr = name;
8594 		} else {
8595 			string weightstr = weightToString(weight);
8596 			string sizestr;
8597 			if(size == 0)
8598 				sizestr = "*";
8599 			else
8600 				sizestr = toInternal!string(size);
8601 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8602 		}
8603 
8604 		// writeln(xfontstr);
8605 
8606 		auto display = XDisplayConnection.get;
8607 
8608 		font = XLoadQueryFont(display, xfontstr.ptr);
8609 		if(font is null)
8610 			return false;
8611 
8612 		char** lol;
8613 		int lol2;
8614 		char* lol3;
8615 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8616 
8617 		prepareFontInfo();
8618 
8619 		return !isNull();
8620 	}
8621 
8622 	version(X11)
8623 	private void prepareFontInfo() {
8624 		if(font !is null) {
8625 			isMonospace_ = stringWidth("l") == stringWidth("M");
8626 			ascent_ = font.max_bounds.ascent;
8627 			descent_ = font.max_bounds.descent;
8628 		}
8629 	}
8630 
8631 	version(OSXCocoa)
8632 	private void prepareFontInfo() {
8633 		if(font !is null) {
8634 			isMonospace_ = font.isFixedPitch;
8635 			ascent_ = cast(int) font.ascender;
8636 			descent_ = cast(int) - font.descender;
8637 		}
8638 	}
8639 
8640 
8641 	/++
8642 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8643 
8644 		History:
8645 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8646 	+/
8647 	version(Windows)
8648 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8649 		unload();
8650 
8651 		WCharzBuffer buffer = WCharzBuffer(name);
8652 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8653 
8654 		prepareFontInfo(hdc);
8655 
8656 		return !isNull();
8657 	}
8658 
8659 	version(Windows)
8660 	void prepareFontInfo(HDC hdc = null) {
8661 		if(font is null)
8662 			return;
8663 
8664 		TEXTMETRIC tm;
8665 		auto dc = hdc ? hdc : GetDC(null);
8666 		auto orig = SelectObject(dc, font);
8667 		GetTextMetrics(dc, &tm);
8668 		SelectObject(dc, orig);
8669 		if(hdc is null)
8670 			ReleaseDC(null, dc);
8671 
8672 		width_ = tm.tmAveCharWidth;
8673 		height_ = tm.tmHeight;
8674 		ascent_ = tm.tmAscent;
8675 		descent_ = tm.tmDescent;
8676 		// 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.
8677 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8678 	}
8679 
8680 
8681 	/++
8682 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8683 
8684 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8685 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8686 
8687 		On Windows, it forwards directly to [loadWin32].
8688 
8689 		Params:
8690 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8691 			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.
8692 			weight = approximate boldness, results may vary.
8693 			italic = try to get a slanted version of the given font.
8694 
8695 		History:
8696 			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.
8697 	+/
8698 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8699 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
8700 		version(X11) {
8701 			version(with_xft) {
8702 				if(name.length > 5 && name[0 .. 5] == "core:") {
8703 					goto core;
8704 				}
8705 
8706 				if(loadXft(name, size, weight, italic))
8707 					return true;
8708 				// if xft fails, fallback to core to avoid breaking
8709 				// code that already depended on this.
8710 			}
8711 
8712 			core:
8713 
8714 			if(name.length > 5 && name[0 .. 5] == "core:") {
8715 				name = name[5 .. $];
8716 			}
8717 
8718 			return loadCoreX(name, size, weight, italic);
8719 		} else version(Windows) {
8720 			return loadWin32(name, size, weight, italic);
8721 		} else version(OSXCocoa) {
8722 			return loadCocoa(name, size, weight, italic);
8723 		} else static assert(0);
8724 	}
8725 
8726 	version(OSXCocoa)
8727 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
8728 		unload();
8729 
8730 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
8731 		prepareFontInfo();
8732 
8733 		return !isNull();
8734 	}
8735 
8736 	private struct LoadedInfo {
8737 		string name;
8738 		int size;
8739 		FontWeight weight;
8740 		bool italic;
8741 	}
8742 	private LoadedInfo loadedInfo;
8743 
8744 	///
8745 	void unload() {
8746 		if(isNull())
8747 			return;
8748 
8749 		version(X11) {
8750 			auto display = XDisplayConnection.display;
8751 
8752 			if(display is null)
8753 				return;
8754 
8755 			version(with_xft) {
8756 				if(isXft) {
8757 					if(xftFont)
8758 						XftFontClose(display, xftFont);
8759 					isXft = false;
8760 					xftFont = null;
8761 					return;
8762 				}
8763 			}
8764 
8765 			if(font && font !is ScreenPainterImplementation.defaultfont)
8766 				XFreeFont(display, font);
8767 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8768 				XFreeFontSet(display, fontset);
8769 
8770 			font = null;
8771 			fontset = null;
8772 		} else version(Windows) {
8773 			DeleteObject(font);
8774 			font = null;
8775 		} else version(OSXCocoa) {
8776 			font.release();
8777 			font = null;
8778 		} else static assert(0);
8779 	}
8780 
8781 	private bool isMonospace_;
8782 
8783 	/++
8784 		History:
8785 			Added January 16, 2021
8786 	+/
8787 	bool isMonospace() {
8788 		return isMonospace_;
8789 	}
8790 
8791 	/++
8792 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8793 
8794 		History:
8795 			Added March 26, 2020
8796 			Documented January 16, 2021
8797 	+/
8798 	int averageWidth() {
8799 		version(X11) {
8800 			return stringWidth("x");
8801 		} version(OSXCocoa) {
8802 			return stringWidth("x");
8803 		} else version(Windows)
8804 			return width_;
8805 		else assert(0);
8806 	}
8807 
8808 	/++
8809 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8810 
8811 		History:
8812 			Added January 16, 2021
8813 	+/
8814 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8815 	// FIXME: what about tab?
8816 		if(isNull)
8817 			return 0;
8818 
8819 		version(X11) {
8820 			version(with_xft)
8821 				if(isXft && xftFont !is null) {
8822 					//return xftFont.max_advance_width;
8823 					XGlyphInfo extents;
8824 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8825 					// writeln(extents);
8826 					return extents.xOff;
8827 				}
8828 			if(font is null)
8829 				return 0;
8830 			else if(fontset) {
8831 				XRectangle rect;
8832 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8833 
8834 				return rect.width;
8835 			} else {
8836 				return XTextWidth(font, s.ptr, cast(int) s.length);
8837 			}
8838 		} else version(Windows) {
8839 			WCharzBuffer buffer = WCharzBuffer(s);
8840 
8841 			return stringWidth(buffer.slice, window);
8842 		} else version(OSXCocoa) {
8843 			/+
8844 			int charCount = [string length];
8845 			CGGlyph glyphs[charCount];
8846 			CGRect rects[charCount];
8847 
8848 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
8849 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
8850 
8851 			int totalwidth = 0, maxheight = 0;
8852 			for (int i=0; i < charCount; i++)
8853 			{
8854 				totalwidth += rects[i].size.width;
8855 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
8856 			}
8857 
8858 			dim = CGSizeMake(totalwidth, maxheight);
8859 			+/
8860 
8861 			return 16; // FIXME
8862 		}
8863 		else assert(0);
8864 	}
8865 
8866 	version(Windows)
8867 	/// ditto
8868 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8869 		if(isNull)
8870 			return 0;
8871 		version(Windows) {
8872 			SIZE size;
8873 
8874 			prepareContext(window);
8875 			scope(exit) releaseContext();
8876 
8877 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8878 
8879 			return size.cx;
8880 		} else {
8881 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8882 			static assert(0, "not implemented yet");
8883 			//return stringWidth(s, window);
8884 		}
8885 	}
8886 
8887 	private {
8888 		int prepRefcount;
8889 
8890 		version(Windows) {
8891 			HDC dc;
8892 			HANDLE orig;
8893 			HWND hwnd;
8894 		}
8895 	}
8896 	/++
8897 		[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.
8898 
8899 		History:
8900 			Added January 23, 2021
8901 	+/
8902 	void prepareContext(SimpleWindow window = null) {
8903 		prepRefcount++;
8904 		if(prepRefcount == 1) {
8905 			version(Windows) {
8906 				hwnd = window is null ? null : window.impl.hwnd;
8907 				dc = GetDC(hwnd);
8908 				orig = SelectObject(dc, font);
8909 			}
8910 		}
8911 	}
8912 	/// ditto
8913 	void releaseContext() {
8914 		prepRefcount--;
8915 		if(prepRefcount == 0) {
8916 			version(Windows) {
8917 				SelectObject(dc, orig);
8918 				ReleaseDC(hwnd, dc);
8919 				hwnd = null;
8920 				dc = null;
8921 				orig = null;
8922 			}
8923 		}
8924 	}
8925 
8926 	/+
8927 		FIXME: I think I need advance and kerning pair
8928 
8929 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8930 	+/
8931 
8932 	/++
8933 		Returns the height of the font.
8934 
8935 		History:
8936 			Added March 26, 2020
8937 			Documented January 16, 2021
8938 	+/
8939 	int height() {
8940 		version(X11) {
8941 			version(with_xft)
8942 				if(isXft && xftFont !is null) {
8943 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8944 				}
8945 			if(font is null)
8946 				return 0;
8947 			return font.max_bounds.ascent + font.max_bounds.descent;
8948 		} else version(Windows) {
8949 			return height_;
8950 		} else version(OSXCocoa) {
8951 			if(font is null)
8952 				return 0;
8953 			return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight
8954 		}
8955 		else assert(0);
8956 	}
8957 
8958 	private int ascent_;
8959 	private int descent_;
8960 
8961 	/++
8962 		Max ascent above the baseline.
8963 
8964 		History:
8965 			Added January 22, 2021
8966 	+/
8967 	int ascent() {
8968 		return ascent_;
8969 	}
8970 
8971 	/++
8972 		Max descent below the baseline.
8973 
8974 		History:
8975 			Added January 22, 2021
8976 	+/
8977 	int descent() {
8978 		return descent_;
8979 	}
8980 
8981 	/++
8982 		Loads the default font used by [ScreenPainter] if none others are loaded.
8983 
8984 		Returns:
8985 			This method mutates the `this` object, but then returns `this` for
8986 			easy chaining like:
8987 
8988 			---
8989 			auto font = foo.isNull ? foo : foo.loadDefault
8990 			---
8991 
8992 		History:
8993 			Added previously, but left unimplemented until January 24, 2021.
8994 	+/
8995 	OperatingSystemFont loadDefault() {
8996 		unload();
8997 
8998 		loadedInfo = LoadedInfo.init;
8999 
9000 		version(X11) {
9001 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9002 			// but meh since sdpy does its own thing, this should be ok too
9003 
9004 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9005 			this.font = ScreenPainterImplementation.defaultfont;
9006 			this.fontset = ScreenPainterImplementation.defaultfontset;
9007 
9008 			prepareFontInfo();
9009 			return this;
9010 		} else version(Windows) {
9011 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9012 			this.font = ScreenPainterImplementation.defaultGuiFont;
9013 
9014 			prepareFontInfo();
9015 			return this;
9016 		} else version(OSXCocoa) {
9017 			this.font = NSFont.systemFontOfSize(15);
9018 
9019 			prepareFontInfo();
9020 
9021 			// import std.stdio; writeln("Load default: ", this.height());
9022 			return this;
9023 		} else throw new NotYetImplementedException();
9024 	}
9025 
9026 	///
9027 	bool isNull() {
9028 		version(with_xft)
9029 			if(isXft)
9030 				return xftFont is null;
9031 		return font is null;
9032 	}
9033 
9034 	/* Metrics */
9035 	/+
9036 		GetABCWidth
9037 		GetKerningPairs
9038 
9039 		if I do it right, I can size it all here, and match
9040 		what happens when I draw the full string with the OS functions.
9041 
9042 		subclasses might do the same thing while getting the glyphs on images
9043 	struct GlyphInfo {
9044 		int glyph;
9045 
9046 		size_t stringIdxStart;
9047 		size_t stringIdxEnd;
9048 
9049 		Rectangle boundingBox;
9050 	}
9051 	GlyphInfo[] getCharBoxes() {
9052 		// XftTextExtentsUtf8
9053 		return null;
9054 
9055 	}
9056 	+/
9057 
9058 	~this() {
9059 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9060 		unload();
9061 	}
9062 }
9063 
9064 version(Windows)
9065 private string sliceCString(const(wchar)[] w) {
9066 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9067 }
9068 
9069 private inout(char)[] sliceCString(inout(char)* s) {
9070 	import core.stdc.string;
9071 	auto len = strlen(s);
9072 	return s[0 .. len];
9073 }
9074 
9075 version(OSXCocoa)
9076 	alias PaintingHandle = NSObject;
9077 else
9078 	alias PaintingHandle = NativeWindowHandle;
9079 
9080 /**
9081 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9082 	than constructing it directly. Then, it is reference counted so you can pass it
9083 	at around and when the last ref goes out of scope, the buffered drawing activities
9084 	are all carried out.
9085 
9086 
9087 	Most functions use the outlineColor instead of taking a color themselves.
9088 	ScreenPainter is reference counted and draws its buffer to the screen when its
9089 	final reference goes out of scope.
9090 */
9091 struct ScreenPainter {
9092 	CapableOfBeingDrawnUpon window;
9093 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9094 		this.window = window;
9095 		if(window.closed)
9096 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9097 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9098 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9099 		if(window.activeScreenPainter !is null) {
9100 			impl = window.activeScreenPainter;
9101 			if(impl.referenceCount == 0) {
9102 				impl.window = window;
9103 				impl.create(handle);
9104 			}
9105 			impl.manualInvalidations = manualInvalidations;
9106 			impl.referenceCount++;
9107 		//	writeln("refcount ++ ", impl.referenceCount);
9108 		} else {
9109 			impl = new ScreenPainterImplementation;
9110 			impl.window = window;
9111 			impl.create(handle);
9112 			impl.referenceCount = 1;
9113 			impl.manualInvalidations = manualInvalidations;
9114 			window.activeScreenPainter = impl;
9115 			// writeln("constructed");
9116 		}
9117 
9118 		copyActiveOriginals();
9119 	}
9120 
9121 	/++
9122 		EXPERIMENTAL. subject to change.
9123 
9124 		When you draw a cursor, you can draw this to notify your window of where it is,
9125 		for IME systems to use.
9126 	+/
9127 	void notifyCursorPosition(int x, int y, int width, int height) {
9128 		if(auto w = cast(SimpleWindow) window) {
9129 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9130 		}
9131 	}
9132 
9133 	/++
9134 		If you are using manual invalidations, this informs the
9135 		window system that a section needs to be redrawn.
9136 
9137 		If you didn't opt into manual invalidation, you don't
9138 		have to call this.
9139 
9140 		History:
9141 			Added December 30, 2021 (dub v10.5)
9142 	+/
9143 	void invalidateRect(Rectangle rect) {
9144 		if(impl is null) return;
9145 
9146 		// transform(rect)
9147 		rect.left += _originX;
9148 		rect.right += _originX;
9149 		rect.top += _originY;
9150 		rect.bottom += _originY;
9151 
9152 		impl.invalidateRect(rect);
9153 	}
9154 
9155 	private Pen originalPen;
9156 	private Color originalFillColor;
9157 	private arsd.color.Rectangle originalClipRectangle;
9158 	private OperatingSystemFont originalFont;
9159 	void copyActiveOriginals() {
9160 		if(impl is null) return;
9161 		originalPen = impl._activePen;
9162 		originalFillColor = impl._fillColor;
9163 		originalClipRectangle = impl._clipRectangle;
9164 		version(OSXCocoa) {} else
9165 		originalFont = impl._activeFont;
9166 	}
9167 
9168 	~this() {
9169 		if(impl is null) return;
9170 		impl.referenceCount--;
9171 		//writeln("refcount -- ", impl.referenceCount);
9172 		if(impl.referenceCount == 0) {
9173 			// writeln("destructed");
9174 			impl.dispose();
9175 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9176 			// writeln("paint finished");
9177 		} else {
9178 			// there is still an active reference, reset stuff so the
9179 			// next user doesn't get weirdness via the reference
9180 			this.rasterOp = RasterOp.normal;
9181 			pen = originalPen;
9182 			fillColor = originalFillColor;
9183 			if(originalFont)
9184 				setFont(originalFont);
9185 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9186 		}
9187 	}
9188 
9189 	this(this) {
9190 		if(impl is null) return;
9191 		impl.referenceCount++;
9192 		//writeln("refcount ++ ", impl.referenceCount);
9193 
9194 		copyActiveOriginals();
9195 	}
9196 
9197 	private int _originX;
9198 	private int _originY;
9199 	@property int originX() { return _originX; }
9200 	@property int originY() { return _originY; }
9201 	@property int originX(int a) {
9202 		_originX = a;
9203 		return _originX;
9204 	}
9205 	@property int originY(int a) {
9206 		_originY = a;
9207 		return _originY;
9208 	}
9209 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9210 	private void transform(ref Point p) {
9211 		if(impl is null) return;
9212 		p.x += _originX;
9213 		p.y += _originY;
9214 	}
9215 
9216 	// this needs to be checked BEFORE the originX/Y transformation
9217 	private bool isClipped(Point p) {
9218 		return !currentClipRectangle.contains(p);
9219 	}
9220 	private bool isClipped(Point p, int width, int height) {
9221 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9222 	}
9223 	private bool isClipped(Point p, Size s) {
9224 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9225 	}
9226 	private bool isClipped(Point p, Point p2) {
9227 		// need to ensure the end points are actually included inside, so the +1 does that
9228 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9229 	}
9230 
9231 
9232 	/++
9233 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9234 
9235 		Returns:
9236 			The old clip rectangle.
9237 
9238 		History:
9239 			Return value was `void` prior to May 10, 2021.
9240 
9241 	+/
9242 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9243 		if(impl is null) return currentClipRectangle;
9244 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9245 			return currentClipRectangle; // no need to do anything
9246 		auto old = currentClipRectangle;
9247 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9248 		transform(pt);
9249 
9250 		impl.setClipRectangle(pt.x, pt.y, width, height);
9251 
9252 		return old;
9253 	}
9254 
9255 	/// ditto
9256 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9257 		if(impl is null) return currentClipRectangle;
9258 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9259 	}
9260 
9261 	///
9262 	void setFont(OperatingSystemFont font) {
9263 		if(impl is null) return;
9264 		impl.setFont(font);
9265 	}
9266 
9267 	///
9268 	int fontHeight() {
9269 		if(impl is null) return 0;
9270 		return impl.fontHeight();
9271 	}
9272 
9273 	private Pen activePen;
9274 
9275 	///
9276 	@property void pen(Pen p) {
9277 		if(impl is null) return;
9278 		activePen = p;
9279 		impl.pen(p);
9280 	}
9281 
9282 	///
9283 	@scriptable
9284 	@property void outlineColor(Color c) {
9285 		if(impl is null) return;
9286 		if(activePen.color == c)
9287 			return;
9288 		activePen.color = c;
9289 		impl.pen(activePen);
9290 	}
9291 
9292 	///
9293 	@scriptable
9294 	@property void fillColor(Color c) {
9295 		if(impl is null) return;
9296 		impl.fillColor(c);
9297 	}
9298 
9299 	///
9300 	@property void rasterOp(RasterOp op) {
9301 		if(impl is null) return;
9302 		impl.rasterOp(op);
9303 	}
9304 
9305 
9306 	void updateDisplay() {
9307 		// FIXME this should do what the dtor does
9308 	}
9309 
9310 	/// 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)
9311 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9312 		if(impl is null) return;
9313 		if(isClipped(upperLeft, width, height)) return;
9314 		transform(upperLeft);
9315 		version(Windows) {
9316 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9317 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9318 			RECT clip = scroll;
9319 			RECT uncovered;
9320 			HRGN hrgn;
9321 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9322 				throw new WindowsApiException("ScrollDC", GetLastError());
9323 
9324 		} else version(X11) {
9325 			// FIXME: clip stuff outside this rectangle
9326 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9327 		} else version(OSXCocoa) {
9328 			throw new NotYetImplementedException();
9329 		} else static assert(0);
9330 	}
9331 
9332 	///
9333 	void clear(Color color = Color.white()) {
9334 		if(impl is null) return;
9335 		fillColor = color;
9336 		outlineColor = color;
9337 		drawRectangle(Point(0, 0), window.width, window.height);
9338 	}
9339 
9340 	/++
9341 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9342 
9343 		Params:
9344 			upperLeft = point on the window where the upper left corner of the image will be drawn
9345 			imageUpperLeft = point on the image to start the slice to draw
9346 			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.
9347 		History:
9348 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9349 	+/
9350 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9351 		if(impl is null) return;
9352 		if(isClipped(upperLeft, s.width, s.height)) return;
9353 		transform(upperLeft);
9354 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9355 	}
9356 
9357 	///
9358 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9359 		if(impl is null) return;
9360 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9361 		transform(upperLeft);
9362 		if(w == 0 || w > i.width)
9363 			w = i.width;
9364 		if(h == 0 || h > i.height)
9365 			h = i.height;
9366 		if(upperLeftOfImage.x < 0)
9367 			upperLeftOfImage.x = 0;
9368 		if(upperLeftOfImage.y < 0)
9369 			upperLeftOfImage.y = 0;
9370 
9371 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9372 	}
9373 
9374 	///
9375 	Size textSize(in char[] text) {
9376 		if(impl is null) return Size(0, 0);
9377 		return impl.textSize(text);
9378 	}
9379 
9380 	/++
9381 		Draws a string in the window with the set font (see [setFont] to change it).
9382 
9383 		Params:
9384 			upperLeft = the upper left point of the bounding box of the text
9385 			text = the string to draw
9386 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9387 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9388 	+/
9389 	@scriptable
9390 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9391 		if(impl is null) return;
9392 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9393 			if(isClipped(upperLeft, lowerRight)) return;
9394 			transform(lowerRight);
9395 		} else {
9396 			if(isClipped(upperLeft, textSize(text))) return;
9397 		}
9398 		transform(upperLeft);
9399 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9400 	}
9401 
9402 	/++
9403 		Draws text using a custom font.
9404 
9405 		This is still MAJOR work in progress.
9406 
9407 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9408 	+/
9409 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9410 		if(impl is null) return;
9411 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9412 		transform(upperLeft);
9413 		font.drawString(this, upperLeft, text);
9414 	}
9415 
9416 	version(Windows)
9417 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9418 		if(impl is null) return;
9419 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9420 		transform(upperLeft);
9421 
9422 		if(text.length && text[$-1] == '\n')
9423 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9424 
9425 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9426 	}
9427 
9428 	static struct TextDrawingContext {
9429 		Point boundingBoxUpperLeft;
9430 		Point boundingBoxLowerRight;
9431 
9432 		Point currentLocation;
9433 
9434 		Point lastDrewUpperLeft;
9435 		Point lastDrewLowerRight;
9436 
9437 		// how do i do right aligned rich text?
9438 		// i kinda want to do a pre-made drawing then right align
9439 		// draw the whole block.
9440 		//
9441 		// That's exactly the diff: inline vs block stuff.
9442 
9443 		// I need to get coordinates of an inline section out too,
9444 		// not just a bounding box, but a series of bounding boxes
9445 		// should be ok. Consider what's needed to detect a click
9446 		// on a link in the middle of a paragraph breaking a line.
9447 		//
9448 		// Generally, we should be able to get the rectangles of
9449 		// any portion we draw.
9450 		//
9451 		// It also needs to tell what text is left if it overflows
9452 		// out of the box, so we can do stuff like float images around
9453 		// it. It should not attempt to draw a letter that would be
9454 		// clipped.
9455 		//
9456 		// I might also turn off word wrap stuff.
9457 	}
9458 
9459 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9460 		if(impl is null) return;
9461 		// FIXME
9462 	}
9463 
9464 	/// Drawing an individual pixel is slow. Avoid it if possible.
9465 	void drawPixel(Point where) {
9466 		if(impl is null) return;
9467 		if(isClipped(where)) return;
9468 		transform(where);
9469 		impl.drawPixel(where.x, where.y);
9470 	}
9471 
9472 
9473 	/// Draws a pen using the current pen / outlineColor
9474 	@scriptable
9475 	void drawLine(Point starting, Point ending) {
9476 		if(impl is null) return;
9477 		if(isClipped(starting, ending)) return;
9478 		transform(starting);
9479 		transform(ending);
9480 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9481 	}
9482 
9483 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9484 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9485 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9486 	@scriptable
9487 	void drawRectangle(Point upperLeft, int width, int height) {
9488 		if(impl is null) return;
9489 		if(isClipped(upperLeft, width, height)) return;
9490 		transform(upperLeft);
9491 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9492 	}
9493 
9494 	/// ditto
9495 	void drawRectangle(Point upperLeft, Size size) {
9496 		if(impl is null) return;
9497 		if(isClipped(upperLeft, size.width, size.height)) return;
9498 		transform(upperLeft);
9499 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9500 	}
9501 
9502 	/// ditto
9503 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9504 		if(impl is null) return;
9505 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9506 		transform(upperLeft);
9507 		transform(lowerRightInclusive);
9508 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9509 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9510 	}
9511 
9512 	// overload added on May 12, 2021
9513 	/// ditto
9514 	void drawRectangle(Rectangle rect) {
9515 		drawRectangle(rect.upperLeft, rect.size);
9516 	}
9517 
9518 	/// Arguments are the points of the bounding rectangle
9519 	void drawEllipse(Point upperLeft, Point lowerRight) {
9520 		if(impl is null) return;
9521 		if(isClipped(upperLeft, lowerRight)) return;
9522 		transform(upperLeft);
9523 		transform(lowerRight);
9524 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9525 	}
9526 
9527 	/++
9528 		start and finish are units of degrees * 64
9529 
9530 		History:
9531 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9532 
9533 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9534 	+/
9535 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9536 		if(impl is null) return;
9537 		// FIXME: not actually implemented
9538 		if(isClipped(upperLeft, width, height)) return;
9539 		transform(upperLeft);
9540 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9541 	}
9542 
9543 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9544 	void drawCircle(Point upperLeft, int diameter) {
9545 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9546 	}
9547 
9548 	/// .
9549 	void drawPolygon(Point[] vertexes) {
9550 		if(impl is null) return;
9551 		assert(vertexes.length);
9552 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9553 		foreach(ref vertex; vertexes) {
9554 			if(vertex.x < minX)
9555 				minX = vertex.x;
9556 			if(vertex.y < minY)
9557 				minY = vertex.y;
9558 			if(vertex.x > maxX)
9559 				maxX = vertex.x;
9560 			if(vertex.y > maxY)
9561 				maxY = vertex.y;
9562 			transform(vertex);
9563 		}
9564 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9565 		impl.drawPolygon(vertexes);
9566 	}
9567 
9568 	/// ditto
9569 	void drawPolygon(Point[] vertexes...) {
9570 		if(impl is null) return;
9571 		drawPolygon(vertexes);
9572 	}
9573 
9574 
9575 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9576 
9577 	//mixin NativeScreenPainterImplementation!() impl;
9578 
9579 
9580 	// HACK: if I mixin the impl directly, it won't let me override the copy
9581 	// constructor! The linker complains about there being multiple definitions.
9582 	// I'll make the best of it and reference count it though.
9583 	ScreenPainterImplementation* impl;
9584 }
9585 
9586 	// HACK: I need a pointer to the implementation so it's separate
9587 	struct ScreenPainterImplementation {
9588 		CapableOfBeingDrawnUpon window;
9589 		int referenceCount;
9590 		mixin NativeScreenPainterImplementation!();
9591 	}
9592 
9593 // FIXME: i haven't actually tested the sprite class on MS Windows
9594 
9595 /**
9596 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9597 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9598 
9599 
9600 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9601 	though I'm not sure that's ideal and the implementation might change.
9602 
9603 	You create one by giving a window and an image. It optimizes for that window,
9604 	and copies the image into it to use as the initial picture. Creating a sprite
9605 	can be quite slow (especially over a network connection) so you should do it
9606 	as little as possible and just hold on to your sprite handles after making them.
9607 	simpledisplay does try to do its best though, using the XSHM extension if available,
9608 	but you should still write your code as if it will always be slow.
9609 
9610 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9611 	a fast operation - much faster than drawing the Image itself every time.
9612 
9613 	`Sprite` represents a scarce resource which should be freed when you
9614 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9615 	after it has been disposed. If you are unsure about this, don't take chances,
9616 	just let the garbage collector do it for you. But ideally, you can manage its
9617 	lifetime more efficiently.
9618 
9619 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9620 	support alpha blending in its drawing at this time. That might change in the
9621 	future, but if you need alpha blending right now, use OpenGL instead. See
9622 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9623 
9624 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9625 	in by setting the enableAlpha = true in the constructor.
9626 */
9627 class Sprite : CapableOfBeingDrawnUpon {
9628 
9629 	///
9630 	ScreenPainter draw() {
9631 		return ScreenPainter(this, handle, false);
9632 	}
9633 
9634 	/++
9635 		Copies the sprite's current state into a [TrueColorImage].
9636 
9637 		Be warned: this can be a very slow operation
9638 
9639 		History:
9640 			Actually implemented on March 14, 2021
9641 	+/
9642 	TrueColorImage takeScreenshot() {
9643 		return trueColorImageFromNativeHandle(handle, width, height);
9644 	}
9645 
9646 	void delegate() paintingFinishedDg() { return null; }
9647 	bool closed() { return false; }
9648 	ScreenPainterImplementation* activeScreenPainter_;
9649 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9650 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9651 
9652 	version(Windows)
9653 		private ubyte* rawData;
9654 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9655 	// ditto on the XPicture stuff
9656 
9657 	version(X11) {
9658 		private static XRenderPictFormat* RGB24;
9659 		private static XRenderPictFormat* ARGB32;
9660 
9661 		private Picture xrenderPicture;
9662 	}
9663 
9664 	version(X11)
9665 	private static void requireXRender() {
9666 		if(!XRenderLibrary.loadAttempted) {
9667 			XRenderLibrary.loadDynamicLibrary();
9668 		}
9669 
9670 		if(!XRenderLibrary.loadSuccessful)
9671 			throw new Exception("XRender library load failure");
9672 
9673 		auto display = XDisplayConnection.get;
9674 
9675 		// FIXME: if we migrate X displays, these need to be changed
9676 		if(RGB24 is null)
9677 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9678 		if(ARGB32 is null)
9679 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9680 	}
9681 
9682 	protected this() {}
9683 
9684 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9685 		this._width = width;
9686 		this._height = height;
9687 		this.enableAlpha = enableAlpha;
9688 
9689 		version(X11) {
9690 			auto display = XDisplayConnection.get();
9691 
9692 			if(enableAlpha) {
9693 				requireXRender();
9694 			}
9695 
9696 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9697 
9698 			if(enableAlpha) {
9699 				XRenderPictureAttributes attrs;
9700 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9701 			}
9702 		} else version(Windows) {
9703 			version(CRuntime_DigitalMars) {
9704 				//if(enableAlpha)
9705 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9706 			}
9707 
9708 			BITMAPINFO infoheader;
9709 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9710 			infoheader.bmiHeader.biWidth = width;
9711 			infoheader.bmiHeader.biHeight = height;
9712 			infoheader.bmiHeader.biPlanes = 1;
9713 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9714 			infoheader.bmiHeader.biCompression = BI_RGB;
9715 
9716 			// FIXME: this should prolly be a device dependent bitmap...
9717 			handle = CreateDIBSection(
9718 				null,
9719 				&infoheader,
9720 				DIB_RGB_COLORS,
9721 				cast(void**) &rawData,
9722 				null,
9723 				0);
9724 
9725 			if(handle is null)
9726 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
9727 		}
9728 	}
9729 
9730 	/// Makes a sprite based on the image with the initial contents from the Image
9731 	this(SimpleWindow win, Image i) {
9732 		this(win, i.width, i.height, i.enableAlpha);
9733 
9734 		version(X11) {
9735 			auto display = XDisplayConnection.get();
9736 			auto gc = XCreateGC(display, this.handle, 0, null);
9737 			scope(exit) XFreeGC(display, gc);
9738 			if(i.usingXshm)
9739 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9740 			else
9741 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9742 		} else version(Windows) {
9743 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9744 			auto arrLength = itemsPerLine * height;
9745 			rawData[0..arrLength] = i.rawData[0..arrLength];
9746 		} else version(OSXCocoa) {
9747 			// FIXME: I have no idea if this is even any good
9748 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9749 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
9750 				colorSpace,
9751 				kCGImageAlphaPremultipliedLast
9752 				|kCGBitmapByteOrder32Big);
9753 			CGColorSpaceRelease(colorSpace);
9754 			auto rawData = CGBitmapContextGetData(handle);
9755 
9756 			auto rdl = (width * height * 4);
9757 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9758 		} else static assert(0);
9759 	}
9760 
9761 	/++
9762 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9763 
9764 		Params:
9765 			where = point on the window where the upper left corner of the image will be drawn
9766 			imageUpperLeft = point on the image to start the slice to draw
9767 			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.
9768 		History:
9769 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9770 	+/
9771 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9772 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9773 	}
9774 
9775 	/// Call this when you're ready to get rid of it
9776 	void dispose() {
9777 		version(X11) {
9778 			staticDispose(xrenderPicture, handle);
9779 			xrenderPicture = None;
9780 			handle = None;
9781 		} else version(Windows) {
9782 			staticDispose(handle);
9783 			handle = null;
9784 		} else version(OSXCocoa) {
9785 			staticDispose(handle);
9786 			handle = null;
9787 		} else static assert(0);
9788 
9789 	}
9790 
9791 	version(X11)
9792 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9793 		if(xrenderPicture)
9794 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9795 		if(handle)
9796 			XFreePixmap(XDisplayConnection.get(), handle);
9797 	}
9798 	else version(Windows)
9799 	static void staticDispose(HBITMAP handle) {
9800 		if(handle)
9801 			DeleteObject(handle);
9802 	}
9803 	else version(OSXCocoa)
9804 	static void staticDispose(CGContextRef context) {
9805 		if(context)
9806 			CGContextRelease(context);
9807 	}
9808 
9809 	~this() {
9810 		version(X11) { if(xrenderPicture || handle)
9811 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9812 		} else version(Windows) { if(handle)
9813 			cleanupQueue.queue!staticDispose(handle);
9814 		} else version(OSXCocoa) { if(handle)
9815 			cleanupQueue.queue!staticDispose(handle);
9816 		} else static assert(0);
9817 	}
9818 
9819 	///
9820 	final @property int width() { return _width; }
9821 
9822 	///
9823 	final @property int height() { return _height; }
9824 
9825 	///
9826 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9827 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9828 	}
9829 
9830 	auto nativeHandle() {
9831 		return handle;
9832 	}
9833 
9834 	private:
9835 
9836 	int _width;
9837 	int _height;
9838 	bool enableAlpha;
9839 	version(X11)
9840 		Pixmap handle;
9841 	else version(Windows)
9842 		HBITMAP handle;
9843 	else version(OSXCocoa)
9844 		CGContextRef handle;
9845 	else static assert(0);
9846 }
9847 
9848 /++
9849 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9850 
9851 	History:
9852 		Added November 20, 2021 (dub v10.4)
9853 +/
9854 version(OSXCocoa) {} else // NotYetImplementedException
9855 abstract class Gradient : Sprite {
9856 	protected this(int w, int h) {
9857 		version(X11) {
9858 			Sprite.requireXRender();
9859 
9860 			super();
9861 			enableAlpha = true;
9862 			_width = w;
9863 			_height = h;
9864 		} else version(Windows) {
9865 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9866 		}
9867 	}
9868 
9869 	version(Windows)
9870 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9871 		auto ptr = rawData;
9872 		foreach(j; 0 .. _height)
9873 		foreach(i; 0 .. _width) {
9874 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9875 			*rawData = (color.a * color.b) / 255; rawData++;
9876 			*rawData = (color.a * color.g) / 255; rawData++;
9877 			*rawData = (color.a * color.r) / 255; rawData++;
9878 			*rawData = color.a; rawData++;
9879 		}
9880 	}
9881 
9882 	version(X11)
9883 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9884 		assert(stops.length > 0);
9885 		assert(stops.length <= 16, "I got lazy with buffers");
9886 
9887 		XFixed[16] stopsPositions = void;
9888 		XRenderColor[16] colors = void;
9889 
9890 		foreach(idx, stop; stops) {
9891 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9892 			auto c = stop.c;
9893 			colors[idx] = XRenderColor(
9894 				cast(ushort)(c.r * ushort.max / 255),
9895 				cast(ushort)(c.g * ushort.max / 255),
9896 				cast(ushort)(c.b * ushort.max / 255),
9897 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9898 			);
9899 		}
9900 
9901 		xrenderPicture = dg(stopsPositions, colors);
9902 	}
9903 
9904 	///
9905 	static struct Stop {
9906 		float percentage; /// between 0 and 1.0
9907 		Color c;
9908 	}
9909 }
9910 
9911 /++
9912 	Creates a linear gradient between p1 and p2.
9913 
9914 	X ONLY RIGHT NOW
9915 
9916 	History:
9917 		Added November 20, 2021 (dub v10.4)
9918 
9919 	Bugs:
9920 		Not yet implemented on Windows.
9921 +/
9922 version(OSXCocoa) {} else // NotYetImplementedException
9923 class LinearGradient : Gradient {
9924 	/++
9925 
9926 	+/
9927 	this(Point p1, Point p2, Stop[] stops...) {
9928 		super(p2.x, p2.y);
9929 
9930 		version(X11) {
9931 			XLinearGradient gradient;
9932 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9933 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9934 
9935 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9936 				return XRenderCreateLinearGradient(
9937 					XDisplayConnection.get,
9938 					&gradient,
9939 					stopsPositions.ptr,
9940 					colors.ptr,
9941 					cast(int) stops.length);
9942 			});
9943 		} else version(Windows) {
9944 			// FIXME
9945 			forEachPixel((int x, int y) {
9946 				import core.stdc.math;
9947 
9948 				//sqrtf(
9949 
9950 				return Color.transparent;
9951 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9952 			});
9953 		}
9954 	}
9955 }
9956 
9957 /++
9958 	A conical gradient goes from color to color around a circumference from a center point.
9959 
9960 	X ONLY RIGHT NOW
9961 
9962 	History:
9963 		Added November 20, 2021 (dub v10.4)
9964 
9965 	Bugs:
9966 		Not yet implemented on Windows.
9967 +/
9968 version(OSXCocoa) {} else // NotYetImplementedException
9969 class ConicalGradient : Gradient {
9970 	/++
9971 
9972 	+/
9973 	this(Point center, float angleInDegrees, Stop[] stops...) {
9974 		super(center.x * 2, center.y * 2);
9975 
9976 		version(X11) {
9977 			XConicalGradient gradient;
9978 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9979 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9980 
9981 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9982 				return XRenderCreateConicalGradient(
9983 					XDisplayConnection.get,
9984 					&gradient,
9985 					stopsPositions.ptr,
9986 					colors.ptr,
9987 					cast(int) stops.length);
9988 			});
9989 		} else version(Windows) {
9990 			// FIXME
9991 			forEachPixel((int x, int y) {
9992 				import core.stdc.math;
9993 
9994 				//sqrtf(
9995 
9996 				return Color.transparent;
9997 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9998 			});
9999 
10000 		}
10001 	}
10002 }
10003 
10004 /++
10005 	A radial gradient goes from color to color based on distance from the center.
10006 	It is like rings of color.
10007 
10008 	X ONLY RIGHT NOW
10009 
10010 
10011 	More specifically, you create two circles: an inner circle and an outer circle.
10012 	The gradient is only drawn in the area outside the inner circle but inside the outer
10013 	circle. The closest line between those two circles forms the line for the gradient
10014 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10015 
10016 	History:
10017 		Added November 20, 2021 (dub v10.4)
10018 
10019 	Bugs:
10020 		Not yet implemented on Windows.
10021 +/
10022 version(OSXCocoa) {} else // NotYetImplementedException
10023 class RadialGradient : Gradient {
10024 	/++
10025 
10026 	+/
10027 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10028 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10029 
10030 		version(X11) {
10031 			XRadialGradient gradient;
10032 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10033 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10034 
10035 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10036 				return XRenderCreateRadialGradient(
10037 					XDisplayConnection.get,
10038 					&gradient,
10039 					stopsPositions.ptr,
10040 					colors.ptr,
10041 					cast(int) stops.length);
10042 			});
10043 		} else version(Windows) {
10044 			// FIXME
10045 			forEachPixel((int x, int y) {
10046 				import core.stdc.math;
10047 
10048 				//sqrtf(
10049 
10050 				return Color.transparent;
10051 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10052 			});
10053 		}
10054 	}
10055 }
10056 
10057 
10058 
10059 /+
10060 	NOT IMPLEMENTED
10061 
10062 	A display-stored image optimized for relatively quick drawing, like
10063 	[Sprite], but this one supports alpha channel blending and does NOT
10064 	support direct drawing upon it with a [ScreenPainter].
10065 
10066 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10067 	plain [ScreenPainter]... sort of.
10068 
10069 	On X11, it requires the Xrender extension and library. This is available
10070 	almost everywhere though.
10071 
10072 	History:
10073 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10074 +/
10075 version(none)
10076 class AlphaSprite {
10077 	/++
10078 		Copies the given image into it.
10079 	+/
10080 	this(MemoryImage img) {
10081 
10082 		if(!XRenderLibrary.loadAttempted) {
10083 			XRenderLibrary.loadDynamicLibrary();
10084 
10085 			// FIXME: this needs to be reconstructed when the X server changes
10086 			repopulateX();
10087 		}
10088 		if(!XRenderLibrary.loadSuccessful)
10089 			throw new Exception("XRender library load failure");
10090 
10091 		// I probably need to put the alpha mask in a separate Picture
10092 		// ugh
10093 		// maybe the Sprite itself can have an alpha bitmask anyway
10094 
10095 
10096 		auto display = XDisplayConnection.get();
10097 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10098 
10099 
10100 		XRenderPictureAttributes attrs;
10101 
10102 		handle = XRenderCreatePicture(
10103 			XDisplayConnection.get,
10104 			pixmap,
10105 			RGBA,
10106 			0,
10107 			&attrs
10108 		);
10109 
10110 	}
10111 
10112 	// maybe i'll use the create gradient functions too with static factories..
10113 
10114 	void drawAt(ScreenPainter painter, Point where) {
10115 		//painter.drawPixmap(this, where);
10116 
10117 		XRenderPictureAttributes attrs;
10118 
10119 		auto pic = XRenderCreatePicture(
10120 			XDisplayConnection.get,
10121 			painter.impl.d,
10122 			RGB,
10123 			0,
10124 			&attrs
10125 		);
10126 
10127 		XRenderComposite(
10128 			XDisplayConnection.get,
10129 			3, // PictOpOver
10130 			handle,
10131 			None,
10132 			pic,
10133 			0, // src
10134 			0,
10135 			0, // mask
10136 			0,
10137 			10, // dest
10138 			10,
10139 			100, // width
10140 			100
10141 		);
10142 
10143 		/+
10144 		XRenderFreePicture(
10145 			XDisplayConnection.get,
10146 			pic
10147 		);
10148 
10149 		XRenderFreePicture(
10150 			XDisplayConnection.get,
10151 			fill
10152 		);
10153 		+/
10154 		// on Windows you can stretch but Xrender still can't :(
10155 	}
10156 
10157 	static XRenderPictFormat* RGB;
10158 	static XRenderPictFormat* RGBA;
10159 	static void repopulateX() {
10160 		auto display = XDisplayConnection.get;
10161 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10162 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10163 	}
10164 
10165 	XPixmap pixmap;
10166 	Picture handle;
10167 }
10168 
10169 ///
10170 interface CapableOfBeingDrawnUpon {
10171 	///
10172 	ScreenPainter draw();
10173 	///
10174 	int width();
10175 	///
10176 	int height();
10177 	protected ScreenPainterImplementation* activeScreenPainter();
10178 	protected void activeScreenPainter(ScreenPainterImplementation*);
10179 	bool closed();
10180 
10181 	void delegate() paintingFinishedDg();
10182 
10183 	/// Be warned: this can be a very slow operation
10184 	TrueColorImage takeScreenshot();
10185 }
10186 
10187 /// 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].
10188 void flushGui() {
10189 	version(X11) {
10190 		auto dpy = XDisplayConnection.get();
10191 		XLockDisplay(dpy);
10192 		scope(exit) XUnlockDisplay(dpy);
10193 		XFlush(dpy);
10194 	}
10195 }
10196 
10197 /++
10198 	Runs the given code in the GUI thread when its event loop
10199 	is available, blocking until it completes. This allows you
10200 	to create and manipulate windows from another thread without
10201 	invoking undefined behavior.
10202 
10203 	If this is the gui thread, it runs the code immediately.
10204 
10205 	If no gui thread exists yet, the current thread is assumed
10206 	to be it. Attempting to create windows or run the event loop
10207 	in any other thread will cause an assertion failure.
10208 
10209 
10210 	$(TIP
10211 		Did you know you can use UFCS on delegate literals?
10212 
10213 		() {
10214 			// code here
10215 		}.runInGuiThread;
10216 	)
10217 
10218 	Returns:
10219 		`true` if the function was called, `false` if it was not.
10220 		The function may not be called because the gui thread had
10221 		already terminated by the time you called this.
10222 
10223 	History:
10224 		Added April 10, 2020 (v7.2.0)
10225 
10226 		Return value added and implementation tweaked to avoid locking
10227 		at program termination on February 24, 2021 (v9.2.1).
10228 +/
10229 bool runInGuiThread(scope void delegate() dg) @trusted {
10230 	claimGuiThread();
10231 
10232 	if(thisIsGuiThread) {
10233 		dg();
10234 		return true;
10235 	}
10236 
10237 	if(guiThreadTerminating)
10238 		return false;
10239 
10240 	import core.sync.semaphore;
10241 	static Semaphore sc;
10242 	if(sc is null)
10243 		sc = new Semaphore();
10244 
10245 	static RunQueueMember* rqm;
10246 	if(rqm is null)
10247 		rqm = new RunQueueMember;
10248 	rqm.dg = cast(typeof(rqm.dg)) dg;
10249 	rqm.signal = sc;
10250 	rqm.thrown = null;
10251 
10252 	synchronized(runInGuiThreadLock) {
10253 		runInGuiThreadQueue ~= rqm;
10254 	}
10255 
10256 	if(!SimpleWindow.eventWakeUp())
10257 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10258 
10259 	rqm.signal.wait();
10260 	auto t = rqm.thrown;
10261 
10262 	if(t)
10263 		throw t;
10264 
10265 	return true;
10266 }
10267 
10268 // note it runs sync if this is the gui thread....
10269 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10270 	claimGuiThread();
10271 
10272 	try {
10273 
10274 		if(thisIsGuiThread) {
10275 			dg();
10276 			return;
10277 		}
10278 
10279 		if(guiThreadTerminating)
10280 			return;
10281 
10282 		RunQueueMember* rqm = new RunQueueMember;
10283 		rqm.dg = cast(typeof(rqm.dg)) dg;
10284 		rqm.signal = null;
10285 		rqm.thrown = null;
10286 
10287 		synchronized(runInGuiThreadLock) {
10288 			runInGuiThreadQueue ~= rqm;
10289 		}
10290 
10291 		if(!SimpleWindow.eventWakeUp())
10292 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10293 	} catch(Exception e) {
10294 		if(handleError)
10295 			handleError(e);
10296 	}
10297 }
10298 
10299 private void runPendingRunInGuiThreadDelegates() {
10300 	more:
10301 	RunQueueMember* next;
10302 	synchronized(runInGuiThreadLock) {
10303 		if(runInGuiThreadQueue.length) {
10304 			next = runInGuiThreadQueue[0];
10305 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10306 		} else {
10307 			next = null;
10308 		}
10309 	}
10310 
10311 	if(next) {
10312 		try {
10313 			next.dg();
10314 			next.thrown = null;
10315 		} catch(Throwable t) {
10316 			next.thrown = t;
10317 		}
10318 
10319 		if(next.signal)
10320 			next.signal.notify();
10321 
10322 		goto more;
10323 	}
10324 }
10325 
10326 private void claimGuiThread() nothrow {
10327 	import core.atomic;
10328 	if(cas(&guiThreadExists_, false, true))
10329 		thisIsGuiThread = true;
10330 }
10331 
10332 private struct RunQueueMember {
10333 	void delegate() dg;
10334 	import core.sync.semaphore;
10335 	Semaphore signal;
10336 	Throwable thrown;
10337 }
10338 
10339 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10340 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10341 private bool thisIsGuiThread = false;
10342 private shared bool guiThreadExists_ = false;
10343 private shared bool guiThreadTerminating = false;
10344 
10345 /++
10346 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10347 	event loop. All windows must be exclusively created and managed by a single thread.
10348 
10349 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10350 	when you call one of its constructors.
10351 
10352 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10353 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10354 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10355 
10356 	The reason this function is available is in case you want to message pass between a gui
10357 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10358 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10359 
10360 	History:
10361 		Added December 3, 2021 (dub v10.5)
10362 +/
10363 public bool guiThreadExists() {
10364 	return guiThreadExists_;
10365 }
10366 
10367 /++
10368 	Returns `true` if this thread is either running or set to be running the
10369 	simpledisplay.d gui core event loop because it owns windows.
10370 
10371 	It is important to keep gui-related functionality in the right thread, so you will
10372 	want to `runInGuiThread` when you call them (with some specific exceptions called
10373 	out in those specific functions' documentation). Notably, all windows must be
10374 	created and managed only from the gui thread.
10375 
10376 	Will return false if simpledisplay's other functions haven't been called
10377 	yet; check [guiThreadExists] in addition to this.
10378 
10379 	History:
10380 		Added December 3, 2021 (dub v10.5)
10381 +/
10382 public bool thisThreadRunningGui() {
10383 	return thisIsGuiThread;
10384 }
10385 
10386 /++
10387 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10388 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10389 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10390 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10391 	file instead if you are in one of those situations).
10392 
10393 	It does not support outputting very many types; just strings and ints are likely to actually work.
10394 
10395 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10396 	is unspecified meaning I can change it at any time. The only point of this function is to help
10397 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10398 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10399 	in those contexts.
10400 
10401 	$(WARNING
10402 		I reserve the right to change this function at any time. You can use it if it helps you
10403 		but do not rely on it for anything permanent.
10404 	)
10405 
10406 	History:
10407 		Added December 3, 2021. Not formally supported under any stable tag.
10408 +/
10409 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10410 	try {
10411 		version(Windows) {
10412 			import core.sys.windows.wincon;
10413 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10414 				AllocConsole();
10415 			const(char)* fn = "CONOUT$";
10416 		} else version(Posix) {
10417 			const(char)* fn = "/dev/tty";
10418 		} else static assert(0, "Function not implemented for your system");
10419 
10420 		if(fileOverride.length)
10421 			fn = fileOverride.ptr;
10422 
10423 		import core.stdc.stdio;
10424 		auto fp = fopen(fn, "wt");
10425 		if(fp is null) return;
10426 		scope(exit) fclose(fp);
10427 
10428 		string str;
10429 		foreach(item; t) {
10430 			static if(is(typeof(item) : const(char)[]))
10431 				str ~= item;
10432 			else
10433 				str ~= toInternal!string(item);
10434 			str ~= " ";
10435 		}
10436 		str ~= "\n";
10437 
10438 		fwrite(str.ptr, 1, str.length, fp);
10439 		fflush(fp);
10440 	} catch(Exception e) {
10441 		// sorry no hope
10442 	}
10443 }
10444 
10445 private void guiThreadFinalize() {
10446 	assert(thisIsGuiThread);
10447 
10448 	guiThreadTerminating = true; // don't add any more from this point on
10449 	runPendingRunInGuiThreadDelegates();
10450 }
10451 
10452 /+
10453 interface IPromise {
10454 	void reportProgress(int current, int max, string message);
10455 
10456 	/+ // not formally in cuz of templates but still
10457 	IPromise Then();
10458 	IPromise Catch();
10459 	IPromise Finally();
10460 	+/
10461 }
10462 
10463 /+
10464 	auto promise = async({ ... });
10465 	promise.Then(whatever).
10466 		Then(whateverelse).
10467 		Catch((exception) { });
10468 
10469 
10470 	A promise is run inside a fiber and it looks something like:
10471 
10472 	try {
10473 		auto res = whatever();
10474 		auto res2 = whateverelse(res);
10475 	} catch(Exception e) {
10476 		{ }(e);
10477 	}
10478 
10479 	When a thing succeeds, it is passed as an arg to the next
10480 +/
10481 class Promise(T) : IPromise {
10482 	auto Then() { return null; }
10483 	auto Catch() { return null; }
10484 	auto Finally() { return null; }
10485 
10486 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10487 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10488 	T await();
10489 }
10490 
10491 interface Task {
10492 }
10493 
10494 interface Resolvable(T) : Task {
10495 	void run();
10496 
10497 	void resolve(T);
10498 
10499 	Resolvable!T then(void delegate(T)); // returns a new promise
10500 	Resolvable!T error(Throwable); // js catch
10501 	Resolvable!T completed(); // js finally
10502 
10503 }
10504 
10505 /++
10506 	Runs `work` in a helper thread and sends its return value back to the main gui
10507 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10508 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10509 	kill the program.
10510 
10511 	You can call reportProgress(position, max, message) to update your parent window
10512 	on your progress.
10513 
10514 	I should also use `shared` methods. FIXME
10515 
10516 	History:
10517 		Added March 6, 2021 (dub version 9.3).
10518 +/
10519 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10520 	uponCompletion(work(null));
10521 }
10522 
10523 +/
10524 
10525 /// Used internal to dispatch events to various classes.
10526 interface CapableOfHandlingNativeEvent {
10527 	NativeEventHandler getNativeEventHandler();
10528 
10529 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10530 
10531 	version(X11) {
10532 		// if this is impossible, you are allowed to just throw from it
10533 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10534 		void recreateAfterDisconnect();
10535 		// discard any *connection specific* state, but keep enough that you
10536 		// can be recreated if possible. discardConnectionState() is always called immediately
10537 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10538 		// you need initialization order
10539 		void discardConnectionState();
10540 	}
10541 }
10542 
10543 version(X11)
10544 /++
10545 	State of keys on mouse events, especially motion.
10546 
10547 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10548 +/
10549 enum ModifierState : uint {
10550 	shift = 1, ///
10551 	capsLock = 2, ///
10552 	ctrl = 4, ///
10553 	alt = 8, /// Not always available on Windows
10554 	windows = 64, /// ditto
10555 	numLock = 16, ///
10556 
10557 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10558 	middleButtonDown = 512, /// ditto
10559 	rightButtonDown = 1024, /// ditto
10560 }
10561 else version(Windows)
10562 /// ditto
10563 enum ModifierState : uint {
10564 	shift = 4, ///
10565 	ctrl = 8, ///
10566 
10567 	// i'm not sure if the next two are available
10568 	alt = 256, /// not always available on Windows
10569 	windows = 512, /// ditto
10570 
10571 	capsLock = 1024, ///
10572 	numLock = 2048, ///
10573 
10574 	leftButtonDown = 1, /// not available on key events
10575 	middleButtonDown = 16, /// ditto
10576 	rightButtonDown = 2, /// ditto
10577 
10578 	backButtonDown = 0x20, /// not available on X
10579 	forwardButtonDown = 0x40, /// ditto
10580 }
10581 else version(OSXCocoa)
10582 // FIXME FIXME NotYetImplementedException
10583 enum ModifierState : uint {
10584 	shift = 1, ///
10585 	capsLock = 2, ///
10586 	ctrl = 4, ///
10587 	alt = 8, /// Not always available on Windows
10588 	windows = 64, /// ditto
10589 	numLock = 16, ///
10590 
10591 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10592 	middleButtonDown = 512, /// ditto
10593 	rightButtonDown = 1024, /// ditto
10594 }
10595 
10596 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10597 enum MouseButton : int {
10598 	none = 0,
10599 	left = 1, ///
10600 	right = 2, ///
10601 	middle = 4, ///
10602 	wheelUp = 8, ///
10603 	wheelDown = 16, ///
10604 	backButton = 32, /// often found on the thumb and used for back in browsers
10605 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10606 }
10607 
10608 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
10609 enum MouseButtonLinear : ubyte {
10610 	left = 1, ///
10611 	right, ///
10612 	middle, ///
10613 	wheelUp, ///
10614 	wheelDown, ///
10615 	backButton, /// often found on the thumb and used for back in browsers
10616 	forwardButton, /// often found on the thumb and used for forward in browsers
10617 }
10618 
10619 version(X11) {
10620 	// FIXME: match ASCII whenever we can. Most of it is already there,
10621 	// but there's a few exceptions and mismatches with Windows
10622 
10623 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10624 	enum Key {
10625 		Escape = 0xff1b, ///
10626 		F1 = 0xffbe, ///
10627 		F2 = 0xffbf, ///
10628 		F3 = 0xffc0, ///
10629 		F4 = 0xffc1, ///
10630 		F5 = 0xffc2, ///
10631 		F6 = 0xffc3, ///
10632 		F7 = 0xffc4, ///
10633 		F8 = 0xffc5, ///
10634 		F9 = 0xffc6, ///
10635 		F10 = 0xffc7, ///
10636 		F11 = 0xffc8, ///
10637 		F12 = 0xffc9, ///
10638 		PrintScreen = 0xff61, ///
10639 		ScrollLock = 0xff14, ///
10640 		Pause = 0xff13, ///
10641 		Grave = 0x60, /// The $(BACKTICK) ~ key
10642 		// number keys across the top of the keyboard
10643 		N1 = 0x31, /// Number key atop the keyboard
10644 		N2 = 0x32, ///
10645 		N3 = 0x33, ///
10646 		N4 = 0x34, ///
10647 		N5 = 0x35, ///
10648 		N6 = 0x36, ///
10649 		N7 = 0x37, ///
10650 		N8 = 0x38, ///
10651 		N9 = 0x39, ///
10652 		N0 = 0x30, ///
10653 		Dash = 0x2d, ///
10654 		Equals = 0x3d, ///
10655 		Backslash = 0x5c, /// The \ | key
10656 		Backspace = 0xff08, ///
10657 		Insert = 0xff63, ///
10658 		Home = 0xff50, ///
10659 		PageUp = 0xff55, ///
10660 		Delete = 0xffff, ///
10661 		End = 0xff57, ///
10662 		PageDown = 0xff56, ///
10663 		Up = 0xff52, ///
10664 		Down = 0xff54, ///
10665 		Left = 0xff51, ///
10666 		Right = 0xff53, ///
10667 
10668 		Tab = 0xff09, ///
10669 		Q = 0x71, ///
10670 		W = 0x77, ///
10671 		E = 0x65, ///
10672 		R = 0x72, ///
10673 		T = 0x74, ///
10674 		Y = 0x79, ///
10675 		U = 0x75, ///
10676 		I = 0x69, ///
10677 		O = 0x6f, ///
10678 		P = 0x70, ///
10679 		LeftBracket = 0x5b, /// the [ { key
10680 		RightBracket = 0x5d, /// the ] } key
10681 		CapsLock = 0xffe5, ///
10682 		A = 0x61, ///
10683 		S = 0x73, ///
10684 		D = 0x64, ///
10685 		F = 0x66, ///
10686 		G = 0x67, ///
10687 		H = 0x68, ///
10688 		J = 0x6a, ///
10689 		K = 0x6b, ///
10690 		L = 0x6c, ///
10691 		Semicolon = 0x3b, ///
10692 		Apostrophe = 0x27, ///
10693 		Enter = 0xff0d, ///
10694 		Shift = 0xffe1, ///
10695 		Z = 0x7a, ///
10696 		X = 0x78, ///
10697 		C = 0x63, ///
10698 		V = 0x76, ///
10699 		B = 0x62, ///
10700 		N = 0x6e, ///
10701 		M = 0x6d, ///
10702 		Comma = 0x2c, ///
10703 		Period = 0x2e, ///
10704 		Slash = 0x2f, /// the / ? key
10705 		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
10706 		Ctrl = 0xffe3, ///
10707 		Windows = 0xffeb, ///
10708 		Alt = 0xffe9, ///
10709 		Space = 0x20, ///
10710 		Alt_r = 0xffea, /// ditto of shift_r
10711 		Windows_r = 0xffec, ///
10712 		Menu = 0xff67, ///
10713 		Ctrl_r = 0xffe4, ///
10714 
10715 		NumLock = 0xff7f, ///
10716 		Divide = 0xffaf, /// The / key on the number pad
10717 		Multiply = 0xffaa, /// The * key on the number pad
10718 		Minus = 0xffad, /// The - key on the number pad
10719 		Plus = 0xffab, /// The + key on the number pad
10720 		PadEnter = 0xff8d, /// Numberpad enter key
10721 		Pad1 = 0xff9c, /// Numberpad keys
10722 		Pad2 = 0xff99, ///
10723 		Pad3 = 0xff9b, ///
10724 		Pad4 = 0xff96, ///
10725 		Pad5 = 0xff9d, ///
10726 		Pad6 = 0xff98, ///
10727 		Pad7 = 0xff95, ///
10728 		Pad8 = 0xff97, ///
10729 		Pad9 = 0xff9a, ///
10730 		Pad0 = 0xff9e, ///
10731 		PadDot = 0xff9f, ///
10732 	}
10733 } else version(Windows) {
10734 	// the character here is for en-us layouts and for illustration only
10735 	// if you actually want to get characters, wait for character events
10736 	// (the argument to your event handler is simply a dchar)
10737 	// those will be converted by the OS for the right locale.
10738 
10739 	enum Key {
10740 		Escape = 0x1b,
10741 		F1 = 0x70,
10742 		F2 = 0x71,
10743 		F3 = 0x72,
10744 		F4 = 0x73,
10745 		F5 = 0x74,
10746 		F6 = 0x75,
10747 		F7 = 0x76,
10748 		F8 = 0x77,
10749 		F9 = 0x78,
10750 		F10 = 0x79,
10751 		F11 = 0x7a,
10752 		F12 = 0x7b,
10753 		PrintScreen = 0x2c,
10754 		ScrollLock = 0x91,
10755 		Pause = 0x13,
10756 		Grave = 0xc0,
10757 		// number keys across the top of the keyboard
10758 		N1 = 0x31,
10759 		N2 = 0x32,
10760 		N3 = 0x33,
10761 		N4 = 0x34,
10762 		N5 = 0x35,
10763 		N6 = 0x36,
10764 		N7 = 0x37,
10765 		N8 = 0x38,
10766 		N9 = 0x39,
10767 		N0 = 0x30,
10768 		Dash = 0xbd,
10769 		Equals = 0xbb,
10770 		Backslash = 0xdc,
10771 		Backspace = 0x08,
10772 		Insert = 0x2d,
10773 		Home = 0x24,
10774 		PageUp = 0x21,
10775 		Delete = 0x2e,
10776 		End = 0x23,
10777 		PageDown = 0x22,
10778 		Up = 0x26,
10779 		Down = 0x28,
10780 		Left = 0x25,
10781 		Right = 0x27,
10782 
10783 		Tab = 0x09,
10784 		Q = 0x51,
10785 		W = 0x57,
10786 		E = 0x45,
10787 		R = 0x52,
10788 		T = 0x54,
10789 		Y = 0x59,
10790 		U = 0x55,
10791 		I = 0x49,
10792 		O = 0x4f,
10793 		P = 0x50,
10794 		LeftBracket = 0xdb,
10795 		RightBracket = 0xdd,
10796 		CapsLock = 0x14,
10797 		A = 0x41,
10798 		S = 0x53,
10799 		D = 0x44,
10800 		F = 0x46,
10801 		G = 0x47,
10802 		H = 0x48,
10803 		J = 0x4a,
10804 		K = 0x4b,
10805 		L = 0x4c,
10806 		Semicolon = 0xba,
10807 		Apostrophe = 0xde,
10808 		Enter = 0x0d,
10809 		Shift = 0x10,
10810 		Z = 0x5a,
10811 		X = 0x58,
10812 		C = 0x43,
10813 		V = 0x56,
10814 		B = 0x42,
10815 		N = 0x4e,
10816 		M = 0x4d,
10817 		Comma = 0xbc,
10818 		Period = 0xbe,
10819 		Slash = 0xbf,
10820 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10821 		Ctrl = 0x11,
10822 		Windows = 0x5b,
10823 		Alt = -5, // FIXME
10824 		Space = 0x20,
10825 		Alt_r = 0xffea, // ditto of shift_r
10826 		Windows_r = 0x5c, // ditto of shift_r
10827 		Menu = 0x5d,
10828 		Ctrl_r = 0xa3, // ditto of shift_r
10829 
10830 		NumLock = 0x90,
10831 		Divide = 0x6f,
10832 		Multiply = 0x6a,
10833 		Minus = 0x6d,
10834 		Plus = 0x6b,
10835 		PadEnter = -8, // FIXME
10836 		Pad1 = 0x61,
10837 		Pad2 = 0x62,
10838 		Pad3 = 0x63,
10839 		Pad4 = 0x64,
10840 		Pad5 = 0x65,
10841 		Pad6 = 0x66,
10842 		Pad7 = 0x67,
10843 		Pad8 = 0x68,
10844 		Pad9 = 0x69,
10845 		Pad0 = 0x60,
10846 		PadDot = 0x6e,
10847 	}
10848 
10849 	// I'm keeping this around for reference purposes
10850 	// ideally all these buttons will be listed for all platforms,
10851 	// but now now I'm just focusing on my US keyboard
10852 	version(none)
10853 	enum Key {
10854 		LBUTTON = 0x01,
10855 		RBUTTON = 0x02,
10856 		CANCEL = 0x03,
10857 		MBUTTON = 0x04,
10858 		//static if (_WIN32_WINNT > =  0x500) {
10859 		XBUTTON1 = 0x05,
10860 		XBUTTON2 = 0x06,
10861 		//}
10862 		BACK = 0x08,
10863 		TAB = 0x09,
10864 		CLEAR = 0x0C,
10865 		RETURN = 0x0D,
10866 		SHIFT = 0x10,
10867 		CONTROL = 0x11,
10868 		MENU = 0x12,
10869 		PAUSE = 0x13,
10870 		CAPITAL = 0x14,
10871 		KANA = 0x15,
10872 		HANGEUL = 0x15,
10873 		HANGUL = 0x15,
10874 		JUNJA = 0x17,
10875 		FINAL = 0x18,
10876 		HANJA = 0x19,
10877 		KANJI = 0x19,
10878 		ESCAPE = 0x1B,
10879 		CONVERT = 0x1C,
10880 		NONCONVERT = 0x1D,
10881 		ACCEPT = 0x1E,
10882 		MODECHANGE = 0x1F,
10883 		SPACE = 0x20,
10884 		PRIOR = 0x21,
10885 		NEXT = 0x22,
10886 		END = 0x23,
10887 		HOME = 0x24,
10888 		LEFT = 0x25,
10889 		UP = 0x26,
10890 		RIGHT = 0x27,
10891 		DOWN = 0x28,
10892 		SELECT = 0x29,
10893 		PRINT = 0x2A,
10894 		EXECUTE = 0x2B,
10895 		SNAPSHOT = 0x2C,
10896 		INSERT = 0x2D,
10897 		DELETE = 0x2E,
10898 		HELP = 0x2F,
10899 		LWIN = 0x5B,
10900 		RWIN = 0x5C,
10901 		APPS = 0x5D,
10902 		SLEEP = 0x5F,
10903 		NUMPAD0 = 0x60,
10904 		NUMPAD1 = 0x61,
10905 		NUMPAD2 = 0x62,
10906 		NUMPAD3 = 0x63,
10907 		NUMPAD4 = 0x64,
10908 		NUMPAD5 = 0x65,
10909 		NUMPAD6 = 0x66,
10910 		NUMPAD7 = 0x67,
10911 		NUMPAD8 = 0x68,
10912 		NUMPAD9 = 0x69,
10913 		MULTIPLY = 0x6A,
10914 		ADD = 0x6B,
10915 		SEPARATOR = 0x6C,
10916 		SUBTRACT = 0x6D,
10917 		DECIMAL = 0x6E,
10918 		DIVIDE = 0x6F,
10919 		F1 = 0x70,
10920 		F2 = 0x71,
10921 		F3 = 0x72,
10922 		F4 = 0x73,
10923 		F5 = 0x74,
10924 		F6 = 0x75,
10925 		F7 = 0x76,
10926 		F8 = 0x77,
10927 		F9 = 0x78,
10928 		F10 = 0x79,
10929 		F11 = 0x7A,
10930 		F12 = 0x7B,
10931 		F13 = 0x7C,
10932 		F14 = 0x7D,
10933 		F15 = 0x7E,
10934 		F16 = 0x7F,
10935 		F17 = 0x80,
10936 		F18 = 0x81,
10937 		F19 = 0x82,
10938 		F20 = 0x83,
10939 		F21 = 0x84,
10940 		F22 = 0x85,
10941 		F23 = 0x86,
10942 		F24 = 0x87,
10943 		NUMLOCK = 0x90,
10944 		SCROLL = 0x91,
10945 		LSHIFT = 0xA0,
10946 		RSHIFT = 0xA1,
10947 		LCONTROL = 0xA2,
10948 		RCONTROL = 0xA3,
10949 		LMENU = 0xA4,
10950 		RMENU = 0xA5,
10951 		//static if (_WIN32_WINNT > =  0x500) {
10952 		BROWSER_BACK = 0xA6,
10953 		BROWSER_FORWARD = 0xA7,
10954 		BROWSER_REFRESH = 0xA8,
10955 		BROWSER_STOP = 0xA9,
10956 		BROWSER_SEARCH = 0xAA,
10957 		BROWSER_FAVORITES = 0xAB,
10958 		BROWSER_HOME = 0xAC,
10959 		VOLUME_MUTE = 0xAD,
10960 		VOLUME_DOWN = 0xAE,
10961 		VOLUME_UP = 0xAF,
10962 		MEDIA_NEXT_TRACK = 0xB0,
10963 		MEDIA_PREV_TRACK = 0xB1,
10964 		MEDIA_STOP = 0xB2,
10965 		MEDIA_PLAY_PAUSE = 0xB3,
10966 		LAUNCH_MAIL = 0xB4,
10967 		LAUNCH_MEDIA_SELECT = 0xB5,
10968 		LAUNCH_APP1 = 0xB6,
10969 		LAUNCH_APP2 = 0xB7,
10970 		//}
10971 		OEM_1 = 0xBA,
10972 		//static if (_WIN32_WINNT > =  0x500) {
10973 		OEM_PLUS = 0xBB,
10974 		OEM_COMMA = 0xBC,
10975 		OEM_MINUS = 0xBD,
10976 		OEM_PERIOD = 0xBE,
10977 		//}
10978 		OEM_2 = 0xBF,
10979 		OEM_3 = 0xC0,
10980 		OEM_4 = 0xDB,
10981 		OEM_5 = 0xDC,
10982 		OEM_6 = 0xDD,
10983 		OEM_7 = 0xDE,
10984 		OEM_8 = 0xDF,
10985 		//static if (_WIN32_WINNT > =  0x500) {
10986 		OEM_102 = 0xE2,
10987 		//}
10988 		PROCESSKEY = 0xE5,
10989 		//static if (_WIN32_WINNT > =  0x500) {
10990 		PACKET = 0xE7,
10991 		//}
10992 		ATTN = 0xF6,
10993 		CRSEL = 0xF7,
10994 		EXSEL = 0xF8,
10995 		EREOF = 0xF9,
10996 		PLAY = 0xFA,
10997 		ZOOM = 0xFB,
10998 		NONAME = 0xFC,
10999 		PA1 = 0xFD,
11000 		OEM_CLEAR = 0xFE,
11001 	}
11002 
11003 } else version(OSXCocoa) {
11004 	enum Key {
11005 		Escape = 53,
11006 		F1 = 122,
11007 		F2 = 120,
11008 		F3 = 99,
11009 		F4 = 118,
11010 		F5 = 96,
11011 		F6 = 97,
11012 		F7 = 98,
11013 		F8 = 100,
11014 		F9 = 101,
11015 		F10 = 109,
11016 		F11 = 103,
11017 		F12 = 111,
11018 		PrintScreen = 105,
11019 		ScrollLock = 107,
11020 		Pause = 113,
11021 		Grave = 50,
11022 		// number keys across the top of the keyboard
11023 		N1 = 18,
11024 		N2 = 19,
11025 		N3 = 20,
11026 		N4 = 21,
11027 		N5 = 23,
11028 		N6 = 22,
11029 		N7 = 26,
11030 		N8 = 28,
11031 		N9 = 25,
11032 		N0 = 29,
11033 		Dash = 27,
11034 		Equals = 24,
11035 		Backslash = 42,
11036 		Backspace = 51,
11037 		Insert = 114,
11038 		Home = 115,
11039 		PageUp = 116,
11040 		Delete = 117,
11041 		End = 119,
11042 		PageDown = 121,
11043 		Up = 126,
11044 		Down = 125,
11045 		Left = 123,
11046 		Right = 124,
11047 
11048 		Tab = 48,
11049 		Q = 12,
11050 		W = 13,
11051 		E = 14,
11052 		R = 15,
11053 		T = 17,
11054 		Y = 16,
11055 		U = 32,
11056 		I = 34,
11057 		O = 31,
11058 		P = 35,
11059 		LeftBracket = 33,
11060 		RightBracket = 30,
11061 		CapsLock = 57,
11062 		A = 0,
11063 		S = 1,
11064 		D = 2,
11065 		F = 3,
11066 		G = 5,
11067 		H = 4,
11068 		J = 38,
11069 		K = 40,
11070 		L = 37,
11071 		Semicolon = 41,
11072 		Apostrophe = 39,
11073 		Enter = 36,
11074 		Shift = 56,
11075 		Z = 6,
11076 		X = 7,
11077 		C = 8,
11078 		V = 9,
11079 		B = 11,
11080 		N = 45,
11081 		M = 46,
11082 		Comma = 43,
11083 		Period = 47,
11084 		Slash = 44,
11085 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11086 		Ctrl = 59,
11087 		Windows = 55,
11088 		Alt = 58,
11089 		Space = 49,
11090 		Alt_r = -3, // ditto of shift_r
11091 		Windows_r = -2,
11092 		Menu = 110,
11093 		Ctrl_r = -1,
11094 
11095 		NumLock = 1,
11096 		Divide = 75,
11097 		Multiply = 67,
11098 		Minus = 78,
11099 		Plus = 69,
11100 		PadEnter = 76,
11101 		Pad1 = 83,
11102 		Pad2 = 84,
11103 		Pad3 = 85,
11104 		Pad4 = 86,
11105 		Pad5 = 87,
11106 		Pad6 = 88,
11107 		Pad7 = 89,
11108 		Pad8 = 91,
11109 		Pad9 = 92,
11110 		Pad0 = 82,
11111 		PadDot = 65,
11112 	}
11113 
11114 }
11115 
11116 /* Additional utilities */
11117 
11118 
11119 Color fromHsl(real h, real s, real l) {
11120 	return arsd.color.fromHsl([h,s,l]);
11121 }
11122 
11123 
11124 
11125 /* ********** What follows is the system-specific implementations *********/
11126 version(Windows) {
11127 
11128 
11129 	// helpers for making HICONs from MemoryImages
11130 	class WindowsIcon {
11131 		struct Win32Icon {
11132 			align(1):
11133 			uint biSize;
11134 			int biWidth;
11135 			int biHeight;
11136 			ushort biPlanes;
11137 			ushort biBitCount;
11138 			uint biCompression;
11139 			uint biSizeImage;
11140 			int biXPelsPerMeter;
11141 			int biYPelsPerMeter;
11142 			uint biClrUsed;
11143 			uint biClrImportant;
11144 			// RGBQUAD[colorCount] biColors;
11145 			/* Pixels:
11146 			Uint8 pixels[]
11147 			*/
11148 			/* Mask:
11149 			Uint8 mask[]
11150 			*/
11151 		}
11152 
11153 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11154 
11155 			assert(mi.width <= 256, "image too wide");
11156 			assert(mi.height <= 256, "image too tall");
11157 			assert(mi.width % 8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy
11158 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11159 
11160 			int icon_plen = mi.width * mi.height * 4;
11161 			int icon_mlen = mi.width * mi.height / 8;
11162 
11163 			int colorCount = 0;
11164 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11165 
11166 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11167 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11168 
11169 			auto data = memory[Win32Icon.sizeof .. $];
11170 
11171 			width = mi.width;
11172 			height = mi.height;
11173 
11174 			auto trueColorImage = mi.getAsTrueColorImage();
11175 
11176 			icon_win32.biSize = 40;
11177 			icon_win32.biWidth = mi.width;
11178 			icon_win32.biHeight = mi.height*2;
11179 			icon_win32.biPlanes = 1;
11180 			icon_win32.biBitCount = 32;
11181 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11182 
11183 			int offset = 0;
11184 			int andOff = icon_plen * 8; // the and offset is in bits
11185 
11186 			// leaving the and mask as the default 0 so the rgba alpha blend
11187 			// does its thing instead
11188 			for(int y = height - 1; y >= 0; y--) {
11189 				int off2 = y * width * 4;
11190 				foreach(x; 0 .. width) {
11191 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11192 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11193 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11194 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11195 
11196 					offset += 4;
11197 					off2 += 4;
11198 				}
11199 			}
11200 
11201 			return memory;
11202 		}
11203 
11204 		this(MemoryImage mi) {
11205 			int icon_len, width, height;
11206 
11207 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11208 
11209 			/*
11210 			PNG* png = readPnpngData);
11211 			PNGHeader pngh = getHeader(png);
11212 			void* icon_win32;
11213 			if(pngh.depth == 4) {
11214 				auto i = new Win32Icon!(16);
11215 				i.fromPNG(png, pngh, icon_len, width, height);
11216 				icon_win32 = i;
11217 			}
11218 			else if(pngh.depth == 8) {
11219 				auto i = new Win32Icon!(256);
11220 				i.fromPNG(png, pngh, icon_len, width, height);
11221 				icon_win32 = i;
11222 			} else assert(0);
11223 			*/
11224 
11225 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11226 
11227 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11228 		}
11229 
11230 		~this() {
11231 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11232 			DestroyIcon(hIcon);
11233 		}
11234 
11235 		HICON hIcon;
11236 	}
11237 
11238 
11239 
11240 
11241 
11242 
11243 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11244 	alias HWND NativeWindowHandle;
11245 
11246 	extern(Windows)
11247 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11248 		try {
11249 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11250 				// it returns zero if the message is handled, so we won't do anything more there
11251 				// do I like that though?
11252 				int mustReturn;
11253 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11254 				if(mustReturn)
11255 					return ret;
11256 			}
11257 
11258 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11259 				if(window.getNativeEventHandler !is null) {
11260 					int mustReturn;
11261 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11262 					if(mustReturn)
11263 						return ret;
11264 				}
11265 				if(auto w = cast(SimpleWindow) (*window))
11266 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11267 				else
11268 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11269 			} else {
11270 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11271 			}
11272 		} catch (Exception e) {
11273 			try {
11274 				sdpy_abort(e);
11275 				return 0;
11276 			} catch(Exception e) { assert(0); }
11277 		}
11278 	}
11279 
11280 	void sdpy_abort(Throwable e) nothrow {
11281 		try
11282 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11283 		catch(Exception e)
11284 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11285 		ExitProcess(1);
11286 	}
11287 
11288 	mixin template NativeScreenPainterImplementation() {
11289 		HDC hdc;
11290 		HWND hwnd;
11291 		//HDC windowHdc;
11292 		HBITMAP oldBmp;
11293 
11294 		void create(PaintingHandle window) {
11295 			hwnd = window;
11296 
11297 			if(auto sw = cast(SimpleWindow) this.window) {
11298 				// drawing on a window, double buffer
11299 				auto windowHdc = GetDC(hwnd);
11300 
11301 				auto buffer = sw.impl.buffer;
11302 				if(buffer is null) {
11303 					hdc = windowHdc;
11304 					windowDc = true;
11305 				} else {
11306 					hdc = CreateCompatibleDC(windowHdc);
11307 
11308 					ReleaseDC(hwnd, windowHdc);
11309 
11310 					oldBmp = SelectObject(hdc, buffer);
11311 				}
11312 			} else {
11313 				// drawing on something else, draw directly
11314 				hdc = CreateCompatibleDC(null);
11315 				SelectObject(hdc, window);
11316 			}
11317 
11318 			// X doesn't draw a text background, so neither should we
11319 			SetBkMode(hdc, TRANSPARENT);
11320 
11321 			ensureDefaultFontLoaded();
11322 
11323 			if(defaultGuiFont) {
11324 				SelectObject(hdc, defaultGuiFont);
11325 				// DeleteObject(defaultGuiFont);
11326 			}
11327 		}
11328 
11329 		static HFONT defaultGuiFont;
11330 		static void ensureDefaultFontLoaded() {
11331 			static bool triedDefaultGuiFont = false;
11332 			if(!triedDefaultGuiFont) {
11333 				NONCLIENTMETRICS params;
11334 				params.cbSize = params.sizeof;
11335 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11336 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11337 				}
11338 				triedDefaultGuiFont = true;
11339 			}
11340 		}
11341 
11342 		private OperatingSystemFont _activeFont;
11343 
11344 		void setFont(OperatingSystemFont font) {
11345 			_activeFont = font;
11346 			if(font && font.font) {
11347 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11348 					// error... how to handle tho?
11349 				} else {
11350 
11351 				}
11352 			}
11353 			else if(defaultGuiFont)
11354 				SelectObject(hdc, defaultGuiFont);
11355 		}
11356 
11357 		arsd.color.Rectangle _clipRectangle;
11358 
11359 		void setClipRectangle(int x, int y, int width, int height) {
11360 			auto old = _clipRectangle;
11361 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11362 			if(old == _clipRectangle)
11363 				return;
11364 
11365 			if(width == 0 || height == 0) {
11366 				SelectClipRgn(hdc, null);
11367 			} else {
11368 				auto region = CreateRectRgn(x, y, x + width, y + height);
11369 				SelectClipRgn(hdc, region);
11370 				DeleteObject(region);
11371 			}
11372 		}
11373 
11374 
11375 		// just because we can on Windows...
11376 		//void create(Image image);
11377 
11378 		void invalidateRect(Rectangle invalidRect) {
11379 			RECT rect;
11380 			rect.left = invalidRect.left;
11381 			rect.right = invalidRect.right;
11382 			rect.top = invalidRect.top;
11383 			rect.bottom = invalidRect.bottom;
11384 			InvalidateRect(hwnd, &rect, false);
11385 		}
11386 		bool manualInvalidations;
11387 
11388 		void dispose() {
11389 			// FIXME: this.window.width/height is probably wrong
11390 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11391 			// ReleaseDC(hwnd, windowHdc);
11392 
11393 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11394 			if(cast(SimpleWindow) this.window) {
11395 				if(!manualInvalidations)
11396 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11397 			}
11398 
11399 			if(originalPen !is null)
11400 				SelectObject(hdc, originalPen);
11401 			if(currentPen !is null)
11402 				DeleteObject(currentPen);
11403 			if(originalBrush !is null)
11404 				SelectObject(hdc, originalBrush);
11405 			if(currentBrush !is null)
11406 				DeleteObject(currentBrush);
11407 
11408 			SelectObject(hdc, oldBmp);
11409 
11410 			if(windowDc)
11411 				ReleaseDC(hwnd, hdc);
11412 			else
11413 				DeleteDC(hdc);
11414 
11415 			if(window.paintingFinishedDg !is null)
11416 				window.paintingFinishedDg()();
11417 		}
11418 
11419 		bool windowDc;
11420 		HPEN originalPen;
11421 		HPEN currentPen;
11422 
11423 		Pen _activePen;
11424 
11425 		Color _outlineColor;
11426 
11427 		@property void pen(Pen p) {
11428 			_activePen = p;
11429 			_outlineColor = p.color;
11430 
11431 			HPEN pen;
11432 			if(p.color.a == 0) {
11433 				pen = GetStockObject(NULL_PEN);
11434 			} else {
11435 				int style = PS_SOLID;
11436 				final switch(p.style) {
11437 					case Pen.Style.Solid:
11438 						style = PS_SOLID;
11439 					break;
11440 					case Pen.Style.Dashed:
11441 						style = PS_DASH;
11442 					break;
11443 					case Pen.Style.Dotted:
11444 						style = PS_DOT;
11445 					break;
11446 				}
11447 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11448 			}
11449 			auto orig = SelectObject(hdc, pen);
11450 			if(originalPen is null)
11451 				originalPen = orig;
11452 
11453 			if(currentPen !is null)
11454 				DeleteObject(currentPen);
11455 
11456 			currentPen = pen;
11457 
11458 			// the outline is like a foreground since it's done that way on X
11459 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11460 
11461 		}
11462 
11463 		@property void rasterOp(RasterOp op) {
11464 			int mode;
11465 			final switch(op) {
11466 				case RasterOp.normal:
11467 					mode = R2_COPYPEN;
11468 				break;
11469 				case RasterOp.xor:
11470 					mode = R2_XORPEN;
11471 				break;
11472 			}
11473 			SetROP2(hdc, mode);
11474 		}
11475 
11476 		HBRUSH originalBrush;
11477 		HBRUSH currentBrush;
11478 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11479 		@property void fillColor(Color c) {
11480 			if(c == _fillColor)
11481 				return;
11482 			_fillColor = c;
11483 			HBRUSH brush;
11484 			if(c.a == 0) {
11485 				brush = GetStockObject(HOLLOW_BRUSH);
11486 			} else {
11487 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11488 			}
11489 			auto orig = SelectObject(hdc, brush);
11490 			if(originalBrush is null)
11491 				originalBrush = orig;
11492 
11493 			if(currentBrush !is null)
11494 				DeleteObject(currentBrush);
11495 
11496 			currentBrush = brush;
11497 
11498 			// background color is NOT set because X doesn't draw text backgrounds
11499 			//   SetBkColor(hdc, RGB(255, 255, 255));
11500 		}
11501 
11502 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11503 			BITMAP bm;
11504 
11505 			HDC hdcMem = CreateCompatibleDC(hdc);
11506 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11507 
11508 			GetObject(i.handle, bm.sizeof, &bm);
11509 
11510 			// or should I AlphaBlend!??!?!
11511 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11512 
11513 			SelectObject(hdcMem, hbmOld);
11514 			DeleteDC(hdcMem);
11515 		}
11516 
11517 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11518 			BITMAP bm;
11519 
11520 			HDC hdcMem = CreateCompatibleDC(hdc);
11521 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11522 
11523 			GetObject(s.handle, bm.sizeof, &bm);
11524 
11525 			version(CRuntime_DigitalMars) goto noalpha;
11526 
11527 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11528 			if(s.enableAlpha) {
11529 				auto dw = w ? w : bm.bmWidth;
11530 				auto dh = h ? h : bm.bmHeight;
11531 				BLENDFUNCTION bf;
11532 				bf.BlendOp = AC_SRC_OVER;
11533 				bf.SourceConstantAlpha = 255;
11534 				bf.AlphaFormat = AC_SRC_ALPHA;
11535 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11536 			} else {
11537 				noalpha:
11538 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11539 			}
11540 
11541 			SelectObject(hdcMem, hbmOld);
11542 			DeleteDC(hdcMem);
11543 		}
11544 
11545 		Size textSize(scope const(char)[] text) {
11546 			bool dummyX;
11547 			if(text.length == 0) {
11548 				text = " ";
11549 				dummyX = true;
11550 			}
11551 			RECT rect;
11552 			WCharzBuffer buffer = WCharzBuffer(text);
11553 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11554 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11555 		}
11556 
11557 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11558 			if(text.length && text[$-1] == '\n')
11559 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11560 			if(text.length && text[$-1] == '\r')
11561 				text = text[0 .. $-1];
11562 
11563 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11564 			if(x2 == 0 && y2 == 0) {
11565 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11566 			} else {
11567 				RECT rect;
11568 				rect.left = x;
11569 				rect.top = y;
11570 				rect.right = x2;
11571 				rect.bottom = y2;
11572 
11573 				uint mode = DT_LEFT;
11574 				if(alignment & TextAlignment.Right)
11575 					mode = DT_RIGHT;
11576 				else if(alignment & TextAlignment.Center)
11577 					mode = DT_CENTER;
11578 
11579 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11580 				if(alignment & TextAlignment.VerticalCenter)
11581 					mode |= DT_VCENTER | DT_SINGLELINE;
11582 
11583 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11584 			}
11585 
11586 			/*
11587 			uint mode;
11588 
11589 			if(alignment & TextAlignment.Center)
11590 				mode = TA_CENTER;
11591 
11592 			SetTextAlign(hdc, mode);
11593 			*/
11594 		}
11595 
11596 		int fontHeight() {
11597 			TEXTMETRIC metric;
11598 			if(GetTextMetricsW(hdc, &metric)) {
11599 				return metric.tmHeight;
11600 			}
11601 
11602 			return 16; // idk just guessing here, maybe we should throw
11603 		}
11604 
11605 		void drawPixel(int x, int y) {
11606 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11607 		}
11608 
11609 		// The basic shapes, outlined
11610 
11611 		void drawLine(int x1, int y1, int x2, int y2) {
11612 			MoveToEx(hdc, x1, y1, null);
11613 			LineTo(hdc, x2, y2);
11614 		}
11615 
11616 		void drawRectangle(int x, int y, int width, int height) {
11617 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11618 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11619 		}
11620 
11621 		/// Arguments are the points of the bounding rectangle
11622 		void drawEllipse(int x1, int y1, int x2, int y2) {
11623 			Ellipse(hdc, x1, y1, x2, y2);
11624 		}
11625 
11626 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11627 			if((start % (360*64)) == (finish % (360*64)))
11628 				drawEllipse(x1, y1, x1 + width, y1 + height);
11629 			else {
11630 				import core.stdc.math;
11631 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11632 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11633 
11634 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11635 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11636 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11637 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11638 
11639 				if(_activePen.color.a)
11640 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11641 				if(_fillColor.a)
11642 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11643 			}
11644 		}
11645 
11646 		void drawPolygon(Point[] vertexes) {
11647 			POINT[] points;
11648 			points.length = vertexes.length;
11649 
11650 			foreach(i, p; vertexes) {
11651 				points[i].x = p.x;
11652 				points[i].y = p.y;
11653 			}
11654 
11655 			Polygon(hdc, points.ptr, cast(int) points.length);
11656 		}
11657 	}
11658 
11659 
11660 	// Mix this into the SimpleWindow class
11661 	mixin template NativeSimpleWindowImplementation() {
11662 		int curHidden = 0; // counter
11663 		__gshared static bool[string] knownWinClasses;
11664 		static bool altPressed = false;
11665 
11666 		HANDLE oldCursor;
11667 
11668 		void hideCursor () {
11669 			if(curHidden == 0)
11670 				oldCursor = SetCursor(null);
11671 			++curHidden;
11672 		}
11673 
11674 		void showCursor () {
11675 			--curHidden;
11676 			if(curHidden == 0) {
11677 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11678 			}
11679 		}
11680 
11681 
11682 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11683 
11684 		void setMinSize (int minwidth, int minheight) {
11685 			minWidth = minwidth;
11686 			minHeight = minheight;
11687 		}
11688 		void setMaxSize (int maxwidth, int maxheight) {
11689 			maxWidth = maxwidth;
11690 			maxHeight = maxheight;
11691 		}
11692 
11693 		// FIXME i'm not sure that Windows has this functionality
11694 		// though it is nonessential anyway.
11695 		void setResizeGranularity (int granx, int grany) {}
11696 
11697 		ScreenPainter getPainter(bool manualInvalidations) {
11698 			return ScreenPainter(this, hwnd, manualInvalidations);
11699 		}
11700 
11701 		HBITMAP buffer;
11702 
11703 		void setTitle(string title) {
11704 			WCharzBuffer bfr = WCharzBuffer(title);
11705 			SetWindowTextW(hwnd, bfr.ptr);
11706 		}
11707 
11708 		string getTitle() {
11709 			auto len = GetWindowTextLengthW(hwnd);
11710 			if (!len)
11711 				return null;
11712 			wchar[256] tmpBuffer;
11713 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11714 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11715 			auto str = buffer[0 .. len2];
11716 			return makeUtf8StringFromWindowsString(str);
11717 		}
11718 
11719 		void move(int x, int y) {
11720 			RECT rect;
11721 			GetWindowRect(hwnd, &rect);
11722 			// move it while maintaining the same size...
11723 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11724 		}
11725 
11726 		void resize(int w, int h) {
11727 			RECT rect;
11728 			GetWindowRect(hwnd, &rect);
11729 
11730 			RECT client;
11731 			GetClientRect(hwnd, &client);
11732 
11733 			rect.right = rect.right - client.right + w;
11734 			rect.bottom = rect.bottom - client.bottom + h;
11735 
11736 			// same position, new size for the client rectangle
11737 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11738 
11739 			updateOpenglViewportIfNeeded(w, h);
11740 		}
11741 
11742 		void moveResize (int x, int y, int w, int h) {
11743 			// what's given is the client rectangle, we need to adjust
11744 
11745 			RECT rect;
11746 			rect.left = x;
11747 			rect.top = y;
11748 			rect.right = w + x;
11749 			rect.bottom = h + y;
11750 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11751 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
11752 
11753 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11754 			updateOpenglViewportIfNeeded(w, h);
11755 			if (windowResized !is null) windowResized(w, h);
11756 		}
11757 
11758 		version(without_opengl) {} else {
11759 			HGLRC ghRC;
11760 			HDC ghDC;
11761 		}
11762 
11763 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11764 			string cnamec;
11765 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11766 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11767 				cnamec = "DSimpleWindow";
11768 			} else {
11769 				cnamec = sdpyWindowClass;
11770 			}
11771 
11772 			WCharzBuffer cn = WCharzBuffer(cnamec);
11773 
11774 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11775 
11776 			if(cnamec !in knownWinClasses) {
11777 				WNDCLASSEX wc;
11778 
11779 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11780 				// to the object. Maybe.
11781 				wc.cbSize = wc.sizeof;
11782 				wc.cbClsExtra = 0;
11783 				wc.cbWndExtra = 0;
11784 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11785 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11786 				wc.hIcon = LoadIcon(hInstance, null);
11787 				wc.hInstance = hInstance;
11788 				wc.lpfnWndProc = &WndProc;
11789 				wc.lpszClassName = cn.ptr;
11790 				wc.hIconSm = null;
11791 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11792 				if(!RegisterClassExW(&wc))
11793 					throw new WindowsApiException("RegisterClassExW", GetLastError());
11794 				knownWinClasses[cnamec] = true;
11795 			}
11796 
11797 			int style;
11798 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11799 
11800 			// FIXME: windowType and customizationFlags
11801 			final switch(windowType) {
11802 				case WindowTypes.normal:
11803 					if(resizability == Resizability.fixedSize) {
11804 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11805 					} else {
11806 						style = WS_OVERLAPPEDWINDOW;
11807 					}
11808 				break;
11809 				case WindowTypes.undecorated:
11810 					style = WS_POPUP | WS_SYSMENU;
11811 				break;
11812 				case WindowTypes.eventOnly:
11813 					_hidden = true;
11814 				break;
11815 				case WindowTypes.dropdownMenu:
11816 				case WindowTypes.popupMenu:
11817 				case WindowTypes.notification:
11818 					style = WS_POPUP;
11819 					flags |= WS_EX_NOACTIVATE;
11820 				break;
11821 				case WindowTypes.nestedChild:
11822 					style = WS_CHILD;
11823 				break;
11824 				case WindowTypes.minimallyWrapped:
11825 					assert(0, "construct minimally wrapped through the other ctor overlad");
11826 			}
11827 
11828 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11829 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11830 
11831 			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
11832 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11833 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11834 
11835 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11836 				setOpacity(255);
11837 
11838 			SimpleWindow.nativeMapping[hwnd] = this;
11839 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11840 
11841 			if(windowType == WindowTypes.eventOnly)
11842 				return;
11843 
11844 			HDC hdc = GetDC(hwnd);
11845 
11846 
11847 			version(without_opengl) {}
11848 			else {
11849 				if(opengl == OpenGlOptions.yes) {
11850 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11851 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11852 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11853 					ghDC = hdc;
11854 					PIXELFORMATDESCRIPTOR pfd;
11855 
11856 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11857 					pfd.nVersion = 1;
11858 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11859 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11860 					pfd.iPixelType = PFD_TYPE_RGBA;
11861 					pfd.cColorBits = 24;
11862 					pfd.cDepthBits = 24;
11863 					pfd.cAccumBits = 0;
11864 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11865 
11866 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11867 
11868 					if (pixelformat == 0)
11869 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
11870 
11871 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11872 						throw new WindowsApiException("SetPixelFormat", GetLastError());
11873 
11874 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11875 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11876 						// so we will create fake context to get that stupid address
11877 						auto tmpcc = wglCreateContext(ghDC);
11878 						if (tmpcc !is null) {
11879 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11880 							wglMakeCurrent(ghDC, tmpcc);
11881 							wglInitOtherFunctions();
11882 						}
11883 					}
11884 
11885 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11886 						int[9] contextAttribs = [
11887 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11888 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11889 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11890 							// for modern context, set "forward compatibility" flag too
11891 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11892 							0/*None*/,
11893 						];
11894 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11895 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11896 							// activate fallback mode
11897 							// 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;
11898 							ghRC = wglCreateContext(ghDC);
11899 						}
11900 						if (ghRC is null)
11901 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
11902 					} else {
11903 						// try to do at least something
11904 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11905 							sdpyOpenGLContextVersion = 0;
11906 							ghRC = wglCreateContext(ghDC);
11907 						}
11908 						if (ghRC is null)
11909 							throw new WindowsApiException("wglCreateContext", GetLastError());
11910 					}
11911 				}
11912 			}
11913 
11914 			if(opengl == OpenGlOptions.no) {
11915 				buffer = CreateCompatibleBitmap(hdc, width, height);
11916 
11917 				auto hdcBmp = CreateCompatibleDC(hdc);
11918 				// make sure it's filled with a blank slate
11919 				auto oldBmp = SelectObject(hdcBmp, buffer);
11920 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11921 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11922 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11923 				SelectObject(hdcBmp, oldBmp);
11924 				SelectObject(hdcBmp, oldBrush);
11925 				SelectObject(hdcBmp, oldPen);
11926 				DeleteDC(hdcBmp);
11927 
11928 				bmpWidth = width;
11929 				bmpHeight = height;
11930 
11931 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11932 			}
11933 
11934 			// We want the window's client area to match the image size
11935 			RECT rcClient, rcWindow;
11936 			POINT ptDiff;
11937 			GetClientRect(hwnd, &rcClient);
11938 			GetWindowRect(hwnd, &rcWindow);
11939 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11940 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11941 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11942 
11943 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11944 				ShowWindow(hwnd, SW_SHOWNORMAL);
11945 			} else {
11946 				_hidden = true;
11947 			}
11948 			this._visibleForTheFirstTimeCalled = false; // hack!
11949 		}
11950 
11951 
11952 		void dispose() {
11953 			if(buffer)
11954 				DeleteObject(buffer);
11955 		}
11956 
11957 		void closeWindow() {
11958 			if(ghRC) {
11959 				wglDeleteContext(ghRC);
11960 				ghRC = null;
11961 			}
11962 			DestroyWindow(hwnd);
11963 		}
11964 
11965 		bool setOpacity(ubyte alpha) {
11966 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11967 		}
11968 
11969 		HANDLE currentCursor;
11970 
11971 		// returns zero if it recognized the event
11972 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11973 			MouseEvent mouse;
11974 
11975 			void mouseEvent(bool isScreen, ulong mods) {
11976 				auto x = LOWORD(lParam);
11977 				auto y = HIWORD(lParam);
11978 				if(isScreen) {
11979 					POINT p;
11980 					p.x = x;
11981 					p.y = y;
11982 					ScreenToClient(hwnd, &p);
11983 					x = cast(ushort) p.x;
11984 					y = cast(ushort) p.y;
11985 				}
11986 
11987 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11988 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11989 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11990 				}
11991 
11992 				mouse.x = x + offsetX;
11993 				mouse.y = y + offsetY;
11994 
11995 				wind.mdx(mouse);
11996 				mouse.modifierState = cast(int) mods;
11997 				mouse.window = wind;
11998 
11999 				if(wind.handleMouseEvent)
12000 					wind.handleMouseEvent(mouse);
12001 			}
12002 
12003 			switch(msg) {
12004 				case WM_GETMINMAXINFO:
12005 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
12006 
12007 					if(wind.minWidth > 0) {
12008 						RECT rect;
12009 						rect.left = 100;
12010 						rect.top = 100;
12011 						rect.right = wind.minWidth + 100;
12012 						rect.bottom = wind.minHeight + 100;
12013 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12014 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12015 
12016 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12017 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12018 					}
12019 
12020 					if(wind.maxWidth < int.max) {
12021 						RECT rect;
12022 						rect.left = 100;
12023 						rect.top = 100;
12024 						rect.right = wind.maxWidth + 100;
12025 						rect.bottom = wind.maxHeight + 100;
12026 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12027 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12028 
12029 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12030 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12031 					}
12032 				break;
12033 				case WM_CHAR:
12034 					wchar c = cast(wchar) wParam;
12035 					if(wind.handleCharEvent)
12036 						wind.handleCharEvent(cast(dchar) c);
12037 				break;
12038 				  case WM_SETFOCUS:
12039 				  case WM_KILLFOCUS:
12040 					wind._focused = (msg == WM_SETFOCUS);
12041 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12042 					if(wind.onFocusChange)
12043 						wind.onFocusChange(msg == WM_SETFOCUS);
12044 				  break;
12045 
12046 				case WM_SYSKEYDOWN:
12047 					goto case;
12048 				case WM_SYSKEYUP:
12049 					if(lParam & (1 << 29)) {
12050 						goto case;
12051 					} else {
12052 						// no window has keyboard focus
12053 						goto default;
12054 					}
12055 				case WM_KEYDOWN:
12056 				case WM_KEYUP:
12057 					KeyEvent ev;
12058 					ev.key = cast(Key) wParam;
12059 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12060 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12061 
12062 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12063 
12064 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12065 						ev.modifierState |= ModifierState.shift;
12066 					//k8: this doesn't work; thanks for nothing, windows
12067 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12068 						ev.modifierState |= ModifierState.alt;*/
12069 					// this never seems to actually be set
12070 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12071 
12072 					if (wParam == 0x12) {
12073 						altPressed = (msg == WM_SYSKEYDOWN);
12074 					}
12075 
12076 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12077 						altPressed = false;
12078 					}
12079 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12080 
12081 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12082 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12083 						ev.modifierState |= ModifierState.ctrl;
12084 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12085 						ev.modifierState |= ModifierState.windows;
12086 					if(GetKeyState(Key.NumLock))
12087 						ev.modifierState |= ModifierState.numLock;
12088 					if(GetKeyState(Key.CapsLock))
12089 						ev.modifierState |= ModifierState.capsLock;
12090 
12091 					/+
12092 					// we always want to send the character too, so let's convert it
12093 					ubyte[256] state;
12094 					wchar[16] buffer;
12095 					GetKeyboardState(state.ptr);
12096 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12097 
12098 					foreach(dchar d; buffer) {
12099 						ev.character = d;
12100 						break;
12101 					}
12102 					+/
12103 
12104 					ev.window = wind;
12105 					if(wind.handleKeyEvent)
12106 						wind.handleKeyEvent(ev);
12107 				break;
12108 				case 0x020a /*WM_MOUSEWHEEL*/:
12109 					// send click
12110 					mouse.type = cast(MouseEventType) 1;
12111 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12112 					mouseEvent(true, LOWORD(wParam));
12113 
12114 					// also send release
12115 					mouse.type = cast(MouseEventType) 2;
12116 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12117 					mouseEvent(true, LOWORD(wParam));
12118 				break;
12119 				case WM_MOUSEMOVE:
12120 					mouse.type = cast(MouseEventType) 0;
12121 					mouseEvent(false, wParam);
12122 				break;
12123 				case WM_LBUTTONDOWN:
12124 				case WM_LBUTTONDBLCLK:
12125 					mouse.type = cast(MouseEventType) 1;
12126 					mouse.button = MouseButton.left;
12127 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12128 					mouseEvent(false, wParam);
12129 				break;
12130 				case WM_LBUTTONUP:
12131 					mouse.type = cast(MouseEventType) 2;
12132 					mouse.button = MouseButton.left;
12133 					mouseEvent(false, wParam);
12134 				break;
12135 				case WM_RBUTTONDOWN:
12136 				case WM_RBUTTONDBLCLK:
12137 					mouse.type = cast(MouseEventType) 1;
12138 					mouse.button = MouseButton.right;
12139 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12140 					mouseEvent(false, wParam);
12141 				break;
12142 				case WM_RBUTTONUP:
12143 					mouse.type = cast(MouseEventType) 2;
12144 					mouse.button = MouseButton.right;
12145 					mouseEvent(false, wParam);
12146 				break;
12147 				case WM_MBUTTONDOWN:
12148 				case WM_MBUTTONDBLCLK:
12149 					mouse.type = cast(MouseEventType) 1;
12150 					mouse.button = MouseButton.middle;
12151 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12152 					mouseEvent(false, wParam);
12153 				break;
12154 				case WM_MBUTTONUP:
12155 					mouse.type = cast(MouseEventType) 2;
12156 					mouse.button = MouseButton.middle;
12157 					mouseEvent(false, wParam);
12158 				break;
12159 				case WM_XBUTTONDOWN:
12160 				case WM_XBUTTONDBLCLK:
12161 					mouse.type = cast(MouseEventType) 1;
12162 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12163 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12164 					mouseEvent(false, wParam);
12165 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12166 				case WM_XBUTTONUP:
12167 					mouse.type = cast(MouseEventType) 2;
12168 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12169 					mouseEvent(false, wParam);
12170 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12171 
12172 				default: return 1;
12173 			}
12174 			return 0;
12175 		}
12176 
12177 		HWND hwnd;
12178 		private int oldWidth;
12179 		private int oldHeight;
12180 		private bool inSizeMove;
12181 
12182 		/++
12183 			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.
12184 
12185 			History:
12186 				Added November 23, 2021
12187 
12188 				Not fully stable, may be moved out of the impl struct.
12189 
12190 				Default value changed to `true` on February 15, 2021
12191 		+/
12192 		bool doLiveResizing = true;
12193 
12194 		package int bmpWidth;
12195 		package int bmpHeight;
12196 
12197 		// the extern(Windows) wndproc should just forward to this
12198 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12199 		try {
12200 			assert(hwnd is this.hwnd);
12201 
12202 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12203 			switch(msg) {
12204 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12205 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12206 					// The main things we can do are select, execute, close, or ignore
12207 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12208 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12209 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12210 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12211 
12212 					// returns the value in the *high order word* of the return value
12213 					// hence the << 16
12214 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12215 				case WM_SETCURSOR:
12216 					if(cast(HWND) wParam !is hwnd)
12217 						return 0; // further processing elsewhere
12218 
12219 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12220 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12221 						return 1;
12222 					} else {
12223 						return DefWindowProc(hwnd, msg, wParam, lParam);
12224 					}
12225 				//break;
12226 
12227 				case WM_CLOSE:
12228 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12229 				break;
12230 				case WM_DESTROY:
12231 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12232 					SimpleWindow.nativeMapping.remove(hwnd);
12233 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12234 
12235 					bool anyImportant = false;
12236 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12237 						if(w.beingOpenKeepsAppOpen) {
12238 							anyImportant = true;
12239 							break;
12240 						}
12241 					if(!anyImportant) {
12242 						PostQuitMessage(0);
12243 					}
12244 				break;
12245 				case 0x02E0 /*WM_DPICHANGED*/:
12246 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12247 
12248 					RECT* prcNewWindow = cast(RECT*)lParam;
12249 					// docs say this is the recommended position and we should honor it
12250 					SetWindowPos(hwnd,
12251 							null,
12252 							prcNewWindow.left,
12253 							prcNewWindow.top,
12254 							prcNewWindow.right - prcNewWindow.left,
12255 							prcNewWindow.bottom - prcNewWindow.top,
12256 							SWP_NOZORDER | SWP_NOACTIVATE);
12257 
12258 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12259 					// im not sure it is completely correct
12260 					// but without it the tabs and such do look weird as things change.
12261 					if(SystemParametersInfoForDpi) {
12262 						LOGFONT lfText;
12263 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12264 						HFONT hFontNew = CreateFontIndirect(&lfText);
12265 						if (hFontNew)
12266 						{
12267 							//DeleteObject(hFontOld);
12268 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12269 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12270 								return TRUE;
12271 							}
12272 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12273 						}
12274 					}
12275 
12276 					if(this.onDpiChanged)
12277 						this.onDpiChanged();
12278 				break;
12279 				case WM_ENTERIDLE:
12280 					// when a menu is up, it stops normal event processing (modal message loop)
12281 					// but this at least gives us a chance to SOMETIMES catch up
12282 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12283 					SimpleWindow.processAllCustomEvents;
12284 					SimpleWindow.processAllCustomEvents;
12285 					SleepEx(0, true);
12286 					break;
12287 				case WM_SIZE:
12288 					if(wParam == 1 /* SIZE_MINIMIZED */)
12289 						break;
12290 					_width = LOWORD(lParam);
12291 					_height = HIWORD(lParam);
12292 
12293 					// I want to avoid tearing in the windows (my code is inefficient
12294 					// so this is a hack around that) so while sizing, we don't trigger,
12295 					// but we do want to trigger on events like mazimize.
12296 					if(!inSizeMove || doLiveResizing)
12297 						goto size_changed;
12298 				break;
12299 				/+
12300 				case WM_SIZING:
12301 					writeln("size");
12302 				break;
12303 				+/
12304 				// I don't like the tearing I get when redrawing on WM_SIZE
12305 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12306 				// so instead it is going to redraw only at the end of a size.
12307 				case 0x0231: /* WM_ENTERSIZEMOVE */
12308 					inSizeMove = true;
12309 				break;
12310 				case 0x0232: /* WM_EXITSIZEMOVE */
12311 					inSizeMove = false;
12312 
12313 					size_changed:
12314 
12315 					// nothing relevant changed, don't bother redrawing
12316 					if(oldWidth == _width && oldHeight == _height) {
12317 						if(msg == 0x0232)
12318 							goto finalize_resize;
12319 						break;
12320 					}
12321 
12322 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12323 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12324 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12325 						// gotta get the double buffer bmp to match the window
12326 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12327 
12328 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12329 						if(resizability != Resizability.automaticallyScaleIfPossible)
12330 						if(_width > bmpWidth || _height > bmpHeight) {
12331 							auto hdc = GetDC(hwnd);
12332 							auto oldBuffer = buffer;
12333 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12334 
12335 							auto hdcBmp = CreateCompatibleDC(hdc);
12336 							auto oldBmp = SelectObject(hdcBmp, buffer);
12337 
12338 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12339 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12340 
12341 							/+
12342 							RECT r;
12343 							r.left = 0;
12344 							r.top = 0;
12345 							r.right = width;
12346 							r.bottom = height;
12347 							auto c = Color.green;
12348 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12349 							FillRect(hdcBmp, &r, brush);
12350 							DeleteObject(brush);
12351 							+/
12352 
12353 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12354 
12355 							bmpWidth = _width;
12356 							bmpHeight = _height;
12357 
12358 							SelectObject(hdcOldBmp, oldOldBmp);
12359 							DeleteDC(hdcOldBmp);
12360 
12361 							SelectObject(hdcBmp, oldBmp);
12362 							DeleteDC(hdcBmp);
12363 
12364 							ReleaseDC(hwnd, hdc);
12365 
12366 							DeleteObject(oldBuffer);
12367 						}
12368 					}
12369 
12370 					updateOpenglViewportIfNeeded(_width, _height);
12371 
12372 					if(resizability != Resizability.automaticallyScaleIfPossible)
12373 					if(windowResized !is null)
12374 						windowResized(_width, _height);
12375 
12376 					/+
12377 					if(inSizeMove) {
12378 						// SimpleWindow.processAllCustomEvents();
12379 						// SimpleWindow.processAllCustomEvents();
12380 
12381 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12382 						//sdpyPrintDebugString("redraw b");
12383 					} else {
12384 					+/ {
12385 						finalize_resize:
12386 						// when it is all done, make sure everything is freshly drawn or there might be
12387 						// weird bugs left.
12388 						SimpleWindow.processAllCustomEvents();
12389 						SimpleWindow.processAllCustomEvents();
12390 
12391 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12392 						// sdpyPrintDebugString("redraw");
12393 					}
12394 
12395 					oldWidth = this._width;
12396 					oldHeight = this._height;
12397 				break;
12398 				case WM_ERASEBKGND:
12399 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12400 					if (!this._visibleForTheFirstTimeCalled) {
12401 						this._visibleForTheFirstTimeCalled = true;
12402 						if (this.visibleForTheFirstTime !is null) {
12403 							this.visibleForTheFirstTime();
12404 						}
12405 					}
12406 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12407 					version(without_opengl) {} else {
12408 						if (openglMode == OpenGlOptions.yes) return 1;
12409 					}
12410 					// call windows default handler, so it can paint standard controls
12411 					goto default;
12412 				case WM_CTLCOLORBTN:
12413 				case WM_CTLCOLORSTATIC:
12414 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12415 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12416 					GetSysColorBrush(COLOR_3DFACE);
12417 				//break;
12418 				case WM_SHOWWINDOW:
12419 					this._visible = (wParam != 0);
12420 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12421 						this._visibleForTheFirstTimeCalled = true;
12422 						if (this.visibleForTheFirstTime !is null) {
12423 							this.visibleForTheFirstTime();
12424 						}
12425 					}
12426 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12427 					break;
12428 				case WM_PAINT: {
12429 					if (!this._visibleForTheFirstTimeCalled) {
12430 						this._visibleForTheFirstTimeCalled = true;
12431 						if (this.visibleForTheFirstTime !is null) {
12432 							this.visibleForTheFirstTime();
12433 						}
12434 					}
12435 
12436 					BITMAP bm;
12437 					PAINTSTRUCT ps;
12438 
12439 					HDC hdc = BeginPaint(hwnd, &ps);
12440 
12441 					if(openglMode == OpenGlOptions.no) {
12442 
12443 						HDC hdcMem = CreateCompatibleDC(hdc);
12444 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12445 
12446 						GetObject(buffer, bm.sizeof, &bm);
12447 
12448 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12449 						if(resizability == Resizability.automaticallyScaleIfPossible)
12450 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12451 						else
12452 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12453 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12454 
12455 						SelectObject(hdcMem, hbmOld);
12456 						DeleteDC(hdcMem);
12457 						EndPaint(hwnd, &ps);
12458 					} else {
12459 						EndPaint(hwnd, &ps);
12460 						version(without_opengl) {} else
12461 							redrawOpenGlSceneSoon();
12462 					}
12463 				} break;
12464 				  default:
12465 					return DefWindowProc(hwnd, msg, wParam, lParam);
12466 			}
12467 			 return 0;
12468 
12469 		}
12470 		catch(Throwable t) {
12471 			sdpyPrintDebugString(t.toString);
12472 			return 0;
12473 		}
12474 		}
12475 	}
12476 
12477 	mixin template NativeImageImplementation() {
12478 		HBITMAP handle;
12479 		ubyte* rawData;
12480 
12481 	final:
12482 
12483 		Color getPixel(int x, int y) {
12484 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12485 			// remember, bmps are upside down
12486 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12487 
12488 			Color c;
12489 			if(enableAlpha)
12490 				c.a = rawData[offset + 3];
12491 			else
12492 				c.a = 255;
12493 			c.b = rawData[offset + 0];
12494 			c.g = rawData[offset + 1];
12495 			c.r = rawData[offset + 2];
12496 			c.unPremultiply();
12497 			return c;
12498 		}
12499 
12500 		void setPixel(int x, int y, Color c) {
12501 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12502 			// remember, bmps are upside down
12503 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12504 
12505 			if(enableAlpha)
12506 				c.premultiply();
12507 
12508 			rawData[offset + 0] = c.b;
12509 			rawData[offset + 1] = c.g;
12510 			rawData[offset + 2] = c.r;
12511 			if(enableAlpha)
12512 				rawData[offset + 3] = c.a;
12513 		}
12514 
12515 		void convertToRgbaBytes(ubyte[] where) {
12516 			assert(where.length == this.width * this.height * 4);
12517 
12518 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12519 			int idx = 0;
12520 			int offset = itemsPerLine * (height - 1);
12521 			// remember, bmps are upside down
12522 			for(int y = height - 1; y >= 0; y--) {
12523 				auto offsetStart = offset;
12524 				for(int x = 0; x < width; x++) {
12525 					where[idx + 0] = rawData[offset + 2]; // r
12526 					where[idx + 1] = rawData[offset + 1]; // g
12527 					where[idx + 2] = rawData[offset + 0]; // b
12528 					if(enableAlpha) {
12529 						where[idx + 3] = rawData[offset + 3]; // a
12530 						unPremultiplyRgba(where[idx .. idx + 4]);
12531 						offset++;
12532 					} else
12533 						where[idx + 3] = 255; // a
12534 					idx += 4;
12535 					offset += 3;
12536 				}
12537 
12538 				offset = offsetStart - itemsPerLine;
12539 			}
12540 		}
12541 
12542 		void setFromRgbaBytes(in ubyte[] what) {
12543 			assert(what.length == this.width * this.height * 4);
12544 
12545 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12546 			int idx = 0;
12547 			int offset = itemsPerLine * (height - 1);
12548 			// remember, bmps are upside down
12549 			for(int y = height - 1; y >= 0; y--) {
12550 				auto offsetStart = offset;
12551 				for(int x = 0; x < width; x++) {
12552 					if(enableAlpha) {
12553 						auto a = what[idx + 3];
12554 
12555 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12556 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12557 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12558 						rawData[offset + 3] = a; // a
12559 						//premultiplyBgra(rawData[offset .. offset + 4]);
12560 						offset++;
12561 					} else {
12562 						rawData[offset + 2] = what[idx + 0]; // r
12563 						rawData[offset + 1] = what[idx + 1]; // g
12564 						rawData[offset + 0] = what[idx + 2]; // b
12565 					}
12566 					idx += 4;
12567 					offset += 3;
12568 				}
12569 
12570 				offset = offsetStart - itemsPerLine;
12571 			}
12572 		}
12573 
12574 
12575 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12576 			BITMAPINFO infoheader;
12577 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12578 			infoheader.bmiHeader.biWidth = width;
12579 			infoheader.bmiHeader.biHeight = height;
12580 			infoheader.bmiHeader.biPlanes = 1;
12581 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12582 			infoheader.bmiHeader.biCompression = BI_RGB;
12583 
12584 			handle = CreateDIBSection(
12585 				null,
12586 				&infoheader,
12587 				DIB_RGB_COLORS,
12588 				cast(void**) &rawData,
12589 				null,
12590 				0);
12591 			if(handle is null)
12592 				throw new WindowsApiException("create image failed", GetLastError());
12593 
12594 		}
12595 
12596 		void dispose() {
12597 			DeleteObject(handle);
12598 		}
12599 	}
12600 
12601 	enum KEY_ESCAPE = 27;
12602 }
12603 version(X11) {
12604 	/// This is the default font used. You might change this before doing anything else with
12605 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12606 	/// for cross-platform compatibility.
12607 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12608 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12609 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12610 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12611 
12612 	alias int delegate(XEvent) NativeEventHandler;
12613 	alias Window NativeWindowHandle;
12614 
12615 	enum KEY_ESCAPE = 9;
12616 
12617 	mixin template NativeScreenPainterImplementation() {
12618 		Display* display;
12619 		Drawable d;
12620 		Drawable destiny;
12621 
12622 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12623 		GC gc;
12624 
12625 		__gshared bool fontAttempted;
12626 
12627 		__gshared XFontStruct* defaultfont;
12628 		__gshared XFontSet defaultfontset;
12629 
12630 		XFontStruct* font;
12631 		XFontSet fontset;
12632 
12633 		void create(PaintingHandle window) {
12634 			this.display = XDisplayConnection.get();
12635 
12636 			Drawable buffer = None;
12637 			if(auto sw = cast(SimpleWindow) this.window) {
12638 				buffer = sw.impl.buffer;
12639 				this.destiny = cast(Drawable) window;
12640 			} else {
12641 				buffer = cast(Drawable) window;
12642 				this.destiny = None;
12643 			}
12644 
12645 			this.d = cast(Drawable) buffer;
12646 
12647 			auto dgc = DefaultGC(display, DefaultScreen(display));
12648 
12649 			this.gc = XCreateGC(display, d, 0, null);
12650 
12651 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12652 
12653 			ensureDefaultFontLoaded();
12654 
12655 			font = defaultfont;
12656 			fontset = defaultfontset;
12657 
12658 			if(font) {
12659 				XSetFont(display, gc, font.fid);
12660 			}
12661 		}
12662 
12663 		static void ensureDefaultFontLoaded() {
12664 			if(!fontAttempted) {
12665 				auto display = XDisplayConnection.get;
12666 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12667 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12668 				if(font is null) {
12669 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12670 					font = XLoadQueryFont(display, xfontstr.ptr);
12671 				}
12672 
12673 				char** lol;
12674 				int lol2;
12675 				char* lol3;
12676 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12677 
12678 				fontAttempted = true;
12679 
12680 				defaultfont = font;
12681 				defaultfontset = fontset;
12682 			}
12683 		}
12684 
12685 		arsd.color.Rectangle _clipRectangle;
12686 		void setClipRectangle(int x, int y, int width, int height) {
12687 			auto old = _clipRectangle;
12688 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12689 			if(old == _clipRectangle)
12690 				return;
12691 
12692 			if(width == 0 || height == 0) {
12693 				XSetClipMask(display, gc, None);
12694 
12695 				if(xrenderPicturePainter) {
12696 
12697 					XRectangle[1] rects;
12698 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12699 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12700 				}
12701 
12702 				version(with_xft) {
12703 					if(xftFont is null || xftDraw is null)
12704 						return;
12705 					XftDrawSetClip(xftDraw, null);
12706 				}
12707 			} else {
12708 				XRectangle[1] rects;
12709 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12710 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12711 
12712 				if(xrenderPicturePainter)
12713 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12714 
12715 				version(with_xft) {
12716 					if(xftFont is null || xftDraw is null)
12717 						return;
12718 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12719 				}
12720 			}
12721 		}
12722 
12723 		version(with_xft) {
12724 			XftFont* xftFont;
12725 			XftDraw* xftDraw;
12726 
12727 			XftColor xftColor;
12728 
12729 			void updateXftColor() {
12730 				if(xftFont is null)
12731 					return;
12732 
12733 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12734 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12735 
12736 				XftColorAllocValue(
12737 					display,
12738 					DefaultVisual(display, DefaultScreen(display)),
12739 					DefaultColormap(display, 0),
12740 					&colorIn,
12741 					&xftColor
12742 				);
12743 			}
12744 		}
12745 
12746 		private OperatingSystemFont _activeFont;
12747 		void setFont(OperatingSystemFont font) {
12748 			_activeFont = font;
12749 			version(with_xft) {
12750 				if(font && font.isXft && font.xftFont)
12751 					this.xftFont = font.xftFont;
12752 				else
12753 					this.xftFont = null;
12754 
12755 				if(this.xftFont) {
12756 					if(xftDraw is null) {
12757 						xftDraw = XftDrawCreate(
12758 							display,
12759 							d,
12760 							DefaultVisual(display, DefaultScreen(display)),
12761 							DefaultColormap(display, 0)
12762 						);
12763 
12764 						updateXftColor();
12765 					}
12766 
12767 					return;
12768 				}
12769 			}
12770 
12771 			if(font && font.font) {
12772 				this.font = font.font;
12773 				this.fontset = font.fontset;
12774 				XSetFont(display, gc, font.font.fid);
12775 			} else {
12776 				this.font = defaultfont;
12777 				this.fontset = defaultfontset;
12778 			}
12779 
12780 		}
12781 
12782 		private Picture xrenderPicturePainter;
12783 
12784 		bool manualInvalidations;
12785 		void invalidateRect(Rectangle invalidRect) {
12786 			// FIXME if manualInvalidations
12787 		}
12788 
12789 		void dispose() {
12790 			this.rasterOp = RasterOp.normal;
12791 
12792 			if(xrenderPicturePainter) {
12793 				XRenderFreePicture(display, xrenderPicturePainter);
12794 				xrenderPicturePainter = None;
12795 			}
12796 
12797 			// FIXME: this.window.width/height is probably wrong
12798 
12799 			// src x,y     then dest x, y
12800 			if(destiny != None) {
12801 				// FIXME: if manual invalidations we can actually only copy some of the area.
12802 				// if(manualInvalidations)
12803 				XSetClipMask(display, gc, None);
12804 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12805 			}
12806 
12807 			XFreeGC(display, gc);
12808 
12809 			version(with_xft)
12810 			if(xftDraw) {
12811 				XftDrawDestroy(xftDraw);
12812 				xftDraw = null;
12813 			}
12814 
12815 			/+
12816 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12817 			if(font && font !is defaultfont) {
12818 				XFreeFont(display, font);
12819 				font = null;
12820 			}
12821 			if(fontset && fontset !is defaultfontset) {
12822 				XFreeFontSet(display, fontset);
12823 				fontset = null;
12824 			}
12825 			+/
12826 			XFlush(display);
12827 
12828 			if(window.paintingFinishedDg !is null)
12829 				window.paintingFinishedDg()();
12830 		}
12831 
12832 		bool backgroundIsNotTransparent = true;
12833 		bool foregroundIsNotTransparent = true;
12834 
12835 		bool _penInitialized = false;
12836 		Pen _activePen;
12837 
12838 		Color _outlineColor;
12839 		Color _fillColor;
12840 
12841 		@property void pen(Pen p) {
12842 			if(_penInitialized && p == _activePen) {
12843 				return;
12844 			}
12845 			_penInitialized = true;
12846 			_activePen = p;
12847 			_outlineColor = p.color;
12848 
12849 			int style;
12850 
12851 			byte dashLength;
12852 
12853 			final switch(p.style) {
12854 				case Pen.Style.Solid:
12855 					style = 0 /*LineSolid*/;
12856 				break;
12857 				case Pen.Style.Dashed:
12858 					style = 1 /*LineOnOffDash*/;
12859 					dashLength = 4;
12860 				break;
12861 				case Pen.Style.Dotted:
12862 					style = 1 /*LineOnOffDash*/;
12863 					dashLength = 1;
12864 				break;
12865 			}
12866 
12867 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
12868 			if(dashLength)
12869 				XSetDashes(display, gc, 0, &dashLength, 1);
12870 
12871 			if(p.color.a == 0) {
12872 				foregroundIsNotTransparent = false;
12873 				return;
12874 			}
12875 
12876 			foregroundIsNotTransparent = true;
12877 
12878 			XSetForeground(display, gc, colorToX(p.color, display));
12879 
12880 			version(with_xft)
12881 				updateXftColor();
12882 		}
12883 
12884 		RasterOp _currentRasterOp;
12885 		bool _currentRasterOpInitialized = false;
12886 		@property void rasterOp(RasterOp op) {
12887 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12888 				return;
12889 			_currentRasterOp = op;
12890 			_currentRasterOpInitialized = true;
12891 			int mode;
12892 			final switch(op) {
12893 				case RasterOp.normal:
12894 					mode = GXcopy;
12895 				break;
12896 				case RasterOp.xor:
12897 					mode = GXxor;
12898 				break;
12899 			}
12900 			XSetFunction(display, gc, mode);
12901 		}
12902 
12903 
12904 		bool _fillColorInitialized = false;
12905 
12906 		@property void fillColor(Color c) {
12907 			if(_fillColorInitialized && _fillColor == c)
12908 				return; // already good, no need to waste time calling it
12909 			_fillColor = c;
12910 			_fillColorInitialized = true;
12911 			if(c.a == 0) {
12912 				backgroundIsNotTransparent = false;
12913 				return;
12914 			}
12915 
12916 			backgroundIsNotTransparent = true;
12917 
12918 			XSetBackground(display, gc, colorToX(c, display));
12919 
12920 		}
12921 
12922 		void swapColors() {
12923 			auto tmp = _fillColor;
12924 			fillColor = _outlineColor;
12925 			auto newPen = _activePen;
12926 			newPen.color = tmp;
12927 			pen(newPen);
12928 		}
12929 
12930 		uint colorToX(Color c, Display* display) {
12931 			auto visual = DefaultVisual(display, DefaultScreen(display));
12932 			import core.bitop;
12933 			uint color = 0;
12934 			{
12935 			auto startBit = bsf(visual.red_mask);
12936 			auto lastBit = bsr(visual.red_mask);
12937 			auto r = cast(uint) c.r;
12938 			r >>= 7 - (lastBit - startBit);
12939 			r <<= startBit;
12940 			color |= r;
12941 			}
12942 			{
12943 			auto startBit = bsf(visual.green_mask);
12944 			auto lastBit = bsr(visual.green_mask);
12945 			auto g = cast(uint) c.g;
12946 			g >>= 7 - (lastBit - startBit);
12947 			g <<= startBit;
12948 			color |= g;
12949 			}
12950 			{
12951 			auto startBit = bsf(visual.blue_mask);
12952 			auto lastBit = bsr(visual.blue_mask);
12953 			auto b = cast(uint) c.b;
12954 			b >>= 7 - (lastBit - startBit);
12955 			b <<= startBit;
12956 			color |= b;
12957 			}
12958 
12959 
12960 
12961 			return color;
12962 		}
12963 
12964 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12965 			// source x, source y
12966 			if(ix >= i.width) return;
12967 			if(iy >= i.height) return;
12968 			if(ix + w > i.width) w = i.width - ix;
12969 			if(iy + h > i.height) h = i.height - iy;
12970 			if(i.usingXshm)
12971 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12972 			else
12973 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12974 		}
12975 
12976 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12977 			if(s.enableAlpha) {
12978 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12979 				if(this.xrenderPicturePainter == None) {
12980 					XRenderPictureAttributes attrs;
12981 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12982 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12983 
12984 					// need to initialize the clip
12985 					XRectangle[1] rects;
12986 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12987 
12988 					if(_clipRectangle != Rectangle.init)
12989 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12990 				}
12991 
12992 				XRenderComposite(
12993 					display,
12994 					3, // PicOpOver
12995 					s.xrenderPicture,
12996 					None,
12997 					this.xrenderPicturePainter,
12998 					ix,
12999 					iy,
13000 					0,
13001 					0,
13002 					x,
13003 					y,
13004 					w ? w : s.width,
13005 					h ? h : s.height
13006 				);
13007 			} else {
13008 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
13009 			}
13010 		}
13011 
13012 		int fontHeight() {
13013 			version(with_xft)
13014 				if(xftFont !is null)
13015 					return xftFont.height;
13016 			if(font)
13017 				return font.max_bounds.ascent + font.max_bounds.descent;
13018 			return 12; // pretty common default...
13019 		}
13020 
13021 		int textWidth(in char[] line) {
13022 			version(with_xft)
13023 			if(xftFont) {
13024 				if(line.length == 0)
13025 					return 0;
13026 				XGlyphInfo extents;
13027 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
13028 				return extents.width;
13029 			}
13030 
13031 			if(fontset) {
13032 				if(line.length == 0)
13033 					return 0;
13034 				XRectangle rect;
13035 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13036 
13037 				return rect.width;
13038 			}
13039 
13040 			if(font)
13041 				// FIXME: unicode
13042 				return XTextWidth( font, line.ptr, cast(int) line.length);
13043 			else
13044 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13045 		}
13046 
13047 		Size textSize(in char[] text) {
13048 			auto maxWidth = 0;
13049 			auto lineHeight = fontHeight;
13050 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13051 			foreach(line; text.split('\n')) {
13052 				int textWidth = this.textWidth(line);
13053 				if(textWidth > maxWidth)
13054 					maxWidth = textWidth;
13055 				h += lineHeight + 4;
13056 			}
13057 			return Size(maxWidth, h);
13058 		}
13059 
13060 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13061 			const(char)[] text;
13062 			version(with_xft)
13063 			if(xftFont) {
13064 				text = originalText;
13065 				goto loaded;
13066 			}
13067 
13068 			if(fontset)
13069 				text = originalText;
13070 			else {
13071 				text.reserve(originalText.length);
13072 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13073 				// then strip the rest so there isn't garbage
13074 				foreach(dchar ch; originalText)
13075 					if(ch < 256)
13076 						text ~= cast(ubyte) ch;
13077 					else
13078 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13079 			}
13080 			loaded:
13081 			if(text.length == 0)
13082 				return;
13083 
13084 			// FIXME: should we clip it to the bounding box?
13085 			int textHeight = fontHeight;
13086 
13087 			auto lines = text.split('\n');
13088 
13089 			const lineHeight = textHeight;
13090 			textHeight *= lines.length;
13091 
13092 			int cy = y;
13093 
13094 			if(alignment & TextAlignment.VerticalBottom) {
13095 				if(y2 <= 0)
13096 					return;
13097 				auto h = y2 - y;
13098 				if(h > textHeight) {
13099 					cy += h - textHeight;
13100 					cy -= lineHeight / 2;
13101 				}
13102 			} else if(alignment & TextAlignment.VerticalCenter) {
13103 				if(y2 <= 0)
13104 					return;
13105 				auto h = y2 - y;
13106 				if(textHeight < h) {
13107 					cy += (h - textHeight) / 2;
13108 					//cy -= lineHeight / 4;
13109 				}
13110 			}
13111 
13112 			foreach(line; text.split('\n')) {
13113 				int textWidth = this.textWidth(line);
13114 
13115 				int px = x, py = cy;
13116 
13117 				if(alignment & TextAlignment.Center) {
13118 					if(x2 <= 0)
13119 						return;
13120 					auto w = x2 - x;
13121 					if(w > textWidth)
13122 						px += (w - textWidth) / 2;
13123 				} else if(alignment & TextAlignment.Right) {
13124 					if(x2 <= 0)
13125 						return;
13126 					auto pos = x2 - textWidth;
13127 					if(pos > x)
13128 						px = pos;
13129 				}
13130 
13131 				version(with_xft)
13132 				if(xftFont) {
13133 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13134 
13135 					goto carry_on;
13136 				}
13137 
13138 				if(fontset)
13139 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13140 				else
13141 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13142 				carry_on:
13143 				cy += lineHeight + 4;
13144 			}
13145 		}
13146 
13147 		void drawPixel(int x, int y) {
13148 			XDrawPoint(display, d, gc, x, y);
13149 		}
13150 
13151 		// The basic shapes, outlined
13152 
13153 		void drawLine(int x1, int y1, int x2, int y2) {
13154 			if(foregroundIsNotTransparent)
13155 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13156 		}
13157 
13158 		void drawRectangle(int x, int y, int width, int height) {
13159 			if(backgroundIsNotTransparent) {
13160 				swapColors();
13161 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13162 				swapColors();
13163 			}
13164 			// 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
13165 			if(foregroundIsNotTransparent)
13166 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13167 		}
13168 
13169 		/// Arguments are the points of the bounding rectangle
13170 		void drawEllipse(int x1, int y1, int x2, int y2) {
13171 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13172 		}
13173 
13174 		// NOTE: start and finish are in units of degrees * 64
13175 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
13176 			if(backgroundIsNotTransparent) {
13177 				swapColors();
13178 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
13179 				swapColors();
13180 			}
13181 			if(foregroundIsNotTransparent) {
13182 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
13183 
13184 				// Windows draws the straight lines on the edges too so FIXME sort of
13185 			}
13186 		}
13187 
13188 		void drawPolygon(Point[] vertexes) {
13189 			XPoint[16] pointsBuffer;
13190 			XPoint[] points;
13191 			if(vertexes.length <= pointsBuffer.length)
13192 				points = pointsBuffer[0 .. vertexes.length];
13193 			else
13194 				points.length = vertexes.length;
13195 
13196 			foreach(i, p; vertexes) {
13197 				points[i].x = cast(short) p.x;
13198 				points[i].y = cast(short) p.y;
13199 			}
13200 
13201 			if(backgroundIsNotTransparent) {
13202 				swapColors();
13203 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13204 				swapColors();
13205 			}
13206 			if(foregroundIsNotTransparent) {
13207 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13208 			}
13209 		}
13210 	}
13211 
13212 	/* XRender { */
13213 
13214 	struct XRenderColor {
13215 		ushort red;
13216 		ushort green;
13217 		ushort blue;
13218 		ushort alpha;
13219 	}
13220 
13221 	alias Picture = XID;
13222 	alias PictFormat = XID;
13223 
13224 	struct XGlyphInfo {
13225 		ushort width;
13226 		ushort height;
13227 		short x;
13228 		short y;
13229 		short xOff;
13230 		short yOff;
13231 	}
13232 
13233 struct XRenderDirectFormat {
13234     short   red;
13235     short   redMask;
13236     short   green;
13237     short   greenMask;
13238     short   blue;
13239     short   blueMask;
13240     short   alpha;
13241     short   alphaMask;
13242 }
13243 
13244 struct XRenderPictFormat {
13245     PictFormat		id;
13246     int			type;
13247     int			depth;
13248     XRenderDirectFormat	direct;
13249     Colormap		colormap;
13250 }
13251 
13252 enum PictFormatID	=   (1 << 0);
13253 enum PictFormatType	=   (1 << 1);
13254 enum PictFormatDepth	=   (1 << 2);
13255 enum PictFormatRed	=   (1 << 3);
13256 enum PictFormatRedMask  =(1 << 4);
13257 enum PictFormatGreen	=   (1 << 5);
13258 enum PictFormatGreenMask=(1 << 6);
13259 enum PictFormatBlue	=   (1 << 7);
13260 enum PictFormatBlueMask =(1 << 8);
13261 enum PictFormatAlpha	=   (1 << 9);
13262 enum PictFormatAlphaMask=(1 << 10);
13263 enum PictFormatColormap =(1 << 11);
13264 
13265 struct XRenderPictureAttributes {
13266 	int 		repeat;
13267 	Picture		alpha_map;
13268 	int			alpha_x_origin;
13269 	int			alpha_y_origin;
13270 	int			clip_x_origin;
13271 	int			clip_y_origin;
13272 	Pixmap		clip_mask;
13273 	Bool		graphics_exposures;
13274 	int			subwindow_mode;
13275 	int			poly_edge;
13276 	int			poly_mode;
13277 	Atom		dither;
13278 	Bool		component_alpha;
13279 }
13280 
13281 alias int XFixed;
13282 
13283 struct XPointFixed {
13284     XFixed  x, y;
13285 }
13286 
13287 struct XCircle {
13288     XFixed x;
13289     XFixed y;
13290     XFixed radius;
13291 }
13292 
13293 struct XTransform {
13294     XFixed[3][3]  matrix;
13295 }
13296 
13297 struct XFilters {
13298     int	    nfilter;
13299     char    **filter;
13300     int	    nalias;
13301     short   *alias_;
13302 }
13303 
13304 struct XIndexValue {
13305     c_ulong    pixel;
13306     ushort   red, green, blue, alpha;
13307 }
13308 
13309 struct XAnimCursor {
13310     Cursor	    cursor;
13311     c_ulong   delay;
13312 }
13313 
13314 struct XLinearGradient {
13315     XPointFixed p1;
13316     XPointFixed p2;
13317 }
13318 
13319 struct XRadialGradient {
13320     XCircle inner;
13321     XCircle outer;
13322 }
13323 
13324 struct XConicalGradient {
13325     XPointFixed center;
13326     XFixed angle; /* in degrees */
13327 }
13328 
13329 enum PictStandardARGB32  = 0;
13330 enum PictStandardRGB24   = 1;
13331 enum PictStandardA8	 =  2;
13332 enum PictStandardA4	 =  3;
13333 enum PictStandardA1	 =  4;
13334 enum PictStandardNUM	 =  5;
13335 
13336 interface XRender {
13337 extern(C) @nogc:
13338 
13339 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13340 
13341 	Status XRenderQueryVersion (Display *dpy,
13342 			int     *major_versionp,
13343 			int     *minor_versionp);
13344 
13345 	Status XRenderQueryFormats (Display *dpy);
13346 
13347 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13348 
13349 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13350 
13351 	XRenderPictFormat *
13352 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13353 
13354 	XRenderPictFormat *
13355 		XRenderFindFormat (Display			*dpy,
13356 				c_ulong		mask,
13357 				const XRenderPictFormat	*templ,
13358 				int				count);
13359 	XRenderPictFormat *
13360 		XRenderFindStandardFormat (Display		*dpy,
13361 				int			format);
13362 
13363 	XIndexValue *
13364 		XRenderQueryPictIndexValues(Display			*dpy,
13365 				const XRenderPictFormat	*format,
13366 				int				*num);
13367 
13368 	Picture XRenderCreatePicture(
13369 		Display *dpy,
13370 		Drawable drawable,
13371 		const XRenderPictFormat *format,
13372 		c_ulong valuemask,
13373 		const XRenderPictureAttributes *attributes);
13374 
13375 	void XRenderChangePicture (Display				*dpy,
13376 				Picture				picture,
13377 				c_ulong			valuemask,
13378 				const XRenderPictureAttributes  *attributes);
13379 
13380 	void
13381 		XRenderSetPictureClipRectangles (Display	    *dpy,
13382 				Picture	    picture,
13383 				int		    xOrigin,
13384 				int		    yOrigin,
13385 				const XRectangle *rects,
13386 				int		    n);
13387 
13388 	void
13389 		XRenderSetPictureClipRegion (Display	    *dpy,
13390 				Picture	    picture,
13391 				Region	    r);
13392 
13393 	void
13394 		XRenderSetPictureTransform (Display	    *dpy,
13395 				Picture	    picture,
13396 				XTransform	    *transform);
13397 
13398 	void
13399 		XRenderFreePicture (Display                   *dpy,
13400 				Picture                   picture);
13401 
13402 	void
13403 		XRenderComposite (Display   *dpy,
13404 				int	    op,
13405 				Picture   src,
13406 				Picture   mask,
13407 				Picture   dst,
13408 				int	    src_x,
13409 				int	    src_y,
13410 				int	    mask_x,
13411 				int	    mask_y,
13412 				int	    dst_x,
13413 				int	    dst_y,
13414 				uint	width,
13415 				uint	height);
13416 
13417 
13418 	Picture XRenderCreateSolidFill (Display *dpy,
13419 			const XRenderColor *color);
13420 
13421 	Picture XRenderCreateLinearGradient (Display *dpy,
13422 			const XLinearGradient *gradient,
13423 			const XFixed *stops,
13424 			const XRenderColor *colors,
13425 			int nstops);
13426 
13427 	Picture XRenderCreateRadialGradient (Display *dpy,
13428 			const XRadialGradient *gradient,
13429 			const XFixed *stops,
13430 			const XRenderColor *colors,
13431 			int nstops);
13432 
13433 	Picture XRenderCreateConicalGradient (Display *dpy,
13434 			const XConicalGradient *gradient,
13435 			const XFixed *stops,
13436 			const XRenderColor *colors,
13437 			int nstops);
13438 
13439 
13440 
13441 	Cursor
13442 		XRenderCreateCursor (Display	    *dpy,
13443 				Picture	    source,
13444 				uint   x,
13445 				uint   y);
13446 
13447 	XFilters *
13448 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13449 
13450 	void
13451 		XRenderSetPictureFilter (Display    *dpy,
13452 				Picture    picture,
13453 				const char *filter,
13454 				XFixed	    *params,
13455 				int	    nparams);
13456 
13457 	Cursor
13458 		XRenderCreateAnimCursor (Display	*dpy,
13459 				int		ncursor,
13460 				XAnimCursor	*cursors);
13461 }
13462 
13463 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13464 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13465 
13466 	/* XRender } */
13467 
13468 	/* Xrandr { */
13469 
13470 struct XRRMonitorInfo {
13471     Atom name;
13472     Bool primary;
13473     Bool automatic;
13474     int noutput;
13475     int x;
13476     int y;
13477     int width;
13478     int height;
13479     int mwidth;
13480     int mheight;
13481     /*RROutput*/ void *outputs;
13482 }
13483 
13484 struct XRRScreenChangeNotifyEvent {
13485     int type;                   /* event base */
13486     c_ulong serial;       /* # of last request processed by server */
13487     Bool send_event;            /* true if this came from a SendEvent request */
13488     Display *display;           /* Display the event was read from */
13489     Window window;              /* window which selected for this event */
13490     Window root;                /* Root window for changed screen */
13491     Time timestamp;             /* when the screen change occurred */
13492     Time config_timestamp;      /* when the last configuration change */
13493     ushort/*SizeID*/ size_index;
13494     ushort/*SubpixelOrder*/ subpixel_order;
13495     ushort/*Rotation*/ rotation;
13496     int width;
13497     int height;
13498     int mwidth;
13499     int mheight;
13500 }
13501 
13502 enum RRScreenChangeNotify = 0;
13503 
13504 enum RRScreenChangeNotifyMask = 1;
13505 
13506 __gshared int xrrEventBase = -1;
13507 
13508 
13509 interface XRandr {
13510 extern(C) @nogc:
13511 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13512 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13513 
13514 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13515 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13516 
13517 	void XRRSelectInput(Display *dpy, Window window, int mask);
13518 }
13519 
13520 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13521 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13522 	/* Xrandr } */
13523 
13524 	/* Xft { */
13525 
13526 	// actually freetype
13527 	alias void FT_Face;
13528 
13529 	// actually fontconfig
13530 	private alias FcBool = int;
13531 	alias void FcCharSet;
13532 	alias void FcPattern;
13533 	alias void FcResult;
13534 	enum FcEndian { FcEndianBig, FcEndianLittle }
13535 	struct FcFontSet {
13536 		int nfont;
13537 		int sfont;
13538 		FcPattern** fonts;
13539 	}
13540 
13541 	// actually XRegion
13542 	struct BOX {
13543 		short x1, x2, y1, y2;
13544 	}
13545 	struct _XRegion {
13546 		c_long size;
13547 		c_long numRects;
13548 		BOX* rects;
13549 		BOX extents;
13550 	}
13551 
13552 	alias Region = _XRegion*;
13553 
13554 	// ok actually Xft
13555 
13556 	struct XftFontInfo;
13557 
13558 	struct XftFont {
13559 		int         ascent;
13560 		int         descent;
13561 		int         height;
13562 		int         max_advance_width;
13563 		FcCharSet*  charset;
13564 		FcPattern*  pattern;
13565 	}
13566 
13567 	struct XftDraw;
13568 
13569 	struct XftColor {
13570 		c_ulong pixel;
13571 		XRenderColor color;
13572 	}
13573 
13574 	struct XftCharSpec {
13575 		dchar           ucs4;
13576 		short           x;
13577 		short           y;
13578 	}
13579 
13580 	struct XftCharFontSpec {
13581 		XftFont         *font;
13582 		dchar           ucs4;
13583 		short           x;
13584 		short           y;
13585 	}
13586 
13587 	struct XftGlyphSpec {
13588 		uint            glyph;
13589 		short           x;
13590 		short           y;
13591 	}
13592 
13593 	struct XftGlyphFontSpec {
13594 		XftFont         *font;
13595 		uint            glyph;
13596 		short           x;
13597 		short           y;
13598 	}
13599 
13600 	interface Xft {
13601 	extern(C) @nogc pure:
13602 
13603 	Bool XftColorAllocName (Display  *dpy,
13604 				const Visual   *visual,
13605 				Colormap cmap,
13606 				const char     *name,
13607 				XftColor *result);
13608 
13609 	Bool XftColorAllocValue (Display         *dpy,
13610 				Visual          *visual,
13611 				Colormap        cmap,
13612 				const XRenderColor    *color,
13613 				XftColor        *result);
13614 
13615 	void XftColorFree (Display   *dpy,
13616 				Visual    *visual,
13617 				Colormap  cmap,
13618 				XftColor  *color);
13619 
13620 	Bool XftDefaultHasRender (Display *dpy);
13621 
13622 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13623 
13624 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13625 
13626 	XftDraw * XftDrawCreate (Display   *dpy,
13627 		       Drawable  drawable,
13628 		       Visual    *visual,
13629 		       Colormap  colormap);
13630 
13631 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13632 			     Pixmap   bitmap);
13633 
13634 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13635 			    Pixmap  pixmap,
13636 			    int     depth);
13637 
13638 	void XftDrawChange (XftDraw  *draw,
13639 		       Drawable drawable);
13640 
13641 	Display * XftDrawDisplay (XftDraw *draw);
13642 
13643 	Drawable XftDrawDrawable (XftDraw *draw);
13644 
13645 	Colormap XftDrawColormap (XftDraw *draw);
13646 
13647 	Visual * XftDrawVisual (XftDraw *draw);
13648 
13649 	void XftDrawDestroy (XftDraw *draw);
13650 
13651 	Picture XftDrawPicture (XftDraw *draw);
13652 
13653 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13654 
13655 	void XftDrawGlyphs (XftDraw          *draw,
13656 				const XftColor *color,
13657 				XftFont          *pub,
13658 				int              x,
13659 				int              y,
13660 				const uint  *glyphs,
13661 				int              nglyphs);
13662 
13663 	void XftDrawString8 (XftDraw             *draw,
13664 				const XftColor    *color,
13665 				XftFont             *pub,
13666 				int                 x,
13667 				int                 y,
13668 				const char     *string,
13669 				int                 len);
13670 
13671 	void XftDrawString16 (XftDraw            *draw,
13672 				const XftColor   *color,
13673 				XftFont            *pub,
13674 				int                x,
13675 				int                y,
13676 				const wchar   *string,
13677 				int                len);
13678 
13679 	void XftDrawString32 (XftDraw            *draw,
13680 				const XftColor   *color,
13681 				XftFont            *pub,
13682 				int                x,
13683 				int                y,
13684 				const dchar   *string,
13685 				int                len);
13686 
13687 	void XftDrawStringUtf8 (XftDraw          *draw,
13688 				const XftColor *color,
13689 				XftFont          *pub,
13690 				int              x,
13691 				int              y,
13692 				const char  *string,
13693 				int              len);
13694 	void XftDrawStringUtf16 (XftDraw             *draw,
13695 				const XftColor    *color,
13696 				XftFont             *pub,
13697 				int                 x,
13698 				int                 y,
13699 				const char     *string,
13700 				FcEndian            endian,
13701 				int                 len);
13702 
13703 	void XftDrawCharSpec (XftDraw                *draw,
13704 				const XftColor       *color,
13705 				XftFont                *pub,
13706 				const XftCharSpec    *chars,
13707 				int                    len);
13708 
13709 	void XftDrawCharFontSpec (XftDraw                    *draw,
13710 				const XftColor           *color,
13711 				const XftCharFontSpec    *chars,
13712 				int                        len);
13713 
13714 	void XftDrawGlyphSpec (XftDraw               *draw,
13715 				const XftColor      *color,
13716 				XftFont               *pub,
13717 				const XftGlyphSpec  *glyphs,
13718 				int                   len);
13719 
13720 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13721 				const XftColor          *color,
13722 				const XftGlyphFontSpec  *glyphs,
13723 				int                       len);
13724 
13725 	void XftDrawRect (XftDraw            *draw,
13726 				const XftColor   *color,
13727 				int                x,
13728 				int                y,
13729 				uint       width,
13730 				uint       height);
13731 
13732 	Bool XftDrawSetClip (XftDraw     *draw,
13733 				Region      r);
13734 
13735 
13736 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13737 				int                   xOrigin,
13738 				int                   yOrigin,
13739 				const XRectangle    *rects,
13740 				int                   n);
13741 
13742 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13743 				int        mode);
13744 
13745 	void XftGlyphExtents (Display            *dpy,
13746 				XftFont            *pub,
13747 				const uint    *glyphs,
13748 				int                nglyphs,
13749 				XGlyphInfo         *extents);
13750 
13751 	void XftTextExtents8 (Display            *dpy,
13752 				XftFont            *pub,
13753 				const char    *string,
13754 				int                len,
13755 				XGlyphInfo         *extents);
13756 
13757 	void XftTextExtents16 (Display           *dpy,
13758 				XftFont           *pub,
13759 				const wchar  *string,
13760 				int               len,
13761 				XGlyphInfo        *extents);
13762 
13763 	void XftTextExtents32 (Display           *dpy,
13764 				XftFont           *pub,
13765 				const dchar  *string,
13766 				int               len,
13767 				XGlyphInfo        *extents);
13768 
13769 	void XftTextExtentsUtf8 (Display         *dpy,
13770 				XftFont         *pub,
13771 				const char *string,
13772 				int             len,
13773 				XGlyphInfo      *extents);
13774 
13775 	void XftTextExtentsUtf16 (Display            *dpy,
13776 				XftFont            *pub,
13777 				const char    *string,
13778 				FcEndian           endian,
13779 				int                len,
13780 				XGlyphInfo         *extents);
13781 
13782 	FcPattern * XftFontMatch (Display           *dpy,
13783 				int               screen,
13784 				const FcPattern *pattern,
13785 				FcResult          *result);
13786 
13787 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13788 
13789 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13790 
13791 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13792 
13793 	FT_Face XftLockFace (XftFont *pub);
13794 
13795 	void XftUnlockFace (XftFont *pub);
13796 
13797 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13798 
13799 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13800 
13801 	dchar XftFontInfoHash (const XftFontInfo *fi);
13802 
13803 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13804 
13805 	XftFont * XftFontOpenInfo (Display        *dpy,
13806 				FcPattern      *pattern,
13807 				XftFontInfo    *fi);
13808 
13809 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13810 
13811 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13812 
13813 	void XftFontClose (Display *dpy, XftFont *pub);
13814 
13815 	FcBool XftInitFtLibrary();
13816 	void XftFontLoadGlyphs (Display          *dpy,
13817 				XftFont          *pub,
13818 				FcBool           need_bitmaps,
13819 				const uint  *glyphs,
13820 				int              nglyph);
13821 
13822 	void XftFontUnloadGlyphs (Display            *dpy,
13823 				XftFont            *pub,
13824 				const uint    *glyphs,
13825 				int                nglyph);
13826 
13827 	FcBool XftFontCheckGlyph (Display  *dpy,
13828 				XftFont  *pub,
13829 				FcBool   need_bitmaps,
13830 				uint  glyph,
13831 				uint  *missing,
13832 				int      *nmissing);
13833 
13834 	FcBool XftCharExists (Display      *dpy,
13835 				XftFont      *pub,
13836 				dchar    ucs4);
13837 
13838 	uint XftCharIndex (Display       *dpy,
13839 				XftFont       *pub,
13840 				dchar      ucs4);
13841 	FcBool XftInit (const char *config);
13842 
13843 	int XftGetVersion ();
13844 
13845 	FcFontSet * XftListFonts (Display   *dpy,
13846 				int       screen,
13847 				...);
13848 
13849 	FcPattern *XftNameParse (const char *name);
13850 
13851 	void XftGlyphRender (Display         *dpy,
13852 				int             op,
13853 				Picture         src,
13854 				XftFont         *pub,
13855 				Picture         dst,
13856 				int             srcx,
13857 				int             srcy,
13858 				int             x,
13859 				int             y,
13860 				const uint *glyphs,
13861 				int             nglyphs);
13862 
13863 	void XftGlyphSpecRender (Display                 *dpy,
13864 				int                     op,
13865 				Picture                 src,
13866 				XftFont                 *pub,
13867 				Picture                 dst,
13868 				int                     srcx,
13869 				int                     srcy,
13870 				const XftGlyphSpec    *glyphs,
13871 				int                     nglyphs);
13872 
13873 	void XftCharSpecRender (Display              *dpy,
13874 				int                  op,
13875 				Picture              src,
13876 				XftFont              *pub,
13877 				Picture              dst,
13878 				int                  srcx,
13879 				int                  srcy,
13880 				const XftCharSpec  *chars,
13881 				int                  len);
13882 	void XftGlyphFontSpecRender (Display                     *dpy,
13883 				int                         op,
13884 				Picture                     src,
13885 				Picture                     dst,
13886 				int                         srcx,
13887 				int                         srcy,
13888 				const XftGlyphFontSpec    *glyphs,
13889 				int                         nglyphs);
13890 
13891 	void XftCharFontSpecRender (Display                  *dpy,
13892 				int                      op,
13893 				Picture                  src,
13894 				Picture                  dst,
13895 				int                      srcx,
13896 				int                      srcy,
13897 				const XftCharFontSpec  *chars,
13898 				int                      len);
13899 
13900 	void XftTextRender8 (Display         *dpy,
13901 				int             op,
13902 				Picture         src,
13903 				XftFont         *pub,
13904 				Picture         dst,
13905 				int             srcx,
13906 				int             srcy,
13907 				int             x,
13908 				int             y,
13909 				const char *string,
13910 				int             len);
13911 	void XftTextRender16 (Display            *dpy,
13912 				int                op,
13913 				Picture            src,
13914 				XftFont            *pub,
13915 				Picture            dst,
13916 				int                srcx,
13917 				int                srcy,
13918 				int                x,
13919 				int                y,
13920 				const wchar   *string,
13921 				int                len);
13922 
13923 	void XftTextRender16BE (Display          *dpy,
13924 				int              op,
13925 				Picture          src,
13926 				XftFont          *pub,
13927 				Picture          dst,
13928 				int              srcx,
13929 				int              srcy,
13930 				int              x,
13931 				int              y,
13932 				const char  *string,
13933 				int              len);
13934 
13935 	void XftTextRender16LE (Display          *dpy,
13936 				int              op,
13937 				Picture          src,
13938 				XftFont          *pub,
13939 				Picture          dst,
13940 				int              srcx,
13941 				int              srcy,
13942 				int              x,
13943 				int              y,
13944 				const char  *string,
13945 				int              len);
13946 
13947 	void XftTextRender32 (Display            *dpy,
13948 				int                op,
13949 				Picture            src,
13950 				XftFont            *pub,
13951 				Picture            dst,
13952 				int                srcx,
13953 				int                srcy,
13954 				int                x,
13955 				int                y,
13956 				const dchar   *string,
13957 				int                len);
13958 
13959 	void XftTextRender32BE (Display          *dpy,
13960 				int              op,
13961 				Picture          src,
13962 				XftFont          *pub,
13963 				Picture          dst,
13964 				int              srcx,
13965 				int              srcy,
13966 				int              x,
13967 				int              y,
13968 				const char  *string,
13969 				int              len);
13970 
13971 	void XftTextRender32LE (Display          *dpy,
13972 				int              op,
13973 				Picture          src,
13974 				XftFont          *pub,
13975 				Picture          dst,
13976 				int              srcx,
13977 				int              srcy,
13978 				int              x,
13979 				int              y,
13980 				const char  *string,
13981 				int              len);
13982 
13983 	void XftTextRenderUtf8 (Display          *dpy,
13984 				int              op,
13985 				Picture          src,
13986 				XftFont          *pub,
13987 				Picture          dst,
13988 				int              srcx,
13989 				int              srcy,
13990 				int              x,
13991 				int              y,
13992 				const char  *string,
13993 				int              len);
13994 
13995 	void XftTextRenderUtf16 (Display         *dpy,
13996 				int             op,
13997 				Picture         src,
13998 				XftFont         *pub,
13999 				Picture         dst,
14000 				int             srcx,
14001 				int             srcy,
14002 				int             x,
14003 				int             y,
14004 				const char *string,
14005 				FcEndian        endian,
14006 				int             len);
14007 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
14008 
14009 	}
14010 
14011 	interface FontConfig {
14012 	extern(C) @nogc pure:
14013 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
14014 		void FcFontSetDestroy(FcFontSet*);
14015 		char* FcNameUnparse(const FcPattern *);
14016 	}
14017 
14018 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
14019 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
14020 
14021 
14022 	/* Xft } */
14023 
14024 	class XDisconnectException : Exception {
14025 		bool userRequested;
14026 		this(bool userRequested = true) {
14027 			this.userRequested = userRequested;
14028 			super("X disconnected");
14029 		}
14030 	}
14031 
14032 	/++
14033 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14034 
14035 		Please note that it returns
14036 	+/
14037 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14038 
14039 		static XErrorEvent[] errorBuffer;
14040 
14041 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14042 			errorBuffer ~= *evt;
14043 			return 0;
14044 		}
14045 
14046 		auto savedErrorHandler = XSetErrorHandler(&handler);
14047 
14048 		try {
14049 			dg();
14050 		} finally {
14051 			XSync(XDisplayConnection.get, 0/*False*/);
14052 			XSetErrorHandler(savedErrorHandler);
14053 		}
14054 
14055 		auto bfr = errorBuffer;
14056 		errorBuffer = null;
14057 
14058 		return bfr;
14059 	}
14060 
14061 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14062 	class XDisplayConnection {
14063 		private __gshared Display* display;
14064 		private __gshared XIM xim;
14065 		private __gshared char* displayName;
14066 
14067 		private __gshared int connectionSequence_;
14068 		private __gshared bool isLocal_;
14069 
14070 		/// use this for lazy caching when reconnection
14071 		static int connectionSequenceNumber() { return connectionSequence_; }
14072 
14073 		/++
14074 			Guesses if the connection appears to be local.
14075 
14076 			History:
14077 				Added June 3, 2021
14078 		+/
14079 		static @property bool isLocal() nothrow @trusted @nogc {
14080 			return isLocal_;
14081 		}
14082 
14083 		/// Attempts recreation of state, may require application assistance
14084 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14085 		/// then call this, and if successful, reenter the loop.
14086 		static void discardAndRecreate(string newDisplayString = null) {
14087 			if(insideXEventLoop)
14088 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14089 
14090 			// 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
14091 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14092 
14093 			foreach(handle; chnenhm) {
14094 				handle.discardConnectionState();
14095 			}
14096 
14097 			discardState();
14098 
14099 			if(newDisplayString !is null)
14100 				setDisplayName(newDisplayString);
14101 
14102 			auto display = get();
14103 
14104 			foreach(handle; chnenhm) {
14105 				handle.recreateAfterDisconnect();
14106 			}
14107 		}
14108 
14109 		private __gshared EventMask rootEventMask;
14110 
14111 		/++
14112 			Requests the specified input from the root window on the connection, in addition to any other request.
14113 
14114 
14115 			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.
14116 
14117 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14118 		+/
14119 		static void addRootInput(EventMask mask) {
14120 			auto old = rootEventMask;
14121 			rootEventMask |= mask;
14122 			get(); // to ensure display connected
14123 			if(display !is null && rootEventMask != old)
14124 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14125 		}
14126 
14127 		static void discardState() {
14128 			freeImages();
14129 
14130 			foreach(atomPtr; interredAtoms)
14131 				*atomPtr = 0;
14132 			interredAtoms = null;
14133 			interredAtoms.assumeSafeAppend();
14134 
14135 			ScreenPainterImplementation.fontAttempted = false;
14136 			ScreenPainterImplementation.defaultfont = null;
14137 			ScreenPainterImplementation.defaultfontset = null;
14138 
14139 			Image.impl.xshmQueryCompleted = false;
14140 			Image.impl._xshmAvailable = false;
14141 
14142 			SimpleWindow.nativeMapping = null;
14143 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14144 			// GlobalHotkeyManager
14145 
14146 			display = null;
14147 			xim = null;
14148 		}
14149 
14150 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14151 		private static void createXIM () {
14152 			import core.stdc.locale : setlocale, LC_ALL;
14153 			import core.stdc.stdio : stderr, fprintf;
14154 			import core.stdc.stdlib : free;
14155 			import core.stdc.string : strdup;
14156 
14157 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14158 
14159 			auto olocale = strdup(setlocale(LC_ALL, null));
14160 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14161 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14162 
14163 			//fprintf(stderr, "opening IM...\n");
14164 			foreach (string s; mtry) {
14165 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14166 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14167 			}
14168 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14169 		}
14170 
14171 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14172 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14173 		static struct ImgList {
14174 			size_t img; // class; hide it from GC
14175 			ImgList* next;
14176 		}
14177 
14178 		static __gshared ImgList* imglist = null;
14179 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14180 
14181 		static void registerImage (Image img) {
14182 			if (!imglistLocked && img !is null) {
14183 				import core.stdc.stdlib : malloc;
14184 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14185 				assert(it !is null); // do proper checks
14186 				it.img = cast(size_t)cast(void*)img;
14187 				it.next = imglist;
14188 				imglist = it;
14189 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14190 			}
14191 		}
14192 
14193 		static void unregisterImage (Image img) {
14194 			if (!imglistLocked && img !is null) {
14195 				import core.stdc.stdlib : free;
14196 				ImgList* prev = null;
14197 				ImgList* cur = imglist;
14198 				while (cur !is null) {
14199 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14200 					prev = cur;
14201 					cur = cur.next;
14202 				}
14203 				if (cur !is null) {
14204 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14205 					free(cur);
14206 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14207 				} else {
14208 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14209 				}
14210 			}
14211 		}
14212 
14213 		static void freeImages () { // needed for discardAndRecreate
14214 			imglistLocked = true;
14215 			scope(exit) imglistLocked = false;
14216 			ImgList* cur = imglist;
14217 			ImgList* next = null;
14218 			while (cur !is null) {
14219 				import core.stdc.stdlib : free;
14220 				next = cur.next;
14221 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14222 				(cast(Image)cast(void*)cur.img).dispose();
14223 				free(cur);
14224 				cur = next;
14225 			}
14226 			imglist = null;
14227 		}
14228 
14229 		/// can be used to override normal handling of display name
14230 		/// from environment and/or command line
14231 		static setDisplayName(string newDisplayName) {
14232 			displayName = cast(char*) (newDisplayName ~ '\0');
14233 		}
14234 
14235 		/// resets to the default display string
14236 		static resetDisplayName() {
14237 			displayName = null;
14238 		}
14239 
14240 		///
14241 		static Display* get() {
14242 			if(display is null) {
14243 				if(!librariesSuccessfullyLoaded)
14244 					throw new Exception("Unable to load X11 client libraries");
14245 				display = XOpenDisplay(displayName);
14246 
14247 				isLocal_ = false;
14248 
14249 				connectionSequence_++;
14250 				if(display is null)
14251 					throw new Exception("Unable to open X display");
14252 
14253 				auto str = display.display_name;
14254 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14255 				// and otherwise it probably isn't
14256 				if(str is null || (str[0] != ':' && str[0] != '/'))
14257 					isLocal_ = false;
14258 				else
14259 					isLocal_ = true;
14260 
14261 				debug(sdpy_x_errors) {
14262 					XSetErrorHandler(&adrlogger);
14263 					XSynchronize(display, true);
14264 
14265 					extern(C) int wtf() {
14266 						if(errorHappened) {
14267 							asm { int 3; }
14268 							errorHappened = false;
14269 						}
14270 						return 0;
14271 					}
14272 					XSetAfterFunction(display, &wtf);
14273 				}
14274 
14275 
14276 				XSetIOErrorHandler(&x11ioerrCB);
14277 				Bool sup;
14278 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14279 				createXIM();
14280 				version(with_eventloop) {
14281 					import arsd.eventloop;
14282 					addFileEventListeners(display.fd, &eventListener, null, null);
14283 				}
14284 			}
14285 
14286 			return display;
14287 		}
14288 
14289 		extern(C)
14290 		static int x11ioerrCB(Display* dpy) {
14291 			throw new XDisconnectException(false);
14292 		}
14293 
14294 		version(with_eventloop) {
14295 			import arsd.eventloop;
14296 			static void eventListener(OsFileHandle fd) {
14297 				//this.mtLock();
14298 				//scope(exit) this.mtUnlock();
14299 				while(XPending(display))
14300 					doXNextEvent(display);
14301 			}
14302 		}
14303 
14304 		// close connection on program exit -- we need this to properly free all images
14305 		static ~this () {
14306 			// the gui thread must clean up after itself or else Xlib might deadlock
14307 			// using this flag on any thread destruction is the easiest way i know of
14308 			// (shared static this is run by the LAST thread to exit, which may not be
14309 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14310 			if(thisIsGuiThread)
14311 				close();
14312 		}
14313 
14314 		///
14315 		static void close() {
14316 			if(display is null)
14317 				return;
14318 
14319 			version(with_eventloop) {
14320 				import arsd.eventloop;
14321 				removeFileEventListeners(display.fd);
14322 			}
14323 
14324 			// now remove all registered images to prevent shared memory leaks
14325 			freeImages();
14326 
14327 			// tbh I don't know why it is doing this but like if this happens to run
14328 			// from the other thread there's frequent hanging inside here.
14329 			if(thisIsGuiThread)
14330 				XCloseDisplay(display);
14331 			display = null;
14332 		}
14333 	}
14334 
14335 	mixin template NativeImageImplementation() {
14336 		XImage* handle;
14337 		ubyte* rawData;
14338 
14339 		XShmSegmentInfo shminfo;
14340 		bool premultiply = true;
14341 
14342 		__gshared bool xshmQueryCompleted;
14343 		__gshared bool _xshmAvailable;
14344 		public static @property bool xshmAvailable() {
14345 			if(!xshmQueryCompleted) {
14346 				int i1, i2, i3;
14347 				xshmQueryCompleted = true;
14348 
14349 				if(!XDisplayConnection.isLocal)
14350 					_xshmAvailable = false;
14351 				else
14352 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14353 			}
14354 			return _xshmAvailable;
14355 		}
14356 
14357 		bool usingXshm;
14358 	final:
14359 
14360 		private __gshared bool xshmfailed;
14361 
14362 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14363 			auto display = XDisplayConnection.get();
14364 			assert(display !is null);
14365 			auto screen = DefaultScreen(display);
14366 
14367 			// it will only use shared memory for somewhat largish images,
14368 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14369 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14370 
14371 
14372 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14373 				// the actual use still fails. For example, if the program is in a container and permission denied
14374 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14375 				//
14376 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14377 
14378 
14379 				// synchronize so preexisting buffers are clear
14380 				XSync(display, false);
14381 				xshmfailed = false;
14382 
14383 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14384 
14385 
14386 				usingXshm = true;
14387 				handle = XShmCreateImage(
14388 					display,
14389 					DefaultVisual(display, screen),
14390 					enableAlpha ? 32: 24,
14391 					ImageFormat.ZPixmap,
14392 					null,
14393 					&shminfo,
14394 					width, height);
14395 				if(handle is null)
14396 					goto abortXshm1;
14397 
14398 				if(handle.bytes_per_line != 4 * width)
14399 					goto abortXshm2;
14400 
14401 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14402 				if(shminfo.shmid < 0)
14403 					goto abortXshm3;
14404 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14405 				if(rawData == cast(ubyte*) -1)
14406 					goto abortXshm4;
14407 				shminfo.readOnly = 0;
14408 				XShmAttach(display, &shminfo);
14409 
14410 				// and now to the final error check to ensure it actually worked.
14411 				XSync(display, false);
14412 				if(xshmfailed)
14413 					goto abortXshm5;
14414 
14415 				XSetErrorHandler(oldErrorHandler);
14416 
14417 				XDisplayConnection.registerImage(this);
14418 				// if I don't flush here there's a chance the dtor will run before the
14419 				// ctor and lead to a bad value X error. While this hurts the efficiency
14420 				// it is local anyway so prolly better to keep it simple
14421 				XFlush(display);
14422 
14423 				return;
14424 
14425 				abortXshm5:
14426 					shmdt(shminfo.shmaddr);
14427 					rawData = null;
14428 
14429 				abortXshm4:
14430 					shmctl(shminfo.shmid, IPC_RMID, null);
14431 
14432 				abortXshm3:
14433 					// nothing needed, the shmget failed so there's nothing to free
14434 
14435 				abortXshm2:
14436 					XDestroyImage(handle);
14437 					handle = null;
14438 
14439 				abortXshm1:
14440 					XSetErrorHandler(oldErrorHandler);
14441 					usingXshm = false;
14442 					handle = null;
14443 
14444 					shminfo = typeof(shminfo).init;
14445 
14446 					_xshmAvailable = false; // don't try again in the future
14447 
14448 					// writeln("fallingback");
14449 
14450 					goto fallback;
14451 
14452 			} else {
14453 				fallback:
14454 
14455 				if (forcexshm) throw new Exception("can't create XShm Image");
14456 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14457 				import core.stdc.stdlib : malloc;
14458 				rawData = cast(ubyte*) malloc(width * height * 4);
14459 
14460 				handle = XCreateImage(
14461 					display,
14462 					DefaultVisual(display, screen),
14463 					enableAlpha ? 32 : 24, // bpp
14464 					ImageFormat.ZPixmap,
14465 					0, // offset
14466 					rawData,
14467 					width, height,
14468 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14469 			}
14470 		}
14471 
14472 		void dispose() {
14473 			// note: this calls free(rawData) for us
14474 			if(handle) {
14475 				if (usingXshm) {
14476 					XDisplayConnection.unregisterImage(this);
14477 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14478 				}
14479 				XDestroyImage(handle);
14480 				if(usingXshm) {
14481 					shmdt(shminfo.shmaddr);
14482 					shmctl(shminfo.shmid, IPC_RMID, null);
14483 				}
14484 				handle = null;
14485 			}
14486 		}
14487 
14488 		Color getPixel(int x, int y) {
14489 			auto offset = (y * width + x) * 4;
14490 			Color c;
14491 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14492 			c.b = rawData[offset + 0];
14493 			c.g = rawData[offset + 1];
14494 			c.r = rawData[offset + 2];
14495 			if(enableAlpha && premultiply)
14496 				c.unPremultiply;
14497 			return c;
14498 		}
14499 
14500 		void setPixel(int x, int y, Color c) {
14501 			if(enableAlpha && premultiply)
14502 				c.premultiply();
14503 			auto offset = (y * width + x) * 4;
14504 			rawData[offset + 0] = c.b;
14505 			rawData[offset + 1] = c.g;
14506 			rawData[offset + 2] = c.r;
14507 			if(enableAlpha)
14508 				rawData[offset + 3] = c.a;
14509 		}
14510 
14511 		void convertToRgbaBytes(ubyte[] where) {
14512 			assert(where.length == this.width * this.height * 4);
14513 
14514 			// if rawData had a length....
14515 			//assert(rawData.length == where.length);
14516 			for(int idx = 0; idx < where.length; idx += 4) {
14517 				where[idx + 0] = rawData[idx + 2]; // r
14518 				where[idx + 1] = rawData[idx + 1]; // g
14519 				where[idx + 2] = rawData[idx + 0]; // b
14520 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14521 
14522 				if(enableAlpha && premultiply)
14523 					unPremultiplyRgba(where[idx .. idx + 4]);
14524 			}
14525 		}
14526 
14527 		void setFromRgbaBytes(in ubyte[] where) {
14528 			assert(where.length == this.width * this.height * 4);
14529 
14530 			// if rawData had a length....
14531 			//assert(rawData.length == where.length);
14532 			for(int idx = 0; idx < where.length; idx += 4) {
14533 				rawData[idx + 2] = where[idx + 0]; // r
14534 				rawData[idx + 1] = where[idx + 1]; // g
14535 				rawData[idx + 0] = where[idx + 2]; // b
14536 				if(enableAlpha) {
14537 					rawData[idx + 3] = where[idx + 3]; // a
14538 					if(premultiply)
14539 						premultiplyBgra(rawData[idx .. idx + 4]);
14540 				}
14541 			}
14542 		}
14543 
14544 	}
14545 
14546 	mixin template NativeSimpleWindowImplementation() {
14547 		GC gc;
14548 		Window window;
14549 		Display* display;
14550 
14551 		Pixmap buffer;
14552 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14553 		XIC xic; // input context
14554 		int curHidden = 0; // counter
14555 		Cursor blankCurPtr = 0;
14556 		int cursorSequenceNumber = 0;
14557 		int warpEventCount = 0; // number of mouse movement events to eat
14558 
14559 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14560 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14561 
14562 		version(without_opengl) {} else
14563 		GLXContext glc;
14564 
14565 		private void fixFixedSize(bool forced=false) (int width, int height) {
14566 			if (forced || this.resizability == Resizability.fixedSize) {
14567 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14568 				XSizeHints sh;
14569 				static if (!forced) {
14570 					c_long spr;
14571 					XGetWMNormalHints(display, window, &sh, &spr);
14572 					sh.flags |= PMaxSize | PMinSize;
14573 				} else {
14574 					sh.flags = PMaxSize | PMinSize;
14575 				}
14576 				sh.min_width = width;
14577 				sh.min_height = height;
14578 				sh.max_width = width;
14579 				sh.max_height = height;
14580 				XSetWMNormalHints(display, window, &sh);
14581 				//XFlush(display);
14582 			}
14583 		}
14584 
14585 		ScreenPainter getPainter(bool manualInvalidations) {
14586 			return ScreenPainter(this, window, manualInvalidations);
14587 		}
14588 
14589 		void move(int x, int y) {
14590 			XMoveWindow(display, window, x, y);
14591 		}
14592 
14593 		void resize(int w, int h) {
14594 			if (w < 1) w = 1;
14595 			if (h < 1) h = 1;
14596 			XResizeWindow(display, window, w, h);
14597 
14598 			// calling this now to avoid waiting for the server to
14599 			// acknowledge the resize; draws without returning to the
14600 			// event loop will thus actually work. the server's event
14601 			// btw might overrule this and resize it again
14602 			recordX11Resize(display, this, w, h);
14603 
14604 			updateOpenglViewportIfNeeded(w, h);
14605 		}
14606 
14607 		void moveResize (int x, int y, int w, int h) {
14608 			if (w < 1) w = 1;
14609 			if (h < 1) h = 1;
14610 			XMoveResizeWindow(display, window, x, y, w, h);
14611 			updateOpenglViewportIfNeeded(w, h);
14612 		}
14613 
14614 		void hideCursor () {
14615 			if (curHidden++ == 0) {
14616 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14617 					static const(char)[1] cmbmp = 0;
14618 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14619 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14620 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14621 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14622 					XFreePixmap(display, pm);
14623 				}
14624 				XDefineCursor(display, window, blankCurPtr);
14625 			}
14626 		}
14627 
14628 		void showCursor () {
14629 			if (--curHidden == 0) XUndefineCursor(display, window);
14630 		}
14631 
14632 		void warpMouse (int x, int y) {
14633 			// here i will send dummy "ignore next mouse motion" event,
14634 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14635 			// and we don't need to report it to the user (as warping is
14636 			// used when the user needs movement deltas).
14637 			//XClientMessageEvent xclient;
14638 			XEvent e;
14639 			e.xclient.type = EventType.ClientMessage;
14640 			e.xclient.window = window;
14641 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14642 			e.xclient.format = 32;
14643 			e.xclient.data.l[0] = 0;
14644 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14645 			//{ 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]); }
14646 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14647 			// now warp pointer...
14648 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14649 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14650 			// ...and flush
14651 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14652 			XFlush(display);
14653 		}
14654 
14655 		void sendDummyEvent () {
14656 			// here i will send dummy event to ping event queue
14657 			XEvent e;
14658 			e.xclient.type = EventType.ClientMessage;
14659 			e.xclient.window = window;
14660 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14661 			e.xclient.format = 32;
14662 			e.xclient.data.l[0] = 0;
14663 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14664 			XFlush(display);
14665 		}
14666 
14667 		void setTitle(string title) {
14668 			if (title.ptr is null) title = "";
14669 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14670 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14671 			XTextProperty windowName;
14672 			windowName.value = title.ptr;
14673 			windowName.encoding = XA_UTF8; //XA_STRING;
14674 			windowName.format = 8;
14675 			windowName.nitems = cast(uint)title.length;
14676 			XSetWMName(display, window, &windowName);
14677 			char[1024] namebuf = 0;
14678 			auto maxlen = namebuf.length-1;
14679 			if (maxlen > title.length) maxlen = title.length;
14680 			namebuf[0..maxlen] = title[0..maxlen];
14681 			XStoreName(display, window, namebuf.ptr);
14682 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14683 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14684 		}
14685 
14686 		string[] getTitles() {
14687 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14688 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14689 			XTextProperty textProp;
14690 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14691 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14692 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14693 				} else
14694 					return [];
14695 			} else
14696 				return null;
14697 		}
14698 
14699 		string getTitle() {
14700 			auto titles = getTitles();
14701 			return titles.length ? titles[0] : null;
14702 		}
14703 
14704 		void setMinSize (int minwidth, int minheight) {
14705 			import core.stdc.config : c_long;
14706 			if (minwidth < 1) minwidth = 1;
14707 			if (minheight < 1) minheight = 1;
14708 			XSizeHints sh;
14709 			c_long spr;
14710 			XGetWMNormalHints(display, window, &sh, &spr);
14711 			sh.min_width = minwidth;
14712 			sh.min_height = minheight;
14713 			sh.flags |= PMinSize;
14714 			XSetWMNormalHints(display, window, &sh);
14715 			flushGui();
14716 		}
14717 
14718 		void setMaxSize (int maxwidth, int maxheight) {
14719 			import core.stdc.config : c_long;
14720 			if (maxwidth < 1) maxwidth = 1;
14721 			if (maxheight < 1) maxheight = 1;
14722 			XSizeHints sh;
14723 			c_long spr;
14724 			XGetWMNormalHints(display, window, &sh, &spr);
14725 			sh.max_width = maxwidth;
14726 			sh.max_height = maxheight;
14727 			sh.flags |= PMaxSize;
14728 			XSetWMNormalHints(display, window, &sh);
14729 			flushGui();
14730 		}
14731 
14732 		void setResizeGranularity (int granx, int grany) {
14733 			import core.stdc.config : c_long;
14734 			if (granx < 1) granx = 1;
14735 			if (grany < 1) grany = 1;
14736 			XSizeHints sh;
14737 			c_long spr;
14738 			XGetWMNormalHints(display, window, &sh, &spr);
14739 			sh.width_inc = granx;
14740 			sh.height_inc = grany;
14741 			sh.flags |= PResizeInc;
14742 			XSetWMNormalHints(display, window, &sh);
14743 			flushGui();
14744 		}
14745 
14746 		void setOpacity (uint opacity) {
14747 			arch_ulong o = opacity;
14748 			if (opacity == uint.max)
14749 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14750 			else
14751 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14752 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14753 		}
14754 
14755 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14756 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14757 			display = XDisplayConnection.get();
14758 			auto screen = DefaultScreen(display);
14759 
14760 			bool overrideRedirect = false;
14761 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14762 				overrideRedirect = true;
14763 
14764 			version(without_opengl) {}
14765 			else {
14766 				if(opengl == OpenGlOptions.yes) {
14767 					GLXFBConfig fbconf = null;
14768 					XVisualInfo* vi = null;
14769 					bool useLegacy = false;
14770 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14771 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14772 						int[23] visualAttribs = [
14773 							GLX_X_RENDERABLE , 1/*True*/,
14774 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14775 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14776 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14777 							GLX_RED_SIZE     , 8,
14778 							GLX_GREEN_SIZE   , 8,
14779 							GLX_BLUE_SIZE    , 8,
14780 							GLX_ALPHA_SIZE   , 8,
14781 							GLX_DEPTH_SIZE   , 24,
14782 							GLX_STENCIL_SIZE , 8,
14783 							GLX_DOUBLEBUFFER , 1/*True*/,
14784 							0/*None*/,
14785 						];
14786 						int fbcount;
14787 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14788 						if (fbcount == 0) {
14789 							useLegacy = true; // try to do at least something
14790 						} else {
14791 							// pick the FB config/visual with the most samples per pixel
14792 							int bestidx = -1, bestns = -1;
14793 							foreach (int fbi; 0..fbcount) {
14794 								int sb, samples;
14795 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14796 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14797 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14798 							}
14799 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14800 							fbconf = fbc[bestidx];
14801 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14802 							XFree(fbc);
14803 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14804 						}
14805 					}
14806 					if (vi is null || useLegacy) {
14807 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14808 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14809 						useLegacy = true;
14810 					}
14811 					if (vi is null) throw new Exception("no open gl visual found");
14812 
14813 					XSetWindowAttributes swa;
14814 					auto root = RootWindow(display, screen);
14815 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14816 
14817 					swa.override_redirect = overrideRedirect;
14818 
14819 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14820 						0, 0, width, height,
14821 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14822 
14823 					// now try to use `glXCreateContextAttribsARB()` if it's here
14824 					if (!useLegacy) {
14825 						// request fairly advanced context, even with stencil buffer!
14826 						int[9] contextAttribs = [
14827 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14828 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14829 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14830 							// for modern context, set "forward compatibility" flag too
14831 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14832 							0/*None*/,
14833 						];
14834 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14835 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14836 							sdpyOpenGLContextVersion = 0;
14837 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14838 						}
14839 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14840 					} else {
14841 						// fallback to old GLX call
14842 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14843 							sdpyOpenGLContextVersion = 0;
14844 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14845 						}
14846 					}
14847 					// sync to ensure any errors generated are processed
14848 					XSync(display, 0/*False*/);
14849 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14850 					if(glc is null)
14851 						throw new Exception("glc");
14852 				}
14853 			}
14854 
14855 			if(opengl == OpenGlOptions.no) {
14856 
14857 				XSetWindowAttributes swa;
14858 				swa.background_pixel = WhitePixel(display, screen);
14859 				swa.border_pixel = BlackPixel(display, screen);
14860 				swa.override_redirect = overrideRedirect;
14861 				auto root = RootWindow(display, screen);
14862 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14863 
14864 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14865 					0, 0, width, height,
14866 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14867 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14868 
14869 
14870 
14871 				/*
14872 				window = XCreateSimpleWindow(
14873 					display,
14874 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14875 					0, 0, // x, y
14876 					width, height,
14877 					1, // border width
14878 					BlackPixel(display, screen), // border
14879 					WhitePixel(display, screen)); // background
14880 				*/
14881 
14882 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14883 				bufferw = width;
14884 				bufferh = height;
14885 
14886 				gc = DefaultGC(display, screen);
14887 
14888 				// clear out the buffer to get us started...
14889 				XSetForeground(display, gc, WhitePixel(display, screen));
14890 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14891 				XSetForeground(display, gc, BlackPixel(display, screen));
14892 			}
14893 
14894 			// input context
14895 			//TODO: create this only for top-level windows, and reuse that?
14896 			populateXic();
14897 
14898 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14899 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14900 			// window class
14901 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14902 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14903 				XClassHint klass;
14904 				XWMHints wh;
14905 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14906 					wh.input = true;
14907 					wh.flags |= InputHint;
14908 				}
14909 				XSizeHints size;
14910 				klass.res_name = sdpyWindowClassStr;
14911 				klass.res_class = sdpyWindowClassStr;
14912 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14913 			}
14914 
14915 			setTitle(title);
14916 			SimpleWindow.nativeMapping[window] = this;
14917 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14918 
14919 			// This gives our window a close button
14920 			if (windowType != WindowTypes.eventOnly) {
14921 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14922 				int useAtoms;
14923 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14924 					useAtoms = 2;
14925 				} else {
14926 					useAtoms = 1;
14927 				}
14928 				assert(useAtoms <= atoms.length);
14929 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14930 			}
14931 
14932 			// FIXME: windowType and customizationFlags
14933 			Atom[8] wsatoms; // here, due to goto
14934 			int wmsacount = 0; // here, due to goto
14935 
14936 			try
14937 			final switch(windowType) {
14938 				case WindowTypes.normal:
14939 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14940 				break;
14941 				case WindowTypes.undecorated:
14942 					motifHideDecorations();
14943 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14944 				break;
14945 				case WindowTypes.eventOnly:
14946 					_hidden = true;
14947 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14948 					goto hiddenWindow;
14949 				//break;
14950 				case WindowTypes.nestedChild:
14951 					// handled in XCreateWindow calls
14952 				break;
14953 
14954 				case WindowTypes.dropdownMenu:
14955 					motifHideDecorations();
14956 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14957 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14958 				break;
14959 				case WindowTypes.popupMenu:
14960 					motifHideDecorations();
14961 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14962 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14963 				break;
14964 				case WindowTypes.notification:
14965 					motifHideDecorations();
14966 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14967 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14968 				break;
14969 				case WindowTypes.minimallyWrapped:
14970 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14971 				/+
14972 				case WindowTypes.menu:
14973 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14974 					motifHideDecorations();
14975 				break;
14976 				case WindowTypes.desktop:
14977 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14978 				break;
14979 				case WindowTypes.dock:
14980 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14981 				break;
14982 				case WindowTypes.toolbar:
14983 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14984 				break;
14985 				case WindowTypes.menu:
14986 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14987 				break;
14988 				case WindowTypes.utility:
14989 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14990 				break;
14991 				case WindowTypes.splash:
14992 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14993 				break;
14994 				case WindowTypes.dialog:
14995 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14996 				break;
14997 				case WindowTypes.tooltip:
14998 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14999 				break;
15000 				case WindowTypes.notification:
15001 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
15002 				break;
15003 				case WindowTypes.combo:
15004 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
15005 				break;
15006 				case WindowTypes.dnd:
15007 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
15008 				break;
15009 				+/
15010 			}
15011 			catch(Exception e) {
15012 				// XInternAtom failed, prolly a WM
15013 				// that doesn't support these things
15014 			}
15015 
15016 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
15017 			// the two following flags may be ignored by WM
15018 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
15019 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
15020 
15021 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
15022 
15023 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
15024 
15025 			// What would be ideal here is if they only were
15026 			// selected if there was actually an event handler
15027 			// for them...
15028 
15029 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15030 
15031 			hiddenWindow:
15032 
15033 			// set the pid property for lookup later by window managers
15034 			// a standard convenience
15035 			import core.sys.posix.unistd;
15036 			arch_ulong pid = getpid();
15037 
15038 			XChangeProperty(
15039 				display,
15040 				impl.window,
15041 				GetAtom!("_NET_WM_PID", true)(display),
15042 				XA_CARDINAL,
15043 				32 /* bits */,
15044 				0 /*PropModeReplace*/,
15045 				&pid,
15046 				1);
15047 
15048 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15049 				if(parent is null) assert(0);
15050 				XChangeProperty(
15051 					display,
15052 					impl.window,
15053 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15054 					XA_WINDOW,
15055 					32 /* bits */,
15056 					0 /*PropModeReplace*/,
15057 					&parent.impl.window,
15058 					1);
15059 
15060 			}
15061 
15062 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15063 				XMapWindow(display, window);
15064 			} else {
15065 				_hidden = true;
15066 			}
15067 		}
15068 
15069 		void populateXic() {
15070 			if (XDisplayConnection.xim !is null) {
15071 				xic = XCreateIC(XDisplayConnection.xim,
15072 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15073 						/*XNClientWindow*/"clientWindow".ptr, window,
15074 						/*XNFocusWindow*/"focusWindow".ptr, window,
15075 						null);
15076 				if (xic is null) {
15077 					import core.stdc.stdio : stderr, fprintf;
15078 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15079 				}
15080 			}
15081 		}
15082 
15083 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15084 			auto mask = EventMask.ExposureMask |
15085 				EventMask.KeyPressMask |
15086 				EventMask.KeyReleaseMask |
15087 				EventMask.PropertyChangeMask |
15088 				EventMask.FocusChangeMask |
15089 				EventMask.StructureNotifyMask |
15090 				EventMask.SubstructureNotifyMask |
15091 				EventMask.VisibilityChangeMask
15092 				| EventMask.ButtonPressMask
15093 				| EventMask.ButtonReleaseMask
15094 			;
15095 
15096 			// xshm is our shortcut for local connections
15097 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15098 				mask |= EventMask.PointerMotionMask;
15099 			else
15100 				mask |= EventMask.ButtonMotionMask;
15101 
15102 			XSelectInput(display, window, mask);
15103 		}
15104 
15105 
15106 		void setNetWMWindowType(Atom type) {
15107 			Atom[2] atoms;
15108 
15109 			atoms[0] = type;
15110 			// generic fallback
15111 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15112 
15113 			XChangeProperty(
15114 				display,
15115 				impl.window,
15116 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15117 				XA_ATOM,
15118 				32 /* bits */,
15119 				0 /*PropModeReplace*/,
15120 				atoms.ptr,
15121 				cast(int) atoms.length);
15122 		}
15123 
15124 		void motifHideDecorations(bool hide = true) {
15125 			MwmHints hints;
15126 			hints.flags = MWM_HINTS_DECORATIONS;
15127 			hints.decorations = hide ? 0 : 1;
15128 
15129 			XChangeProperty(
15130 				display,
15131 				impl.window,
15132 				GetAtom!"_MOTIF_WM_HINTS"(display),
15133 				GetAtom!"_MOTIF_WM_HINTS"(display),
15134 				32 /* bits */,
15135 				0 /*PropModeReplace*/,
15136 				&hints,
15137 				hints.sizeof / 4);
15138 		}
15139 
15140 		/*k8: unused
15141 		void createOpenGlContext() {
15142 
15143 		}
15144 		*/
15145 
15146 		void closeWindow() {
15147 			// I can't close this or a child window closing will
15148 			// break events for everyone. So I'm just leaking it right
15149 			// now and that is probably perfectly fine...
15150 			version(none)
15151 			if (customEventFDRead != -1) {
15152 				import core.sys.posix.unistd : close;
15153 				auto same = customEventFDRead == customEventFDWrite;
15154 
15155 				close(customEventFDRead);
15156 				if(!same)
15157 					close(customEventFDWrite);
15158 				customEventFDRead = -1;
15159 				customEventFDWrite = -1;
15160 			}
15161 
15162 			version(without_opengl) {} else
15163 			if(glc !is null) {
15164 				glXDestroyContext(display, glc);
15165 				glc = null;
15166 			}
15167 
15168 			if(buffer)
15169 				XFreePixmap(display, buffer);
15170 			bufferw = bufferh = 0;
15171 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15172 			XDestroyWindow(display, window);
15173 			XFlush(display);
15174 		}
15175 
15176 		void dispose() {
15177 		}
15178 
15179 		bool destroyed = false;
15180 	}
15181 
15182 	bool insideXEventLoop;
15183 }
15184 
15185 version(X11) {
15186 
15187 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15188 
15189 	private class ResizeEvent {
15190 		int width, height;
15191 	}
15192 
15193 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15194 		if(win.windowType == WindowTypes.minimallyWrapped)
15195 			return;
15196 
15197 		if(win.pendingResizeEvent is null) {
15198 			win.pendingResizeEvent = new ResizeEvent();
15199 			win.addEventListener((ResizeEvent re) {
15200 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15201 			});
15202 		}
15203 		win.pendingResizeEvent.width = width;
15204 		win.pendingResizeEvent.height = height;
15205 		if(!win.eventQueued!ResizeEvent) {
15206 			win.postEvent(win.pendingResizeEvent);
15207 		}
15208 	}
15209 
15210 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15211 		if(win.windowType == WindowTypes.minimallyWrapped)
15212 			return;
15213 		if(win.closed)
15214 			return;
15215 
15216 		if(width != win.width || height != win.height) {
15217 
15218 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15219 			win._width = width;
15220 			win._height = height;
15221 
15222 			if(win.openglMode == OpenGlOptions.no) {
15223 				// FIXME: could this be more efficient?
15224 
15225 				if (win.bufferw < width || win.bufferh < height) {
15226 					//{ 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); }
15227 					// grow the internal buffer to match the window...
15228 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15229 					{
15230 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15231 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15232 						scope(exit) XFreeGC(win.display, xgc);
15233 						XSetClipMask(win.display, xgc, None);
15234 						XSetForeground(win.display, xgc, 0);
15235 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15236 					}
15237 					XCopyArea(display,
15238 						cast(Drawable) win.buffer,
15239 						cast(Drawable) newPixmap,
15240 						win.gc, 0, 0,
15241 						win.bufferw < width ? win.bufferw : win.width,
15242 						win.bufferh < height ? win.bufferh : win.height,
15243 						0, 0);
15244 
15245 					XFreePixmap(display, win.buffer);
15246 					win.buffer = newPixmap;
15247 					win.bufferw = width;
15248 					win.bufferh = height;
15249 				}
15250 
15251 				// clear unused parts of the buffer
15252 				if (win.bufferw > width || win.bufferh > height) {
15253 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15254 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15255 					scope(exit) XFreeGC(win.display, xgc);
15256 					XSetClipMask(win.display, xgc, None);
15257 					XSetForeground(win.display, xgc, 0);
15258 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15259 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15260 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15261 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15262 				}
15263 
15264 			}
15265 
15266 			win.updateOpenglViewportIfNeeded(width, height);
15267 
15268 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15269 
15270 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15271 			if(win.windowResized !is null) {
15272 				XUnlockDisplay(display);
15273 				scope(exit) XLockDisplay(display);
15274 				win.windowResized(width, height);
15275 			}
15276 		}
15277 	}
15278 
15279 
15280 	/// Platform-specific, you might use it when doing a custom event loop.
15281 	bool doXNextEvent(Display* display) {
15282 		bool done;
15283 		XEvent e;
15284 		XNextEvent(display, &e);
15285 		version(sddddd) {
15286 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15287 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15288 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15289 			}
15290 		}
15291 
15292 		// filter out compose events
15293 		if (XFilterEvent(&e, None)) {
15294 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15295 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15296 			return false;
15297 		}
15298 		// process keyboard mapping changes
15299 		if (e.type == EventType.KeymapNotify) {
15300 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15301 			XRefreshKeyboardMapping(&e.xmapping);
15302 			return false;
15303 		}
15304 
15305 		version(with_eventloop)
15306 			import arsd.eventloop;
15307 
15308 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15309 			// see windows impl's comments
15310 			XUnlockDisplay(display);
15311 			scope(exit) XLockDisplay(display);
15312 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15313 			if(ret == 0)
15314 				return done;
15315 		}
15316 
15317 
15318 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15319 			if(win.getNativeEventHandler !is null) {
15320 				XUnlockDisplay(display);
15321 				scope(exit) XLockDisplay(display);
15322 				auto ret = win.getNativeEventHandler()(e);
15323 				if(ret == 0)
15324 					return done;
15325 			}
15326 		}
15327 
15328 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15329 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15330 				// we get this because of the RRScreenChangeNotifyMask
15331 
15332 				// this isn't actually an ideal way to do it since it wastes time
15333 				// but meh it is simple and it works.
15334 				win.actualDpiLoadAttempted = false;
15335 				SimpleWindow.xRandrInfoLoadAttemped = false;
15336 				win.updateActualDpi(); // trigger a reload
15337 			}
15338 		}
15339 
15340 		switch(e.type) {
15341 		  case EventType.SelectionClear:
15342 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15343 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15344 				// writeln("SelectionClear");
15345 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15346 			}
15347 		  break;
15348 		  case EventType.SelectionRequest:
15349 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15350 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15351 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15352 				XUnlockDisplay(display);
15353 				scope(exit) XLockDisplay(display);
15354 				(*ssh).handleRequest(e);
15355 			}
15356 		  break;
15357 		  case EventType.PropertyNotify:
15358 			// printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15359 
15360 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15361 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15362 					ssh.sendMoreIncr(&e.xproperty);
15363 			}
15364 
15365 
15366 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15367 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15368 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15369 					Atom target;
15370 					int format;
15371 					arch_ulong bytesafter, length;
15372 					void* value;
15373 
15374 					ubyte[] s;
15375 					Atom targetToKeep;
15376 
15377 					XGetWindowProperty(
15378 						e.xproperty.display,
15379 						e.xproperty.window,
15380 						e.xproperty.atom,
15381 						0,
15382 						100000 /* length */,
15383 						true, /* erase it to signal we got it and want more */
15384 						0 /*AnyPropertyType*/,
15385 						&target, &format, &length, &bytesafter, &value);
15386 
15387 					if(!targetToKeep)
15388 						targetToKeep = target;
15389 
15390 					auto id = (cast(ubyte*) value)[0 .. length];
15391 
15392 					handler.handleIncrData(targetToKeep, id);
15393 
15394 					XFree(value);
15395 				}
15396 			}
15397 		  break;
15398 		  case EventType.SelectionNotify:
15399 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15400 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15401 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15402 					XUnlockDisplay(display);
15403 					scope(exit) XLockDisplay(display);
15404 					handler.handleData(None, null);
15405 				} else {
15406 					Atom target;
15407 					int format;
15408 					arch_ulong bytesafter, length;
15409 					void* value;
15410 					XGetWindowProperty(
15411 						e.xselection.display,
15412 						e.xselection.requestor,
15413 						e.xselection.property,
15414 						0,
15415 						100000 /* length */,
15416 						//false, /* don't erase it */
15417 						true, /* do erase it lol */
15418 						0 /*AnyPropertyType*/,
15419 						&target, &format, &length, &bytesafter, &value);
15420 
15421 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15422 
15423 					{
15424 						XUnlockDisplay(display);
15425 						scope(exit) XLockDisplay(display);
15426 
15427 						if(target == XA_ATOM) {
15428 							// initial request, see what they are able to work with and request the best one
15429 							// we can handle, if available
15430 
15431 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15432 							Atom best = handler.findBestFormat(answer);
15433 
15434 							/+
15435 							writeln("got ", answer);
15436 							foreach(a; answer)
15437 								printf("%s\n", XGetAtomName(display, a));
15438 							writeln("best ", best);
15439 							+/
15440 
15441 							if(best != None) {
15442 								// actually request the best format
15443 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15444 							}
15445 						} else if(target == GetAtom!"INCR"(display)) {
15446 							// incremental
15447 
15448 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15449 
15450 							// signal the sending program that we see
15451 							// the incr and are ready to receive more.
15452 							XDeleteProperty(
15453 								e.xselection.display,
15454 								e.xselection.requestor,
15455 								e.xselection.property);
15456 						} else {
15457 							// unsupported type... maybe, forward
15458 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15459 						}
15460 					}
15461 					XFree(value);
15462 					/*
15463 					XDeleteProperty(
15464 						e.xselection.display,
15465 						e.xselection.requestor,
15466 						e.xselection.property);
15467 					*/
15468 				}
15469 			}
15470 		  break;
15471 		  case EventType.ConfigureNotify:
15472 			auto event = e.xconfigure;
15473 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15474 				if(win.windowType == WindowTypes.minimallyWrapped)
15475 					break;
15476 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
15477 
15478 				/+
15479 					The ICCCM says window managers must send a synthetic event when the window
15480 					is moved but NOT when it is resized. In the resize case, an event is sent
15481 					with position (0, 0) which can be wrong and break the dpi calculations.
15482 
15483 					So we only consider the synthetic events from the WM and otherwise
15484 					need to wait for some other event to get the position which... sucks.
15485 
15486 					I'd rather not have windows changing their layout on mouse motion after
15487 					switching monitors... might be forced to but for now just ignoring it.
15488 
15489 					Easiest way to switch monitors without sending a size position is by
15490 					maximize or fullscreen in a setup like mine, but on most setups those
15491 					work on the monitor it is already living on, so it should be ok most the
15492 					time.
15493 				+/
15494 				if(event.send_event) {
15495 					win.screenPositionKnown = true;
15496 					win.screenPositionX = event.x;
15497 					win.screenPositionY = event.y;
15498 					win.updateActualDpi();
15499 				}
15500 
15501 				win.updateIMEPopupLocation();
15502 				recordX11ResizeAsync(display, *win, event.width, event.height);
15503 			}
15504 		  break;
15505 		  case EventType.Expose:
15506 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15507 				if(win.windowType == WindowTypes.minimallyWrapped)
15508 					break;
15509 				// if it is closing from a popup menu, it can get
15510 				// an Expose event right by the end and trigger a
15511 				// BadDrawable error ... we'll just check
15512 				// closed to handle that.
15513 				if((*win).closed) break;
15514 				if((*win).openglMode == OpenGlOptions.no) {
15515 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15516 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15517 					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);
15518 				} else {
15519 					// need to redraw the scene somehow
15520 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15521 						XUnlockDisplay(display);
15522 						scope(exit) XLockDisplay(display);
15523 						version(without_opengl) {} else
15524 						win.redrawOpenGlSceneSoon();
15525 					}
15526 				}
15527 			}
15528 		  break;
15529 		  case EventType.FocusIn:
15530 		  case EventType.FocusOut:
15531 
15532 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15533 				/+
15534 
15535 				void info(string detail) {
15536 					string s;
15537 					// import std.conv;
15538 					// import std.datetime;
15539 					s ~= to!string(Clock.currTime);
15540 					s ~= " ";
15541 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15542 					s ~= " ";
15543 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15544 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15545 					s ~= detail;
15546 					s ~= " ";
15547 
15548 					sdpyPrintDebugString(s);
15549 
15550 				}
15551 
15552 				switch(e.xfocus.detail) {
15553 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15554 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15555 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15556 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15557 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15558 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15559 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15560 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15561 					default:
15562 
15563 				}
15564 				+/
15565 
15566 
15567 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15568 					break; // just ignore these they seem irrelevant
15569 
15570 				auto old = win._focused;
15571 				win._focused = e.type == EventType.FocusIn;
15572 
15573 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15574 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15575 					win._focused = true;
15576 
15577 				if(win.demandingAttention)
15578 					demandAttention(*win, false);
15579 
15580 				win.updateIMEFocused();
15581 
15582 				if(old != win._focused && win.onFocusChange) {
15583 					XUnlockDisplay(display);
15584 					scope(exit) XLockDisplay(display);
15585 					win.onFocusChange(win._focused);
15586 				}
15587 			}
15588 		  break;
15589 		  case EventType.VisibilityNotify:
15590 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15591 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15592 						if (win.visibilityChanged !is null) {
15593 								XUnlockDisplay(display);
15594 								scope(exit) XLockDisplay(display);
15595 								win.visibilityChanged(false);
15596 							}
15597 					} else {
15598 						if (win.visibilityChanged !is null) {
15599 							XUnlockDisplay(display);
15600 							scope(exit) XLockDisplay(display);
15601 							win.visibilityChanged(true);
15602 						}
15603 					}
15604 				}
15605 				break;
15606 		  case EventType.ClientMessage:
15607 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15608 					// "ignore next mouse motion" event, increment ignore counter for teh window
15609 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15610 						++(*win).warpEventCount;
15611 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15612 					} else {
15613 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15614 					}
15615 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15616 					// user clicked the close button on the window manager
15617 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15618 						XUnlockDisplay(display);
15619 						scope(exit) XLockDisplay(display);
15620 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15621 					}
15622 
15623 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15624 					// writeln("HAPPENED");
15625 					// user clicked the close button on the window manager
15626 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15627 						XUnlockDisplay(display);
15628 						scope(exit) XLockDisplay(display);
15629 
15630 						auto setTo = *win;
15631 
15632 						if(win.setRequestedInputFocus !is null) {
15633 							auto s = win.setRequestedInputFocus();
15634 							if(s !is null) {
15635 								setTo = s;
15636 							}
15637 						}
15638 
15639 						assert(setTo !is null);
15640 
15641 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15642 
15643 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15644 					}
15645 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15646 					foreach(nai; NotificationAreaIcon.activeIcons)
15647 						nai.newManager();
15648 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15649 
15650 					bool xDragWindow = true;
15651 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15652 						//XDefineCursor(display, xDragWindow.impl.window,
15653 							//writeln("XdndStatus ", e.xclient.data.l);
15654 					}
15655 					if(auto dh = win.dropHandler) {
15656 
15657 						static Atom[3] xFormatsBuffer;
15658 						static Atom[] xFormats;
15659 
15660 						void resetXFormats() {
15661 							xFormatsBuffer[] = 0;
15662 							xFormats = xFormatsBuffer[];
15663 						}
15664 
15665 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15666 							// on Windows it is supposed to return the effect you actually do FIXME
15667 
15668 							auto sourceWindow =  e.xclient.data.l[0];
15669 
15670 							xFormatsBuffer[0] = e.xclient.data.l[2];
15671 							xFormatsBuffer[1] = e.xclient.data.l[3];
15672 							xFormatsBuffer[2] = e.xclient.data.l[4];
15673 
15674 							if(e.xclient.data.l[1] & 1) {
15675 								// can just grab it all but like we don't necessarily need them...
15676 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15677 							} else {
15678 								int len;
15679 								foreach(fmt; xFormatsBuffer)
15680 									if(fmt) len++;
15681 								xFormats = xFormatsBuffer[0 .. len];
15682 							}
15683 
15684 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15685 
15686 							dh.dragEnter(&pkg);
15687 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15688 
15689 							auto pack = e.xclient.data.l[2];
15690 
15691 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15692 
15693 
15694 							XClientMessageEvent xclient;
15695 
15696 							xclient.type = EventType.ClientMessage;
15697 							xclient.window = e.xclient.data.l[0];
15698 							xclient.message_type = GetAtom!"XdndStatus"(display);
15699 							xclient.format = 32;
15700 							xclient.data.l[0] = win.impl.window;
15701 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15702 							auto r = result.consistentWithin;
15703 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15704 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15705 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15706 
15707 							XSendEvent(
15708 								display,
15709 								e.xclient.data.l[0],
15710 								false,
15711 								EventMask.NoEventMask,
15712 								cast(XEvent*) &xclient
15713 							);
15714 
15715 
15716 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15717 							//writeln("XdndLeave");
15718 							// drop cancelled.
15719 							// data.l[0] is the source window
15720 							dh.dragLeave();
15721 
15722 							resetXFormats();
15723 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15724 							// drop happening, should fetch data, then send finished
15725 							// writeln("XdndDrop");
15726 
15727 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15728 
15729 							dh.drop(&pkg);
15730 
15731 							resetXFormats();
15732 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15733 							// writeln("XdndFinished");
15734 
15735 							dh.finish();
15736 						}
15737 
15738 					}
15739 				}
15740 		  break;
15741 		  case EventType.MapNotify:
15742 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15743 					(*win)._visible = true;
15744 					if (!(*win)._visibleForTheFirstTimeCalled) {
15745 						(*win)._visibleForTheFirstTimeCalled = true;
15746 						if ((*win).visibleForTheFirstTime !is null) {
15747 							XUnlockDisplay(display);
15748 							scope(exit) XLockDisplay(display);
15749 							(*win).visibleForTheFirstTime();
15750 						}
15751 					}
15752 					if ((*win).visibilityChanged !is null) {
15753 						XUnlockDisplay(display);
15754 						scope(exit) XLockDisplay(display);
15755 						(*win).visibilityChanged(true);
15756 					}
15757 				}
15758 		  break;
15759 		  case EventType.UnmapNotify:
15760 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15761 					win._visible = false;
15762 					if (win.visibilityChanged !is null) {
15763 						XUnlockDisplay(display);
15764 						scope(exit) XLockDisplay(display);
15765 						win.visibilityChanged(false);
15766 					}
15767 			}
15768 		  break;
15769 		  case EventType.DestroyNotify:
15770 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15771 				if(win.destroyed)
15772 					break; // might get a notification both for itself and from its parent
15773 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15774 				win._closed = true; // just in case
15775 				win.destroyed = true;
15776 				if (win.xic !is null) {
15777 					XDestroyIC(win.xic);
15778 					win.xic = null; // just in case
15779 				}
15780 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15781 				bool anyImportant = false;
15782 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15783 					if(w.beingOpenKeepsAppOpen) {
15784 						anyImportant = true;
15785 						break;
15786 					}
15787 				if(!anyImportant) {
15788 					EventLoop.quitApplication();
15789 					done = true;
15790 				}
15791 			}
15792 			auto window = e.xdestroywindow.window;
15793 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15794 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15795 
15796 			version(with_eventloop) {
15797 				if(done) exit();
15798 			}
15799 		  break;
15800 
15801 		  case EventType.MotionNotify:
15802 			MouseEvent mouse;
15803 			auto event = e.xmotion;
15804 
15805 			mouse.type = MouseEventType.motion;
15806 			mouse.x = event.x;
15807 			mouse.y = event.y;
15808 			mouse.modifierState = event.state;
15809 
15810 			mouse.timestamp = event.time;
15811 
15812 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15813 				mouse.window = *win;
15814 				if (win.warpEventCount > 0) {
15815 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15816 					--(*win).warpEventCount;
15817 					(*win).mdx(mouse); // so deltas will be correctly updated
15818 				} else {
15819 					win.warpEventCount = 0; // just in case
15820 					(*win).mdx(mouse);
15821 					if((*win).handleMouseEvent) {
15822 						XUnlockDisplay(display);
15823 						scope(exit) XLockDisplay(display);
15824 						(*win).handleMouseEvent(mouse);
15825 					}
15826 				}
15827 			}
15828 
15829 		  	version(with_eventloop)
15830 				send(mouse);
15831 		  break;
15832 		  case EventType.ButtonPress:
15833 		  case EventType.ButtonRelease:
15834 			MouseEvent mouse;
15835 			auto event = e.xbutton;
15836 
15837 			mouse.timestamp = event.time;
15838 
15839 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15840 			mouse.x = event.x;
15841 			mouse.y = event.y;
15842 
15843 			static Time lastMouseDownTime = 0;
15844 			static int lastMouseDownButton = -1;
15845 
15846 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15847 			if(e.type == EventType.ButtonPress) {
15848 				lastMouseDownTime = event.time;
15849 				lastMouseDownButton = event.button;
15850 			}
15851 
15852 			switch(event.button) {
15853 				case 1: mouse.button = MouseButton.left; break; // left
15854 				case 2: mouse.button = MouseButton.middle; break; // middle
15855 				case 3: mouse.button = MouseButton.right; break; // right
15856 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15857 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15858 				case 6: break; // idk
15859 				case 7: break; // idk
15860 				case 8: mouse.button = MouseButton.backButton; break;
15861 				case 9: mouse.button = MouseButton.forwardButton; break;
15862 				default:
15863 			}
15864 
15865 			// FIXME: double check this
15866 			mouse.modifierState = event.state;
15867 
15868 			//mouse.modifierState = event.detail;
15869 
15870 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15871 				mouse.window = *win;
15872 				(*win).mdx(mouse);
15873 				if((*win).handleMouseEvent) {
15874 					XUnlockDisplay(display);
15875 					scope(exit) XLockDisplay(display);
15876 					(*win).handleMouseEvent(mouse);
15877 				}
15878 			}
15879 			version(with_eventloop)
15880 				send(mouse);
15881 		  break;
15882 
15883 		  case EventType.KeyPress:
15884 		  case EventType.KeyRelease:
15885 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15886 			KeyEvent ke;
15887 			ke.pressed = e.type == EventType.KeyPress;
15888 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15889 
15890 			auto sym = XKeycodeToKeysym(
15891 				XDisplayConnection.get(),
15892 				e.xkey.keycode,
15893 				0);
15894 
15895 			ke.key = cast(Key) sym;//e.xkey.keycode;
15896 
15897 			ke.modifierState = e.xkey.state;
15898 
15899 			// writefln("%x", sym);
15900 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15901 			int charbuflen = 0; // return value of XwcLookupString
15902 			if (ke.pressed) {
15903 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15904 				if (win !is null && win.xic !is null) {
15905 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15906 					Status status;
15907 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15908 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15909 				} else {
15910 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15911 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15912 					char[16] buffer;
15913 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15914 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15915 				}
15916 			}
15917 
15918 			// if there's no char, subst one
15919 			if (charbuflen == 0) {
15920 				switch (sym) {
15921 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15922 					case 0xff8d: // keypad enter
15923 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15924 					default : // ignore
15925 				}
15926 			}
15927 
15928 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15929 				ke.window = *win;
15930 
15931 
15932 				if(win.inputProxy)
15933 					win = &win.inputProxy;
15934 
15935 				// char events are separate since they are on Windows too
15936 				// also, xcompose can generate long char sequences
15937 				// don't send char events if Meta and/or Hyper is pressed
15938 				// TODO: ctrl+char should only send control chars; not yet
15939 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15940 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15941 				}
15942 
15943 				dchar[32] charsComingBuffer;
15944 				int charsComingPosition;
15945 				dchar[] charsComing = charsComingBuffer[];
15946 
15947 				if (ke.pressed && charbuflen > 0) {
15948 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15949 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15950 						if(charsComingPosition >= charsComing.length)
15951 							charsComing.length = charsComingPosition + 8;
15952 
15953 						charsComing[charsComingPosition++] = ch;
15954 					}
15955 
15956 					charsComing = charsComing[0 .. charsComingPosition];
15957 				} else {
15958 					charsComing = null;
15959 				}
15960 
15961 				ke.charsPossible = charsComing;
15962 
15963 				if (win.handleKeyEvent) {
15964 					XUnlockDisplay(display);
15965 					scope(exit) XLockDisplay(display);
15966 					win.handleKeyEvent(ke);
15967 				}
15968 
15969 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15970 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15971 					XUnlockDisplay(display);
15972 					scope(exit) XLockDisplay(display);
15973 					foreach(ch; charsComing)
15974 						win.handleCharEvent(ch);
15975 				}
15976 			}
15977 
15978 			version(with_eventloop)
15979 				send(ke);
15980 		  break;
15981 		  default:
15982 		}
15983 
15984 		return done;
15985 	}
15986 }
15987 
15988 /* *************************************** */
15989 /*      Done with simpledisplay stuff      */
15990 /* *************************************** */
15991 
15992 // Necessary C library bindings follow
15993 version(Windows) {} else
15994 version(X11) {
15995 
15996 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15997 
15998 // X11 bindings needed here
15999 /*
16000 	A little of this is from the bindings project on
16001 	D Source and some of it is copy/paste from the C
16002 	header.
16003 
16004 	The DSource listing consistently used D's long
16005 	where C used long. That's wrong - C long is 32 bit, so
16006 	it should be int in D. I changed that here.
16007 
16008 	Note:
16009 	This isn't complete, just took what I needed for myself.
16010 */
16011 
16012 import core.stdc.stddef : wchar_t;
16013 
16014 interface XLib {
16015 extern(C) nothrow @nogc {
16016 	char* XResourceManagerString(Display*);
16017 	void XrmInitialize();
16018 	XrmDatabase XrmGetStringDatabase(char* data);
16019 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
16020 
16021 	Cursor XCreateFontCursor(Display*, uint shape);
16022 	int XDefineCursor(Display* display, Window w, Cursor cursor);
16023 	int XUndefineCursor(Display* display, Window w);
16024 
16025 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
16026 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
16027 	int XFreeCursor(Display* display, Cursor cursor);
16028 
16029 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16030 
16031 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16032 
16033 	XVaNestedList XVaCreateNestedList(int unused, ...);
16034 
16035 	char *XKeysymToString(KeySym keysym);
16036 	KeySym XKeycodeToKeysym(
16037 		Display*		/* display */,
16038 		KeyCode		/* keycode */,
16039 		int			/* index */
16040 	);
16041 
16042 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16043 
16044 	int XFree(void*);
16045 	int XDeleteProperty(Display *display, Window w, Atom property);
16046 
16047 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16048 
16049 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16050 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16051 		*actual_type_return, int *actual_format_return, arch_ulong
16052 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16053 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16054 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16055 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16056 
16057 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16058 
16059 	Window XGetSelectionOwner(Display *display, Atom selection);
16060 
16061 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16062 
16063 	char** XListFonts(Display*, const char*, int, int*);
16064 	void XFreeFontNames(char**);
16065 
16066 	Display* XOpenDisplay(const char*);
16067 	int XCloseDisplay(Display*);
16068 
16069 	int function() XSynchronize(Display*, bool);
16070 	int function() XSetAfterFunction(Display*, int function() proc);
16071 
16072 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16073 
16074 	Bool XSupportsLocale();
16075 	char* XSetLocaleModifiers(const(char)* modifier_list);
16076 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16077 	Status XCloseOM(XOM om);
16078 
16079 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16080 	Status XCloseIM(XIM im);
16081 
16082 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16083 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16084 	Display* XDisplayOfIM(XIM im);
16085 	char* XLocaleOfIM(XIM im);
16086 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16087 	void XDestroyIC(XIC ic);
16088 	void XSetICFocus(XIC ic);
16089 	void XUnsetICFocus(XIC ic);
16090 	//wchar_t* XwcResetIC(XIC ic);
16091 	char* XmbResetIC(XIC ic);
16092 	char* Xutf8ResetIC(XIC ic);
16093 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16094 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16095 	XIM XIMOfIC(XIC ic);
16096 
16097 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16098 
16099 
16100 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16101 	int XFreeFont(Display *display, XFontStruct *font_struct);
16102 	int XSetFont(Display* display, GC gc, Font font);
16103 	int XTextWidth(XFontStruct*, scope const char*, int);
16104 
16105 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16106 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16107 
16108 	Window XCreateSimpleWindow(
16109 		Display*	/* display */,
16110 		Window		/* parent */,
16111 		int			/* x */,
16112 		int			/* y */,
16113 		uint		/* width */,
16114 		uint		/* height */,
16115 		uint		/* border_width */,
16116 		uint		/* border */,
16117 		uint		/* background */
16118 	);
16119 	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);
16120 
16121 	int XReparentWindow(Display*, Window, Window, int, int);
16122 	int XClearWindow(Display*, Window);
16123 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16124 	int XMoveWindow(Display*, Window, int, int);
16125 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16126 
16127 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16128 
16129 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16130 
16131 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16132 
16133 	XImage *XCreateImage(
16134 		Display*		/* display */,
16135 		Visual*		/* visual */,
16136 		uint	/* depth */,
16137 		int			/* format */,
16138 		int			/* offset */,
16139 		ubyte*		/* data */,
16140 		uint	/* width */,
16141 		uint	/* height */,
16142 		int			/* bitmap_pad */,
16143 		int			/* bytes_per_line */
16144 	);
16145 
16146 	Status XInitImage (XImage* image);
16147 
16148 	Atom XInternAtom(
16149 		Display*		/* display */,
16150 		const char*	/* atom_name */,
16151 		Bool		/* only_if_exists */
16152 	);
16153 
16154 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16155 	char* XGetAtomName(Display*, Atom);
16156 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16157 
16158 	int XPutImage(
16159 		Display*	/* display */,
16160 		Drawable	/* d */,
16161 		GC			/* gc */,
16162 		XImage*	/* image */,
16163 		int			/* src_x */,
16164 		int			/* src_y */,
16165 		int			/* dest_x */,
16166 		int			/* dest_y */,
16167 		uint		/* width */,
16168 		uint		/* height */
16169 	);
16170 
16171 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16172 
16173 
16174 	int XDestroyWindow(
16175 		Display*	/* display */,
16176 		Window		/* w */
16177 	);
16178 
16179 	int XDestroyImage(XImage*);
16180 
16181 	int XSelectInput(
16182 		Display*	/* display */,
16183 		Window		/* w */,
16184 		EventMask	/* event_mask */
16185 	);
16186 
16187 	int XMapWindow(
16188 		Display*	/* display */,
16189 		Window		/* w */
16190 	);
16191 
16192 	Status XIconifyWindow(Display*, Window, int);
16193 	int XMapRaised(Display*, Window);
16194 	int XMapSubwindows(Display*, Window);
16195 
16196 	int XNextEvent(
16197 		Display*	/* display */,
16198 		XEvent*		/* event_return */
16199 	);
16200 
16201 	int XMaskEvent(Display*, arch_long, XEvent*);
16202 
16203 	Bool XFilterEvent(XEvent *event, Window window);
16204 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16205 
16206 	Status XSetWMProtocols(
16207 		Display*	/* display */,
16208 		Window		/* w */,
16209 		Atom*		/* protocols */,
16210 		int			/* count */
16211 	);
16212 
16213 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16214 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16215 
16216 
16217 	Status XInitThreads();
16218 	void XLockDisplay (Display* display);
16219 	void XUnlockDisplay (Display* display);
16220 
16221 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16222 
16223 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16224 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16225 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16226 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16227 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16228 
16229 
16230 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16231 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16232 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16233 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16234 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16235 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16236 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16237 	int XDrawPoint(Display*, Drawable, GC, int, int);
16238 	int XSetForeground(Display*, GC, uint);
16239 	int XSetBackground(Display*, GC, uint);
16240 
16241 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16242 	void XFreeFontSet(Display*, XFontSet);
16243 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16244 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16245 
16246 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16247 
16248 
16249 //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);
16250 
16251 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16252 	int XSetFunction(Display*, GC, int);
16253 
16254 	GC XCreateGC(Display*, Drawable, uint, void*);
16255 	int XCopyGC(Display*, GC, uint, GC);
16256 	int XFreeGC(Display*, GC);
16257 
16258 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16259 	bool XCheckMaskEvent(Display*, int, XEvent*);
16260 
16261 	int XPending(Display*);
16262 	int XEventsQueued(Display* display, int mode);
16263 
16264 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16265 	int XFreePixmap(Display*, Pixmap);
16266 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16267 	int XFlush(Display*);
16268 	int XBell(Display*, int);
16269 	int XSync(Display*, bool);
16270 
16271 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16272 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16273 
16274 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16275 	int XUngrabKeyboard(Display*, Time);
16276 
16277 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16278 
16279 	KeySym XStringToKeysym(const char *string);
16280 
16281 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16282 
16283 	Window XDefaultRootWindow(Display*);
16284 
16285 	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);
16286 
16287 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16288 
16289 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16290 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16291 
16292 	Status XAllocColor(Display*, Colormap, XColor*);
16293 
16294 	int XWithdrawWindow(Display*, Window, int);
16295 	int XUnmapWindow(Display*, Window);
16296 	int XLowerWindow(Display*, Window);
16297 	int XRaiseWindow(Display*, Window);
16298 
16299 	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);
16300 	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);
16301 
16302 	int XGetInputFocus(Display*, Window*, int*);
16303 	int XSetInputFocus(Display*, Window, int, Time);
16304 
16305 	XErrorHandler XSetErrorHandler(XErrorHandler);
16306 
16307 	int XGetErrorText(Display*, int, char*, int);
16308 
16309 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16310 
16311 
16312 	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);
16313 	int XUngrabPointer(Display *display, Time time);
16314 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16315 
16316 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16317 
16318 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16319 	int XSetClipMask(Display*, GC, Pixmap);
16320 	int XSetClipOrigin(Display*, GC, int, int);
16321 
16322 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16323 
16324 	void XSetWMName(Display*, Window, XTextProperty*);
16325 	Status XGetWMName(Display*, Window, XTextProperty*);
16326 	int XStoreName(Display* display, Window w, const(char)* window_name);
16327 
16328 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16329 
16330 }
16331 }
16332 
16333 interface Xext {
16334 extern(C) nothrow @nogc {
16335 	Status XShmAttach(Display*, XShmSegmentInfo*);
16336 	Status XShmDetach(Display*, XShmSegmentInfo*);
16337 	Status XShmPutImage(
16338 		Display*            /* dpy */,
16339 		Drawable            /* d */,
16340 		GC                  /* gc */,
16341 		XImage*             /* image */,
16342 		int                 /* src_x */,
16343 		int                 /* src_y */,
16344 		int                 /* dst_x */,
16345 		int                 /* dst_y */,
16346 		uint        /* src_width */,
16347 		uint        /* src_height */,
16348 		Bool                /* send_event */
16349 	);
16350 
16351 	Status XShmQueryExtension(Display*);
16352 
16353 	XImage *XShmCreateImage(
16354 		Display*            /* dpy */,
16355 		Visual*             /* visual */,
16356 		uint        /* depth */,
16357 		int                 /* format */,
16358 		char*               /* data */,
16359 		XShmSegmentInfo*    /* shminfo */,
16360 		uint        /* width */,
16361 		uint        /* height */
16362 	);
16363 
16364 	Pixmap XShmCreatePixmap(
16365 		Display*            /* dpy */,
16366 		Drawable            /* d */,
16367 		char*               /* data */,
16368 		XShmSegmentInfo*    /* shminfo */,
16369 		uint        /* width */,
16370 		uint        /* height */,
16371 		uint        /* depth */
16372 	);
16373 
16374 }
16375 }
16376 
16377 	// this requires -lXpm
16378 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16379 
16380 
16381 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16382 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16383 shared static this() {
16384 	xlib.loadDynamicLibrary();
16385 	xext.loadDynamicLibrary();
16386 }
16387 
16388 
16389 extern(C) nothrow @nogc {
16390 
16391 alias XrmDatabase = void*;
16392 struct XrmValue {
16393 	uint size;
16394 	void* addr;
16395 }
16396 
16397 struct XVisualInfo {
16398 	Visual* visual;
16399 	VisualID visualid;
16400 	int screen;
16401 	uint depth;
16402 	int c_class;
16403 	c_ulong red_mask;
16404 	c_ulong green_mask;
16405 	c_ulong blue_mask;
16406 	int colormap_size;
16407 	int bits_per_rgb;
16408 }
16409 
16410 enum VisualNoMask=	0x0;
16411 enum VisualIDMask=	0x1;
16412 enum VisualScreenMask=0x2;
16413 enum VisualDepthMask=	0x4;
16414 enum VisualClassMask=	0x8;
16415 enum VisualRedMaskMask=0x10;
16416 enum VisualGreenMaskMask=0x20;
16417 enum VisualBlueMaskMask=0x40;
16418 enum VisualColormapSizeMask=0x80;
16419 enum VisualBitsPerRGBMask=0x100;
16420 enum VisualAllMask=	0x1FF;
16421 
16422 enum AnyKey = 0;
16423 enum AnyModifier = 1 << 15;
16424 
16425 // XIM and other crap
16426 struct _XOM {}
16427 struct _XIM {}
16428 struct _XIC {}
16429 alias XOM = _XOM*;
16430 alias XIM = _XIM*;
16431 alias XIC = _XIC*;
16432 
16433 alias XVaNestedList = void*;
16434 
16435 alias XIMStyle = arch_ulong;
16436 enum : arch_ulong {
16437 	XIMPreeditArea      = 0x0001,
16438 	XIMPreeditCallbacks = 0x0002,
16439 	XIMPreeditPosition  = 0x0004,
16440 	XIMPreeditNothing   = 0x0008,
16441 	XIMPreeditNone      = 0x0010,
16442 	XIMStatusArea       = 0x0100,
16443 	XIMStatusCallbacks  = 0x0200,
16444 	XIMStatusNothing    = 0x0400,
16445 	XIMStatusNone       = 0x0800,
16446 }
16447 
16448 
16449 /* X Shared Memory Extension functions */
16450 	//pragma(lib, "Xshm");
16451 	alias arch_ulong ShmSeg;
16452 	struct XShmSegmentInfo {
16453 		ShmSeg shmseg;
16454 		int shmid;
16455 		ubyte* shmaddr;
16456 		Bool readOnly;
16457 	}
16458 
16459 	// and the necessary OS functions
16460 	int shmget(int, size_t, int);
16461 	void* shmat(int, scope const void*, int);
16462 	int shmdt(scope const void*);
16463 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16464 
16465 	enum IPC_PRIVATE = 0;
16466 	enum IPC_CREAT = 512;
16467 	enum IPC_RMID = 0;
16468 
16469 /* MIT-SHM end */
16470 
16471 
16472 enum MappingType:int {
16473 	MappingModifier		=0,
16474 	MappingKeyboard		=1,
16475 	MappingPointer		=2
16476 }
16477 
16478 /* ImageFormat -- PutImage, GetImage */
16479 enum ImageFormat:int {
16480 	XYBitmap	=0,	/* depth 1, XYFormat */
16481 	XYPixmap	=1,	/* depth == drawable depth */
16482 	ZPixmap	=2	/* depth == drawable depth */
16483 }
16484 
16485 enum ModifierName:int {
16486 	ShiftMapIndex	=0,
16487 	LockMapIndex	=1,
16488 	ControlMapIndex	=2,
16489 	Mod1MapIndex	=3,
16490 	Mod2MapIndex	=4,
16491 	Mod3MapIndex	=5,
16492 	Mod4MapIndex	=6,
16493 	Mod5MapIndex	=7
16494 }
16495 
16496 enum ButtonMask:int {
16497 	Button1Mask	=1<<8,
16498 	Button2Mask	=1<<9,
16499 	Button3Mask	=1<<10,
16500 	Button4Mask	=1<<11,
16501 	Button5Mask	=1<<12,
16502 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16503 }
16504 
16505 enum KeyOrButtonMask:uint {
16506 	ShiftMask	=1<<0,
16507 	LockMask	=1<<1,
16508 	ControlMask	=1<<2,
16509 	Mod1Mask	=1<<3,
16510 	Mod2Mask	=1<<4,
16511 	Mod3Mask	=1<<5,
16512 	Mod4Mask	=1<<6,
16513 	Mod5Mask	=1<<7,
16514 	Button1Mask	=1<<8,
16515 	Button2Mask	=1<<9,
16516 	Button3Mask	=1<<10,
16517 	Button4Mask	=1<<11,
16518 	Button5Mask	=1<<12,
16519 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16520 }
16521 
16522 enum ButtonName:int {
16523 	Button1	=1,
16524 	Button2	=2,
16525 	Button3	=3,
16526 	Button4	=4,
16527 	Button5	=5
16528 }
16529 
16530 /* Notify modes */
16531 enum NotifyModes:int
16532 {
16533 	NotifyNormal		=0,
16534 	NotifyGrab			=1,
16535 	NotifyUngrab		=2,
16536 	NotifyWhileGrabbed	=3
16537 }
16538 enum NotifyHint = 1;	/* for MotionNotify events */
16539 
16540 /* Notify detail */
16541 enum NotifyDetail:int
16542 {
16543 	NotifyAncestor			=0,
16544 	NotifyVirtual			=1,
16545 	NotifyInferior			=2,
16546 	NotifyNonlinear			=3,
16547 	NotifyNonlinearVirtual	=4,
16548 	NotifyPointer			=5,
16549 	NotifyPointerRoot		=6,
16550 	NotifyDetailNone		=7
16551 }
16552 
16553 /* Visibility notify */
16554 
16555 enum VisibilityNotify:int
16556 {
16557 VisibilityUnobscured		=0,
16558 VisibilityPartiallyObscured	=1,
16559 VisibilityFullyObscured		=2
16560 }
16561 
16562 
16563 enum WindowStackingMethod:int
16564 {
16565 	Above		=0,
16566 	Below		=1,
16567 	TopIf		=2,
16568 	BottomIf	=3,
16569 	Opposite	=4
16570 }
16571 
16572 /* Circulation request */
16573 enum CirculationRequest:int
16574 {
16575 	PlaceOnTop		=0,
16576 	PlaceOnBottom	=1
16577 }
16578 
16579 enum PropertyNotification:int
16580 {
16581 	PropertyNewValue	=0,
16582 	PropertyDelete		=1
16583 }
16584 
16585 enum ColorMapNotification:int
16586 {
16587 	ColormapUninstalled	=0,
16588 	ColormapInstalled		=1
16589 }
16590 
16591 
16592 	struct _XPrivate {}
16593 	struct _XrmHashBucketRec {}
16594 
16595 	alias void* XPointer;
16596 	alias void* XExtData;
16597 
16598 	version( X86_64 ) {
16599 		alias ulong XID;
16600 		alias ulong arch_ulong;
16601 		alias long arch_long;
16602 	} else version (AArch64) {
16603 		alias ulong XID;
16604 		alias ulong arch_ulong;
16605 		alias long arch_long;
16606 	} else {
16607 		alias uint XID;
16608 		alias uint arch_ulong;
16609 		alias int arch_long;
16610 	}
16611 
16612 	alias XID Window;
16613 	alias XID Drawable;
16614 	alias XID Pixmap;
16615 
16616 	alias arch_ulong Atom;
16617 	alias int Bool;
16618 	alias Display XDisplay;
16619 
16620 	alias int ByteOrder;
16621 	alias arch_ulong Time;
16622 	alias void ScreenFormat;
16623 
16624 	struct XImage {
16625 		int width, height;			/* size of image */
16626 		int xoffset;				/* number of pixels offset in X direction */
16627 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16628 		void *data;					/* pointer to image data */
16629 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16630 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16631 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16632 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16633 		int depth;					/* depth of image */
16634 		int bytes_per_line;			/* accelarator to next line */
16635 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16636 		arch_ulong red_mask;	/* bits in z arrangment */
16637 		arch_ulong green_mask;
16638 		arch_ulong blue_mask;
16639 		XPointer obdata;			/* hook for the object routines to hang on */
16640 		static struct F {				/* image manipulation routines */
16641 			XImage* function(
16642 				XDisplay* 			/* display */,
16643 				Visual*				/* visual */,
16644 				uint				/* depth */,
16645 				int					/* format */,
16646 				int					/* offset */,
16647 				ubyte*				/* data */,
16648 				uint				/* width */,
16649 				uint				/* height */,
16650 				int					/* bitmap_pad */,
16651 				int					/* bytes_per_line */) create_image;
16652 			int function(XImage *) destroy_image;
16653 			arch_ulong function(XImage *, int, int) get_pixel;
16654 			int function(XImage *, int, int, arch_ulong) put_pixel;
16655 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16656 			int function(XImage *, arch_long) add_pixel;
16657 		}
16658 		F f;
16659 	}
16660 	version(X86_64) static assert(XImage.sizeof == 136);
16661 	else version(X86) static assert(XImage.sizeof == 88);
16662 
16663 struct XCharStruct {
16664 	short       lbearing;       /* origin to left edge of raster */
16665 	short       rbearing;       /* origin to right edge of raster */
16666 	short       width;          /* advance to next char's origin */
16667 	short       ascent;         /* baseline to top edge of raster */
16668 	short       descent;        /* baseline to bottom edge of raster */
16669 	ushort attributes;  /* per char flags (not predefined) */
16670 }
16671 
16672 /*
16673  * To allow arbitrary information with fonts, there are additional properties
16674  * returned.
16675  */
16676 struct XFontProp {
16677 	Atom name;
16678 	arch_ulong card32;
16679 }
16680 
16681 alias Atom Font;
16682 
16683 struct XFontStruct {
16684 	XExtData *ext_data;           /* Hook for extension to hang data */
16685 	Font fid;                     /* Font ID for this font */
16686 	uint direction;           /* Direction the font is painted */
16687 	uint min_char_or_byte2;   /* First character */
16688 	uint max_char_or_byte2;   /* Last character */
16689 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16690 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16691 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16692 	uint default_char;        /* Char to print for undefined character */
16693 	int n_properties;             /* How many properties there are */
16694 	XFontProp *properties;        /* Pointer to array of additional properties*/
16695 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16696 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16697 	XCharStruct *per_char;        /* first_char to last_char information */
16698 	int ascent;                   /* Max extent above baseline for spacing */
16699 	int descent;                  /* Max descent below baseline for spacing */
16700 }
16701 
16702 
16703 /*
16704  * Definitions of specific events.
16705  */
16706 struct XKeyEvent
16707 {
16708 	int type;			/* of event */
16709 	arch_ulong serial;		/* # of last request processed by server */
16710 	Bool send_event;	/* true if this came from a SendEvent request */
16711 	Display *display;	/* Display the event was read from */
16712 	Window window;	        /* "event" window it is reported relative to */
16713 	Window root;	        /* root window that the event occurred on */
16714 	Window subwindow;	/* child window */
16715 	Time time;		/* milliseconds */
16716 	int x, y;		/* pointer x, y coordinates in event window */
16717 	int x_root, y_root;	/* coordinates relative to root */
16718 	KeyOrButtonMask state;	/* key or button mask */
16719 	uint keycode;	/* detail */
16720 	Bool same_screen;	/* same screen flag */
16721 }
16722 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16723 alias XKeyEvent XKeyPressedEvent;
16724 alias XKeyEvent XKeyReleasedEvent;
16725 
16726 struct XButtonEvent
16727 {
16728 	int type;		/* of event */
16729 	arch_ulong serial;	/* # of last request processed by server */
16730 	Bool send_event;	/* true if this came from a SendEvent request */
16731 	Display *display;	/* Display the event was read from */
16732 	Window window;	        /* "event" window it is reported relative to */
16733 	Window root;	        /* root window that the event occurred on */
16734 	Window subwindow;	/* child window */
16735 	Time time;		/* milliseconds */
16736 	int x, y;		/* pointer x, y coordinates in event window */
16737 	int x_root, y_root;	/* coordinates relative to root */
16738 	KeyOrButtonMask state;	/* key or button mask */
16739 	uint button;	/* detail */
16740 	Bool same_screen;	/* same screen flag */
16741 }
16742 alias XButtonEvent XButtonPressedEvent;
16743 alias XButtonEvent XButtonReleasedEvent;
16744 
16745 struct XMotionEvent{
16746 	int type;		/* of event */
16747 	arch_ulong serial;	/* # of last request processed by server */
16748 	Bool send_event;	/* true if this came from a SendEvent request */
16749 	Display *display;	/* Display the event was read from */
16750 	Window window;	        /* "event" window reported relative to */
16751 	Window root;	        /* root window that the event occurred on */
16752 	Window subwindow;	/* child window */
16753 	Time time;		/* milliseconds */
16754 	int x, y;		/* pointer x, y coordinates in event window */
16755 	int x_root, y_root;	/* coordinates relative to root */
16756 	KeyOrButtonMask state;	/* key or button mask */
16757 	byte is_hint;		/* detail */
16758 	Bool same_screen;	/* same screen flag */
16759 }
16760 alias XMotionEvent XPointerMovedEvent;
16761 
16762 struct XCrossingEvent{
16763 	int type;		/* of event */
16764 	arch_ulong serial;	/* # of last request processed by server */
16765 	Bool send_event;	/* true if this came from a SendEvent request */
16766 	Display *display;	/* Display the event was read from */
16767 	Window window;	        /* "event" window reported relative to */
16768 	Window root;	        /* root window that the event occurred on */
16769 	Window subwindow;	/* child window */
16770 	Time time;		/* milliseconds */
16771 	int x, y;		/* pointer x, y coordinates in event window */
16772 	int x_root, y_root;	/* coordinates relative to root */
16773 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16774 	NotifyDetail detail;
16775 	/*
16776 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16777 	 * NotifyNonlinear,NotifyNonlinearVirtual
16778 	 */
16779 	Bool same_screen;	/* same screen flag */
16780 	Bool focus;		/* Boolean focus */
16781 	KeyOrButtonMask state;	/* key or button mask */
16782 }
16783 alias XCrossingEvent XEnterWindowEvent;
16784 alias XCrossingEvent XLeaveWindowEvent;
16785 
16786 struct XFocusChangeEvent{
16787 	int type;		/* FocusIn or FocusOut */
16788 	arch_ulong serial;	/* # of last request processed by server */
16789 	Bool send_event;	/* true if this came from a SendEvent request */
16790 	Display *display;	/* Display the event was read from */
16791 	Window window;		/* window of event */
16792 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16793 				   NotifyGrab, NotifyUngrab */
16794 	NotifyDetail detail;
16795 	/*
16796 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16797 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16798 	 * NotifyPointerRoot, NotifyDetailNone
16799 	 */
16800 }
16801 alias XFocusChangeEvent XFocusInEvent;
16802 alias XFocusChangeEvent XFocusOutEvent;
16803 
16804 enum CWBackPixmap              = (1L<<0);
16805 enum CWBackPixel               = (1L<<1);
16806 enum CWBorderPixmap            = (1L<<2);
16807 enum CWBorderPixel             = (1L<<3);
16808 enum CWBitGravity              = (1L<<4);
16809 enum CWWinGravity              = (1L<<5);
16810 enum CWBackingStore            = (1L<<6);
16811 enum CWBackingPlanes           = (1L<<7);
16812 enum CWBackingPixel            = (1L<<8);
16813 enum CWOverrideRedirect        = (1L<<9);
16814 enum CWSaveUnder               = (1L<<10);
16815 enum CWEventMask               = (1L<<11);
16816 enum CWDontPropagate           = (1L<<12);
16817 enum CWColormap                = (1L<<13);
16818 enum CWCursor                  = (1L<<14);
16819 
16820 struct XWindowAttributes {
16821 	int x, y;			/* location of window */
16822 	int width, height;		/* width and height of window */
16823 	int border_width;		/* border width of window */
16824 	int depth;			/* depth of window */
16825 	Visual *visual;			/* the associated visual structure */
16826 	Window root;			/* root of screen containing window */
16827 	int class_;			/* InputOutput, InputOnly*/
16828 	int bit_gravity;		/* one of the bit gravity values */
16829 	int win_gravity;		/* one of the window gravity values */
16830 	int backing_store;		/* NotUseful, WhenMapped, Always */
16831 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16832 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16833 	Bool save_under;		/* boolean, should bits under be saved? */
16834 	Colormap colormap;		/* color map to be associated with window */
16835 	Bool map_installed;		/* boolean, is color map currently installed*/
16836 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16837 	arch_long all_event_masks;		/* set of events all people have interest in*/
16838 	arch_long your_event_mask;		/* my event mask */
16839 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16840 	Bool override_redirect;		/* boolean value for override-redirect */
16841 	Screen *screen;			/* back pointer to correct screen */
16842 }
16843 
16844 enum IsUnmapped = 0;
16845 enum IsUnviewable = 1;
16846 enum IsViewable = 2;
16847 
16848 struct XSetWindowAttributes {
16849 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16850 	arch_ulong background_pixel;/* background pixel */
16851 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16852 	arch_ulong border_pixel;/* border pixel value */
16853 	int bit_gravity;         /* one of bit gravity values */
16854 	int win_gravity;         /* one of the window gravity values */
16855 	int backing_store;       /* NotUseful, WhenMapped, Always */
16856 	arch_ulong backing_planes;/* planes to be preserved if possible */
16857 	arch_ulong backing_pixel;/* value to use in restoring planes */
16858 	Bool save_under;         /* should bits under be saved? (popups) */
16859 	arch_long event_mask;         /* set of events that should be saved */
16860 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16861 	Bool override_redirect;  /* boolean value for override_redirect */
16862 	Colormap colormap;       /* color map to be associated with window */
16863 	Cursor cursor;           /* cursor to be displayed (or None) */
16864 }
16865 
16866 
16867 alias int Status;
16868 
16869 
16870 enum EventMask:int
16871 {
16872 	NoEventMask				=0,
16873 	KeyPressMask			=1<<0,
16874 	KeyReleaseMask			=1<<1,
16875 	ButtonPressMask			=1<<2,
16876 	ButtonReleaseMask		=1<<3,
16877 	EnterWindowMask			=1<<4,
16878 	LeaveWindowMask			=1<<5,
16879 	PointerMotionMask		=1<<6,
16880 	PointerMotionHintMask	=1<<7,
16881 	Button1MotionMask		=1<<8,
16882 	Button2MotionMask		=1<<9,
16883 	Button3MotionMask		=1<<10,
16884 	Button4MotionMask		=1<<11,
16885 	Button5MotionMask		=1<<12,
16886 	ButtonMotionMask		=1<<13,
16887 	KeymapStateMask		=1<<14,
16888 	ExposureMask			=1<<15,
16889 	VisibilityChangeMask	=1<<16,
16890 	StructureNotifyMask		=1<<17,
16891 	ResizeRedirectMask		=1<<18,
16892 	SubstructureNotifyMask	=1<<19,
16893 	SubstructureRedirectMask=1<<20,
16894 	FocusChangeMask			=1<<21,
16895 	PropertyChangeMask		=1<<22,
16896 	ColormapChangeMask		=1<<23,
16897 	OwnerGrabButtonMask		=1<<24
16898 }
16899 
16900 struct MwmHints {
16901 	c_ulong flags;
16902 	c_ulong functions;
16903 	c_ulong decorations;
16904 	c_long input_mode;
16905 	c_ulong status;
16906 }
16907 
16908 enum {
16909 	MWM_HINTS_FUNCTIONS = (1L << 0),
16910 	MWM_HINTS_DECORATIONS =  (1L << 1),
16911 
16912 	MWM_FUNC_ALL = (1L << 0),
16913 	MWM_FUNC_RESIZE = (1L << 1),
16914 	MWM_FUNC_MOVE = (1L << 2),
16915 	MWM_FUNC_MINIMIZE = (1L << 3),
16916 	MWM_FUNC_MAXIMIZE = (1L << 4),
16917 	MWM_FUNC_CLOSE = (1L << 5),
16918 
16919 	MWM_DECOR_ALL = (1L << 0),
16920 	MWM_DECOR_BORDER = (1L << 1),
16921 	MWM_DECOR_RESIZEH = (1L << 2),
16922 	MWM_DECOR_TITLE = (1L << 3),
16923 	MWM_DECOR_MENU = (1L << 4),
16924 	MWM_DECOR_MINIMIZE = (1L << 5),
16925 	MWM_DECOR_MAXIMIZE = (1L << 6),
16926 }
16927 
16928 import core.stdc.config : c_long, c_ulong;
16929 
16930 	/* Size hints mask bits */
16931 
16932 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16933 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16934 	enum   PPosition   = (1L << 2)          /* program specified position */;
16935 	enum   PSize       = (1L << 3)          /* program specified size */;
16936 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16937 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16938 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16939 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16940 	enum   PBaseSize   = (1L << 8);
16941 	enum   PWinGravity = (1L << 9);
16942 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16943 	struct XSizeHints {
16944 		arch_long flags;         /* marks which fields in this structure are defined */
16945 		int x, y;           /* Obsolete */
16946 		int width, height;  /* Obsolete */
16947 		int min_width, min_height;
16948 		int max_width, max_height;
16949 		int width_inc, height_inc;
16950 		struct Aspect {
16951 			int x;       /* numerator */
16952 			int y;       /* denominator */
16953 		}
16954 
16955 		Aspect min_aspect;
16956 		Aspect max_aspect;
16957 		int base_width, base_height;
16958 		int win_gravity;
16959 		/* this structure may be extended in the future */
16960 	}
16961 
16962 
16963 
16964 enum EventType:int
16965 {
16966 	KeyPress			=2,
16967 	KeyRelease			=3,
16968 	ButtonPress			=4,
16969 	ButtonRelease		=5,
16970 	MotionNotify		=6,
16971 	EnterNotify			=7,
16972 	LeaveNotify			=8,
16973 	FocusIn				=9,
16974 	FocusOut			=10,
16975 	KeymapNotify		=11,
16976 	Expose				=12,
16977 	GraphicsExpose		=13,
16978 	NoExpose			=14,
16979 	VisibilityNotify	=15,
16980 	CreateNotify		=16,
16981 	DestroyNotify		=17,
16982 	UnmapNotify		=18,
16983 	MapNotify			=19,
16984 	MapRequest			=20,
16985 	ReparentNotify		=21,
16986 	ConfigureNotify		=22,
16987 	ConfigureRequest	=23,
16988 	GravityNotify		=24,
16989 	ResizeRequest		=25,
16990 	CirculateNotify		=26,
16991 	CirculateRequest	=27,
16992 	PropertyNotify		=28,
16993 	SelectionClear		=29,
16994 	SelectionRequest	=30,
16995 	SelectionNotify		=31,
16996 	ColormapNotify		=32,
16997 	ClientMessage		=33,
16998 	MappingNotify		=34,
16999 	LASTEvent			=35	/* must be bigger than any event # */
17000 }
17001 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
17002 struct XKeymapEvent
17003 {
17004 	int type;
17005 	arch_ulong serial;	/* # of last request processed by server */
17006 	Bool send_event;	/* true if this came from a SendEvent request */
17007 	Display *display;	/* Display the event was read from */
17008 	Window window;
17009 	byte[32] key_vector;
17010 }
17011 
17012 struct XExposeEvent
17013 {
17014 	int type;
17015 	arch_ulong serial;	/* # of last request processed by server */
17016 	Bool send_event;	/* true if this came from a SendEvent request */
17017 	Display *display;	/* Display the event was read from */
17018 	Window window;
17019 	int x, y;
17020 	int width, height;
17021 	int count;		/* if non-zero, at least this many more */
17022 }
17023 
17024 struct XGraphicsExposeEvent{
17025 	int type;
17026 	arch_ulong serial;	/* # of last request processed by server */
17027 	Bool send_event;	/* true if this came from a SendEvent request */
17028 	Display *display;	/* Display the event was read from */
17029 	Drawable drawable;
17030 	int x, y;
17031 	int width, height;
17032 	int count;		/* if non-zero, at least this many more */
17033 	int major_code;		/* core is CopyArea or CopyPlane */
17034 	int minor_code;		/* not defined in the core */
17035 }
17036 
17037 struct XNoExposeEvent{
17038 	int type;
17039 	arch_ulong serial;	/* # of last request processed by server */
17040 	Bool send_event;	/* true if this came from a SendEvent request */
17041 	Display *display;	/* Display the event was read from */
17042 	Drawable drawable;
17043 	int major_code;		/* core is CopyArea or CopyPlane */
17044 	int minor_code;		/* not defined in the core */
17045 }
17046 
17047 struct XVisibilityEvent{
17048 	int type;
17049 	arch_ulong serial;	/* # of last request processed by server */
17050 	Bool send_event;	/* true if this came from a SendEvent request */
17051 	Display *display;	/* Display the event was read from */
17052 	Window window;
17053 	VisibilityNotify state;		/* Visibility state */
17054 }
17055 
17056 struct XCreateWindowEvent{
17057 	int type;
17058 	arch_ulong serial;	/* # of last request processed by server */
17059 	Bool send_event;	/* true if this came from a SendEvent request */
17060 	Display *display;	/* Display the event was read from */
17061 	Window parent;		/* parent of the window */
17062 	Window window;		/* window id of window created */
17063 	int x, y;		/* window location */
17064 	int width, height;	/* size of window */
17065 	int border_width;	/* border width */
17066 	Bool override_redirect;	/* creation should be overridden */
17067 }
17068 
17069 struct XDestroyWindowEvent
17070 {
17071 	int type;
17072 	arch_ulong serial;		/* # of last request processed by server */
17073 	Bool send_event;	/* true if this came from a SendEvent request */
17074 	Display *display;	/* Display the event was read from */
17075 	Window event;
17076 	Window window;
17077 }
17078 
17079 struct XUnmapEvent
17080 {
17081 	int type;
17082 	arch_ulong serial;		/* # of last request processed by server */
17083 	Bool send_event;	/* true if this came from a SendEvent request */
17084 	Display *display;	/* Display the event was read from */
17085 	Window event;
17086 	Window window;
17087 	Bool from_configure;
17088 }
17089 
17090 struct XMapEvent
17091 {
17092 	int type;
17093 	arch_ulong serial;		/* # of last request processed by server */
17094 	Bool send_event;	/* true if this came from a SendEvent request */
17095 	Display *display;	/* Display the event was read from */
17096 	Window event;
17097 	Window window;
17098 	Bool override_redirect;	/* Boolean, is override set... */
17099 }
17100 
17101 struct XMapRequestEvent
17102 {
17103 	int type;
17104 	arch_ulong serial;	/* # of last request processed by server */
17105 	Bool send_event;	/* true if this came from a SendEvent request */
17106 	Display *display;	/* Display the event was read from */
17107 	Window parent;
17108 	Window window;
17109 }
17110 
17111 struct XReparentEvent
17112 {
17113 	int type;
17114 	arch_ulong serial;	/* # of last request processed by server */
17115 	Bool send_event;	/* true if this came from a SendEvent request */
17116 	Display *display;	/* Display the event was read from */
17117 	Window event;
17118 	Window window;
17119 	Window parent;
17120 	int x, y;
17121 	Bool override_redirect;
17122 }
17123 
17124 struct XConfigureEvent
17125 {
17126 	int type;
17127 	arch_ulong serial;	/* # of last request processed by server */
17128 	Bool send_event;	/* true if this came from a SendEvent request */
17129 	Display *display;	/* Display the event was read from */
17130 	Window event;
17131 	Window window;
17132 	int x, y;
17133 	int width, height;
17134 	int border_width;
17135 	Window above;
17136 	Bool override_redirect;
17137 }
17138 
17139 struct XGravityEvent
17140 {
17141 	int type;
17142 	arch_ulong serial;	/* # of last request processed by server */
17143 	Bool send_event;	/* true if this came from a SendEvent request */
17144 	Display *display;	/* Display the event was read from */
17145 	Window event;
17146 	Window window;
17147 	int x, y;
17148 }
17149 
17150 struct XResizeRequestEvent
17151 {
17152 	int type;
17153 	arch_ulong serial;	/* # of last request processed by server */
17154 	Bool send_event;	/* true if this came from a SendEvent request */
17155 	Display *display;	/* Display the event was read from */
17156 	Window window;
17157 	int width, height;
17158 }
17159 
17160 struct  XConfigureRequestEvent
17161 {
17162 	int type;
17163 	arch_ulong serial;	/* # of last request processed by server */
17164 	Bool send_event;	/* true if this came from a SendEvent request */
17165 	Display *display;	/* Display the event was read from */
17166 	Window parent;
17167 	Window window;
17168 	int x, y;
17169 	int width, height;
17170 	int border_width;
17171 	Window above;
17172 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17173 	arch_ulong value_mask;
17174 }
17175 
17176 struct XCirculateEvent
17177 {
17178 	int type;
17179 	arch_ulong serial;	/* # of last request processed by server */
17180 	Bool send_event;	/* true if this came from a SendEvent request */
17181 	Display *display;	/* Display the event was read from */
17182 	Window event;
17183 	Window window;
17184 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17185 }
17186 
17187 struct XCirculateRequestEvent
17188 {
17189 	int type;
17190 	arch_ulong serial;	/* # of last request processed by server */
17191 	Bool send_event;	/* true if this came from a SendEvent request */
17192 	Display *display;	/* Display the event was read from */
17193 	Window parent;
17194 	Window window;
17195 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17196 }
17197 
17198 struct XPropertyEvent
17199 {
17200 	int type;
17201 	arch_ulong serial;	/* # of last request processed by server */
17202 	Bool send_event;	/* true if this came from a SendEvent request */
17203 	Display *display;	/* Display the event was read from */
17204 	Window window;
17205 	Atom atom;
17206 	Time time;
17207 	PropertyNotification state;		/* NewValue, Deleted */
17208 }
17209 
17210 struct XSelectionClearEvent
17211 {
17212 	int type;
17213 	arch_ulong serial;	/* # of last request processed by server */
17214 	Bool send_event;	/* true if this came from a SendEvent request */
17215 	Display *display;	/* Display the event was read from */
17216 	Window window;
17217 	Atom selection;
17218 	Time time;
17219 }
17220 
17221 struct XSelectionRequestEvent
17222 {
17223 	int type;
17224 	arch_ulong serial;	/* # of last request processed by server */
17225 	Bool send_event;	/* true if this came from a SendEvent request */
17226 	Display *display;	/* Display the event was read from */
17227 	Window owner;
17228 	Window requestor;
17229 	Atom selection;
17230 	Atom target;
17231 	Atom property;
17232 	Time time;
17233 }
17234 
17235 struct XSelectionEvent
17236 {
17237 	int type;
17238 	arch_ulong serial;	/* # of last request processed by server */
17239 	Bool send_event;	/* true if this came from a SendEvent request */
17240 	Display *display;	/* Display the event was read from */
17241 	Window requestor;
17242 	Atom selection;
17243 	Atom target;
17244 	Atom property;		/* ATOM or None */
17245 	Time time;
17246 }
17247 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17248 
17249 struct XColormapEvent
17250 {
17251 	int type;
17252 	arch_ulong serial;	/* # of last request processed by server */
17253 	Bool send_event;	/* true if this came from a SendEvent request */
17254 	Display *display;	/* Display the event was read from */
17255 	Window window;
17256 	Colormap colormap;	/* COLORMAP or None */
17257 	Bool new_;		/* C++ */
17258 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17259 }
17260 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17261 
17262 struct XClientMessageEvent
17263 {
17264 	int type;
17265 	arch_ulong serial;	/* # of last request processed by server */
17266 	Bool send_event;	/* true if this came from a SendEvent request */
17267 	Display *display;	/* Display the event was read from */
17268 	Window window;
17269 	Atom message_type;
17270 	int format;
17271 	union Data{
17272 		byte[20] b;
17273 		short[10] s;
17274 		arch_ulong[5] l;
17275 	}
17276 	Data data;
17277 
17278 }
17279 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17280 
17281 struct XMappingEvent
17282 {
17283 	int type;
17284 	arch_ulong serial;	/* # of last request processed by server */
17285 	Bool send_event;	/* true if this came from a SendEvent request */
17286 	Display *display;	/* Display the event was read from */
17287 	Window window;		/* unused */
17288 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17289 				   MappingPointer */
17290 	int first_keycode;	/* first keycode */
17291 	int count;		/* defines range of change w. first_keycode*/
17292 }
17293 
17294 struct XErrorEvent
17295 {
17296 	int type;
17297 	Display *display;	/* Display the event was read from */
17298 	XID resourceid;		/* resource id */
17299 	arch_ulong serial;	/* serial number of failed request */
17300 	ubyte error_code;	/* error code of failed request */
17301 	ubyte request_code;	/* Major op-code of failed request */
17302 	ubyte minor_code;	/* Minor op-code of failed request */
17303 }
17304 
17305 struct XAnyEvent
17306 {
17307 	int type;
17308 	arch_ulong serial;	/* # of last request processed by server */
17309 	Bool send_event;	/* true if this came from a SendEvent request */
17310 	Display *display;/* Display the event was read from */
17311 	Window window;	/* window on which event was requested in event mask */
17312 }
17313 
17314 union XEvent{
17315 	int type;		/* must not be changed; first element */
17316 	XAnyEvent xany;
17317 	XKeyEvent xkey;
17318 	XButtonEvent xbutton;
17319 	XMotionEvent xmotion;
17320 	XCrossingEvent xcrossing;
17321 	XFocusChangeEvent xfocus;
17322 	XExposeEvent xexpose;
17323 	XGraphicsExposeEvent xgraphicsexpose;
17324 	XNoExposeEvent xnoexpose;
17325 	XVisibilityEvent xvisibility;
17326 	XCreateWindowEvent xcreatewindow;
17327 	XDestroyWindowEvent xdestroywindow;
17328 	XUnmapEvent xunmap;
17329 	XMapEvent xmap;
17330 	XMapRequestEvent xmaprequest;
17331 	XReparentEvent xreparent;
17332 	XConfigureEvent xconfigure;
17333 	XGravityEvent xgravity;
17334 	XResizeRequestEvent xresizerequest;
17335 	XConfigureRequestEvent xconfigurerequest;
17336 	XCirculateEvent xcirculate;
17337 	XCirculateRequestEvent xcirculaterequest;
17338 	XPropertyEvent xproperty;
17339 	XSelectionClearEvent xselectionclear;
17340 	XSelectionRequestEvent xselectionrequest;
17341 	XSelectionEvent xselection;
17342 	XColormapEvent xcolormap;
17343 	XClientMessageEvent xclient;
17344 	XMappingEvent xmapping;
17345 	XErrorEvent xerror;
17346 	XKeymapEvent xkeymap;
17347 	arch_ulong[24] pad;
17348 }
17349 
17350 
17351 	struct Display {
17352 		XExtData *ext_data;	/* hook for extension to hang data */
17353 		_XPrivate *private1;
17354 		int fd;			/* Network socket. */
17355 		int private2;
17356 		int proto_major_version;/* major version of server's X protocol */
17357 		int proto_minor_version;/* minor version of servers X protocol */
17358 		char *vendor;		/* vendor of the server hardware */
17359 	    	XID private3;
17360 		XID private4;
17361 		XID private5;
17362 		int private6;
17363 		XID function(Display*)resource_alloc;/* allocator function */
17364 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17365 		int bitmap_unit;	/* padding and data requirements */
17366 		int bitmap_pad;		/* padding requirements on bitmaps */
17367 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17368 		int nformats;		/* number of pixmap formats in list */
17369 		ScreenFormat *pixmap_format;	/* pixmap format list */
17370 		int private8;
17371 		int release;		/* release of the server */
17372 		_XPrivate *private9;
17373 		_XPrivate *private10;
17374 		int qlen;		/* Length of input event queue */
17375 		arch_ulong last_request_read; /* seq number of last event read */
17376 		arch_ulong request;	/* sequence number of last request. */
17377 		XPointer private11;
17378 		XPointer private12;
17379 		XPointer private13;
17380 		XPointer private14;
17381 		uint max_request_size; /* maximum number 32 bit words in request*/
17382 		_XrmHashBucketRec *db;
17383 		int function  (Display*)private15;
17384 		char *display_name;	/* "host:display" string used on this connect*/
17385 		int default_screen;	/* default screen for operations */
17386 		int nscreens;		/* number of screens on this server*/
17387 		Screen *screens;	/* pointer to list of screens */
17388 		arch_ulong motion_buffer;	/* size of motion buffer */
17389 		arch_ulong private16;
17390 		int min_keycode;	/* minimum defined keycode */
17391 		int max_keycode;	/* maximum defined keycode */
17392 		XPointer private17;
17393 		XPointer private18;
17394 		int private19;
17395 		byte *xdefaults;	/* contents of defaults from server */
17396 		/* there is more to this structure, but it is private to Xlib */
17397 	}
17398 
17399 	// I got these numbers from a C program as a sanity test
17400 	version(X86_64) {
17401 		static assert(Display.sizeof == 296);
17402 		static assert(XPointer.sizeof == 8);
17403 		static assert(XErrorEvent.sizeof == 40);
17404 		static assert(XAnyEvent.sizeof == 40);
17405 		static assert(XMappingEvent.sizeof == 56);
17406 		static assert(XEvent.sizeof == 192);
17407     	} else version (AArch64) {
17408         	// omit check for aarch64
17409 	} else {
17410 		static assert(Display.sizeof == 176);
17411 		static assert(XPointer.sizeof == 4);
17412 		static assert(XEvent.sizeof == 96);
17413 	}
17414 
17415 struct Depth
17416 {
17417 	int depth;		/* this depth (Z) of the depth */
17418 	int nvisuals;		/* number of Visual types at this depth */
17419 	Visual *visuals;	/* list of visuals possible at this depth */
17420 }
17421 
17422 alias void* GC;
17423 alias c_ulong VisualID;
17424 alias XID Colormap;
17425 alias XID Cursor;
17426 alias XID KeySym;
17427 alias uint KeyCode;
17428 enum None = 0;
17429 }
17430 
17431 version(without_opengl) {}
17432 else {
17433 extern(C) nothrow @nogc {
17434 
17435 
17436 static if(!SdpyIsUsingIVGLBinds) {
17437 enum GLX_USE_GL=            1;       /* support GLX rendering */
17438 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17439 enum GLX_LEVEL=             3;       /* level in plane stacking */
17440 enum GLX_RGBA=              4;       /* true if RGBA mode */
17441 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17442 enum GLX_STEREO=            6;       /* stereo buffering supported */
17443 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17444 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17445 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17446 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17447 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17448 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17449 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17450 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17451 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17452 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17453 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17454 
17455 
17456 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17457 
17458 
17459 
17460 enum GL_TRUE = 1;
17461 enum GL_FALSE = 0;
17462 }
17463 
17464 alias XID GLXContextID;
17465 alias XID GLXPixmap;
17466 alias XID GLXDrawable;
17467 alias XID GLXPbuffer;
17468 alias XID GLXWindow;
17469 alias XID GLXFBConfigID;
17470 alias void* GLXContext;
17471 
17472 }
17473 }
17474 
17475 enum AllocNone = 0;
17476 
17477 extern(C) {
17478 	/* WARNING, this type not in Xlib spec */
17479 	extern(C) alias XIOErrorHandler = int function (Display* display);
17480 }
17481 
17482 extern(C) nothrow
17483 alias XErrorHandler = int function(Display*, XErrorEvent*);
17484 
17485 extern(C) nothrow @nogc {
17486 struct Screen{
17487 	XExtData *ext_data;		/* hook for extension to hang data */
17488 	Display *display;		/* back pointer to display structure */
17489 	Window root;			/* Root window id. */
17490 	int width, height;		/* width and height of screen */
17491 	int mwidth, mheight;	/* width and height of  in millimeters */
17492 	int ndepths;			/* number of depths possible */
17493 	Depth *depths;			/* list of allowable depths on the screen */
17494 	int root_depth;			/* bits per pixel */
17495 	Visual *root_visual;	/* root visual */
17496 	GC default_gc;			/* GC for the root root visual */
17497 	Colormap cmap;			/* default color map */
17498 	uint white_pixel;
17499 	uint black_pixel;		/* White and Black pixel values */
17500 	int max_maps, min_maps;	/* max and min color maps */
17501 	int backing_store;		/* Never, WhenMapped, Always */
17502 	bool save_unders;
17503 	int root_input_mask;	/* initial root input mask */
17504 }
17505 
17506 struct Visual
17507 {
17508 	XExtData *ext_data;	/* hook for extension to hang data */
17509 	VisualID visualid;	/* visual id of this visual */
17510 	int class_;			/* class of screen (monochrome, etc.) */
17511 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17512 	int bits_per_rgb;	/* log base 2 of distinct color values */
17513 	int map_entries;	/* color map entries */
17514 }
17515 
17516 	alias Display* _XPrivDisplay;
17517 
17518 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17519 		assert(dpy !is null);
17520 		return &dpy.screens[scr];
17521 	}
17522 
17523 	extern(D) Window RootWindow(Display *dpy,int scr) {
17524 		return ScreenOfDisplay(dpy,scr).root;
17525 	}
17526 
17527 	struct XWMHints {
17528 		arch_long flags;
17529 		Bool input;
17530 		int initial_state;
17531 		Pixmap icon_pixmap;
17532 		Window icon_window;
17533 		int icon_x, icon_y;
17534 		Pixmap icon_mask;
17535 		XID window_group;
17536 	}
17537 
17538 	struct XClassHint {
17539 		char* res_name;
17540 		char* res_class;
17541 	}
17542 
17543 	extern(D) int DefaultScreen(Display *dpy) {
17544 		return dpy.default_screen;
17545 	}
17546 
17547 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17548 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17549 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17550 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17551 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17552 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17553 
17554 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17555 
17556 	enum int AnyPropertyType = 0;
17557 	enum int Success = 0;
17558 
17559 	enum int RevertToNone = None;
17560 	enum int PointerRoot = 1;
17561 	enum Time CurrentTime = 0;
17562 	enum int RevertToPointerRoot = PointerRoot;
17563 	enum int RevertToParent = 2;
17564 
17565 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17566 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17567 	}
17568 
17569 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17570 		return ScreenOfDisplay(dpy,scr).root_visual;
17571 	}
17572 
17573 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17574 		return ScreenOfDisplay(dpy,scr).default_gc;
17575 	}
17576 
17577 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17578 		return ScreenOfDisplay(dpy,scr).black_pixel;
17579 	}
17580 
17581 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17582 		return ScreenOfDisplay(dpy,scr).white_pixel;
17583 	}
17584 
17585 	alias void* XFontSet; // i think
17586 	struct XmbTextItem {
17587 		char* chars;
17588 		int nchars;
17589 		int delta;
17590 		XFontSet font_set;
17591 	}
17592 
17593 	struct XTextItem {
17594 		char* chars;
17595 		int nchars;
17596 		int delta;
17597 		Font font;
17598 	}
17599 
17600 	enum {
17601 		GXclear        = 0x0, /* 0 */
17602 		GXand          = 0x1, /* src AND dst */
17603 		GXandReverse   = 0x2, /* src AND NOT dst */
17604 		GXcopy         = 0x3, /* src */
17605 		GXandInverted  = 0x4, /* NOT src AND dst */
17606 		GXnoop         = 0x5, /* dst */
17607 		GXxor          = 0x6, /* src XOR dst */
17608 		GXor           = 0x7, /* src OR dst */
17609 		GXnor          = 0x8, /* NOT src AND NOT dst */
17610 		GXequiv        = 0x9, /* NOT src XOR dst */
17611 		GXinvert       = 0xa, /* NOT dst */
17612 		GXorReverse    = 0xb, /* src OR NOT dst */
17613 		GXcopyInverted = 0xc, /* NOT src */
17614 		GXorInverted   = 0xd, /* NOT src OR dst */
17615 		GXnand         = 0xe, /* NOT src OR NOT dst */
17616 		GXset          = 0xf, /* 1 */
17617 	}
17618 	enum QueueMode : int {
17619 		QueuedAlready,
17620 		QueuedAfterReading,
17621 		QueuedAfterFlush
17622 	}
17623 
17624 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17625 
17626 	struct XPoint {
17627 		short x;
17628 		short y;
17629 	}
17630 
17631 	enum CoordMode:int {
17632 		CoordModeOrigin = 0,
17633 		CoordModePrevious = 1
17634 	}
17635 
17636 	enum PolygonShape:int {
17637 		Complex = 0,
17638 		Nonconvex = 1,
17639 		Convex = 2
17640 	}
17641 
17642 	struct XTextProperty {
17643 		const(char)* value;		/* same as Property routines */
17644 		Atom encoding;			/* prop type */
17645 		int format;				/* prop data format: 8, 16, or 32 */
17646 		arch_ulong nitems;		/* number of data items in value */
17647 	}
17648 
17649 	version( X86_64 ) {
17650 		static assert(XTextProperty.sizeof == 32);
17651 	}
17652 
17653 
17654 	struct XGCValues {
17655 		int function_;           /* logical operation */
17656 		arch_ulong plane_mask;/* plane mask */
17657 		arch_ulong foreground;/* foreground pixel */
17658 		arch_ulong background;/* background pixel */
17659 		int line_width;         /* line width */
17660 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17661 		int cap_style;          /* CapNotLast, CapButt,
17662 					   CapRound, CapProjecting */
17663 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17664 		int fill_style;         /* FillSolid, FillTiled,
17665 					   FillStippled, FillOpaeueStippled */
17666 		int fill_rule;          /* EvenOddRule, WindingRule */
17667 		int arc_mode;           /* ArcChord, ArcPieSlice */
17668 		Pixmap tile;            /* tile pixmap for tiling operations */
17669 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17670 		int ts_x_origin;        /* offset for tile or stipple operations */
17671 		int ts_y_origin;
17672 		Font font;              /* default text font for text operations */
17673 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17674 		Bool graphics_exposures;/* boolean, should exposures be generated */
17675 		int clip_x_origin;      /* origin for clipping */
17676 		int clip_y_origin;
17677 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17678 		int dash_offset;        /* patterned/dashed line information */
17679 		char dashes;
17680 	}
17681 
17682 	struct XColor {
17683 		arch_ulong pixel;
17684 		ushort red, green, blue;
17685 		byte flags;
17686 		byte pad;
17687 	}
17688 
17689 	struct XRectangle {
17690 		short x;
17691 		short y;
17692 		ushort width;
17693 		ushort height;
17694 	}
17695 
17696 	enum ClipByChildren = 0;
17697 	enum IncludeInferiors = 1;
17698 
17699 	enum Atom XA_PRIMARY = 1;
17700 	enum Atom XA_SECONDARY = 2;
17701 	enum Atom XA_STRING = 31;
17702 	enum Atom XA_CARDINAL = 6;
17703 	enum Atom XA_WM_NAME = 39;
17704 	enum Atom XA_ATOM = 4;
17705 	enum Atom XA_WINDOW = 33;
17706 	enum Atom XA_WM_HINTS = 35;
17707 	enum int PropModeAppend = 2;
17708 	enum int PropModeReplace = 0;
17709 	enum int PropModePrepend = 1;
17710 
17711 	enum int CopyFromParent = 0;
17712 	enum int InputOutput = 1;
17713 
17714 	// XWMHints
17715 	enum InputHint = 1 << 0;
17716 	enum StateHint = 1 << 1;
17717 	enum IconPixmapHint = (1L << 2);
17718 	enum IconWindowHint = (1L << 3);
17719 	enum IconPositionHint = (1L << 4);
17720 	enum IconMaskHint = (1L << 5);
17721 	enum WindowGroupHint = (1L << 6);
17722 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17723 	enum XUrgencyHint = (1L << 8);
17724 
17725 	// GC Components
17726 	enum GCFunction           =   (1L<<0);
17727 	enum GCPlaneMask         =    (1L<<1);
17728 	enum GCForeground       =     (1L<<2);
17729 	enum GCBackground      =      (1L<<3);
17730 	enum GCLineWidth      =       (1L<<4);
17731 	enum GCLineStyle     =        (1L<<5);
17732 	enum GCCapStyle     =         (1L<<6);
17733 	enum GCJoinStyle   =          (1L<<7);
17734 	enum GCFillStyle  =           (1L<<8);
17735 	enum GCFillRule  =            (1L<<9);
17736 	enum GCTile     =             (1L<<10);
17737 	enum GCStipple           =    (1L<<11);
17738 	enum GCTileStipXOrigin  =     (1L<<12);
17739 	enum GCTileStipYOrigin =      (1L<<13);
17740 	enum GCFont               =   (1L<<14);
17741 	enum GCSubwindowMode     =    (1L<<15);
17742 	enum GCGraphicsExposures=     (1L<<16);
17743 	enum GCClipXOrigin     =      (1L<<17);
17744 	enum GCClipYOrigin    =       (1L<<18);
17745 	enum GCClipMask      =        (1L<<19);
17746 	enum GCDashOffset   =         (1L<<20);
17747 	enum GCDashList    =          (1L<<21);
17748 	enum GCArcMode    =           (1L<<22);
17749 	enum GCLastBit   =            22;
17750 
17751 
17752 	enum int WithdrawnState = 0;
17753 	enum int NormalState = 1;
17754 	enum int IconicState = 3;
17755 
17756 }
17757 } else version (OSXCocoa) {
17758 
17759 /+
17760 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
17761 +/
17762 
17763 	private __gshared AppDelegate globalAppDelegate;
17764 
17765 	extern(Objective-C)
17766 	class AppDelegate : NSObject, NSApplicationDelegate {
17767 		override static AppDelegate alloc() @selector("alloc");
17768 
17769 
17770 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
17771 			SimpleWindow.processAllCustomEvents();
17772 		}
17773 
17774 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
17775 			immutable style = NSWindowStyleMask.resizable |
17776 				NSWindowStyleMask.closable |
17777 				NSWindowStyleMask.miniaturizable |
17778 				NSWindowStyleMask.titled;
17779 
17780 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
17781 
17782 			{
17783 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
17784 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
17785 				mainMenu.setSubmenu(menu, item);
17786 
17787 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
17788 				newItem.target = NSApp;
17789 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
17790 				newItem2.target = NSApp;
17791 			}
17792 
17793 			{
17794 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
17795 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
17796 				mainMenu.setSubmenu(menu, item);
17797 
17798 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
17799 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
17800 			}
17801 
17802 
17803 			NSApp.menu = mainMenu;
17804 
17805 
17806 			// auto controller = ViewController.alloc.init;
17807 
17808 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
17809 
17810 			/+
17811 			this.window = window;
17812 			this.controller = controller;
17813 			+/
17814 		}
17815 
17816 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
17817 			NSApplication.shared_.activateIgnoringOtherApps(true);
17818 		}
17819 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
17820 			return true;
17821 		}
17822 	}
17823 
17824 	extern(Objective-C)
17825 	class SDWindowDelegate : NSObject, NSWindowDelegate {
17826 		override static SDWindowDelegate alloc() @selector("alloc");
17827 		override SDWindowDelegate init() @selector("init");
17828 
17829 		SimpleWindow simpleWindow;
17830 
17831 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
17832 			auto window = cast(void*) notification.object;
17833 
17834 			// FIXME: do i need to release it?
17835 			SimpleWindow.nativeMapping.remove(window);
17836 		}
17837 
17838 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
17839 			if(simpleWindow.windowResized) {
17840 				// FIXME: automaticallyScaleIfPossible behaviors
17841 
17842 				simpleWindow._width = cast(int) frameSize.width;
17843 				simpleWindow._height = cast(int) frameSize.height;
17844 
17845 				simpleWindow.view.setFrameSize(frameSize);
17846 
17847 				/+
17848 				auto size = simpleWindow.view.frame.size;
17849 				writeln(cast(int) size.width, "x", cast(int) size.height);
17850 				+/
17851 
17852 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
17853 
17854 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
17855 
17856 				// simpleWindow.view.setNeedsDisplay(true);
17857 			}
17858 
17859 			return frameSize;
17860 		}
17861 
17862 		/+
17863 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
17864 			if(simpleWindow.windowResized) {
17865 				auto window = simpleWindow.window;
17866 				auto rect = window.contentRectForFrameRect(window.frame);
17867 				import std.stdio; writeln(window.frame.size);
17868 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
17869 			}
17870 		}
17871 		+/
17872 	}
17873 
17874 	extern(Objective-C)
17875 	class SDGraphicsView : NSView {
17876 		SimpleWindow simpleWindow;
17877 
17878 		override static SDGraphicsView alloc() @selector("alloc");
17879 		override SDGraphicsView init() @selector("init") {
17880 			super.init();
17881 			return this;
17882 		}
17883 
17884 		override void drawRect(NSRect rect) @selector("drawRect:") {
17885 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
17886 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
17887 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
17888 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
17889 			CGImageRelease(cgImage);
17890 		}
17891 
17892 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
17893 			MouseEvent me;
17894 			me.type = type;
17895 
17896 			auto pos = event.locationInWindow;
17897 
17898 			me.x = cast(int) pos.x;
17899 			me.y = cast(int) (simpleWindow.height - pos.y);
17900 
17901 			me.dx = 0; // FIXME
17902 			me.dy = 0; // FIXME
17903 
17904 			me.button = button;
17905 			me.modifierState = cast(uint) event.modifierFlags;
17906 			me.window = simpleWindow;
17907 
17908 			me.doubleClick = false;
17909 
17910 			if(simpleWindow && simpleWindow.handleMouseEvent)
17911 				simpleWindow.handleMouseEvent(me);
17912 		}
17913 
17914 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
17915 			// writeln(event.pressedMouseButtons);
17916 
17917 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17918 		}
17919 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
17920 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
17921 		}
17922 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
17923 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
17924 		}
17925 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
17926 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
17927 		}
17928 		/+
17929 			// FIXME
17930 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
17931 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17932 		}
17933 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
17934 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17935 		}
17936 		+/
17937 
17938 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
17939 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
17940 		}
17941 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
17942 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
17943 		}
17944 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
17945 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
17946 		}
17947 
17948 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
17949 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
17950 		}
17951 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
17952 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
17953 		}
17954 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
17955 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
17956 		}
17957 
17958 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
17959 			import std.stdio;
17960 			writeln(event.deltaY);
17961 		}
17962 
17963 		override void keyDown(NSEvent event) @selector("keyDown:") {
17964 			// the event may have multiple characters, and we send them all at once.
17965 			if (simpleWindow.handleCharEvent) {
17966 				auto chars = DeifiedNSString(event.characters);
17967 				foreach (dchar dc; chars.str)
17968 					simpleWindow.handleCharEvent(dc);
17969 			}
17970 
17971 			keyHelper(event, true);
17972 		}
17973 
17974 		override void keyUp(NSEvent event) @selector("keyUp:") {
17975 			keyHelper(event, false);
17976 		}
17977 
17978 		private void keyHelper(NSEvent event, bool pressed) {
17979 			if(simpleWindow.handleKeyEvent) {
17980 				KeyEvent ev;
17981 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
17982 				ev.pressed = pressed;
17983 				ev.hardwareCode = cast(ubyte) event.keyCode;
17984 				ev.modifierState = cast(uint) event.modifierFlags;
17985 				ev.window = simpleWindow;
17986 
17987 				simpleWindow.handleKeyEvent(ev);
17988 			}
17989 		}
17990 
17991 		override bool isFlipped() @selector("isFlipped") {
17992 			return true;
17993 		}
17994 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
17995 			return true;
17996 		}
17997 
17998 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
17999 			if(simpleWindow && simpleWindow.handlePulse)
18000 				simpleWindow.handlePulse();
18001 			/+
18002 			setNeedsDisplay = true;
18003 			+/
18004 		}
18005 	}
18006 
18007 private:
18008 	alias const(void)* CFStringRef;
18009 	alias const(void)* CFAllocatorRef;
18010 	alias const(void)* CFTypeRef;
18011 	alias const(void)* CGColorSpaceRef;
18012 	alias const(void)* CGImageRef;
18013 	alias ulong CGBitmapInfo;
18014 	alias NSGraphicsContext CGContextRef;
18015 
18016 	alias NSPoint CGPoint;
18017 	alias NSSize CGSize;
18018 	alias NSRect CGRect;
18019 
18020 	struct CGAffineTransform {
18021 		double a, b, c, d, tx, ty;
18022 	}
18023 
18024 	enum NSApplicationActivationPolicyRegular = 0;
18025 	enum NSBackingStoreBuffered = 2;
18026 	enum kCFStringEncodingUTF8 = 0x08000100;
18027 
18028 	enum : size_t {
18029 		NSBorderlessWindowMask = 0,
18030 		NSTitledWindowMask = 1 << 0,
18031 		NSClosableWindowMask = 1 << 1,
18032 		NSMiniaturizableWindowMask = 1 << 2,
18033 		NSResizableWindowMask = 1 << 3,
18034 		NSTexturedBackgroundWindowMask = 1 << 8
18035 	}
18036 
18037 	enum : ulong {
18038 		kCGImageAlphaNone,
18039 		kCGImageAlphaPremultipliedLast,
18040 		kCGImageAlphaPremultipliedFirst,
18041 		kCGImageAlphaLast,
18042 		kCGImageAlphaFirst,
18043 		kCGImageAlphaNoneSkipLast,
18044 		kCGImageAlphaNoneSkipFirst
18045 	}
18046 	enum : ulong {
18047 		kCGBitmapAlphaInfoMask = 0x1F,
18048 		kCGBitmapFloatComponents = (1 << 8),
18049 		kCGBitmapByteOrderMask = 0x7000,
18050 		kCGBitmapByteOrderDefault = (0 << 12),
18051 		kCGBitmapByteOrder16Little = (1 << 12),
18052 		kCGBitmapByteOrder32Little = (2 << 12),
18053 		kCGBitmapByteOrder16Big = (3 << 12),
18054 		kCGBitmapByteOrder32Big = (4 << 12)
18055 	}
18056 	enum CGPathDrawingMode {
18057 		kCGPathFill,
18058 		kCGPathEOFill,
18059 		kCGPathStroke,
18060 		kCGPathFillStroke,
18061 		kCGPathEOFillStroke
18062 	}
18063 	enum objc_AssociationPolicy : size_t {
18064 		OBJC_ASSOCIATION_ASSIGN = 0,
18065 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18066 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18067 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18068 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18069 	}
18070 
18071 	extern(C) {
18072 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18073 		void CGContextRelease(CGContextRef c);
18074 		ubyte* CGBitmapContextGetData(CGContextRef c);
18075 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18076 		size_t CGBitmapContextGetWidth(CGContextRef c);
18077 		size_t CGBitmapContextGetHeight(CGContextRef c);
18078 
18079 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18080 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18081 
18082 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18083 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18084 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18085 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18086 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18087 		void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
18088 
18089 		void CGContextBeginPath(CGContextRef c);
18090 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18091 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18092 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18093 		void CGContextAddRect(CGContextRef c, CGRect rect);
18094 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18095 		void CGContextSaveGState(CGContextRef c);
18096 		void CGContextRestoreGState(CGContextRef c);
18097 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18098 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18099 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18100 
18101 		void CGImageRelease(CGImageRef image);
18102 	}
18103 } else static assert(0, "Unsupported operating system");
18104 
18105 
18106 version(OSXCocoa) {
18107 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18108 	//
18109 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18110 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18111 	//
18112 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18113 	// Probably won't even fully compile right now
18114 
18115 	private enum double PI = 3.14159265358979323;
18116 
18117 	alias NSWindow NativeWindowHandle;
18118 	alias void delegate(NSid) NativeEventHandler;
18119 
18120 	enum KEY_ESCAPE = 27;
18121 
18122 	mixin template NativeImageImplementation() {
18123 		CGContextRef context;
18124 		ubyte* rawData;
18125 
18126 		final:
18127 
18128 		void convertToRgbaBytes(ubyte[] where) {
18129 			assert(where.length == this.width * this.height * 4);
18130 
18131 			// if rawData had a length....
18132 			//assert(rawData.length == where.length);
18133 			for(long idx = 0; idx < where.length; idx += 4) {
18134 				auto alpha = rawData[idx + 3];
18135 				if(alpha == 255) {
18136 					where[idx + 0] = rawData[idx + 0]; // r
18137 					where[idx + 1] = rawData[idx + 1]; // g
18138 					where[idx + 2] = rawData[idx + 2]; // b
18139 					where[idx + 3] = rawData[idx + 3]; // a
18140 				} else {
18141 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18142 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18143 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18144 					where[idx + 3] = rawData[idx + 3]; // a
18145 
18146 				}
18147 			}
18148 		}
18149 
18150 		void setFromRgbaBytes(in ubyte[] where) {
18151 			// FIXME: this is probably wrong
18152 			assert(where.length == this.width * this.height * 4);
18153 
18154 			// if rawData had a length....
18155 			//assert(rawData.length == where.length);
18156 			for(long idx = 0; idx < where.length; idx += 4) {
18157 				auto alpha = where[idx + 3];
18158 				if(alpha == 255) {
18159 					rawData[idx + 0] = where[idx + 0]; // r
18160 					rawData[idx + 1] = where[idx + 1]; // g
18161 					rawData[idx + 2] = where[idx + 2]; // b
18162 					rawData[idx + 3] = where[idx + 3]; // a
18163 				} else if(alpha == 0) {
18164 					rawData[idx + 0] = 0;
18165 					rawData[idx + 1] = 0;
18166 					rawData[idx + 2] = 0;
18167 					rawData[idx + 3] = 0;
18168 				} else {
18169 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18170 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18171 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18172 					rawData[idx + 3] = where[idx + 3]; // a
18173 				}
18174 			}
18175 		}
18176 
18177 
18178 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18179 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18180 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18181 			CGColorSpaceRelease(colorSpace);
18182 			rawData = CGBitmapContextGetData(context);
18183 		}
18184 		void dispose() {
18185 			CGContextRelease(context);
18186 		}
18187 
18188 		void setPixel(int x, int y, Color c) {
18189 			auto offset = (y * width + x) * 4;
18190 			if (c.a == 255) {
18191 				rawData[offset + 0] = c.r;
18192 				rawData[offset + 1] = c.g;
18193 				rawData[offset + 2] = c.b;
18194 				rawData[offset + 3] = c.a;
18195 			} else {
18196 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18197 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18198 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18199 				rawData[offset + 3] = c.a;
18200 			}
18201 		}
18202 	}
18203 
18204 	mixin template NativeScreenPainterImplementation() {
18205 		CGContextRef context;
18206 		ubyte[4] _outlineComponents;
18207 		NSView view;
18208 
18209 		Pen _activePen;
18210 		Color _fillColor;
18211 		Rectangle _clipRectangle;
18212 		OperatingSystemFont _font;
18213 
18214 		OperatingSystemFont getFont() {
18215 			if(_font is null) {
18216 				static OperatingSystemFont _defaultFont;
18217 				if(_defaultFont is null) {
18218 					_defaultFont = new OperatingSystemFont();
18219 					_defaultFont.loadDefault();
18220 				}
18221 				_font = _defaultFont;
18222 			}
18223 
18224 			return _font;
18225 		}
18226 
18227 		void create(PaintingHandle window) {
18228 			// this.destiny = window;
18229 			if(auto sw = cast(SimpleWindow) this.window) {
18230 				context = sw.drawingContext;
18231 				view = sw.view;
18232 			} else {
18233 				throw new NotYetImplementedException();
18234 			}
18235 		}
18236 
18237 		void dispose() {
18238 			view.setNeedsDisplay(true);
18239 		}
18240 
18241 		bool manualInvalidations;
18242 		void invalidateRect(Rectangle invalidRect) { }
18243 
18244 		// NotYetImplementedException
18245 		void rasterOp(RasterOp op) {
18246 		}
18247 		void setClipRectangle(int, int, int, int) {
18248 		}
18249 		Size textSize(in char[] txt) {
18250 			auto font = getFont();
18251 			return Size(font.stringWidth(txt), font.height());
18252 		}
18253 
18254 		void setFont(OperatingSystemFont font) {
18255 			_font = font;
18256 			//font.font.setInContext(context);
18257 		}
18258 		int fontHeight() {
18259 			auto font = getFont();
18260 			return font.height;
18261 		}
18262 
18263 		// end
18264 
18265 		void pen(Pen pen) {
18266 			_activePen = pen;
18267 			auto color = pen.color; // FIXME
18268 			double alphaComponent = color.a/255.0f;
18269 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
18270 
18271 			double[2] patternBuffer;
18272 			double[] pattern;
18273 			final switch(pen.style) {
18274 				case Pen.Style.Solid:
18275 					pattern = null;
18276 				break;
18277 				case Pen.Style.Dashed:
18278 					patternBuffer[0] = 4;
18279 					patternBuffer[1] = 1;
18280 					pattern = patternBuffer[];
18281 				break;
18282 				case Pen.Style.Dotted:
18283 					patternBuffer[0] = 1;
18284 					patternBuffer[1] = 1;
18285 					pattern = patternBuffer[];
18286 				break;
18287 			}
18288 
18289 			CGContextSetLineDash(context, 0, pattern.ptr, pattern.length);
18290 
18291 			if (color.a != 255) {
18292 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
18293 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
18294 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
18295 				_outlineComponents[3] = color.a;
18296 			} else {
18297 				_outlineComponents[0] = color.r;
18298 				_outlineComponents[1] = color.g;
18299 				_outlineComponents[2] = color.b;
18300 				_outlineComponents[3] = color.a;
18301 			}
18302 		}
18303 
18304 		@property void fillColor(Color color) {
18305 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
18306 		}
18307 
18308 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
18309 		// NotYetImplementedException for upper left/width/height
18310 			auto cgImage = CGBitmapContextCreateImage(image.context);
18311 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
18312 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18313 			CGImageRelease(cgImage);
18314 		}
18315 
18316 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
18317 		// FIXME: is this efficient?
18318 			auto cgImage = CGBitmapContextCreateImage(s.handle);
18319 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
18320 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18321 			CGImageRelease(cgImage);
18322 		}
18323 
18324 
18325 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
18326 		// FIXME: alignment
18327 			if (_outlineComponents[3] != 0) {
18328 				CGContextSaveGState(context);
18329 				auto invAlpha = 1.0f/_outlineComponents[3];
18330 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
18331 												  _outlineComponents[1]*invAlpha,
18332 												  _outlineComponents[2]*invAlpha,
18333 												  _outlineComponents[3]/255.0f);
18334 
18335 
18336 
18337 				// FIXME: should we clip it to the bounding box?
18338 				int textHeight = fontHeight;
18339 
18340 				auto lines = text.split('\n');
18341 
18342 				const lineHeight = textHeight;
18343 				textHeight *= lines.length;
18344 
18345 				int cy = y;
18346 
18347 				if(alignment & TextAlignment.VerticalBottom) {
18348 					if(y2 <= 0)
18349 						return;
18350 					auto h = y2 - y;
18351 					if(h > textHeight) {
18352 						cy += h - textHeight;
18353 						cy -= lineHeight / 2;
18354 					}
18355 				} else if(alignment & TextAlignment.VerticalCenter) {
18356 					if(y2 <= 0)
18357 						return;
18358 					auto h = y2 - y;
18359 					if(textHeight < h) {
18360 						cy += (h - textHeight) / 2;
18361 						//cy -= lineHeight / 4;
18362 					}
18363 				}
18364 
18365 				foreach(line; text.split('\n')) {
18366 					int textWidth = this.textSize(line).width;
18367 
18368 					int px = x, py = cy;
18369 
18370 					if(alignment & TextAlignment.Center) {
18371 						if(x2 <= 0)
18372 							return;
18373 						auto w = x2 - x;
18374 						if(w > textWidth)
18375 							px += (w - textWidth) / 2;
18376 					} else if(alignment & TextAlignment.Right) {
18377 						if(x2 <= 0)
18378 							return;
18379 						auto pos = x2 - textWidth;
18380 						if(pos > x)
18381 							px = pos;
18382 					}
18383 
18384 					CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length);
18385 
18386 					carry_on:
18387 					cy += lineHeight + 4;
18388 				}
18389 
18390 // auto cfstr = cast(NSid)createCFString(text);
18391 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
18392 // NSPoint(x, y), null);
18393 // CFRelease(cfstr);
18394 				CGContextRestoreGState(context);
18395 			}
18396 		}
18397 
18398 		void drawPixel(int x, int y) {
18399 			auto rawData = CGBitmapContextGetData(context);
18400 			auto width = CGBitmapContextGetWidth(context);
18401 			auto height = CGBitmapContextGetHeight(context);
18402 			auto offset = ((height - y - 1) * width + x) * 4;
18403 			rawData[offset .. offset+4] = _outlineComponents;
18404 		}
18405 
18406 		void drawLine(int x1, int y1, int x2, int y2) {
18407 			CGPoint[2] linePoints;
18408 			linePoints[0] = CGPoint(x1, y1);
18409 			linePoints[1] = CGPoint(x2, y2);
18410 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
18411 		}
18412 
18413 		void drawRectangle(int x, int y, int width, int height) {
18414 			CGContextBeginPath(context);
18415 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18416 			CGContextAddRect(context, rect);
18417 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18418 		}
18419 
18420 		void drawEllipse(int x1, int y1, int x2, int y2) {
18421 			CGContextBeginPath(context);
18422 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18423 			CGContextAddEllipseInRect(context, rect);
18424 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18425 		}
18426 
18427 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
18428 			// @@@BUG@@@ Does not support elliptic arc (width != height).
18429 			CGContextBeginPath(context);
18430 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18431 							start*PI/(180*64), finish*PI/(180*64), 0);
18432 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18433 		}
18434 
18435 		void drawPolygon(Point[] intPoints) {
18436 			CGContextBeginPath(context);
18437 			CGPoint[16] pointsBuffer;
18438 			CGPoint[] points;
18439 			if(intPoints.length <= pointsBuffer.length)
18440 				points = pointsBuffer[0 .. intPoints.length];
18441 			else
18442 				points = new CGPoint[](intPoints.length);
18443 
18444 			foreach(idx, pt; intPoints)
18445 				points[idx] = CGPoint(pt.x, pt.y);
18446 
18447 			CGContextAddLines(context, points.ptr, points.length);
18448 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18449 		}
18450 	}
18451 
18452 	private bool appInitialized = false;
18453 	void initializeApp() {
18454 		if(appInitialized)
18455 			return;
18456 		synchronized {
18457 			if(appInitialized)
18458 				return;
18459 
18460 			auto app = NSApp(); // ensure the is initialized
18461 
18462 			auto dg = AppDelegate.alloc;
18463 			globalAppDelegate = dg;
18464 			NSApp.delegate_ = dg;
18465 
18466 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
18467 
18468 			appInitialized = true;
18469 		}
18470 	}
18471 
18472 	mixin template NativeSimpleWindowImplementation() {
18473 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18474 			initializeApp();
18475 
18476 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18477 
18478 			auto window = NSWindow.alloc.initWithContentRect(
18479 				contentRect,
18480 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
18481 				NSBackingStoreType.buffered,
18482 				true
18483 			);
18484 
18485 			SimpleWindow.nativeMapping[cast(void*) window] = this;
18486 
18487 			window.title = MacString(title).borrow;
18488 
18489 			auto dg = SDWindowDelegate.alloc.init;
18490 			dg.simpleWindow = this;
18491 			window.delegate_ = dg;
18492 
18493 			auto view = SDGraphicsView.alloc.init;
18494 			assert(view !is null);
18495 			window.contentView = view;
18496 			this.view = view;
18497 			view.simpleWindow = this;
18498 
18499 			window.center();
18500 
18501 			window.makeKeyAndOrderFront(null);
18502 
18503 			// no need to make a bitmap on mac since everything is double buffered already
18504 
18505 			// create area to draw on.
18506 			createNewDrawingContext(width, height);
18507 
18508 			window.setBackgroundColor(NSColor.whiteColor);
18509 		}
18510 
18511 		void createNewDrawingContext(int width, int height) {
18512 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
18513 			if(this.drawingContext)
18514 				CGContextRelease(this.drawingContext);
18515 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18516 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18517 			CGColorSpaceRelease(colorSpace);
18518 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18519 			auto matrix = CGContextGetTextMatrix(drawingContext);
18520 			matrix.c = -matrix.c;
18521 			matrix.d = -matrix.d;
18522 			CGContextSetTextMatrix(drawingContext, matrix);
18523 
18524 		}
18525 
18526 		void dispose() {
18527 			closeWindow();
18528 			// window.release(); // closing the window does this automatically i think
18529 		}
18530 		void closeWindow() {
18531 			if(timer)
18532 				timer.invalidate();
18533 			window.close();
18534 		}
18535 
18536 		ScreenPainter getPainter(bool manualInvalidations) {
18537 			return ScreenPainter(this, this.window, manualInvalidations);
18538 		}
18539 
18540 		NSWindow window;
18541 		NSTimer timer;
18542 		NSView view;
18543 		CGContextRef drawingContext;
18544 	}
18545 }
18546 
18547 version(without_opengl) {} else
18548 extern(System) nothrow @nogc {
18549 	//enum uint GL_VERSION = 0x1F02;
18550 	//const(char)* glGetString (/*GLenum*/uint);
18551 	version(X11) {
18552 	static if (!SdpyIsUsingIVGLBinds) {
18553 
18554 		enum GLX_X_RENDERABLE = 0x8012;
18555 		enum GLX_DRAWABLE_TYPE = 0x8010;
18556 		enum GLX_RENDER_TYPE = 0x8011;
18557 		enum GLX_X_VISUAL_TYPE = 0x22;
18558 		enum GLX_TRUE_COLOR = 0x8002;
18559 		enum GLX_WINDOW_BIT = 0x00000001;
18560 		enum GLX_RGBA_BIT = 0x00000001;
18561 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18562 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18563 		enum GLX_SAMPLES = 0x186a1;
18564 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18565 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18566 	}
18567 
18568 		// GLX_EXT_swap_control
18569 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18570 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18571 
18572 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18573 		extern(System) {
18574 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18575 		}
18576 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18577 
18578 		// this made public so we don't have to get it again and again
18579 		public bool glXCreateContextAttribsARB_present () {
18580 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18581 				// get it
18582 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18583 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18584 			}
18585 			return (glXCreateContextAttribsARBFn !is null);
18586 		}
18587 
18588 		// this made public so we don't have to get it again and again
18589 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18590 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18591 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18592 		}
18593 
18594 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18595 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18596 
18597 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18598 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18599 			if (_glx_swapInterval_fn is null) {
18600 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18601 				if (_glx_swapInterval_fn is null) {
18602 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18603 					return;
18604 				}
18605 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
18606 			}
18607 
18608 			if(glXSwapIntervalMESA is null) {
18609 				// it seems to require both to actually take effect on many computers
18610 				// idk why
18611 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18612 				if(glXSwapIntervalMESA is null)
18613 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18614 			}
18615 
18616 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18617 				glXSwapIntervalMESA(wait ? 1 : 0);
18618 
18619 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18620 		}
18621 	} else version(Windows) {
18622 	static if (!SdpyIsUsingIVGLBinds) {
18623 	enum GL_TRUE = 1;
18624 	enum GL_FALSE = 0;
18625 
18626 	public void* glbindGetProcAddress (const(char)* name) {
18627 		void* res = wglGetProcAddress(name);
18628 		if (res is null) {
18629 			/+
18630 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18631 			import core.sys.windows.windef, core.sys.windows.winbase;
18632 			__gshared HINSTANCE dll = null;
18633 			if (dll is null) {
18634 				dll = LoadLibraryA("opengl32.dll");
18635 				if (dll is null) return null; // <32, but idc
18636 			}
18637 			res = GetProcAddress(dll, name);
18638 			+/
18639 			res = GetProcAddress(gl.libHandle, name);
18640 		}
18641 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18642 		return res;
18643 	}
18644 	}
18645 
18646 
18647  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18648         void wglSetVSync(bool wait) {
18649 		if(wglSwapIntervalEXT is null) {
18650 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18651 			if(wglSwapIntervalEXT is null)
18652 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18653 		}
18654 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18655 			return;
18656 
18657 		wglSwapIntervalEXT(wait ? 1 : 0);
18658 	}
18659 
18660 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18661 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18662 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18663 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18664 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18665 
18666 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18667 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18668 
18669 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18670 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18671 
18672 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18673 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18674 
18675 		void wglInitOtherFunctions () {
18676 			if (wglCreateContextAttribsARB is null) {
18677 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18678 			}
18679 		}
18680 	}
18681 
18682 	static if (!SdpyIsUsingIVGLBinds) {
18683 
18684 	interface GL {
18685 		extern(System) @nogc nothrow:
18686 
18687 		void glGetIntegerv(int, void*);
18688 		void glMatrixMode(int);
18689 		void glPushMatrix();
18690 		void glLoadIdentity();
18691 		void glOrtho(double, double, double, double, double, double);
18692 		void glFrustum(double, double, double, double, double, double);
18693 
18694 		void glPopMatrix();
18695 		void glEnable(int);
18696 		void glDisable(int);
18697 		void glClear(int);
18698 		void glBegin(int);
18699 		void glVertex2f(float, float);
18700 		void glVertex3f(float, float, float);
18701 		void glEnd();
18702 		void glColor3b(byte, byte, byte);
18703 		void glColor3ub(ubyte, ubyte, ubyte);
18704 		void glColor4b(byte, byte, byte, byte);
18705 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18706 		void glColor3i(int, int, int);
18707 		void glColor3ui(uint, uint, uint);
18708 		void glColor4i(int, int, int, int);
18709 		void glColor4ui(uint, uint, uint, uint);
18710 		void glColor3f(float, float, float);
18711 		void glColor4f(float, float, float, float);
18712 		void glTranslatef(float, float, float);
18713 		void glScalef(float, float, float);
18714 		version(X11) {
18715 			void glSecondaryColor3b(byte, byte, byte);
18716 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18717 			void glSecondaryColor3i(int, int, int);
18718 			void glSecondaryColor3ui(uint, uint, uint);
18719 			void glSecondaryColor3f(float, float, float);
18720 		}
18721 
18722 		void glDrawElements(int, int, int, void*);
18723 
18724 		void glRotatef(float, float, float, float);
18725 
18726 		uint glGetError();
18727 
18728 		void glDeleteTextures(int, uint*);
18729 
18730 
18731 		void glRasterPos2i(int, int);
18732 		void glDrawPixels(int, int, uint, uint, void*);
18733 		void glClearColor(float, float, float, float);
18734 
18735 
18736 		void glPixelStorei(uint, int);
18737 
18738 		void glGenTextures(uint, uint*);
18739 		void glBindTexture(int, int);
18740 		void glTexParameteri(uint, uint, int);
18741 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18742 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
18743 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18744 			/*GLsizei*/int width, /*GLsizei*/int height,
18745 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18746 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18747 
18748 		void glLineWidth(int);
18749 
18750 
18751 		void glTexCoord2f(float, float);
18752 		void glVertex2i(int, int);
18753 		void glBlendFunc (int, int);
18754 		void glDepthFunc (int);
18755 		void glViewport(int, int, int, int);
18756 
18757 		void glClearDepth(double);
18758 
18759 		void glReadBuffer(uint);
18760 		void glReadPixels(int, int, int, int, int, int, void*);
18761 
18762 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
18763 
18764 		void glFlush();
18765 		void glFinish();
18766 
18767 		version(Windows) {
18768 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18769 			HGLRC wglCreateContext(HDC);
18770 			HGLRC wglCreateLayerContext(HDC, int);
18771 			BOOL wglDeleteContext(HGLRC);
18772 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18773 			HGLRC wglGetCurrentContext();
18774 			HDC wglGetCurrentDC();
18775 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18776 			PROC wglGetProcAddress(LPCSTR);
18777 			BOOL wglMakeCurrent(HDC, HGLRC);
18778 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18779 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18780 			BOOL wglShareLists(HGLRC, HGLRC);
18781 			BOOL wglSwapLayerBuffers(HDC, UINT);
18782 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18783 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18784 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18785 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18786 		}
18787 
18788 	}
18789 
18790 	interface GL3 {
18791 		extern(System) @nogc nothrow:
18792 
18793 		void glGenVertexArrays(GLsizei, GLuint*);
18794 		void glBindVertexArray(GLuint);
18795 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18796 		void glGenerateMipmap(GLenum);
18797 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18798 		void glStencilMask(GLuint);
18799 		void glStencilFunc(GLenum, GLint, GLuint);
18800 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18801 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18802 		GLuint glCreateProgram();
18803 		GLuint glCreateShader(GLenum);
18804 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18805 		void glCompileShader(GLuint);
18806 		void glGetShaderiv(GLuint, GLenum, GLint*);
18807 		void glAttachShader(GLuint, GLuint);
18808 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18809 		void glLinkProgram(GLuint);
18810 		void glGetProgramiv(GLuint, GLenum, GLint*);
18811 		void glDeleteProgram(GLuint);
18812 		void glDeleteShader(GLuint);
18813 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18814 		void glGenBuffers(GLsizei, GLuint*);
18815 
18816 		void glUniform1f(GLint location, GLfloat v0);
18817 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18818 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18819 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18820 		void glUniform1i(GLint location, GLint v0);
18821 		void glUniform2i(GLint location, GLint v0, GLint v1);
18822 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18823 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18824 		void glUniform1ui(GLint location, GLuint v0);
18825 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18826 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18827 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18828 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18829 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18830 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18831 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18832 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18833 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18834 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18835 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18836 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18837 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18838 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18839 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18840 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18841 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18842 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18843 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18844 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18845 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18846 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18847 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18848 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18849 
18850 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18851 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18852 		void glDrawArrays(GLenum, GLint, GLsizei);
18853 		void glStencilOp(GLenum, GLenum, GLenum);
18854 		void glUseProgram(GLuint);
18855 		void glCullFace(GLenum);
18856 		void glFrontFace(GLenum);
18857 		void glActiveTexture(GLenum);
18858 		void glBindBuffer(GLenum, GLuint);
18859 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18860 		void glEnableVertexAttribArray(GLuint);
18861 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18862 		void glUniform1i(GLint, GLint);
18863 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18864 		void glDisableVertexAttribArray(GLuint);
18865 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18866 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18867 		void glLogicOp (GLenum opcode);
18868 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18869 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18870 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18871 		GLenum glCheckFramebufferStatus (GLenum target);
18872 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18873 	}
18874 
18875 	interface GL4 {
18876 		extern(System) @nogc nothrow:
18877 
18878 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18879 			/*GLsizei*/int width, /*GLsizei*/int height,
18880 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18881 	}
18882 
18883 	interface GLU {
18884 		extern(System) @nogc nothrow:
18885 
18886 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18887 		void gluPerspective(double, double, double, double);
18888 
18889 		char* gluErrorString(uint);
18890 	}
18891 
18892 
18893 	enum GL_RED = 0x1903;
18894 	enum GL_ALPHA = 0x1906;
18895 
18896 	enum uint GL_FRONT = 0x0404;
18897 
18898 	enum uint GL_BLEND = 0x0be2;
18899 	enum uint GL_LEQUAL = 0x0203;
18900 
18901 
18902 	enum uint GL_RGB = 0x1907;
18903 	enum uint GL_BGRA = 0x80e1;
18904 	enum uint GL_RGBA = 0x1908;
18905 	enum uint GL_RGBA8 = 0x8058;
18906 	enum uint GL_TEXTURE_2D =   0x0DE1;
18907 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18908 	enum uint GL_NEAREST = 0x2600;
18909 	enum uint GL_LINEAR = 0x2601;
18910 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18911 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18912 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18913 	enum uint GL_REPEAT = 0x2901;
18914 	enum uint GL_CLAMP = 0x2900;
18915 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18916 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18917 	enum uint GL_DECAL = 0x2101;
18918 	enum uint GL_MODULATE = 0x2100;
18919 	enum uint GL_TEXTURE_ENV = 0x2300;
18920 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18921 	enum uint GL_REPLACE = 0x1E01;
18922 	enum uint GL_LIGHTING = 0x0B50;
18923 	enum uint GL_DITHER = 0x0BD0;
18924 
18925 	enum uint GL_NO_ERROR = 0;
18926 
18927 
18928 
18929 	enum int GL_VIEWPORT = 0x0BA2;
18930 	enum int GL_MODELVIEW = 0x1700;
18931 	enum int GL_TEXTURE = 0x1702;
18932 	enum int GL_PROJECTION = 0x1701;
18933 	enum int GL_DEPTH_TEST = 0x0B71;
18934 
18935 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18936 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18937 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18938 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18939 
18940 	enum int GL_POINTS = 0x0000;
18941 	enum int GL_LINES =  0x0001;
18942 	enum int GL_LINE_LOOP = 0x0002;
18943 	enum int GL_LINE_STRIP = 0x0003;
18944 	enum int GL_TRIANGLES = 0x0004;
18945 	enum int GL_TRIANGLE_STRIP = 5;
18946 	enum int GL_TRIANGLE_FAN = 6;
18947 	enum int GL_QUADS = 7;
18948 	enum int GL_QUAD_STRIP = 8;
18949 	enum int GL_POLYGON = 9;
18950 
18951 	alias GLvoid = void;
18952 	alias GLboolean = ubyte;
18953 	alias GLint = int;
18954 	alias GLuint = uint;
18955 	alias GLenum = uint;
18956 	alias GLchar = char;
18957 	alias GLsizei = int;
18958 	alias GLfloat = float;
18959 	alias GLintptr = size_t;
18960 	alias GLsizeiptr = ptrdiff_t;
18961 
18962 
18963 	enum uint GL_INVALID_ENUM = 0x0500;
18964 
18965 	enum uint GL_ZERO = 0;
18966 	enum uint GL_ONE = 1;
18967 
18968 	enum uint GL_BYTE = 0x1400;
18969 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18970 	enum uint GL_SHORT = 0x1402;
18971 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18972 	enum uint GL_INT = 0x1404;
18973 	enum uint GL_UNSIGNED_INT = 0x1405;
18974 	enum uint GL_FLOAT = 0x1406;
18975 	enum uint GL_2_BYTES = 0x1407;
18976 	enum uint GL_3_BYTES = 0x1408;
18977 	enum uint GL_4_BYTES = 0x1409;
18978 	enum uint GL_DOUBLE = 0x140A;
18979 
18980 	enum uint GL_STREAM_DRAW = 0x88E0;
18981 
18982 	enum uint GL_CCW = 0x0901;
18983 
18984 	enum uint GL_STENCIL_TEST = 0x0B90;
18985 	enum uint GL_SCISSOR_TEST = 0x0C11;
18986 
18987 	enum uint GL_EQUAL = 0x0202;
18988 	enum uint GL_NOTEQUAL = 0x0205;
18989 
18990 	enum uint GL_ALWAYS = 0x0207;
18991 	enum uint GL_KEEP = 0x1E00;
18992 
18993 	enum uint GL_INCR = 0x1E02;
18994 
18995 	enum uint GL_INCR_WRAP = 0x8507;
18996 	enum uint GL_DECR_WRAP = 0x8508;
18997 
18998 	enum uint GL_CULL_FACE = 0x0B44;
18999 	enum uint GL_BACK = 0x0405;
19000 
19001 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
19002 	enum uint GL_VERTEX_SHADER = 0x8B31;
19003 
19004 	enum uint GL_COMPILE_STATUS = 0x8B81;
19005 	enum uint GL_LINK_STATUS = 0x8B82;
19006 
19007 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
19008 
19009 	enum uint GL_STATIC_DRAW = 0x88E4;
19010 
19011 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
19012 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
19013 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
19014 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
19015 
19016 	enum uint GL_GENERATE_MIPMAP = 0x8191;
19017 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
19018 
19019 	enum uint GL_TEXTURE0 = 0x84C0U;
19020 	enum uint GL_TEXTURE1 = 0x84C1U;
19021 
19022 	enum uint GL_ARRAY_BUFFER = 0x8892;
19023 
19024 	enum uint GL_SRC_COLOR = 0x0300;
19025 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
19026 	enum uint GL_SRC_ALPHA = 0x0302;
19027 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
19028 	enum uint GL_DST_ALPHA = 0x0304;
19029 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
19030 	enum uint GL_DST_COLOR = 0x0306;
19031 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
19032 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
19033 
19034 	enum uint GL_INVERT = 0x150AU;
19035 
19036 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
19037 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
19038 
19039 	enum uint GL_FRAMEBUFFER = 0x8D40U;
19040 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
19041 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
19042 
19043 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
19044 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
19045 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
19046 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
19047 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
19048 
19049 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
19050 	enum uint GL_CLEAR = 0x1500U;
19051 	enum uint GL_COPY = 0x1503U;
19052 	enum uint GL_XOR = 0x1506U;
19053 
19054 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
19055 
19056 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
19057 
19058 	}
19059 }
19060 
19061 /++
19062 	History:
19063 		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.
19064 +/
19065 __gshared bool gluSuccessfullyLoaded = true;
19066 
19067 version(without_opengl) {} else {
19068 static if(!SdpyIsUsingIVGLBinds) {
19069 	version(Windows) {
19070 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
19071 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
19072 	} else {
19073 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
19074 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
19075 	}
19076 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
19077 
19078 
19079 	shared static this() {
19080 		gl.loadDynamicLibrary();
19081 
19082 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
19083 		// unless those functions are actually used
19084 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
19085 		glu.loadDynamicLibrary();
19086 	}
19087 }
19088 }
19089 
19090 /++
19091 	Convenience method for converting D arrays to opengl buffer data
19092 
19093 	I would LOVE to overload it with the original glBufferData, but D won't
19094 	let me since glBufferData is a function pointer :(
19095 
19096 	Added: August 25, 2020 (version 8.5)
19097 +/
19098 version(without_opengl) {} else
19099 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
19100 	glBufferData(target, data.length, data.ptr, usage);
19101 }
19102 
19103 /+
19104 /++
19105 	A matrix for simple uses that easily integrates with [OpenGlShader].
19106 
19107 	Might not be useful to you since it only as some simple functions and
19108 	probably isn't that fast.
19109 
19110 	Note it uses an inline static array for its storage, so copying it
19111 	may be expensive.
19112 +/
19113 struct BasicMatrix(int columns, int rows, T = float) {
19114 	import core.stdc.math;
19115 
19116 	T[columns * rows] data = 0.0;
19117 
19118 	/++
19119 		Basic operations that operate *in place*.
19120 	+/
19121 	void translate() {
19122 
19123 	}
19124 
19125 	/// ditto
19126 	void scale() {
19127 
19128 	}
19129 
19130 	/// ditto
19131 	void rotate() {
19132 
19133 	}
19134 
19135 	/++
19136 
19137 	+/
19138 	static if(columns == rows)
19139 	static BasicMatrix identity() {
19140 		BasicMatrix m;
19141 		foreach(i; 0 .. columns)
19142 			data[0 + i + i * columns] = 1.0;
19143 		return m;
19144 	}
19145 
19146 	static BasicMatrix ortho() {
19147 		return BasicMatrix.init;
19148 	}
19149 }
19150 +/
19151 
19152 /++
19153 	Convenience class for using opengl shaders.
19154 
19155 	Ensure that you've loaded opengl 3+ and set your active
19156 	context before trying to use this.
19157 
19158 	Added: August 25, 2020 (version 8.5)
19159 +/
19160 version(without_opengl) {} else
19161 final class OpenGlShader {
19162 	private int shaderProgram_;
19163 	private @property void shaderProgram(int a) {
19164 		shaderProgram_ = a;
19165 	}
19166 	/// Get the program ID for use in OpenGL functions.
19167 	public @property int shaderProgram() {
19168 		return shaderProgram_;
19169 	}
19170 
19171 	/++
19172 
19173 	+/
19174 	static struct Source {
19175 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19176 		string code; ///
19177 	}
19178 
19179 	/++
19180 		Helper method to just compile some shader code and check for errors
19181 		while you do glCreateShader, etc. on the outside yourself.
19182 
19183 		This just does `glShaderSource` and `glCompileShader` for the given code.
19184 
19185 		If you the OpenGlShader class constructor, you never need to call this yourself.
19186 	+/
19187 	static void compile(int sid, Source code) {
19188 		const(char)*[1] buffer;
19189 		int[1] lengthBuffer;
19190 
19191 		buffer[0] = code.code.ptr;
19192 		lengthBuffer[0] = cast(int) code.code.length;
19193 
19194 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19195 		glCompileShader(sid);
19196 
19197 		int success;
19198 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19199 		if(!success) {
19200 			char[512] info;
19201 			int len;
19202 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19203 
19204 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19205 		}
19206 	}
19207 
19208 	/++
19209 		Calls `glLinkProgram` and throws if error a occurs.
19210 
19211 		If you the OpenGlShader class constructor, you never need to call this yourself.
19212 	+/
19213 	static void link(int shaderProgram) {
19214 		glLinkProgram(shaderProgram);
19215 		int success;
19216 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19217 		if(!success) {
19218 			char[512] info;
19219 			int len;
19220 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19221 
19222 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19223 		}
19224 	}
19225 
19226 	/++
19227 		Constructs the shader object by calling `glCreateProgram`, then
19228 		compiling each given [Source], and finally, linking them together.
19229 
19230 		Throws: on compile or link failure.
19231 	+/
19232 	this(Source[] codes...) {
19233 		shaderProgram = glCreateProgram();
19234 
19235 		int[16] shadersBufferStack;
19236 
19237 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19238 			shadersBufferStack[0 .. codes.length] :
19239 			new int[](codes.length);
19240 
19241 		foreach(idx, code; codes) {
19242 			shadersBuffer[idx] = glCreateShader(code.type);
19243 
19244 			compile(shadersBuffer[idx], code);
19245 
19246 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19247 		}
19248 
19249 		link(shaderProgram);
19250 
19251 		foreach(s; shadersBuffer)
19252 			glDeleteShader(s);
19253 	}
19254 
19255 	/// Calls `glUseProgram(this.shaderProgram)`
19256 	void use() {
19257 		glUseProgram(this.shaderProgram);
19258 	}
19259 
19260 	/// Deletes the program.
19261 	void delete_() {
19262 		glDeleteProgram(shaderProgram);
19263 		shaderProgram = 0;
19264 	}
19265 
19266 	/++
19267 		[OpenGlShader.uniforms].name gives you one of these.
19268 
19269 		You can get the id out of it or just assign
19270 	+/
19271 	static struct Uniform {
19272 		/// the id passed to glUniform*
19273 		int id;
19274 
19275 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
19276 		void opAssign(float x, float y, float z, float w) {
19277 			if(id != -1)
19278 			glUniform4f(id, x, y, z, w);
19279 		}
19280 
19281 		void opAssign(float x) {
19282 			if(id != -1)
19283 			glUniform1f(id, x);
19284 		}
19285 
19286 		void opAssign(float x, float y) {
19287 			if(id != -1)
19288 			glUniform2f(id, x, y);
19289 		}
19290 
19291 		void opAssign(T)(T t) {
19292 			t.glUniform(id);
19293 		}
19294 	}
19295 
19296 	static struct UniformsHelper {
19297 		OpenGlShader _shader;
19298 
19299 		@property Uniform opDispatch(string name)() {
19300 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
19301 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
19302 			//if(i == -1)
19303 				//throw new Exception("Could not find uniform " ~ name);
19304 			return Uniform(i);
19305 		}
19306 
19307 		@property void opDispatch(string name, T)(T t) {
19308 			Uniform f = this.opDispatch!name;
19309 			t.glUniform(f);
19310 		}
19311 	}
19312 
19313 	/++
19314 		Gives access to the uniforms through dot access.
19315 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
19316 	+/
19317 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
19318 }
19319 
19320 version(without_opengl) {} else {
19321 /++
19322 	A static container of experimental types and value constructors for opengl 3+ shaders.
19323 
19324 
19325 	You can declare variables like:
19326 
19327 	```
19328 	OGL.vec3f something;
19329 	```
19330 
19331 	But generally it would be used with [OpenGlShader]'s uniform helpers like
19332 
19333 	```
19334 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19335 	```
19336 
19337 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19338 
19339 
19340 	History:
19341 		Added December 7, 2021. Not yet stable.
19342 +/
19343 final class OGL {
19344 	static:
19345 
19346 	private template typeFromSpecifier(string specifier) {
19347 		static if(specifier == "f")
19348 			alias typeFromSpecifier = GLfloat;
19349 		else static if(specifier == "i")
19350 			alias typeFromSpecifier = GLint;
19351 		else static if(specifier == "ui")
19352 			alias typeFromSpecifier = GLuint;
19353 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19354 	}
19355 
19356 	private template CommonType(T...) {
19357 		static if(T.length == 1)
19358 			alias CommonType = T[0];
19359 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19360 			alias CommonType = CommonType!(C, T[2 .. $]);
19361 	}
19362 
19363 	private template typesToSpecifier(T...) {
19364 		static if(is(CommonType!T == float))
19365 			enum typesToSpecifier = "f";
19366 		else static if(is(CommonType!T == int))
19367 			enum typesToSpecifier = "i";
19368 		else static if(is(CommonType!T == uint))
19369 			enum typesToSpecifier = "ui";
19370 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19371 	}
19372 
19373 	private template genNames(size_t dim, size_t dim2 = 0) {
19374 		string helper() {
19375 			string s;
19376 			if(dim2) {
19377 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
19378 			} else {
19379 				if(dim > 0) s ~= "type x = 0;";
19380 				if(dim > 1) s ~= "type y = 0;";
19381 				if(dim > 2) s ~= "type z = 0;";
19382 				if(dim > 3) s ~= "type w = 0;";
19383 			}
19384 			return s;
19385 		}
19386 
19387 		enum genNames = helper();
19388 	}
19389 
19390 	// there's vec, arrays of vec, mat, and arrays of mat
19391 	template opDispatch(string name)
19392 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19393 	{
19394 		static if(name[4] == 'x') {
19395 			enum dimX = cast(int) (name[3] - '0');
19396 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19397 
19398 			enum dimY = cast(int) (name[5] - '0');
19399 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19400 
19401 			enum isArray = name[$ - 1] == 'v';
19402 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19403 			alias type = typeFromSpecifier!typeSpecifier;
19404 		} else {
19405 			enum dim = cast(int) (name[3] - '0');
19406 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19407 			enum isArray = name[$ - 1] == 'v';
19408 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19409 			alias type = typeFromSpecifier!typeSpecifier;
19410 		}
19411 
19412 		align(1)
19413 		struct opDispatch {
19414 			align(1):
19415 			static if(name[4] == 'x')
19416 				mixin(genNames!(dimX, dimY));
19417 			else
19418 				mixin(genNames!dim);
19419 
19420 			private void glUniform(OpenGlShader.Uniform assignTo) {
19421 				glUniform(assignTo.id);
19422 			}
19423 			private void glUniform(int assignTo) {
19424 				static if(name[4] == 'x') {
19425 					// FIXME
19426 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19427 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19428 				} else
19429 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19430 			}
19431 		}
19432 	}
19433 
19434 	auto vec(T...)(T members) {
19435 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19436 	}
19437 }
19438 }
19439 
19440 version(linux) {
19441 	version(with_eventloop) {} else {
19442 		private int epollFd = -1;
19443 		void prepareEventLoop() {
19444 			if(epollFd != -1)
19445 				return; // already initialized, no need to do it again
19446 			import ep = core.sys.linux.epoll;
19447 
19448 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19449 			if(epollFd == -1)
19450 				throw new Exception("epoll create failure");
19451 		}
19452 	}
19453 } else version(Posix) {
19454 	void prepareEventLoop() {}
19455 }
19456 
19457 version(X11) {
19458 	import core.stdc.locale : LC_ALL; // rdmd fix
19459 	__gshared bool sdx_isUTF8Locale;
19460 
19461 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19462 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19463 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19464 	// anal magic is here. I (Ketmar) hope you like it.
19465 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19466 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19467 	// later.
19468 
19469 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19470 	shared static this () {
19471 		if(!librariesSuccessfullyLoaded)
19472 			return;
19473 
19474 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19475 
19476 		// this doesn't hurt; it may add some locking, but the speed is still
19477 		// allows doing 60 FPS videogames; also, ignore the result, as most
19478 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19479 		// never seen this failing).
19480 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19481 
19482 		setlocale(LC_ALL, "");
19483 		// check if out locale is UTF-8
19484 		auto lct = setlocale(LC_CTYPE, null);
19485 		if (lct is null) {
19486 			sdx_isUTF8Locale = false;
19487 		} else {
19488 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19489 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19490 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19491 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19492 				{
19493 					sdx_isUTF8Locale = true;
19494 					break;
19495 				}
19496 			}
19497 		}
19498 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19499 	}
19500 }
19501 
19502 class ExperimentalTextComponent2 {
19503 	/+
19504 		Stage 1: get it working monospace
19505 		Stage 2: use proportional font
19506 		Stage 3: allow changes in inline style
19507 		Stage 4: allow new fonts and sizes in the middle
19508 		Stage 5: optimize gap buffer
19509 		Stage 6: optimize layout
19510 		Stage 7: word wrap
19511 		Stage 8: justification
19512 		Stage 9: editing, selection, etc.
19513 
19514 			Operations:
19515 				insert text
19516 				overstrike text
19517 				select
19518 				cut
19519 				modify
19520 	+/
19521 
19522 	/++
19523 		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.
19524 	+/
19525 	this(SimpleWindow window) {
19526 		this.window = window;
19527 	}
19528 
19529 	private SimpleWindow window;
19530 
19531 
19532 	/++
19533 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19534 		representing the internal parts. The first pass is focused on the x parameter, then the
19535 		renderer is responsible for going back to the parts in the current line and calling
19536 		adjustDownForAscent to change the y params.
19537 	+/
19538 	static interface ComponentRenderHelper {
19539 
19540 		/+
19541 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19542 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19543 			to move (adjust y to make room for new line) until you get back to the same position,
19544 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19545 
19546 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19547 			once you reach something that is unchanged, you can stop.
19548 		+/
19549 
19550 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19551 
19552 		int ascent() const;
19553 		int descent() const;
19554 
19555 		int advance() const;
19556 
19557 		bool endsWithExplititLineBreak() const;
19558 	}
19559 
19560 	static interface RenderResult {
19561 		/++
19562 			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.
19563 		+/
19564 		void popFront();
19565 		@property bool empty() const;
19566 		@property ComponentRenderHelper front() const;
19567 
19568 		void repositionForNextLine(Point baseline, int availableWidth);
19569 	}
19570 
19571 	static interface ComponentInFlow {
19572 		void draw(ScreenPainter painter);
19573 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19574 
19575 		bool startsWithExplicitLineBreak() const;
19576 	}
19577 
19578 	static class TextFlowComponent : ComponentInFlow {
19579 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19580 
19581 		Color foreground;
19582 		Color background;
19583 
19584 		OperatingSystemFont font; // should NEVER be null
19585 
19586 		ubyte attributes; // underline, strike through, display on new block
19587 
19588 		version(Windows)
19589 			const(wchar)[] content;
19590 		else
19591 			const(char)[] content; // this should NEVER have a newline, except at the end
19592 
19593 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19594 
19595 		// could prolly put some spacing around it too like margin / padding
19596 
19597 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19598 			in { assert(font !is null);
19599 			     assert(!font.isNull); }
19600 			do
19601 		{
19602 			this.foreground = f;
19603 			this.background = b;
19604 			this.font = font;
19605 
19606 			this.attributes = attr;
19607 			version(Windows) {
19608 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19609 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19610 				auto buffer = new wchar[](sz);
19611 				this.content = makeWindowsString(c, buffer, conversionFlags);
19612 			} else {
19613 				this.content = c.dup;
19614 			}
19615 		}
19616 
19617 		void draw(ScreenPainter painter) {
19618 			painter.setFont(this.font);
19619 			painter.outlineColor = this.foreground;
19620 			painter.fillColor = Color.transparent;
19621 			foreach(rendered; this.rendered) {
19622 				// the component works in term of baseline,
19623 				// but the painter works in term of upper left bounding box
19624 				// so need to translate that
19625 
19626 				if(this.background.a) {
19627 					painter.fillColor = this.background;
19628 					painter.outlineColor = this.background;
19629 
19630 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19631 
19632 					painter.outlineColor = this.foreground;
19633 					painter.fillColor = Color.transparent;
19634 				}
19635 
19636 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19637 
19638 				// FIXME: strike through, underline, highlight selection, etc.
19639 			}
19640 		}
19641 	}
19642 
19643 	// I could split the parts into words on render
19644 	// for easier word-wrap, each one being an unbreakable "inline-block"
19645 	private TextFlowComponent[] parts;
19646 	private int needsRerenderFrom;
19647 
19648 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19649 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19650 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19651 	}
19652 
19653 	static struct RenderedComponent {
19654 		int startX;
19655 		int startY;
19656 		short width;
19657 		// 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!
19658 		// for individual chars in here you've gotta process on demand
19659 		version(Windows)
19660 			const(wchar)[] slice;
19661 		else
19662 			const(char)[] slice;
19663 	}
19664 
19665 
19666 	void rerender(Rectangle boundingBox) {
19667 		Point baseline = boundingBox.upperLeft;
19668 
19669 		this.boundingBox.left = boundingBox.left;
19670 		this.boundingBox.top = boundingBox.top;
19671 
19672 		auto remainingParts = parts;
19673 
19674 		int largestX;
19675 
19676 
19677 		foreach(part; parts)
19678 			part.font.prepareContext(window);
19679 		scope(exit)
19680 		foreach(part; parts)
19681 			part.font.releaseContext();
19682 
19683 		calculateNextLine:
19684 
19685 		int nextLineHeight = 0;
19686 		int nextBiggestDescent = 0;
19687 
19688 		foreach(part; remainingParts) {
19689 			auto height = part.font.ascent;
19690 			if(height > nextLineHeight)
19691 				nextLineHeight = height;
19692 			if(part.font.descent > nextBiggestDescent)
19693 				nextBiggestDescent = part.font.descent;
19694 			if(part.content.length && part.content[$-1] == '\n')
19695 				break;
19696 		}
19697 
19698 		baseline.y += nextLineHeight;
19699 		auto lineStart = baseline;
19700 
19701 		while(remainingParts.length) {
19702 			remainingParts[0].rendered = null;
19703 
19704 			bool eol;
19705 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19706 				eol = true;
19707 
19708 			// FIXME: word wrap
19709 			auto font = remainingParts[0].font;
19710 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19711 			auto width = font.stringWidth(slice, window);
19712 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19713 
19714 			remainingParts = remainingParts[1 .. $];
19715 			baseline.x += width;
19716 
19717 			if(eol) {
19718 				baseline.y += nextBiggestDescent;
19719 				if(baseline.x > largestX)
19720 					largestX = baseline.x;
19721 				baseline.x = lineStart.x;
19722 				goto calculateNextLine;
19723 			}
19724 		}
19725 
19726 		if(baseline.x > largestX)
19727 			largestX = baseline.x;
19728 
19729 		this.boundingBox.right = largestX;
19730 		this.boundingBox.bottom = baseline.y;
19731 	}
19732 
19733 	// you must call rerender first!
19734 	void draw(ScreenPainter painter) {
19735 		foreach(part; parts) {
19736 			part.draw(painter);
19737 		}
19738 	}
19739 
19740 	struct IdentifyResult {
19741 		TextFlowComponent part;
19742 		int charIndexInPart;
19743 		int totalCharIndex = -1; // if this is -1, it just means the end
19744 
19745 		Rectangle boundingBox;
19746 	}
19747 
19748 	IdentifyResult identify(Point pt, bool exact = false) {
19749 		if(parts.length == 0)
19750 			return IdentifyResult(null, 0);
19751 
19752 		if(pt.y < boundingBox.top) {
19753 			if(exact)
19754 				return IdentifyResult(null, 1);
19755 			return IdentifyResult(parts[0], 0);
19756 		}
19757 		if(pt.y > boundingBox.bottom) {
19758 			if(exact)
19759 				return IdentifyResult(null, 2);
19760 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19761 		}
19762 
19763 		int tci = 0;
19764 
19765 		// I should probably like binary search this or something...
19766 		foreach(ref part; parts) {
19767 			foreach(rendered; part.rendered) {
19768 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19769 				if(rect.contains(pt)) {
19770 					auto x = pt.x - rendered.startX;
19771 					auto estimatedIdx = x / part.font.averageWidth;
19772 
19773 					if(estimatedIdx < 0)
19774 						estimatedIdx = 0;
19775 
19776 					if(estimatedIdx > rendered.slice.length)
19777 						estimatedIdx = cast(int) rendered.slice.length;
19778 
19779 					int idx;
19780 					int x1, x2;
19781 					if(part.font.isMonospace) {
19782 						auto w = part.font.averageWidth;
19783 						if(!exact && x > (estimatedIdx + 1) * w)
19784 							return IdentifyResult(null, 4);
19785 						idx = estimatedIdx;
19786 						x1 = idx * w;
19787 						x2 = (idx + 1) * w;
19788 					} else {
19789 						idx = estimatedIdx;
19790 
19791 						part.font.prepareContext(window);
19792 						scope(exit) part.font.releaseContext();
19793 
19794 						// int iterations;
19795 
19796 						while(true) {
19797 							// iterations++;
19798 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19799 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19800 
19801 							x1 += rendered.startX;
19802 							x2 += rendered.startX;
19803 
19804 							if(pt.x < x1) {
19805 								if(idx == 0) {
19806 									if(exact)
19807 										return IdentifyResult(null, 6);
19808 									else
19809 										break;
19810 								}
19811 								idx--;
19812 							} else if(pt.x > x2) {
19813 								idx++;
19814 								if(idx > rendered.slice.length) {
19815 									if(exact)
19816 										return IdentifyResult(null, 5);
19817 									else
19818 										break;
19819 								}
19820 							} else if(pt.x >= x1 && pt.x <= x2) {
19821 								if(idx)
19822 									idx--; // point it at the original index
19823 								break; // we fit
19824 							}
19825 						}
19826 
19827 						// writeln(iterations)
19828 					}
19829 
19830 
19831 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19832 				}
19833 			}
19834 			tci += cast(int) part.content.length; // FIXME: utf-8?
19835 		}
19836 		return IdentifyResult(null, 3);
19837 	}
19838 
19839 	Rectangle boundingBox; // only set after [rerender]
19840 
19841 	// text will be positioned around the exclusion zone
19842 	static struct ExclusionZone {
19843 
19844 	}
19845 
19846 	ExclusionZone[] exclusionZones;
19847 }
19848 
19849 
19850 // Don't use this yet. When I'm happy with it, I will move it to the
19851 // regular module namespace.
19852 mixin template ExperimentalTextComponent() {
19853 
19854 static:
19855 
19856 	alias Rectangle = arsd.color.Rectangle;
19857 
19858 	struct ForegroundColor {
19859 		Color color;
19860 		alias color this;
19861 
19862 		this(Color c) {
19863 			color = c;
19864 		}
19865 
19866 		this(int r, int g, int b, int a = 255) {
19867 			color = Color(r, g, b, a);
19868 		}
19869 
19870 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19871 			return ForegroundColor(mixin("Color." ~ s));
19872 		}
19873 	}
19874 
19875 	struct BackgroundColor {
19876 		Color color;
19877 		alias color this;
19878 
19879 		this(Color c) {
19880 			color = c;
19881 		}
19882 
19883 		this(int r, int g, int b, int a = 255) {
19884 			color = Color(r, g, b, a);
19885 		}
19886 
19887 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19888 			return BackgroundColor(mixin("Color." ~ s));
19889 		}
19890 	}
19891 
19892 	static class InlineElement {
19893 		string text;
19894 
19895 		BlockElement containingBlock;
19896 
19897 		Color color = Color.black;
19898 		Color backgroundColor = Color.transparent;
19899 		ushort styles;
19900 
19901 		string font;
19902 		int fontSize;
19903 
19904 		int lineHeight;
19905 
19906 		void* identifier;
19907 
19908 		Rectangle boundingBox;
19909 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19910 
19911 		bool isMergeCompatible(InlineElement other) {
19912 			return
19913 				containingBlock is other.containingBlock &&
19914 				color == other.color &&
19915 				backgroundColor == other.backgroundColor &&
19916 				styles == other.styles &&
19917 				font == other.font &&
19918 				fontSize == other.fontSize &&
19919 				lineHeight == other.lineHeight &&
19920 				true;
19921 		}
19922 
19923 		int xOfIndex(size_t index) {
19924 			if(index < letterXs.length)
19925 				return letterXs[index];
19926 			else
19927 				return boundingBox.right;
19928 		}
19929 
19930 		InlineElement clone() {
19931 			auto ie = new InlineElement();
19932 			ie.tupleof = this.tupleof;
19933 			return ie;
19934 		}
19935 
19936 		InlineElement getPreviousInlineElement() {
19937 			InlineElement prev = null;
19938 			foreach(ie; this.containingBlock.parts) {
19939 				if(ie is this)
19940 					break;
19941 				prev = ie;
19942 			}
19943 			if(prev is null) {
19944 				BlockElement pb;
19945 				BlockElement cb = this.containingBlock;
19946 				moar:
19947 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19948 					if(ie is cb)
19949 						break;
19950 					pb = ie;
19951 				}
19952 				if(pb is null)
19953 					return null;
19954 				if(pb.parts.length == 0) {
19955 					cb = pb;
19956 					goto moar;
19957 				}
19958 
19959 				prev = pb.parts[$-1];
19960 
19961 			}
19962 			return prev;
19963 		}
19964 
19965 		InlineElement getNextInlineElement() {
19966 			InlineElement next = null;
19967 			foreach(idx, ie; this.containingBlock.parts) {
19968 				if(ie is this) {
19969 					if(idx + 1 < this.containingBlock.parts.length)
19970 						next = this.containingBlock.parts[idx + 1];
19971 					break;
19972 				}
19973 			}
19974 			if(next is null) {
19975 				BlockElement n;
19976 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19977 					if(ie is this.containingBlock) {
19978 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19979 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19980 						break;
19981 					}
19982 				}
19983 				if(n is null)
19984 					return null;
19985 
19986 				if(n.parts.length)
19987 					next = n.parts[0];
19988 				else {} // FIXME
19989 
19990 			}
19991 			return next;
19992 		}
19993 
19994 	}
19995 
19996 	// Block elements are used entirely for positioning inline elements,
19997 	// which are the things that are actually drawn.
19998 	class BlockElement {
19999 		InlineElement[] parts;
20000 		uint alignment;
20001 
20002 		int whiteSpace; // pre, pre-wrap, wrap
20003 
20004 		TextLayout containingLayout;
20005 
20006 		// inputs
20007 		Point where;
20008 		Size minimumSize;
20009 		Size maximumSize;
20010 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
20011 		void* identifier;
20012 
20013 		Rectangle margin;
20014 		Rectangle padding;
20015 
20016 		// outputs
20017 		Rectangle[] boundingBoxes;
20018 	}
20019 
20020 	struct TextIdentifyResult {
20021 		InlineElement element;
20022 		int offset;
20023 
20024 		private TextIdentifyResult fixupNewline() {
20025 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
20026 				offset--;
20027 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
20028 				offset--;
20029 			}
20030 			return this;
20031 		}
20032 	}
20033 
20034 	class TextLayout {
20035 		BlockElement[] blocks;
20036 		Rectangle boundingBox_;
20037 		Rectangle boundingBox() { return boundingBox_; }
20038 		void boundingBox(Rectangle r) {
20039 			if(r != boundingBox_) {
20040 				boundingBox_ = r;
20041 				layoutInvalidated = true;
20042 			}
20043 		}
20044 
20045 		Rectangle contentBoundingBox() {
20046 			Rectangle r;
20047 			foreach(block; blocks)
20048 			foreach(ie; block.parts) {
20049 				if(ie.boundingBox.right > r.right)
20050 					r.right = ie.boundingBox.right;
20051 				if(ie.boundingBox.bottom > r.bottom)
20052 					r.bottom = ie.boundingBox.bottom;
20053 			}
20054 			return r;
20055 		}
20056 
20057 		BlockElement[] getBlocks() {
20058 			return blocks;
20059 		}
20060 
20061 		InlineElement[] getTexts() {
20062 			InlineElement[] elements;
20063 			foreach(block; blocks)
20064 				elements ~= block.parts;
20065 			return elements;
20066 		}
20067 
20068 		string getPlainText() {
20069 			string text;
20070 			foreach(block; blocks)
20071 				foreach(part; block.parts)
20072 					text ~= part.text;
20073 			return text;
20074 		}
20075 
20076 		string getHtml() {
20077 			return null; // FIXME
20078 		}
20079 
20080 		this(Rectangle boundingBox) {
20081 			this.boundingBox = boundingBox;
20082 		}
20083 
20084 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
20085 			auto be = new BlockElement();
20086 			be.containingLayout = this;
20087 			if(after is null)
20088 				blocks ~= be;
20089 			else {
20090 				foreach(idx, b; blocks) {
20091 					if(b is after.containingBlock) {
20092 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
20093 						break;
20094 					}
20095 				}
20096 			}
20097 			return be;
20098 		}
20099 
20100 		void clear() {
20101 			blocks = null;
20102 			selectionStart = selectionEnd = caret = Caret.init;
20103 		}
20104 
20105 		void addText(Args...)(Args args) {
20106 			if(blocks.length == 0)
20107 				addBlock();
20108 
20109 			InlineElement ie = new InlineElement();
20110 			foreach(idx, arg; args) {
20111 				static if(is(typeof(arg) == ForegroundColor))
20112 					ie.color = arg;
20113 				else static if(is(typeof(arg) == TextFormat)) {
20114 					if(arg & 0x8000) // ~TextFormat.something turns it off
20115 						ie.styles &= arg;
20116 					else
20117 						ie.styles |= arg;
20118 				} else static if(is(typeof(arg) == string)) {
20119 					static if(idx == 0 && args.length > 1)
20120 						static assert(0, "Put styles before the string.");
20121 					size_t lastLineIndex;
20122 					foreach(cidx, char a; arg) {
20123 						if(a == '\n') {
20124 							ie.text = arg[lastLineIndex .. cidx + 1];
20125 							lastLineIndex = cidx + 1;
20126 							ie.containingBlock = blocks[$-1];
20127 							blocks[$-1].parts ~= ie.clone;
20128 							ie.text = null;
20129 						} else {
20130 
20131 						}
20132 					}
20133 
20134 					ie.text = arg[lastLineIndex .. $];
20135 					ie.containingBlock = blocks[$-1];
20136 					blocks[$-1].parts ~= ie.clone;
20137 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
20138 				}
20139 			}
20140 
20141 			invalidateLayout();
20142 		}
20143 
20144 		void tryMerge(InlineElement into, InlineElement what) {
20145 			if(!into.isMergeCompatible(what)) {
20146 				return; // cannot merge, different configs
20147 			}
20148 
20149 			// cool, can merge, bring text together...
20150 			into.text ~= what.text;
20151 
20152 			// and remove what
20153 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
20154 				if(what.containingBlock.parts[a] is what) {
20155 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
20156 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
20157 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
20158 
20159 				}
20160 			}
20161 
20162 			// FIXME: ensure no other carets have a reference to it
20163 		}
20164 
20165 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
20166 		TextIdentifyResult identify(int x, int y, bool exact = false) {
20167 			TextIdentifyResult inexactMatch;
20168 			foreach(block; blocks) {
20169 				foreach(part; block.parts) {
20170 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
20171 
20172 						// FIXME binary search
20173 						int tidx;
20174 						int lastX;
20175 						foreach_reverse(idxo, lx; part.letterXs) {
20176 							int idx = cast(int) idxo;
20177 							if(lx <= x) {
20178 								if(lastX && lastX - x < x - lx)
20179 									tidx = idx + 1;
20180 								else
20181 									tidx = idx;
20182 								break;
20183 							}
20184 							lastX = lx;
20185 						}
20186 
20187 						return TextIdentifyResult(part, tidx).fixupNewline;
20188 					} else if(!exact) {
20189 						// we're not in the box, but are we on the same line?
20190 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
20191 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
20192 					}
20193 				}
20194 			}
20195 
20196 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
20197 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
20198 
20199 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
20200 		}
20201 
20202 		void moveCaretToPixelCoordinates(int x, int y) {
20203 			auto result = identify(x, y);
20204 			caret.inlineElement = result.element;
20205 			caret.offset = result.offset;
20206 		}
20207 
20208 		void selectToPixelCoordinates(int x, int y) {
20209 			auto result = identify(x, y);
20210 
20211 			if(y < caretLastDrawnY1) {
20212 				// on a previous line, carat is selectionEnd
20213 				selectionEnd = caret;
20214 
20215 				selectionStart = Caret(this, result.element, result.offset);
20216 			} else if(y > caretLastDrawnY2) {
20217 				// on a later line
20218 				selectionStart = caret;
20219 
20220 				selectionEnd = Caret(this, result.element, result.offset);
20221 			} else {
20222 				// on the same line...
20223 				if(x <= caretLastDrawnX) {
20224 					selectionEnd = caret;
20225 					selectionStart = Caret(this, result.element, result.offset);
20226 				} else {
20227 					selectionStart = caret;
20228 					selectionEnd = Caret(this, result.element, result.offset);
20229 				}
20230 
20231 			}
20232 		}
20233 
20234 
20235 		/// Call this if the inputs change. It will reflow everything
20236 		void redoLayout(ScreenPainter painter) {
20237 			//painter.setClipRectangle(boundingBox);
20238 			auto pos = Point(boundingBox.left, boundingBox.top);
20239 
20240 			int lastHeight;
20241 			void nl() {
20242 				pos.x = boundingBox.left;
20243 				pos.y += lastHeight;
20244 			}
20245 			foreach(block; blocks) {
20246 				nl();
20247 				foreach(part; block.parts) {
20248 					part.letterXs = null;
20249 
20250 					auto size = painter.textSize(part.text);
20251 					version(Windows)
20252 						if(part.text.length && part.text[$-1] == '\n')
20253 							size.height /= 2; // windows counts the new line at the end, but we don't want that
20254 
20255 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
20256 
20257 					foreach(idx, char c; part.text) {
20258 							// FIXME: unicode
20259 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
20260 					}
20261 
20262 					pos.x += size.width;
20263 					if(pos.x >= boundingBox.right) {
20264 						pos.y += size.height;
20265 						pos.x = boundingBox.left;
20266 						lastHeight = 0;
20267 					} else {
20268 						lastHeight = size.height;
20269 					}
20270 
20271 					if(part.text.length && part.text[$-1] == '\n')
20272 						nl();
20273 				}
20274 			}
20275 
20276 			layoutInvalidated = false;
20277 		}
20278 
20279 		bool layoutInvalidated = true;
20280 		void invalidateLayout() {
20281 			layoutInvalidated = true;
20282 		}
20283 
20284 // FIXME: caret can remain sometimes when inserting
20285 // FIXME: inserting at the beginning once you already have something can eff it up.
20286 		void drawInto(ScreenPainter painter, bool focused = false) {
20287 			if(layoutInvalidated)
20288 				redoLayout(painter);
20289 			foreach(block; blocks) {
20290 				foreach(part; block.parts) {
20291 					painter.outlineColor = part.color;
20292 					painter.fillColor = part.backgroundColor;
20293 
20294 					auto pos = part.boundingBox.upperLeft;
20295 					auto size = part.boundingBox.size;
20296 
20297 					painter.drawText(pos, part.text);
20298 					if(part.styles & TextFormat.underline)
20299 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
20300 					if(part.styles & TextFormat.strikethrough)
20301 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
20302 				}
20303 			}
20304 
20305 			// on every redraw, I will force the caret to be
20306 			// redrawn too, in order to eliminate perceived lag
20307 			// when moving around with the mouse.
20308 			eraseCaret(painter);
20309 
20310 			if(focused) {
20311 				highlightSelection(painter);
20312 				drawCaret(painter);
20313 			}
20314 		}
20315 
20316 		Color selectionXorColor = Color(255, 255, 127);
20317 
20318 		void highlightSelection(ScreenPainter painter) {
20319 			if(selectionStart is selectionEnd)
20320 				return; // no selection
20321 
20322 			if(selectionStart.inlineElement is null) return;
20323 			if(selectionEnd.inlineElement is null) return;
20324 
20325 			assert(selectionStart.inlineElement !is null);
20326 			assert(selectionEnd.inlineElement !is null);
20327 
20328 			painter.rasterOp = RasterOp.xor;
20329 			painter.outlineColor = Color.transparent;
20330 			painter.fillColor = selectionXorColor;
20331 
20332 			auto at = selectionStart.inlineElement;
20333 			auto atOffset = selectionStart.offset;
20334 			bool done;
20335 			while(at) {
20336 				auto box = at.boundingBox;
20337 				if(atOffset < at.letterXs.length)
20338 					box.left = at.letterXs[atOffset];
20339 
20340 				if(at is selectionEnd.inlineElement) {
20341 					if(selectionEnd.offset < at.letterXs.length)
20342 						box.right = at.letterXs[selectionEnd.offset];
20343 					done = true;
20344 				}
20345 
20346 				painter.drawRectangle(box.upperLeft, box.width, box.height);
20347 
20348 				if(done)
20349 					break;
20350 
20351 				at = at.getNextInlineElement();
20352 				atOffset = 0;
20353 			}
20354 		}
20355 
20356 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
20357 		bool caretShowingOnScreen = false;
20358 		void drawCaret(ScreenPainter painter) {
20359 			//painter.setClipRectangle(boundingBox);
20360 			int x, y1, y2;
20361 			if(caret.inlineElement is null) {
20362 				x = boundingBox.left;
20363 				y1 = boundingBox.top + 2;
20364 				y2 = boundingBox.top + painter.fontHeight;
20365 			} else {
20366 				x = caret.inlineElement.xOfIndex(caret.offset);
20367 				y1 = caret.inlineElement.boundingBox.top + 2;
20368 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20369 			}
20370 
20371 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
20372 				eraseCaret(painter);
20373 
20374 			painter.pen = Pen(Color.white, 1);
20375 			painter.rasterOp = RasterOp.xor;
20376 			painter.drawLine(
20377 				Point(x, y1),
20378 				Point(x, y2)
20379 			);
20380 			painter.rasterOp = RasterOp.normal;
20381 			caretShowingOnScreen = !caretShowingOnScreen;
20382 
20383 			if(caretShowingOnScreen) {
20384 				caretLastDrawnX = x;
20385 				caretLastDrawnY1 = y1;
20386 				caretLastDrawnY2 = y2;
20387 			}
20388 		}
20389 
20390 		Rectangle caretBoundingBox() {
20391 			int x, y1, y2;
20392 			if(caret.inlineElement is null) {
20393 				x = boundingBox.left;
20394 				y1 = boundingBox.top + 2;
20395 				y2 = boundingBox.top + 16;
20396 			} else {
20397 				x = caret.inlineElement.xOfIndex(caret.offset);
20398 				y1 = caret.inlineElement.boundingBox.top + 2;
20399 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20400 			}
20401 
20402 			return Rectangle(x, y1, x + 1, y2);
20403 		}
20404 
20405 		void eraseCaret(ScreenPainter painter) {
20406 			//painter.setClipRectangle(boundingBox);
20407 			if(!caretShowingOnScreen) return;
20408 			painter.pen = Pen(Color.white, 1);
20409 			painter.rasterOp = RasterOp.xor;
20410 			painter.drawLine(
20411 				Point(caretLastDrawnX, caretLastDrawnY1),
20412 				Point(caretLastDrawnX, caretLastDrawnY2)
20413 			);
20414 
20415 			caretShowingOnScreen = false;
20416 			painter.rasterOp = RasterOp.normal;
20417 		}
20418 
20419 		/// Caret movement api
20420 		/// These should give the user a logical result based on what they see on screen...
20421 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20422 		void moveUp() {
20423 			if(caret.inlineElement is null) return;
20424 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20425 			auto y = caret.inlineElement.boundingBox.top + 2;
20426 
20427 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20428 			if(y < 0)
20429 				return;
20430 
20431 			auto i = identify(x, y);
20432 
20433 			if(i.element) {
20434 				caret.inlineElement = i.element;
20435 				caret.offset = i.offset;
20436 			}
20437 		}
20438 		void moveDown() {
20439 			if(caret.inlineElement is null) return;
20440 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20441 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20442 
20443 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20444 
20445 			auto i = identify(x, y);
20446 			if(i.element) {
20447 				caret.inlineElement = i.element;
20448 				caret.offset = i.offset;
20449 			}
20450 		}
20451 		void moveLeft() {
20452 			if(caret.inlineElement is null) return;
20453 			if(caret.offset)
20454 				caret.offset--;
20455 			else {
20456 				auto p = caret.inlineElement.getPreviousInlineElement();
20457 				if(p) {
20458 					caret.inlineElement = p;
20459 					if(p.text.length && p.text[$-1] == '\n')
20460 						caret.offset = cast(int) p.text.length - 1;
20461 					else
20462 						caret.offset = cast(int) p.text.length;
20463 				}
20464 			}
20465 		}
20466 		void moveRight() {
20467 			if(caret.inlineElement is null) return;
20468 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20469 				caret.offset++;
20470 			} else {
20471 				auto p = caret.inlineElement.getNextInlineElement();
20472 				if(p) {
20473 					caret.inlineElement = p;
20474 					caret.offset = 0;
20475 				}
20476 			}
20477 		}
20478 		void moveHome() {
20479 			if(caret.inlineElement is null) return;
20480 			auto x = 0;
20481 			auto y = caret.inlineElement.boundingBox.top + 2;
20482 
20483 			auto i = identify(x, y);
20484 
20485 			if(i.element) {
20486 				caret.inlineElement = i.element;
20487 				caret.offset = i.offset;
20488 			}
20489 		}
20490 		void moveEnd() {
20491 			if(caret.inlineElement is null) return;
20492 			auto x = int.max;
20493 			auto y = caret.inlineElement.boundingBox.top + 2;
20494 
20495 			auto i = identify(x, y);
20496 
20497 			if(i.element) {
20498 				caret.inlineElement = i.element;
20499 				caret.offset = i.offset;
20500 			}
20501 
20502 		}
20503 		void movePageUp(ref Caret caret) {}
20504 		void movePageDown(ref Caret caret) {}
20505 
20506 		void moveDocumentStart(ref Caret caret) {
20507 			if(blocks.length && blocks[0].parts.length)
20508 				caret = Caret(this, blocks[0].parts[0], 0);
20509 			else
20510 				caret = Caret.init;
20511 		}
20512 
20513 		void moveDocumentEnd(ref Caret caret) {
20514 			if(blocks.length) {
20515 				auto parts = blocks[$-1].parts;
20516 				if(parts.length) {
20517 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20518 				} else {
20519 					caret = Caret.init;
20520 				}
20521 			} else
20522 				caret = Caret.init;
20523 		}
20524 
20525 		void deleteSelection() {
20526 			if(selectionStart is selectionEnd)
20527 				return;
20528 
20529 			if(selectionStart.inlineElement is null) return;
20530 			if(selectionEnd.inlineElement is null) return;
20531 
20532 			assert(selectionStart.inlineElement !is null);
20533 			assert(selectionEnd.inlineElement !is null);
20534 
20535 			auto at = selectionStart.inlineElement;
20536 
20537 			if(selectionEnd.inlineElement is at) {
20538 				// same element, need to chop out
20539 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20540 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20541 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20542 			} else {
20543 				// different elements, we can do it with slicing
20544 				at.text = at.text[0 .. selectionStart.offset];
20545 				if(selectionStart.offset < at.letterXs.length)
20546 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20547 
20548 				at = at.getNextInlineElement();
20549 
20550 				while(at) {
20551 					if(at is selectionEnd.inlineElement) {
20552 						at.text = at.text[selectionEnd.offset .. $];
20553 						if(selectionEnd.offset < at.letterXs.length)
20554 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20555 						selectionEnd.offset = 0;
20556 						break;
20557 					} else {
20558 						auto cfd = at;
20559 						cfd.text = null; // delete the whole thing
20560 
20561 						at = at.getNextInlineElement();
20562 
20563 						if(cfd.text.length == 0) {
20564 							// and remove cfd
20565 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20566 								if(cfd.containingBlock.parts[a] is cfd) {
20567 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20568 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20569 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20570 
20571 								}
20572 							}
20573 						}
20574 					}
20575 				}
20576 			}
20577 
20578 			caret = selectionEnd;
20579 			selectNone();
20580 
20581 			invalidateLayout();
20582 
20583 		}
20584 
20585 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20586 		void insert(in char[] text) {
20587 			foreach(dchar ch; text)
20588 				insert(ch);
20589 		}
20590 		/// ditto
20591 		void insert(dchar ch) {
20592 
20593 			bool selectionDeleted = false;
20594 			if(selectionStart !is selectionEnd) {
20595 				deleteSelection();
20596 				selectionDeleted = true;
20597 			}
20598 
20599 			if(ch == 127) {
20600 				delete_();
20601 				return;
20602 			}
20603 			if(ch == 8) {
20604 				if(!selectionDeleted)
20605 					backspace();
20606 				return;
20607 			}
20608 
20609 			invalidateLayout();
20610 
20611 			if(ch == 13) ch = 10;
20612 			auto e = caret.inlineElement;
20613 			if(e is null) {
20614 				addText("" ~ cast(char) ch) ; // FIXME
20615 				return;
20616 			}
20617 
20618 			if(caret.offset == e.text.length) {
20619 				e.text ~= cast(char) ch; // FIXME
20620 				caret.offset++;
20621 				if(ch == 10) {
20622 					auto c = caret.inlineElement.clone;
20623 					c.text = null;
20624 					c.letterXs = null;
20625 					insertPartAfter(c,e);
20626 					caret = Caret(this, c, 0);
20627 				}
20628 			} else {
20629 				// FIXME cast char sucks
20630 				if(ch == 10) {
20631 					auto c = caret.inlineElement.clone;
20632 					c.text = e.text[caret.offset .. $];
20633 					if(caret.offset < c.letterXs.length)
20634 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20635 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20636 					if(caret.offset <= e.letterXs.length) {
20637 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20638 					}
20639 					insertPartAfter(c,e);
20640 					caret = Caret(this, c, 0);
20641 				} else {
20642 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20643 					caret.offset++;
20644 				}
20645 			}
20646 		}
20647 
20648 		void insertPartAfter(InlineElement what, InlineElement where) {
20649 			foreach(idx, p; where.containingBlock.parts) {
20650 				if(p is where) {
20651 					if(idx + 1 == where.containingBlock.parts.length)
20652 						where.containingBlock.parts ~= what;
20653 					else
20654 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20655 					return;
20656 				}
20657 			}
20658 		}
20659 
20660 		void cleanupStructures() {
20661 			for(size_t i = 0; i < blocks.length; i++) {
20662 				auto block = blocks[i];
20663 				for(size_t a = 0; a < block.parts.length; a++) {
20664 					auto part = block.parts[a];
20665 					if(part.text.length == 0) {
20666 						for(size_t b = a; b < block.parts.length - 1; b++)
20667 							block.parts[b] = block.parts[b+1];
20668 						block.parts = block.parts[0 .. $-1];
20669 					}
20670 				}
20671 				if(block.parts.length == 0) {
20672 					for(size_t a = i; a < blocks.length - 1; a++)
20673 						blocks[a] = blocks[a+1];
20674 					blocks = blocks[0 .. $-1];
20675 				}
20676 			}
20677 		}
20678 
20679 		void backspace() {
20680 			try_again:
20681 			auto e = caret.inlineElement;
20682 			if(e is null)
20683 				return;
20684 			if(caret.offset == 0) {
20685 				auto prev = e.getPreviousInlineElement();
20686 				if(prev is null)
20687 					return;
20688 				auto newOffset = cast(int) prev.text.length;
20689 				tryMerge(prev, e);
20690 				caret.inlineElement = prev;
20691 				caret.offset = prev is null ? 0 : newOffset;
20692 
20693 				goto try_again;
20694 			} else if(caret.offset == e.text.length) {
20695 				e.text = e.text[0 .. $-1];
20696 				caret.offset--;
20697 			} else {
20698 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20699 				caret.offset--;
20700 			}
20701 			//cleanupStructures();
20702 
20703 			invalidateLayout();
20704 		}
20705 		void delete_() {
20706 			if(selectionStart !is selectionEnd)
20707 				deleteSelection();
20708 			else {
20709 				auto before = caret;
20710 				moveRight();
20711 				if(caret != before) {
20712 					backspace();
20713 				}
20714 			}
20715 
20716 			invalidateLayout();
20717 		}
20718 		void overstrike() {}
20719 
20720 		/// Selection API. See also: caret movement.
20721 		void selectAll() {
20722 			moveDocumentStart(selectionStart);
20723 			moveDocumentEnd(selectionEnd);
20724 		}
20725 		bool selectNone() {
20726 			if(selectionStart != selectionEnd) {
20727 				selectionStart = selectionEnd = Caret.init;
20728 				return true;
20729 			}
20730 			return false;
20731 		}
20732 
20733 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20734 		/// They will modify the current selection if there is one and will splice one in if needed.
20735 		void changeAttributes() {}
20736 
20737 
20738 		/// Text search api. They manipulate the selection and/or caret.
20739 		void findText(string text) {}
20740 		void findIndex(size_t textIndex) {}
20741 
20742 		// sample event handlers
20743 
20744 		void handleEvent(KeyEvent event) {
20745 			//if(event.type == KeyEvent.Type.KeyPressed) {
20746 
20747 			//}
20748 		}
20749 
20750 		void handleEvent(dchar ch) {
20751 
20752 		}
20753 
20754 		void handleEvent(MouseEvent event) {
20755 
20756 		}
20757 
20758 		bool contentEditable; // can it be edited?
20759 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20760 		bool contentSelectable; // selectable?
20761 
20762 		Caret caret;
20763 		Caret selectionStart;
20764 		Caret selectionEnd;
20765 
20766 		bool insertMode;
20767 	}
20768 
20769 	struct Caret {
20770 		TextLayout layout;
20771 		InlineElement inlineElement;
20772 		int offset;
20773 	}
20774 
20775 	enum TextFormat : ushort {
20776 		// decorations
20777 		underline = 1,
20778 		strikethrough = 2,
20779 
20780 		// font selectors
20781 
20782 		bold = 0x4000 | 1, // weight 700
20783 		light = 0x4000 | 2, // weight 300
20784 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20785 		// bold | light is really invalid but should give weight 500
20786 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20787 
20788 		italic = 0x4000 | 8,
20789 		smallcaps = 0x4000 | 16,
20790 	}
20791 
20792 	void* findFont(string family, int weight, TextFormat formats) {
20793 		return null;
20794 	}
20795 
20796 }
20797 
20798 /++
20799 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20800 
20801 	History:
20802 		Added February 19, 2021
20803 +/
20804 /// Group: drag_and_drop
20805 interface DropHandler {
20806 	/++
20807 		Called when the drag enters the handler's area.
20808 	+/
20809 	DragAndDropAction dragEnter(DropPackage*);
20810 	/++
20811 		Called when the drag leaves the handler's area or is
20812 		cancelled. You should free your resources when this is called.
20813 	+/
20814 	void dragLeave();
20815 	/++
20816 		Called continually as the drag moves over the handler's area.
20817 
20818 		Returns: feedback to the dragger
20819 	+/
20820 	DropParameters dragOver(Point pt);
20821 	/++
20822 		The user dropped the data and you should process it now. You can
20823 		access the data through the given [DropPackage].
20824 	+/
20825 	void drop(scope DropPackage*);
20826 	/++
20827 		Called when the drop is complete. You should free whatever temporary
20828 		resources you were using. It is often reasonable to simply forward
20829 		this call to [dragLeave].
20830 	+/
20831 	void finish();
20832 
20833 	/++
20834 		Parameters returned by [DropHandler.drop].
20835 	+/
20836 	static struct DropParameters {
20837 		/++
20838 			Acceptable action over this area.
20839 		+/
20840 		DragAndDropAction action;
20841 		/++
20842 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20843 
20844 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20845 		+/
20846 		Rectangle consistentWithin;
20847 	}
20848 }
20849 
20850 /++
20851 	History:
20852 		Added February 19, 2021
20853 +/
20854 /// Group: drag_and_drop
20855 enum DragAndDropAction {
20856 	none = 0,
20857 	copy,
20858 	move,
20859 	link,
20860 	ask,
20861 	custom
20862 }
20863 
20864 /++
20865 	An opaque structure representing dropped data. It contains
20866 	private, platform-specific data that your `drop` function
20867 	should simply forward.
20868 
20869 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20870 
20871 	History:
20872 		Added February 19, 2021
20873 +/
20874 /// Group: drag_and_drop
20875 struct DropPackage {
20876 	/++
20877 		Lists the available formats as magic numbers. You should compare these
20878 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20879 		understand the passed data.
20880 	+/
20881 	DraggableData.FormatId[] availableFormats() {
20882 		version(X11) {
20883 			return xFormats;
20884 		} else version(Windows) {
20885 			if(pDataObj is null)
20886 				return null;
20887 
20888 			typeof(return) ret;
20889 
20890 			IEnumFORMATETC ef;
20891 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20892 				FORMATETC fmt;
20893 				ULONG fetched;
20894 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20895 					if(fetched == 0)
20896 						break;
20897 
20898 					if(fmt.lindex != -1)
20899 						continue;
20900 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20901 						continue;
20902 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20903 						continue;
20904 
20905 					ret ~= fmt.cfFormat;
20906 				}
20907 			}
20908 
20909 			return ret;
20910 		} else throw new NotYetImplementedException();
20911 	}
20912 
20913 	/++
20914 		Gets data from the drop and optionally accepts it.
20915 
20916 		Returns:
20917 			void because the data is fed asynchronously through the `dg` parameter.
20918 
20919 		Params:
20920 			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.
20921 
20922 			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.
20923 
20924 			Calling `getData` again after accepting a drop is not permitted.
20925 
20926 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20927 
20928 			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.
20929 
20930 		Throws:
20931 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20932 
20933 		History:
20934 			Included in first release of [DropPackage].
20935 	+/
20936 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20937 		version(X11) {
20938 
20939 			auto display = XDisplayConnection.get();
20940 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20941 			auto best = format;
20942 
20943 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20944 
20945 				XDisplay* display;
20946 				Atom selectionAtom;
20947 				DraggableData.FormatId best;
20948 				DraggableData.FormatId format;
20949 				void delegate(scope ubyte[] data) dg;
20950 				DragAndDropAction acceptedAction;
20951 				Window sourceWindow;
20952 				SimpleWindow win;
20953 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20954 					this.display = display;
20955 					this.win = win;
20956 					this.sourceWindow = sourceWindow;
20957 					this.format = format;
20958 					this.selectionAtom = selectionAtom;
20959 					this.best = best;
20960 					this.dg = dg;
20961 					this.acceptedAction = acceptedAction;
20962 				}
20963 
20964 
20965 				mixin X11GetSelectionHandler_Basics;
20966 
20967 				void handleData(Atom target, in ubyte[] data) {
20968 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20969 
20970 					dg(cast(ubyte[]) data);
20971 
20972 					if(acceptedAction != DragAndDropAction.none) {
20973 						auto display = XDisplayConnection.get;
20974 
20975 						XClientMessageEvent xclient;
20976 
20977 						xclient.type = EventType.ClientMessage;
20978 						xclient.window = sourceWindow;
20979 						xclient.message_type = GetAtom!"XdndFinished"(display);
20980 						xclient.format = 32;
20981 						xclient.data.l[0] = win.impl.window;
20982 						xclient.data.l[1] = 1; // drop successful
20983 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20984 
20985 						XSendEvent(
20986 							display,
20987 							sourceWindow,
20988 							false,
20989 							EventMask.NoEventMask,
20990 							cast(XEvent*) &xclient
20991 						);
20992 
20993 						XFlush(display);
20994 					}
20995 				}
20996 
20997 				Atom findBestFormat(Atom[] answer) {
20998 					Atom best = None;
20999 					foreach(option; answer) {
21000 						if(option == format) {
21001 							best = option;
21002 							break;
21003 						}
21004 						/*
21005 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
21006 							best = option;
21007 							break;
21008 						} else if(option == XA_STRING) {
21009 							best = option;
21010 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
21011 							best = option;
21012 						}
21013 						*/
21014 					}
21015 					return best;
21016 				}
21017 			}
21018 
21019 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
21020 
21021 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
21022 
21023 		} else version(Windows) {
21024 
21025 			// clean up like DragLeave
21026 			// pass effect back up
21027 
21028 			FORMATETC t;
21029 			assert(format >= 0 && format <= ushort.max);
21030 			t.cfFormat = cast(ushort) format;
21031 			t.lindex = -1;
21032 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21033 			t.tymed = TYMED.TYMED_HGLOBAL;
21034 
21035 			STGMEDIUM m;
21036 
21037 			if(pDataObj.GetData(&t, &m) != S_OK) {
21038 				// fail
21039 			} else {
21040 				// succeed, take the data and clean up
21041 
21042 				// FIXME: ensure it is legit HGLOBAL
21043 				auto handle = m.hGlobal;
21044 
21045 				if(handle) {
21046 					auto sz = GlobalSize(handle);
21047 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
21048 						scope(exit) GlobalUnlock(handle);
21049 						scope(exit) GlobalFree(handle);
21050 
21051 						auto data = ptr[0 .. sz];
21052 
21053 						dg(data);
21054 					}
21055 				}
21056 			}
21057 		}
21058 	}
21059 
21060 	private:
21061 
21062 	version(X11) {
21063 		SimpleWindow win;
21064 		Window sourceWindow;
21065 		Time dataTimestamp;
21066 
21067 		Atom[] xFormats;
21068 	}
21069 	version(Windows) {
21070 		IDataObject pDataObj;
21071 	}
21072 }
21073 
21074 /++
21075 	A generic helper base class for making a drop handler with a preference list of custom types.
21076 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
21077 	droppers too.
21078 
21079 	It assumes the whole window it used, but you can subclass to change that.
21080 
21081 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21082 
21083 	History:
21084 		Added February 19, 2021
21085 +/
21086 /// Group: drag_and_drop
21087 class GenericDropHandlerBase : DropHandler {
21088 	// no fancy state here so no need to do anything here
21089 	void finish() { }
21090 	void dragLeave() { }
21091 
21092 	private DragAndDropAction acceptedAction;
21093 	private DraggableData.FormatId acceptedFormat;
21094 	private void delegate(scope ubyte[]) acceptedHandler;
21095 
21096 	struct FormatHandler {
21097 		DraggableData.FormatId format;
21098 		void delegate(scope ubyte[]) handler;
21099 	}
21100 
21101 	protected abstract FormatHandler[] formatHandlers();
21102 
21103 	DragAndDropAction dragEnter(DropPackage* pkg) {
21104 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
21105 		foreach(fmt; formatHandlers())
21106 		foreach(f; pkg.availableFormats())
21107 			if(f == fmt.format) {
21108 				acceptedFormat = f;
21109 				acceptedHandler = fmt.handler;
21110 				return acceptedAction = DragAndDropAction.copy;
21111 			}
21112 		return acceptedAction = DragAndDropAction.none;
21113 	}
21114 	DropParameters dragOver(Point pt) {
21115 		return DropParameters(acceptedAction);
21116 	}
21117 
21118 	void drop(scope DropPackage* dropPackage) {
21119 		if(!acceptedFormat || acceptedHandler is null) {
21120 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
21121 			return; // prolly shouldn't happen anyway...
21122 		}
21123 
21124 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
21125 	}
21126 }
21127 
21128 /++
21129 	A simple handler for making your window accept drops of plain text.
21130 
21131 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21132 
21133 	History:
21134 		Added February 22, 2021
21135 +/
21136 /// Group: drag_and_drop
21137 class TextDropHandler : GenericDropHandlerBase {
21138 	private void delegate(in char[] text) dg;
21139 
21140 	/++
21141 
21142 	+/
21143 	this(void delegate(in char[] text) dg) {
21144 		this.dg = dg;
21145 	}
21146 
21147 	protected override FormatHandler[] formatHandlers() {
21148 		version(X11)
21149 			return [
21150 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21151 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21152 			];
21153 		else version(Windows)
21154 			return [
21155 				FormatHandler(CF_UNICODETEXT, &translator),
21156 			];
21157 		else throw new NotYetImplementedException();
21158 	}
21159 
21160 	private void translator(scope ubyte[] data) {
21161 		version(X11)
21162 			dg(cast(char[]) data);
21163 		else version(Windows)
21164 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21165 	}
21166 }
21167 
21168 /++
21169 	A simple handler for making your window accept drops of files, issued to you as file names.
21170 
21171 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21172 
21173 	History:
21174 		Added February 22, 2021
21175 +/
21176 /// Group: drag_and_drop
21177 
21178 class FilesDropHandler : GenericDropHandlerBase {
21179 	private void delegate(in char[][]) dg;
21180 
21181 	/++
21182 
21183 	+/
21184 	this(void delegate(in char[][] fileNames) dg) {
21185 		this.dg = dg;
21186 	}
21187 
21188 	protected override FormatHandler[] formatHandlers() {
21189 		version(X11)
21190 			return [
21191 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
21192 			];
21193 		else version(Windows)
21194 			return [
21195 				FormatHandler(CF_HDROP, &translator),
21196 			];
21197 		else throw new NotYetImplementedException();
21198 	}
21199 
21200 	private void translator(scope ubyte[] data) {
21201 		version(X11) {
21202 			char[] listString = cast(char[]) data;
21203 			char[][16] buffer;
21204 			int count;
21205 			char[][] result = buffer[];
21206 
21207 			void commit(char[] s) {
21208 				if(count == result.length)
21209 					result.length += 16;
21210 				if(s.length > 7 && s[0 ..7] == "file://")
21211 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
21212 				result[count++] = s;
21213 			}
21214 
21215 			size_t last;
21216 			foreach(idx, char c; listString) {
21217 				if(c == '\n') {
21218 					commit(listString[last .. idx - 1]); // a \r
21219 					last = idx + 1; // a \n
21220 				}
21221 			}
21222 
21223 			if(last < listString.length) {
21224 				commit(listString[last .. $]);
21225 			}
21226 
21227 			// FIXME: they are uris now, should I translate it to local file names?
21228 			// of course the host name is supposed to be there cuz of X rokking...
21229 
21230 			dg(result[0 .. count]);
21231 		} else version(Windows) {
21232 
21233 			static struct DROPFILES {
21234 				DWORD pFiles;
21235 				POINT pt;
21236 				BOOL  fNC;
21237 				BOOL  fWide;
21238 			}
21239 
21240 
21241 			const(char)[][16] buffer;
21242 			int count;
21243 			const(char)[][] result = buffer[];
21244 			size_t last;
21245 
21246 			void commitA(in char[] stuff) {
21247 				if(count == result.length)
21248 					result.length += 16;
21249 				result[count++] = stuff;
21250 			}
21251 
21252 			void commitW(in wchar[] stuff) {
21253 				commitA(makeUtf8StringFromWindowsString(stuff));
21254 			}
21255 
21256 			void magic(T)(T chars) {
21257 				size_t idx;
21258 				while(chars[idx]) {
21259 					last = idx;
21260 					while(chars[idx]) {
21261 						idx++;
21262 					}
21263 					static if(is(T == char*))
21264 						commitA(chars[last .. idx]);
21265 					else
21266 						commitW(chars[last .. idx]);
21267 					idx++;
21268 				}
21269 			}
21270 
21271 			auto df = cast(DROPFILES*) data.ptr;
21272 			if(df.fWide) {
21273 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
21274 				magic(chars);
21275 			} else {
21276 				char* chars = cast(char*) (data.ptr + df.pFiles);
21277 				magic(chars);
21278 			}
21279 			dg(result[0 .. count]);
21280 		}
21281 		else throw new NotYetImplementedException();
21282 	}
21283 }
21284 
21285 /++
21286 	Interface to describe data being dragged. See also [draggable] helper function.
21287 
21288 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21289 
21290 	History:
21291 		Added February 19, 2021
21292 +/
21293 interface DraggableData {
21294 	version(X11)
21295 		alias FormatId = Atom;
21296 	else
21297 		alias FormatId = uint;
21298 	/++
21299 		Gets the platform-specific FormatId associated with the given named format.
21300 
21301 		This may be a MIME type, but may also be other various strings defined by the
21302 		programs you want to interoperate with.
21303 
21304 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
21305 		and convert it to some particular type for you.
21306 	+/
21307 	static FormatId getFormatId(string name)() {
21308 		version(X11)
21309 			return GetAtom!name(XDisplayConnection.get);
21310 		else version(Windows) {
21311 			static UINT cache;
21312 			if(!cache)
21313 				cache = RegisterClipboardFormatA(name);
21314 			return cache;
21315 		} else
21316 			throw new NotYetImplementedException();
21317 	}
21318 
21319 	/++
21320 		Looks up a string to represent the name for the given format, if there is one.
21321 
21322 		You should avoid using this function because it is slow. It is provided more for
21323 		debugging than for primary use.
21324 	+/
21325 	static string getFormatName(FormatId format) {
21326 		version(X11) {
21327 			if(format == 0)
21328 				return "None";
21329 			else
21330 				return getAtomName(format, XDisplayConnection.get);
21331 		} else version(Windows) {
21332 			switch(format) {
21333 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
21334 				case CF_DIBV5: return "CF_DIBV5";
21335 				case CF_RIFF: return "CF_RIFF";
21336 				case CF_WAVE: return "CF_WAVE";
21337 				case CF_HDROP: return "CF_HDROP";
21338 				default:
21339 					char[1024] name;
21340 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
21341 					return name[0 .. count].idup;
21342 			}
21343 		} else throw new NotYetImplementedException();
21344 	}
21345 
21346 	FormatId[] availableFormats();
21347 	// Return the slice of data you filled, empty slice if done.
21348 	// this is to support the incremental thing
21349 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21350 
21351 	size_t dataLength(FormatId format);
21352 }
21353 
21354 /++
21355 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21356 
21357 	History:
21358 		Added February 19, 2021
21359 +/
21360 DraggableData draggable(string s) {
21361 	version(X11)
21362 	return new class X11SetSelectionHandler_Text, DraggableData {
21363 		this() {
21364 			super(s);
21365 		}
21366 
21367 		override FormatId[] availableFormats() {
21368 			return X11SetSelectionHandler_Text.availableFormats();
21369 		}
21370 
21371 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21372 			return X11SetSelectionHandler_Text.getData(format, data);
21373 		}
21374 
21375 		size_t dataLength(FormatId format) {
21376 			return s.length;
21377 		}
21378 	};
21379 	else version(Windows)
21380 	return new class DraggableData {
21381 		FormatId[] availableFormats() {
21382 			return [CF_UNICODETEXT];
21383 		}
21384 
21385 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21386 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21387 		}
21388 
21389 		size_t dataLength(FormatId format) {
21390 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21391 		}
21392 	};
21393 	else
21394 	throw new NotYetImplementedException();
21395 }
21396 
21397 /++
21398 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21399 
21400 	History:
21401 		Added February 19, 2021
21402 +/
21403 /// Group: drag_and_drop
21404 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21405 in {
21406 	assert(window !is null);
21407 	assert(handler !is null);
21408 }
21409 do
21410 {
21411 	version(X11) {
21412 		auto sh = cast(X11SetSelectionHandler) handler;
21413 		if(sh is null) {
21414 			// gotta make my own adapter.
21415 			sh = new class X11SetSelectionHandler {
21416 				mixin X11SetSelectionHandler_Basics;
21417 
21418 				Atom[] availableFormats() { return handler.availableFormats(); }
21419 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21420 					return handler.getData(format, data);
21421 				}
21422 
21423 				// since the drop selection is only ever used once it isn't important
21424 				// to reset it.
21425 				void done() {}
21426 			};
21427 		}
21428 		return doDragDropX11(window, sh, action);
21429 	} else version(Windows) {
21430 		return doDragDropWindows(window, handler, action);
21431 	} else throw new NotYetImplementedException();
21432 }
21433 
21434 version(Windows)
21435 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21436 	IDataObject obj = new class IDataObject {
21437 		ULONG refCount;
21438 		ULONG AddRef() {
21439 			return ++refCount;
21440 		}
21441 		ULONG Release() {
21442 			return --refCount;
21443 		}
21444 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21445 			if (IID_IUnknown == *riid) {
21446 				*ppv = cast(void*) cast(IUnknown) this;
21447 			}
21448 			else if (IID_IDataObject == *riid) {
21449 				*ppv = cast(void*) cast(IDataObject) this;
21450 			}
21451 			else {
21452 				*ppv = null;
21453 				return E_NOINTERFACE;
21454 			}
21455 
21456 			AddRef();
21457 			return NOERROR;
21458 		}
21459 
21460 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21461 			//  writeln("Advise");
21462 			return E_NOTIMPL;
21463 		}
21464 		HRESULT DUnadvise(DWORD dwConnection) {
21465 			return E_NOTIMPL;
21466 		}
21467 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21468 			//  writeln("EnumDAdvise");
21469 			return OLE_E_ADVISENOTSUPPORTED;
21470 		}
21471 		// tell what formats it supports
21472 
21473 		FORMATETC[] types;
21474 		this() {
21475 			FORMATETC t;
21476 			foreach(ty; handler.availableFormats()) {
21477 				assert(ty <= ushort.max && ty >= 0);
21478 				t.cfFormat = cast(ushort) ty;
21479 				t.lindex = -1;
21480 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21481 				t.tymed = TYMED.TYMED_HGLOBAL;
21482 			}
21483 			types ~= t;
21484 		}
21485 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21486 			if(dwDirection == DATADIR.DATADIR_GET) {
21487 				*ppenumFormatEtc = new class IEnumFORMATETC {
21488 					ULONG refCount;
21489 					ULONG AddRef() {
21490 						return ++refCount;
21491 					}
21492 					ULONG Release() {
21493 						return --refCount;
21494 					}
21495 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21496 						if (IID_IUnknown == *riid) {
21497 							*ppv = cast(void*) cast(IUnknown) this;
21498 						}
21499 						else if (IID_IEnumFORMATETC == *riid) {
21500 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21501 						}
21502 						else {
21503 							*ppv = null;
21504 							return E_NOINTERFACE;
21505 						}
21506 
21507 						AddRef();
21508 						return NOERROR;
21509 					}
21510 
21511 
21512 					int pos;
21513 					this() {
21514 						pos = 0;
21515 					}
21516 
21517 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21518 						// writeln("clone");
21519 						return E_NOTIMPL; // FIXME
21520 					}
21521 
21522 					// Caller is responsible for freeing memory
21523 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21524 						// fetched may be null if celt is one
21525 						if(celt != 1)
21526 							return E_NOTIMPL; // FIXME
21527 
21528 						if(celt + pos > types.length)
21529 							return S_FALSE;
21530 
21531 						*rgelt = types[pos++];
21532 
21533 						if(pceltFetched !is null)
21534 							*pceltFetched = 1;
21535 
21536 						// writeln("ok celt ", celt);
21537 						return S_OK;
21538 					}
21539 
21540 					HRESULT Reset() {
21541 						pos = 0;
21542 						return S_OK;
21543 					}
21544 
21545 					HRESULT Skip(ULONG celt) {
21546 						if(celt + pos <= types.length) {
21547 							pos += celt;
21548 							return S_OK;
21549 						}
21550 						return S_FALSE;
21551 					}
21552 				};
21553 
21554 				return S_OK;
21555 			} else
21556 				return E_NOTIMPL;
21557 		}
21558 		// given a format, return the format you'd prefer to use cuz it is identical
21559 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21560 			// FIXME: prolly could be better but meh
21561 			// writeln("gcf: ", *pformatectIn);
21562 			*pformatetcOut = *pformatectIn;
21563 			return S_OK;
21564 		}
21565 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21566 			foreach(ty; types) {
21567 				if(ty == *pformatetcIn) {
21568 					auto format = ty.cfFormat;
21569 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
21570 					STGMEDIUM medium;
21571 					medium.tymed = TYMED.TYMED_HGLOBAL;
21572 
21573 					auto sz = handler.dataLength(format);
21574 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21575 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21576 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21577 						auto slice = data[0 .. sz];
21578 						scope(exit)
21579 							GlobalUnlock(handle);
21580 
21581 						handler.getData(format, cast(ubyte[]) slice[]);
21582 					}
21583 
21584 
21585 					medium.hGlobal = handle; // FIXME
21586 					*pmedium = medium;
21587 					return S_OK;
21588 				}
21589 			}
21590 			return DV_E_FORMATETC;
21591 		}
21592 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21593 			// writeln("GDH: ", *pformatetcIn);
21594 			return E_NOTIMPL; // FIXME
21595 		}
21596 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21597 			auto search = *pformatetc;
21598 			search.tymed &= TYMED.TYMED_HGLOBAL;
21599 			foreach(ty; types)
21600 				if(ty == search) {
21601 					// writeln("QueryGetData ", search, " ", types[0]);
21602 					return S_OK;
21603 				}
21604 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21605 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
21606 			}
21607 			return S_FALSE;
21608 		}
21609 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21610 			//  writeln("SetData: ");
21611 			return E_NOTIMPL;
21612 		}
21613 	};
21614 
21615 
21616 	IDropSource src = new class IDropSource {
21617 		ULONG refCount;
21618 		ULONG AddRef() {
21619 			return ++refCount;
21620 		}
21621 		ULONG Release() {
21622 			return --refCount;
21623 		}
21624 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21625 			if (IID_IUnknown == *riid) {
21626 				*ppv = cast(void*) cast(IUnknown) this;
21627 			}
21628 			else if (IID_IDropSource == *riid) {
21629 				*ppv = cast(void*) cast(IDropSource) this;
21630 			}
21631 			else {
21632 				*ppv = null;
21633 				return E_NOINTERFACE;
21634 			}
21635 
21636 			AddRef();
21637 			return NOERROR;
21638 		}
21639 
21640 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21641 			if(fEscapePressed)
21642 				return DRAGDROP_S_CANCEL;
21643 			if(!(grfKeyState & MK_LBUTTON))
21644 				return DRAGDROP_S_DROP;
21645 			return S_OK;
21646 		}
21647 
21648 		int GiveFeedback(uint dwEffect) {
21649 			return DRAGDROP_S_USEDEFAULTCURSORS;
21650 		}
21651 	};
21652 
21653 	DWORD effect;
21654 
21655 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21656 
21657 	DROPEFFECT de = win32DragAndDropAction(action);
21658 
21659 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21660 	// but still prolly a FIXME
21661 
21662 	auto ret = DoDragDrop(obj, src, de, &effect);
21663 	/+
21664 	if(ret == DRAGDROP_S_DROP)
21665 		writeln("drop ", effect);
21666 	else if(ret == DRAGDROP_S_CANCEL)
21667 		writeln("cancel");
21668 	else if(ret == S_OK)
21669 		writeln("ok");
21670 	else writeln(ret);
21671 	+/
21672 
21673 	return ret;
21674 }
21675 
21676 version(Windows)
21677 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21678 	DROPEFFECT de;
21679 
21680 	with(DragAndDropAction)
21681 	with(DROPEFFECT)
21682 	final switch(action) {
21683 		case none: de = DROPEFFECT_NONE; break;
21684 		case copy: de = DROPEFFECT_COPY; break;
21685 		case move: de = DROPEFFECT_MOVE; break;
21686 		case link: de = DROPEFFECT_LINK; break;
21687 		case ask: throw new Exception("ask not implemented yet");
21688 		case custom: throw new Exception("custom not implemented yet");
21689 	}
21690 
21691 	return de;
21692 }
21693 
21694 
21695 /++
21696 	History:
21697 		Added February 19, 2021
21698 +/
21699 /// Group: drag_and_drop
21700 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21701 	version(X11) {
21702 		auto display = XDisplayConnection.get;
21703 
21704 		Atom atom = 5; // right???
21705 
21706 		XChangeProperty(
21707 			display,
21708 			window.impl.window,
21709 			GetAtom!"XdndAware"(display),
21710 			XA_ATOM,
21711 			32 /* bits */,
21712 			PropModeReplace,
21713 			&atom,
21714 			1);
21715 
21716 		window.dropHandler = handler;
21717 	} else version(Windows) {
21718 
21719 		initDnd();
21720 
21721 		auto dropTarget = new class (handler) IDropTarget {
21722 			DropHandler handler;
21723 			this(DropHandler handler) {
21724 				this.handler = handler;
21725 			}
21726 			ULONG refCount;
21727 			ULONG AddRef() {
21728 				return ++refCount;
21729 			}
21730 			ULONG Release() {
21731 				return --refCount;
21732 			}
21733 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21734 				if (IID_IUnknown == *riid) {
21735 					*ppv = cast(void*) cast(IUnknown) this;
21736 				}
21737 				else if (IID_IDropTarget == *riid) {
21738 					*ppv = cast(void*) cast(IDropTarget) this;
21739 				}
21740 				else {
21741 					*ppv = null;
21742 					return E_NOINTERFACE;
21743 				}
21744 
21745 				AddRef();
21746 				return NOERROR;
21747 			}
21748 
21749 
21750 			// ///////////////////
21751 
21752 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21753 				DropPackage dropPackage = DropPackage(pDataObj);
21754 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21755 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21756 			}
21757 
21758 			HRESULT DragLeave() {
21759 				handler.dragLeave();
21760 				// release the IDataObject if needed
21761 				return S_OK;
21762 			}
21763 
21764 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21765 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21766 
21767 				*pdwEffect = win32DragAndDropAction(res.action);
21768 				// same as DragEnter basically
21769 				return S_OK;
21770 			}
21771 
21772 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21773 				DropPackage pkg = DropPackage(pDataObj);
21774 				handler.drop(&pkg);
21775 
21776 				return S_OK;
21777 			}
21778 		};
21779 		// Windows can hold on to the handler and try to call it
21780 		// during which time the GC can't see it. so important to
21781 		// manually manage this. At some point i'll FIXME and make
21782 		// all my com instances manually managed since they supposed
21783 		// to respect the refcount.
21784 		import core.memory;
21785 		GC.addRoot(cast(void*) dropTarget);
21786 
21787 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21788 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21789 
21790 		window.dropHandler = handler;
21791 	} else throw new NotYetImplementedException();
21792 }
21793 
21794 
21795 
21796 static if(UsingSimpledisplayX11) {
21797 
21798 enum _NET_WM_STATE_ADD = 1;
21799 enum _NET_WM_STATE_REMOVE = 0;
21800 enum _NET_WM_STATE_TOGGLE = 2;
21801 
21802 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21803 void demandAttention(SimpleWindow window, bool needs = true) {
21804 	demandAttention(window.impl.window, needs);
21805 }
21806 
21807 /// ditto
21808 void demandAttention(Window window, bool needs = true) {
21809 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21810 }
21811 
21812 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21813 	auto display = XDisplayConnection.get();
21814 	if(atom == None)
21815 		return; // non-failure error
21816 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21817 
21818 	XClientMessageEvent xclient;
21819 
21820 	xclient.type = EventType.ClientMessage;
21821 	xclient.window = window;
21822 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21823 	xclient.format = 32;
21824 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21825 	xclient.data.l[1] = atom;
21826 	xclient.data.l[2] = atom2;
21827 	xclient.data.l[3] = 1;
21828 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21829 
21830 	XSendEvent(
21831 		display,
21832 		RootWindow(display, DefaultScreen(display)),
21833 		false,
21834 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21835 		cast(XEvent*) &xclient
21836 	);
21837 
21838 	/+
21839 	XChangeProperty(
21840 		display,
21841 		window.impl.window,
21842 		GetAtom!"_NET_WM_STATE"(display),
21843 		XA_ATOM,
21844 		32 /* bits */,
21845 		PropModeAppend,
21846 		&atom,
21847 		1);
21848 	+/
21849 }
21850 
21851 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21852 	Atom actionAtom;
21853 	with(DragAndDropAction)
21854 	final switch(action) {
21855 		case none: actionAtom = None; break;
21856 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21857 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21858 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21859 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21860 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21861 	}
21862 
21863 	return actionAtom;
21864 }
21865 
21866 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21867 	// FIXME: I need to show user feedback somehow.
21868 	auto display = XDisplayConnection.get;
21869 
21870 	auto actionAtom = dndActionAtom(display, action);
21871 	assert(actionAtom, "Don't use action none to accept a drop");
21872 
21873 	setX11Selection!"XdndSelection"(window, handler, null);
21874 
21875 	auto oldKeyHandler = window.handleKeyEvent;
21876 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21877 
21878 	auto oldCharHandler = window.handleCharEvent;
21879 	scope(exit) window.handleCharEvent = oldCharHandler;
21880 
21881 	auto oldMouseHandler = window.handleMouseEvent;
21882 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21883 
21884 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21885 
21886 	import core.sys.posix.sys.time;
21887 	timeval tv;
21888 	gettimeofday(&tv, null);
21889 
21890 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21891 
21892 	Time lastMouseTimestamp;
21893 
21894 	bool dnding = true;
21895 	Window lastIn = None;
21896 
21897 	void leave() {
21898 		if(lastIn == None)
21899 			return;
21900 
21901 		XEvent ev;
21902 		ev.xclient.type = EventType.ClientMessage;
21903 		ev.xclient.window = lastIn;
21904 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21905 		ev.xclient.format = 32;
21906 		ev.xclient.data.l[0] = window.impl.window;
21907 
21908 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21909 		XFlush(display);
21910 
21911 		lastIn = None;
21912 	}
21913 
21914 	void enter(Window w) {
21915 		assert(lastIn == None);
21916 
21917 		lastIn = w;
21918 
21919 		XEvent ev;
21920 		ev.xclient.type = EventType.ClientMessage;
21921 		ev.xclient.window = lastIn;
21922 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21923 		ev.xclient.format = 32;
21924 		ev.xclient.data.l[0] = window.impl.window;
21925 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21926 
21927 		auto types = handler.availableFormats();
21928 		assert(types.length > 0);
21929 
21930 		ev.xclient.data.l[2] = types[0];
21931 		if(types.length > 1)
21932 			ev.xclient.data.l[3] = types[1];
21933 		if(types.length > 2)
21934 			ev.xclient.data.l[4] = types[2];
21935 
21936 		// FIXME: other types?!?!? and make sure we skip TARGETS
21937 
21938 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21939 		XFlush(display);
21940 	}
21941 
21942 	void position(int rootX, int rootY) {
21943 		assert(lastIn != None);
21944 
21945 		XEvent ev;
21946 		ev.xclient.type = EventType.ClientMessage;
21947 		ev.xclient.window = lastIn;
21948 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21949 		ev.xclient.format = 32;
21950 		ev.xclient.data.l[0] = window.impl.window;
21951 		ev.xclient.data.l[1] = 0; // reserved
21952 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21953 		ev.xclient.data.l[3] = dataTimestamp;
21954 		ev.xclient.data.l[4] = actionAtom;
21955 
21956 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21957 		XFlush(display);
21958 
21959 	}
21960 
21961 	void drop() {
21962 		XEvent ev;
21963 		ev.xclient.type = EventType.ClientMessage;
21964 		ev.xclient.window = lastIn;
21965 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21966 		ev.xclient.format = 32;
21967 		ev.xclient.data.l[0] = window.impl.window;
21968 		ev.xclient.data.l[1] = 0; // reserved
21969 		ev.xclient.data.l[2] = dataTimestamp;
21970 
21971 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21972 		XFlush(display);
21973 
21974 		lastIn = None;
21975 		dnding = false;
21976 	}
21977 
21978 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21979 	// but idk if i should...
21980 
21981 	window.setEventHandlers(
21982 		delegate(KeyEvent ev) {
21983 			if(ev.pressed == true && ev.key == Key.Escape) {
21984 				// cancel
21985 				dnding = false;
21986 			}
21987 		},
21988 		delegate(MouseEvent ev) {
21989 			if(ev.timestamp < lastMouseTimestamp)
21990 				return;
21991 
21992 			lastMouseTimestamp = ev.timestamp;
21993 
21994 			if(ev.type == MouseEventType.motion) {
21995 				auto display = XDisplayConnection.get;
21996 				auto root = RootWindow(display, DefaultScreen(display));
21997 
21998 				Window topWindow;
21999 				int rootX, rootY;
22000 
22001 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
22002 
22003 				if(topWindow == None)
22004 					return;
22005 
22006 				top:
22007 				if(auto result = topWindow in eligibility) {
22008 					auto dropWindow = *result;
22009 					if(dropWindow == None) {
22010 						leave();
22011 						return;
22012 					}
22013 
22014 					if(dropWindow != lastIn) {
22015 						leave();
22016 						enter(dropWindow);
22017 						position(rootX, rootY);
22018 					} else {
22019 						position(rootX, rootY);
22020 					}
22021 				} else {
22022 					// determine eligibility
22023 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
22024 					if(data.length == 1) {
22025 						// in case there is no WM or it isn't reparenting
22026 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
22027 					} else {
22028 
22029 						Window tryScanChildren(Window search, int maxRecurse) {
22030 							// could be reparenting window manager, so gotta check the next few children too
22031 							Window child;
22032 							int x;
22033 							int y;
22034 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
22035 
22036 							if(child == None)
22037 								return None;
22038 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
22039 							if(data.length == 1) {
22040 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
22041 							} else {
22042 								if(maxRecurse)
22043 									return tryScanChildren(child, maxRecurse - 1);
22044 								else
22045 									return None;
22046 							}
22047 
22048 						}
22049 
22050 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
22051 						auto topResult = tryScanChildren(topWindow, 3);
22052 						// it is easy to have a false negative due to the mouse going over a WM
22053 						// child window like the close button if separate from the frame... so I
22054 						// can't really cache negatives, :(
22055 						if(topResult != None) {
22056 							eligibility[topWindow] = topResult;
22057 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
22058 						}
22059 					}
22060 
22061 				}
22062 
22063 			} else if(ev.type == MouseEventType.buttonReleased) {
22064 				drop();
22065 				dnding = false;
22066 			}
22067 		}
22068 	);
22069 
22070 	window.grabInput();
22071 	scope(exit)
22072 		window.releaseInputGrab();
22073 
22074 
22075 	EventLoop.get.run(() => dnding);
22076 
22077 	return 0;
22078 }
22079 
22080 /// X-specific
22081 TrueColorImage getWindowNetWmIcon(Window window) {
22082 	try {
22083 		auto display = XDisplayConnection.get;
22084 
22085 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
22086 
22087 		if (data.length > arch_ulong.sizeof * 2) {
22088 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
22089 			// these are an array of rgba images that we have to convert into pixmaps ourself
22090 
22091 			int width = cast(int) meta[0];
22092 			int height = cast(int) meta[1];
22093 
22094 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
22095 
22096 			static if(arch_ulong.sizeof == 4) {
22097 				bytes = bytes[0 .. width * height * 4];
22098 				alias imageData = bytes;
22099 			} else static if(arch_ulong.sizeof == 8) {
22100 				bytes = bytes[0 .. width * height * 8];
22101 				auto imageData = new ubyte[](4 * width * height);
22102 			} else static assert(0);
22103 
22104 
22105 
22106 			// this returns ARGB. Remember it is little-endian so
22107 			//                                         we have BGRA
22108 			// our thing uses RGBA, which in little endian, is ABGR
22109 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
22110 				auto r = bytes[idx + 2];
22111 				auto g = bytes[idx + 1];
22112 				auto b = bytes[idx + 0];
22113 				auto a = bytes[idx + 3];
22114 
22115 				imageData[idx2 + 0] = r;
22116 				imageData[idx2 + 1] = g;
22117 				imageData[idx2 + 2] = b;
22118 				imageData[idx2 + 3] = a;
22119 			}
22120 
22121 			return new TrueColorImage(width, height, imageData);
22122 		}
22123 
22124 		return null;
22125 	} catch(Exception e) {
22126 		return null;
22127 	}
22128 }
22129 
22130 } /* UsingSimpledisplayX11 */
22131 
22132 
22133 void loadBinNameToWindowClassName () {
22134 	import core.stdc.stdlib : realloc;
22135 	version(linux) {
22136 		// args[0] MAY be empty, so we'll just use this
22137 		import core.sys.posix.unistd : readlink;
22138 		char[1024] ebuf = void; // 1KB should be enough for everyone!
22139 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
22140 		if (len < 1) return;
22141 	} else /*version(Windows)*/ {
22142 		import core.runtime : Runtime;
22143 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
22144 		auto ebuf = Runtime.args[0];
22145 		auto len = ebuf.length;
22146 	}
22147 	auto pos = len;
22148 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22149 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22150 	if (sdpyWindowClassStr is null) return; // oops
22151 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22152 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22153 }
22154 
22155 /++
22156 	An interface representing a font that is drawn with custom facilities.
22157 
22158 	You might want [OperatingSystemFont] instead, which represents
22159 	a font loaded and drawn by functions native to the operating system.
22160 
22161 	WARNING: I might still change this.
22162 +/
22163 interface DrawableFont : MeasurableFont {
22164 	/++
22165 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22166 
22167 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22168 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22169 		fill color, but that's up to the implementation.
22170 	+/
22171 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22172 
22173 	/++
22174 		Requests that the given string is added to the image cache. You should only do this rarely, but
22175 		if you have a string that you know will be used over and over again, adding it to a cache can
22176 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22177 		to implement this as a do-nothing method).
22178 	+/
22179 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22180 }
22181 
22182 /++
22183 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
22184 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
22185 
22186 	You should also consider [OperatingSystemFont], which loads and draws a font with
22187 	facilities native to the user's operating system. You might also consider
22188 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
22189 	of game, as they have their own ways to draw text too.
22190 
22191 	Be warned: this can be slow, especially on remote connections to the X server, since
22192 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
22193 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
22194 	experiment in your specific case.
22195 
22196 	Please note that the return type of [DrawableFont] also includes an implementation of
22197 	[MeasurableFont].
22198 +/
22199 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
22200 	import arsd.ttf;
22201 	static class ArsdTtfFont : DrawableFont {
22202 		TtfFont font;
22203 		int size;
22204 		this(in ubyte[] data, int size) {
22205 			font = TtfFont(data);
22206 			this.size = size;
22207 
22208 
22209 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
22210 			int ascent_, descent_, line_gap;
22211 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
22212 
22213 			int advance, lsb;
22214 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
22215 			xWidth = cast(int) (advance * scale);
22216 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
22217 			MWidth = cast(int) (advance * scale);
22218 		}
22219 
22220 		private int ascent_;
22221 		private int descent_;
22222 		private int xWidth;
22223 		private int MWidth;
22224 
22225 		bool isMonospace() {
22226 			return xWidth == MWidth;
22227 		}
22228 		int averageWidth() {
22229 			return xWidth;
22230 		}
22231 		int height() {
22232 			return size;
22233 		}
22234 		int ascent() {
22235 			return ascent_;
22236 		}
22237 		int descent() {
22238 			return descent_;
22239 		}
22240 
22241 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
22242 			int width, height;
22243 			font.getStringSize(s, size, width, height);
22244 			return width;
22245 		}
22246 
22247 
22248 
22249 		Sprite[string] cache;
22250 
22251 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
22252 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
22253 			cache[text] = sprite;
22254 		}
22255 
22256 		Image stringToImage(Color fg, Color bg, in char[] text) {
22257 			int width, height;
22258 			auto data = font.renderString(text, size, width, height);
22259 			auto image = new TrueColorImage(width, height);
22260 			int pos = 0;
22261 			foreach(y; 0 .. height)
22262 			foreach(x; 0 .. width) {
22263 				fg.a = data[0];
22264 				bg.a = 255;
22265 				auto color = alphaBlend(fg, bg);
22266 				image.imageData.bytes[pos++] = color.r;
22267 				image.imageData.bytes[pos++] = color.g;
22268 				image.imageData.bytes[pos++] = color.b;
22269 				image.imageData.bytes[pos++] = data[0];
22270 				data = data[1 .. $];
22271 			}
22272 			assert(data.length == 0);
22273 
22274 			return Image.fromMemoryImage(image);
22275 		}
22276 
22277 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
22278 			Sprite sprite = (text in cache) ? *(text in cache) : null;
22279 
22280 			auto fg = painter.impl._outlineColor;
22281 			auto bg = painter.impl._fillColor;
22282 
22283 			if(sprite !is null) {
22284 				auto w = cast(SimpleWindow) painter.window;
22285 				assert(w !is null);
22286 
22287 				sprite.drawAt(painter, upperLeft);
22288 			} else {
22289 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
22290 			}
22291 		}
22292 	}
22293 
22294 	return new ArsdTtfFont(data, size);
22295 }
22296 
22297 class NotYetImplementedException : Exception {
22298 	this(string file = __FILE__, size_t line = __LINE__) {
22299 		super("Not yet implemented", file, line);
22300 	}
22301 }
22302 
22303 ///
22304 __gshared bool librariesSuccessfullyLoaded = true;
22305 ///
22306 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
22307 
22308 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
22309 	mixin(staticForeachReplacement!Iface);
22310 
22311 	void loadDynamicLibrary() @nogc {
22312 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22313 	}
22314 
22315         void loadDynamicLibraryForReal() {
22316                 foreach(name; __traits(derivedMembers, Iface)) {
22317                         mixin("alias tmp = " ~ name ~ ";");
22318                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
22319                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
22320                 }
22321         }
22322 }
22323 
22324 private const(char)[] staticForeachReplacement(Iface)() pure {
22325 /*
22326 	// just this for gdc 9....
22327 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
22328 
22329         static foreach(name; __traits(derivedMembers, Iface))
22330                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22331 */
22332 
22333 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
22334 	size_t pos;
22335 
22336 	void append(in char[] what) {
22337 		if(pos + what.length > code.length)
22338 			code.length = (code.length * 3) / 2;
22339 		code[pos .. pos + what.length] = what[];
22340 		pos += what.length;
22341 	}
22342 
22343         foreach(name; __traits(derivedMembers, Iface)) {
22344                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
22345 		append(name);
22346 		append(`")) `);
22347 		append(name);
22348 		append(";");
22349 	}
22350 
22351 	return code[0 .. pos];
22352 }
22353 
22354 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22355 	mixin(staticForeachReplacement!Iface);
22356 
22357 	private __gshared void* libHandle;
22358 	private __gshared bool attempted;
22359 
22360         void loadDynamicLibrary() @nogc {
22361 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22362 	}
22363 
22364 	bool loadAttempted() {
22365 		return attempted;
22366 	}
22367 	bool loadSuccessful() {
22368 		return libHandle !is null;
22369 	}
22370 
22371         void loadDynamicLibraryForReal() {
22372 		attempted = true;
22373                 version(Posix) {
22374                         import core.sys.posix.dlfcn;
22375 			version(OSX) {
22376 				version(X11)
22377                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22378 				else
22379                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22380 			} else {
22381                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22382 				if(libHandle is null)
22383                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22384 			}
22385 
22386 			static void* loadsym(void* l, const char* name) {
22387 				import core.stdc.stdlib;
22388 				if(l is null)
22389 					return &abort;
22390 				return dlsym(l, name);
22391 			}
22392                 } else version(Windows) {
22393                         import core.sys.windows.winbase;
22394                         libHandle = LoadLibrary(library ~ ".dll");
22395 			static void* loadsym(void* l, const char* name) {
22396 				import core.stdc.stdlib;
22397 				if(l is null)
22398 					return &abort;
22399 				return GetProcAddress(l, name);
22400 			}
22401                 }
22402                 if(libHandle is null) {
22403 			success = false;
22404                         //throw new Exception("load failure of library " ~ library);
22405 		}
22406                 foreach(name; __traits(derivedMembers, Iface)) {
22407                         mixin("alias tmp = " ~ name ~ ";");
22408                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22409                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22410                 }
22411         }
22412 
22413         void unloadDynamicLibrary() {
22414                 version(Posix) {
22415                         import core.sys.posix.dlfcn;
22416                         dlclose(libHandle);
22417                 } else version(Windows) {
22418                         import core.sys.windows.winbase;
22419                         FreeLibrary(libHandle);
22420                 }
22421                 foreach(name; __traits(derivedMembers, Iface))
22422                         mixin(name ~ " = null;");
22423         }
22424 }
22425 
22426 /+
22427 	The GC can be called from any thread, and a lot of cleanup must be done
22428 	on the gui thread. Since the GC can interrupt any locks - including being
22429 	triggered inside a critical section - it is vital to avoid deadlocks to get
22430 	these functions called from the right place.
22431 
22432 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22433 	right now.
22434 
22435 	The cleanup function is run when the event loop gets around to it, which is just
22436 	whenever there's something there after it has been woken up for other work. It does
22437 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22438 	(Well actually it might be ok but i don't wanna mess with it right now.)
22439 +/
22440 private struct CleanupQueue {
22441 	import core.stdc.stdlib;
22442 
22443 	void queue(alias func, T...)(T args) {
22444 		static struct Args {
22445 			T args;
22446 		}
22447 		static struct RealJob {
22448 			Job j;
22449 			Args a;
22450 		}
22451 		static void call(Job* data) {
22452 			auto rj = cast(RealJob*) data;
22453 			func(rj.a.args);
22454 		}
22455 
22456 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22457 		thing.j.call = &call;
22458 		thing.a.args = args;
22459 
22460 		buffer[tail++] = cast(Job*) thing;
22461 
22462 		// FIXME: set overflowed
22463 	}
22464 
22465 	void process() {
22466 		const tail = this.tail;
22467 
22468 		while(tail != head) {
22469 			Job* job = cast(Job*) buffer[head++];
22470 			job.call(job);
22471 			free(job);
22472 		}
22473 
22474 		if(overflowed)
22475 			throw new Exception("cleanup overflowed");
22476 	}
22477 
22478 	private:
22479 
22480 	ubyte tail; // must ONLY be written by queue
22481 	ubyte head; // must ONLY be written by process
22482 	bool overflowed;
22483 
22484 	static struct Job {
22485 		void function(Job*) call;
22486 	}
22487 
22488 	void*[256] buffer;
22489 }
22490 private __gshared CleanupQueue cleanupQueue;
22491 
22492 // version(X11)
22493 /++
22494 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22495 
22496 	$(WARNING
22497 		This function is exempted from stability guarantees.
22498 	)
22499 +/
22500 float customScalingFactorForMonitor(int monitorNumber) {
22501 	import core.stdc.stdlib;
22502 	auto val = getenv("ARSD_SCALING_FACTOR");
22503 
22504 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
22505 	if(val is null)
22506 		return 1.0;
22507 
22508 	char[16] buffer = 0;
22509 	int pos;
22510 
22511 	const(char)* at = val;
22512 
22513 	foreach(item; 0 .. monitorNumber + 1) {
22514 		if(*at == 0)
22515 			break; // reuse the last number when we at the end of the string
22516 		pos = 0;
22517 		while(pos + 1 < buffer.length && *at && *at != ';') {
22518 			buffer[pos++] = *at;
22519 			at++;
22520 		}
22521 		if(*at)
22522 			at++; // skip the semicolon
22523 		buffer[pos] = 0;
22524 	}
22525 
22526 	//sdpyPrintDebugString(buffer[0 .. pos]);
22527 
22528 	import core.stdc.math;
22529 	auto f = atof(buffer.ptr);
22530 
22531 	if(f <= 0.0 || isnan(f) || isinf(f))
22532 		return 1.0;
22533 
22534 	return f;
22535 }
22536 
22537 void guiAbortProcess(string msg) {
22538 	import core.stdc.stdlib;
22539 	version(Windows) {
22540 		WCharzBuffer t = WCharzBuffer(msg);
22541 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22542 	} else {
22543 		import core.stdc.stdio;
22544 		fwrite(msg.ptr, 1, msg.length, stderr);
22545 		msg = "\n";
22546 		fwrite(msg.ptr, 1, msg.length, stderr);
22547 		fflush(stderr);
22548 	}
22549 
22550 	abort();
22551 }
22552 
22553 private int minInternal(int a, int b) {
22554 	return (a < b) ? a : b;
22555 }
22556 
22557 private alias scriptable = arsd_jsvar_compatible;