1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 /+
4 	To share some stuff between two opengl threads:
5 	windows
6 	https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading
7 	linux
8 	https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux
9 +/
10 
11 
12 // Search for: FIXME: leaks if multithreaded gc
13 
14 // https://freedesktop.org/wiki/Specifications/XDND/
15 
16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
17 
18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
20 
21 
22 // on Mac with X11: -L-L/usr/X11/lib
23 
24 /+
25 
26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
27 
28 	Progress bar in taskbar
29 		- i can probably just set a property on the window...
30 		  it sets that prop to an integer 0 .. 100. Taskbar
31 		  deletes it or window deletes it when it is handled.
32 		- prolly display it as a nice little line at the bottom.
33 
34 
35 from gtk:
36 
37 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
38 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
39 
40 >+  if (cardinal > 0)
41 >+  {
42 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
45 >+                     XA_CARDINAL, 32,
46 >+                     PropModeReplace,
47 >+                     (guchar *) &cardinal, 1);
48 >+  }
49 >+  else
50 >+  {
51 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
52 >+                     xid,
53 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
54 >+  }
55 
56 from Windows:
57 
58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
59 
60 interface
61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
63 listen for msg, return TRUE
64 interface->SetProgressState(hwnd, TBPF_NORMAL);
65 interface->SetProgressValue(hwnd, 40, 100);
66 
67 
68 	My new notification system.
69 		- use a unix socket? or a x property? or a udp port?
70 		- could of course also get on the dbus train but ugh.
71 		- it could also reply with the info as a string for easy remote examination.
72 
73 +/
74 
75 /*
76 	Event Loop would be nices:
77 
78 	* add on idle - runs when nothing else happens
79 		* which can specify how long to yield for
80 	* send messages without a recipient window
81 	* setTimeout
82 	* setInterval
83 */
84 
85 /*
86 	Classic games I want to add:
87 		* my tetris clone
88 		* pac man
89 */
90 
91 /*
92 	Text layout needs a lot of work. Plain drawText is useful but too
93 	limited. It will need some kind of text context thing which it will
94 	update and you can pass it on and get more details out of it.
95 
96 	It will need a bounding box, a current cursor location that is updated
97 	as drawing continues, and various changable facts (which can also be
98 	changed on the painter i guess) like font, color, size, background,
99 	etc.
100 
101 	We can also fetch the caret location from it somehow.
102 
103 	Should prolly be an overload of drawText
104 
105 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
106 
107 		WS_EX_NOACTIVATE
108 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
109 		full screen windows. Can just set the atom on X. Windows will be harder.
110 
111 		moving windows. resizing windows.
112 
113 		hide cursor, capture cursor, change cursor.
114 
115 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
116 	sure the pieces are there to do its job easily and make other jobs possible.
117 */
118 
119 /++
120 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
121 	including creating windows, drawing on them, working with the clipboard,
122 	timers, OpenGL, and more. However, it does NOT provide high level GUI
123 	widgets. See my minigui.d, an extension to this module, for that
124 	functionality.
125 
126 	simpledisplay provides cross-platform wrapping for Windows and Linux
127 	(and perhaps other OSes that use X11), but also does not prevent you
128 	from using the underlying facilities if you need them. It has a goal
129 	of working efficiently over a remote X link (at least as far as Xlib
130 	reasonably allows.)
131 
132 	simpledisplay depends on [arsd.color|color.d], which should be available from the
133 	same place where you got this file. Other than that, however, it has
134 	very few dependencies and ones that don't come with the OS and/or the
135 	compiler are all opt-in.
136 
137 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
138 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
139 
140 	simpledisplay is basically stable. I plan to refactor the internals,
141 	and may add new features and fix bugs, but It do not expect to
142 	significantly change the API. It has been stable a few years already now.
143 
144 	Installation_instructions:
145 
146 	`simpledisplay.d` does not have any dependencies outside the
147 	operating system and `color.d`, so it should just work most the
148 	time, but there are a few caveats on some systems:
149 
150 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
151 	console to be automatically allocated.
152 
153 	Please note when compiling on Win64, you need to explicitly list
154 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
155 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
156 
157 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
158 	note the "w".
159 
160 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
161 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
162 	See [EnableWindowsSubsystem] for more information.
163 
164 	$(PITFALL
165 		With the Windows subsystem, there is no console, so standard writeln will throw!
166 		You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
167 		create a console as needed.
168 	)
169 
170 	On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command.
171 
172 	On Ubuntu, you might need to install X11 development libraries to
173 	successfully link.
174 
175 	$(CONSOLE
176 		$ sudo apt-get install libglc-dev
177 		$ sudo apt-get install libx11-dev
178 	)
179 
180 
181 	Jump_list:
182 
183 	Don't worry, you don't have to read this whole documentation file!
184 
185 	Check out the [#event-example] and [#Pong-example] to get started quickly.
186 
187 	The main classes you may want to create are [SimpleWindow], [Timer],
188 	[Image], and [Sprite].
189 
190 	The main functions you'll want are [setClipboardText] and [getClipboardText].
191 
192 	There are also platform-specific functions available such as [XDisplayConnection]
193 	and [GetAtom] for X11, among others.
194 
195 	See the examples and topics list below to learn more.
196 
197 	$(WARNING
198 		There should only be one GUI thread per application,
199 		and all windows should be created in it and your
200 		event loop should run there.
201 
202 		To do otherwise is undefined behavior and has no
203 		cross platform guarantees.
204 	)
205 
206 	$(H2 About this documentation)
207 
208 	The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow.
209 
210 	Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples!
211 
212 	All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them.
213 
214 	To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example.
215 
216 	If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file.
217 
218 	At points, I will talk about implementation details in the documentation. These are sometimes
219 	subject to change, but nevertheless useful to understand what is really going on. You can learn
220 	more about some of the referenced things by searching the web for info about using them from C.
221 	You can always look at the source of simpledisplay.d too for the most authoritative source on
222 	its specific implementation. If you disagree with how I did something, please contact me so we
223 	can discuss it!
224 
225 	$(H2 Using with fibers)
226 
227 	simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor).
228 
229 	$(H2 Topics)
230 
231 	$(H3 $(ID topic-windows) Windows)
232 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
233 		window on the user's screen.
234 
235 		You may create multiple windows, if the underlying platform supports it. You may check
236 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
237 		SimpleWindow's constructor at runtime to handle those cases.
238 
239 		A single running event loop will handle as many windows as needed.
240 
241 	$(H3 $(ID topic-event-loops) Event loops)
242 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
243 
244 		The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there:
245 
246 		---
247 		// dmd example.d simpledisplay.d color.d
248 		import arsd.simpledisplay;
249 		void main() {
250 			auto window = new SimpleWindow(200, 200);
251 			window.eventLoop(0,
252 			  delegate (dchar) { /* got a character key press */ }
253 			);
254 		}
255 		---
256 
257 		$(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.)
258 
259 		On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run.
260 
261 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
262 
263 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
264 
265 		You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway.
266 
267 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
268 		Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like.
269 
270 		See the [NotificationAreaIcon] class.
271 
272 	$(H3 $(ID topic-input-handling) Input handling)
273 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
274 
275 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
276 
277 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
278 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
279 
280 		Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example:
281 
282 		---
283 		// dmd example.d simpledisplay.d color.d
284 		import arsd.simpledisplay;
285 		void main() {
286 			auto window = new SimpleWindow(200, 200);
287 			{ // introduce sub-scope
288 				auto painter = window.draw(); // begin drawing
289 				/* draw here */
290 				painter.outlineColor = Color.red;
291 				painter.fillColor = Color.black;
292 				painter.drawRectangle(Point(0, 0), 200, 200);
293 			} // end scope, calling `painter`'s destructor, drawing to the screen.
294 			window.eventLoop(0); // handle events
295 		}
296 		---
297 
298 		Painting is done based on two color properties, a pen and a brush.
299 
300 		At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead.
301 
302 		FIXME Add example of 2d opengl drawing here.
303 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
304 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
305 
306 		Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it.
307 
308 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
309 
310 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
311 
312 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon].
313 
314 		simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program.
315 
316 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
317 
318 		---
319 		import arsd.simpledisplay;
320 
321 		void main() {
322 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
323 
324 			float otherColor = 0.0;
325 			float colorDelta = 0.05;
326 
327 			window.redrawOpenGlScene = delegate() {
328 				glLoadIdentity();
329 				glBegin(GL_QUADS);
330 
331 				glColor3f(1.0, otherColor, 0);
332 				glVertex3f(-0.8, -0.8, 0);
333 
334 				glColor3f(1.0, otherColor, 1.0);
335 				glVertex3f(0.8, -0.8, 0);
336 
337 				glColor3f(0, 1.0, otherColor);
338 				glVertex3f(0.8, 0.8, 0);
339 
340 				glColor3f(otherColor, 0, 1.0);
341 				glVertex3f(-0.8, 0.8, 0);
342 
343 				glEnd();
344 			};
345 
346 			window.eventLoop(50, () {
347 				otherColor += colorDelta;
348 				if(otherColor > 1.0) {
349 					otherColor = 1.0;
350 					colorDelta = -0.05;
351 				}
352 				if(otherColor < 0) {
353 					otherColor = 0;
354 					colorDelta = 0.05;
355 				}
356 				// at the end of the timer, we have to request a redraw
357 				// or we won't see the changes.
358 				window.redrawOpenGlSceneSoon();
359 			});
360 		}
361 		---
362 
363 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
364 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
365 		simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too.
366 
367 		This example program shows how you can set up a shader to draw a rectangle:
368 
369 		---
370 		import arsd.simpledisplay;
371 
372 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
373 
374 		void main() {
375 			// First thing we do, before creating the window, is declare what version we want.
376 			setOpenGLContextVersion(3, 3);
377 			// turning off legacy compat is required to use version 3.3 and newer
378 			openGLContextCompatible = false;
379 
380 			uint VAO;
381 			OpenGlShader shader;
382 
383 			// then we can create the window.
384 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
385 
386 			// additional setup needs to be done when it is visible, simpledisplay offers a property
387 			// for exactly that:
388 			window.visibleForTheFirstTime = delegate() {
389 				// now with the window loaded, we can start loading the modern opengl functions.
390 
391 				// you MUST set the context first.
392 				window.setAsCurrentOpenGlContext;
393 				// then load the remainder of the library
394 				gl3.loadDynamicLibrary();
395 
396 				// now you can create the shaders, etc.
397 				shader = new OpenGlShader(
398 					OpenGlShader.Source(GL_VERTEX_SHADER, `
399 						#version 330 core
400 						layout (location = 0) in vec3 aPos;
401 						void main() {
402 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
403 						}
404 					`),
405 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
406 						#version 330 core
407 						out vec4 FragColor;
408 						uniform vec4 mycolor;
409 						void main() {
410 							FragColor = mycolor;
411 						}
412 					`),
413 				);
414 
415 				// and do whatever other setup you want.
416 
417 				float[] vertices = [
418 					0.5f,  0.5f, 0.0f,  // top right
419 					0.5f, -0.5f, 0.0f,  // bottom right
420 					-0.5f, -0.5f, 0.0f,  // bottom left
421 					-0.5f,  0.5f, 0.0f   // top left
422 				];
423 				uint[] indices = [  // note that we start from 0!
424 					0, 1, 3,  // first Triangle
425 					1, 2, 3   // second Triangle
426 				];
427 				uint VBO, EBO;
428 				glGenVertexArrays(1, &VAO);
429 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
430 				glBindVertexArray(VAO);
431 
432 				glGenBuffers(1, &VBO);
433 				glGenBuffers(1, &EBO);
434 
435 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
436 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
437 
438 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
439 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
440 
441 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
442 				glEnableVertexAttribArray(0);
443 
444 				// the library will set the initial viewport and trigger our first draw,
445 				// so these next two lines are NOT needed. they are just here as comments
446 				// to show what would happen next.
447 
448 				// glViewport(0, 0, window.width, window.height);
449 				// window.redrawOpenGlSceneNow();
450 			};
451 
452 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
453 			// it is our render method.
454 			window.redrawOpenGlScene = delegate() {
455 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
456 				glClear(GL_COLOR_BUFFER_BIT);
457 
458 				glUseProgram(shader.shaderProgram);
459 
460 				// the shader helper class has methods to set uniforms too
461 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
462 
463 				glBindVertexArray(VAO);
464 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
465 			};
466 
467 			window.eventLoop(0);
468 		}
469 		---
470 
471 	This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread.
472 
473 	$(H3 $(ID vulkan) Vulkan)
474 
475 	See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings:
476 
477 	https://github.com/adamdruppe/VulkanizeDSdpy
478 
479 	https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo
480 
481 	$(H3 $(ID topic-images) Displaying images)
482 		You can also load PNG images using [arsd.png].
483 
484 		---
485 		// dmd example.d simpledisplay.d color.d png.d
486 		import arsd.simpledisplay;
487 		import arsd.png;
488 
489 		void main() {
490 			auto image = Image.fromMemoryImage(readPng("image.png"));
491 			displayImage(image);
492 		}
493 		---
494 
495 		Compile with `dmd example.d simpledisplay.d png.d`.
496 
497 		If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel.
498 
499 	$(H3 $(ID topic-sprites) Sprites)
500 		The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link.
501 
502 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
503 
504 	$(H3 $(ID topic-clipboard) Clipboard)
505 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
506 
507 		It also has helpers for handling X-specific events.
508 
509 	$(H3 $(ID topic-dnd) Drag and Drop)
510 		See [enableDragAndDrop] and [draggable].
511 
512 	$(H3 $(ID topic-timers) Timers)
513 		There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer].
514 
515 		The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse.
516 
517 		---
518 			import arsd.simpledisplay;
519 
520 			void main() {
521 				auto window = new SimpleWindow(400, 400);
522 				// every 100 ms, it will draw a random line
523 				// on the window.
524 				window.eventLoop(100, {
525 					auto painter = window.draw();
526 
527 					import std.random;
528 					// random color
529 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
530 					// random line
531 					painter.drawLine(
532 						Point(uniform(0, window.width), uniform(0, window.height)),
533 						Point(uniform(0, window.width), uniform(0, window.height)));
534 
535 				});
536 			}
537 		---
538 
539 		The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish.
540 
541 		The pulse timer and instances of the [Timer] class may be combined at will.
542 
543 		---
544 			import arsd.simpledisplay;
545 
546 			void main() {
547 				auto window = new SimpleWindow(400, 400);
548 				auto timer = new Timer(1000, delegate {
549 					auto painter = window.draw();
550 					painter.clear();
551 				});
552 
553 				window.eventLoop(0);
554 			}
555 		---
556 
557 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
558 
559 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
560 		simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself.
561 
562 		See also: `xwindows.d` from my github.
563 
564 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
565 		`handleNativeEvent` and `handleNativeGlobalEvent`.
566 
567 	$(H3 $(ID topic-integration) Integration with other libraries)
568 		Integration with a third-party event loop is possible.
569 
570 		On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d.
571 
572 	$(H3 $(ID topic-guis) GUI widgets)
573 		simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you!
574 
575 		Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes.
576 
577 		Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.)
578 
579 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
580 
581 	$(H2 Platform-specific tips and tricks)
582 
583 	X_tips:
584 
585 	On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release.
586 
587 	Windows_tips:
588 
589 	You can add icons or manifest files to your exe using a resource file.
590 
591 	To create a Windows .ico file, use the gimp or something. I'll write a helper
592 	program later.
593 
594 	Create `yourapp.rc`:
595 
596 	```rc
597 		1 ICON filename.ico
598 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
599 	```
600 
601 	And `yourapp.exe.manifest`:
602 
603 	```xml
604 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
605 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
606 		<assemblyIdentity
607 		    version="1.0.0.0"
608 		    processorArchitecture="*"
609 		    name="CompanyName.ProductName.YourApplication"
610 		    type="win32"
611 		/>
612 		<description>Your application description here.</description>
613 		<dependency>
614 		    <dependentAssembly>
615 			<assemblyIdentity
616 			    type="win32"
617 			    name="Microsoft.Windows.Common-Controls"
618 			    version="6.0.0.0"
619 			    processorArchitecture="*"
620 			    publicKeyToken="6595b64144ccf1df"
621 			    language="*"
622 			/>
623 		    </dependentAssembly>
624 		</dependency>
625 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
626 			<windowsSettings>
627 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
628 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
629 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
630 				<!-- to render crisply in DPI-unaware contexts -->
631 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
632 			</windowsSettings>
633 		</application>
634 		</assembly>
635 	```
636 
637 	You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`.
638 
639 	Doing this lets you opt into various new things since Windows XP.
640 
641 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
642 
643 	$(H2 Tips)
644 
645 	$(H3 Name conflicts)
646 
647 	simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import:
648 
649 	---
650 	static import sdpy = arsd.simpledisplay;
651 	import arsd.simpledisplay : SimpleWindow;
652 
653 	void main() {
654 		auto window = new SimpleWindow();
655 		sdpy.EventLoop.get.run();
656 	}
657 	---
658 
659 	$(H2 $(ID developer-notes) Developer notes)
660 
661 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
662 	implementation though.
663 
664 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
665 	suck. If I was rewriting it, I wouldn't do it that way again.
666 
667 	This file must not have any more required dependencies. If you need bindings, add
668 	them right to this file. Once it gets into druntime and is there for a while, remove
669 	bindings from here to avoid conflicts (or put them in an appropriate version block
670 	so it continues to just work on old dmd), but wait a couple releases before making the
671 	transition so this module remains usable with older versions of dmd.
672 
673 	You may have optional dependencies if needed by putting them in version blocks or
674 	template functions. You may also extend the module with other modules with UFCS without
675 	actually editing this - that is nice to do if you can.
676 
677 	Try to make functions work the same way across operating systems. I typically make
678 	it thinly wrap Windows, then emulate that on Linux.
679 
680 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
681 	Phobos! So try to avoid it.
682 
683 	See more comments throughout the source.
684 
685 	I realize this file is fairly large, but over half that is just bindings at the bottom
686 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
687 	to understand. I suggest you jump around the source by looking for a particular
688 	declaration you're interested in, like `class SimpleWindow` using your editor's search
689 	function, then look at one piece at a time.
690 
691 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
692 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
693 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
694 
695 	I live in the eastern United States, so I will most likely not be around at night in
696 	that US east timezone.
697 
698 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
699 
700 	Building documentation: use my adrdox generator, `dub run adrdox`.
701 
702 	Examples:
703 
704 	$(DIV $(ID Event-example))
705 	$(H3 $(ID event-example) Event example)
706 	This program creates a window and draws events inside them as they
707 	happen, scrolling the text in the window as needed. Run this program
708 	and experiment to get a feel for where basic input events take place
709 	in the library.
710 
711 	---
712 	// dmd example.d simpledisplay.d color.d
713 	import arsd.simpledisplay;
714 	import std.conv;
715 
716 	void main() {
717 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
718 
719 		int y = 0;
720 
721 		void addLine(string text) {
722 			auto painter = window.draw();
723 
724 			if(y + painter.fontHeight >= window.height) {
725 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
726 				y -= painter.fontHeight;
727 			}
728 
729 			painter.outlineColor = Color.red;
730 			painter.fillColor = Color.black;
731 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
732 
733 			painter.outlineColor = Color.white;
734 
735 			painter.drawText(Point(10, y), text);
736 
737 			y += painter.fontHeight;
738 		}
739 
740 		window.eventLoop(1000,
741 		  () {
742 			addLine("Timer went off!");
743 		  },
744 		  (KeyEvent event) {
745 			addLine(to!string(event));
746 		  },
747 		  (MouseEvent event) {
748 			addLine(to!string(event));
749 		  },
750 		  (dchar ch) {
751 			addLine(to!string(ch));
752 		  }
753 		);
754 	}
755 	---
756 
757 	If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too.
758 
759 	$(COMMENT
760 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
761 
762 	---
763 
764 	---
765 	)
766 
767 	History:
768 		Initial release in April 2011.
769 
770 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
771 
772 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
773 +/
774 module arsd.simpledisplay;
775 
776 import arsd.core;
777 
778 // FIXME: tetris demo
779 // FIXME: space invaders demo
780 // FIXME: asteroids demo
781 
782 /++ $(ID Pong-example)
783 	$(H3 Pong)
784 
785 	This program creates a little Pong-like game. Player one is controlled
786 	with the keyboard.  Player two is controlled with the mouse. It demos
787 	the pulse timer, event handling, and some basic drawing.
788 +/
789 version(demos)
790 unittest {
791 	// dmd example.d simpledisplay.d color.d
792 	import arsd.simpledisplay;
793 
794 	enum paddleMovementSpeed = 8;
795 	enum paddleHeight = 48;
796 
797 	void main() {
798 		auto window = new SimpleWindow(600, 400, "Pong game!");
799 
800 		int playerOnePosition, playerTwoPosition;
801 		int playerOneMovement, playerTwoMovement;
802 		int playerOneScore, playerTwoScore;
803 
804 		int ballX, ballY;
805 		int ballDx, ballDy;
806 
807 		void serve() {
808 			import std.random;
809 
810 			ballX = window.width / 2;
811 			ballY = window.height / 2;
812 			ballDx = uniform(-4, 4) * 3;
813 			ballDy = uniform(-4, 4) * 3;
814 			if(ballDx == 0)
815 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
816 		}
817 
818 		serve();
819 
820 		window.eventLoop(50, // set a 50 ms timer pulls
821 			// This runs once per timer pulse
822 			delegate () {
823 				auto painter = window.draw();
824 
825 				painter.clear();
826 
827 				// Update everyone's motion
828 				playerOnePosition += playerOneMovement;
829 				playerTwoPosition += playerTwoMovement;
830 
831 				ballX += ballDx;
832 				ballY += ballDy;
833 
834 				// Bounce off the top and bottom edges of the window
835 				if(ballY + 7 >= window.height)
836 					ballDy = -ballDy;
837 				if(ballY - 8 <= 0)
838 					ballDy = -ballDy;
839 
840 				// Bounce off the paddle, if it is in position
841 				if(ballX - 8 <= 16) {
842 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
843 						ballDx = -ballDx + 1; // add some speed to keep it interesting
844 						ballDy += playerOneMovement; // and y movement based on your controls too
845 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
846 					} else {
847 						// Missed it
848 						playerTwoScore ++;
849 						serve();
850 					}
851 				}
852 
853 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
854 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
855 						ballDx = -ballDx - 1;
856 						ballDy += playerTwoMovement;
857 						ballX = window.width - 24;
858 					} else {
859 						// Missed it
860 						playerOneScore ++;
861 						serve();
862 					}
863 				}
864 
865 				// Draw the paddles
866 				painter.outlineColor = Color.black;
867 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
868 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
869 
870 				// Draw the ball
871 				painter.fillColor = Color.red;
872 				painter.outlineColor = Color.yellow;
873 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
874 
875 				// Draw the score
876 				painter.outlineColor = Color.blue;
877 				import std.conv;
878 				painter.drawText(Point(64, 4), to!string(playerOneScore));
879 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
880 
881 			},
882 			delegate (KeyEvent event) {
883 				// Player 1's controls are the arrow keys on the keyboard
884 				if(event.key == Key.Down)
885 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
886 				if(event.key == Key.Up)
887 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
888 
889 			},
890 			delegate (MouseEvent event) {
891 				// Player 2's controls are mouse movement while the left button is held down
892 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
893 					if(event.dy > 0)
894 						playerTwoMovement = paddleMovementSpeed;
895 					else if(event.dy < 0)
896 						playerTwoMovement = -paddleMovementSpeed;
897 				} else {
898 					playerTwoMovement = 0;
899 				}
900 			}
901 		);
902 	}
903 }
904 
905 /++ $(H3 $(ID example-minesweeper) Minesweeper)
906 
907 	This minesweeper demo shows how we can implement another classic
908 	game with simpledisplay and shows some mouse input and basic output
909 	code.
910 +/
911 version(demos)
912 unittest {
913 	import arsd.simpledisplay;
914 
915 	enum GameSquare {
916 		mine = 0,
917 		clear,
918 		m1, m2, m3, m4, m5, m6, m7, m8
919 	}
920 
921 	enum UserSquare {
922 		unknown,
923 		revealed,
924 		flagged,
925 		questioned
926 	}
927 
928 	enum GameState {
929 		inProgress,
930 		lose,
931 		win
932 	}
933 
934 	GameSquare[] board;
935 	UserSquare[] userState;
936 	GameState gameState;
937 	int boardWidth;
938 	int boardHeight;
939 
940 	bool isMine(int x, int y) {
941 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
942 			return false;
943 		return board[y * boardWidth + x] == GameSquare.mine;
944 	}
945 
946 	GameState reveal(int x, int y) {
947 		if(board[y * boardWidth + x] == GameSquare.clear) {
948 			floodFill(userState, boardWidth, boardHeight,
949 				UserSquare.unknown, UserSquare.revealed,
950 				x, y,
951 				(x, y) {
952 					if(board[y * boardWidth + x] == GameSquare.clear)
953 						return true;
954 					else {
955 						userState[y * boardWidth + x] = UserSquare.revealed;
956 						return false;
957 					}
958 				});
959 		} else {
960 			userState[y * boardWidth + x] = UserSquare.revealed;
961 			if(isMine(x, y))
962 				return GameState.lose;
963 		}
964 
965 		foreach(state; userState) {
966 			if(state == UserSquare.unknown || state == UserSquare.questioned)
967 				return GameState.inProgress;
968 		}
969 
970 		return GameState.win;
971 	}
972 
973 	void initializeBoard(int width, int height, int numberOfMines) {
974 		boardWidth = width;
975 		boardHeight = height;
976 		board.length = width * height;
977 
978 		userState.length = width * height;
979 		userState[] = UserSquare.unknown;
980 
981 		import std.algorithm, std.random, std.range;
982 
983 		board[] = GameSquare.clear;
984 
985 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
986 			board[minePosition] = GameSquare.mine;
987 
988 		int x;
989 		int y;
990 		foreach(idx, ref square; board) {
991 			if(square == GameSquare.clear) {
992 				int danger = 0;
993 				danger += isMine(x-1, y-1)?1:0;
994 				danger += isMine(x-1, y)?1:0;
995 				danger += isMine(x-1, y+1)?1:0;
996 				danger += isMine(x, y-1)?1:0;
997 				danger += isMine(x, y+1)?1:0;
998 				danger += isMine(x+1, y-1)?1:0;
999 				danger += isMine(x+1, y)?1:0;
1000 				danger += isMine(x+1, y+1)?1:0;
1001 
1002 				square = cast(GameSquare) (danger + 1);
1003 			}
1004 
1005 			x++;
1006 			if(x == width) {
1007 				x = 0;
1008 				y++;
1009 			}
1010 		}
1011 	}
1012 
1013 	void redraw(SimpleWindow window) {
1014 		import std.conv;
1015 
1016 		auto painter = window.draw();
1017 
1018 		painter.clear();
1019 
1020 		final switch(gameState) with(GameState) {
1021 			case inProgress:
1022 				break;
1023 			case win:
1024 				painter.fillColor = Color.green;
1025 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1026 				return;
1027 			case lose:
1028 				painter.fillColor = Color.red;
1029 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1030 				return;
1031 		}
1032 
1033 		int x = 0;
1034 		int y = 0;
1035 
1036 		foreach(idx, square; board) {
1037 			auto state = userState[idx];
1038 
1039 			final switch(state) with(UserSquare) {
1040 				case unknown:
1041 					painter.outlineColor = Color.black;
1042 					painter.fillColor = Color(128,128,128);
1043 
1044 					painter.drawRectangle(
1045 						Point(x * 20, y * 20),
1046 						20, 20
1047 					);
1048 				break;
1049 				case revealed:
1050 					if(square == GameSquare.clear) {
1051 						painter.outlineColor = Color.white;
1052 						painter.fillColor = Color.white;
1053 
1054 						painter.drawRectangle(
1055 							Point(x * 20, y * 20),
1056 							20, 20
1057 						);
1058 					} else {
1059 						painter.outlineColor = Color.black;
1060 						painter.fillColor = Color.white;
1061 
1062 						painter.drawText(
1063 							Point(x * 20, y * 20),
1064 							to!string(square)[1..2],
1065 							Point(x * 20 + 20, y * 20 + 20),
1066 							TextAlignment.Center | TextAlignment.VerticalCenter);
1067 					}
1068 				break;
1069 				case flagged:
1070 					painter.outlineColor = Color.black;
1071 					painter.fillColor = Color.red;
1072 					painter.drawRectangle(
1073 						Point(x * 20, y * 20),
1074 						20, 20
1075 					);
1076 				break;
1077 				case questioned:
1078 					painter.outlineColor = Color.black;
1079 					painter.fillColor = Color.yellow;
1080 					painter.drawRectangle(
1081 						Point(x * 20, y * 20),
1082 						20, 20
1083 					);
1084 				break;
1085 			}
1086 
1087 			x++;
1088 			if(x == boardWidth) {
1089 				x = 0;
1090 				y++;
1091 			}
1092 		}
1093 
1094 	}
1095 
1096 	void main() {
1097 		auto window = new SimpleWindow(200, 200);
1098 
1099 		initializeBoard(10, 10, 10);
1100 
1101 		redraw(window);
1102 		window.eventLoop(0,
1103 			delegate (MouseEvent me) {
1104 				if(me.type != MouseEventType.buttonPressed)
1105 					return;
1106 				auto x = me.x / 20;
1107 				auto y = me.y / 20;
1108 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1109 					if(me.button == MouseButton.left) {
1110 						gameState = reveal(x, y);
1111 					} else {
1112 						userState[y*boardWidth+x] = UserSquare.flagged;
1113 					}
1114 					redraw(window);
1115 				}
1116 			}
1117 		);
1118 	}
1119 }
1120 
1121 import arsd.core;
1122 
1123 // FIXME: tetris demo
1124 // FIXME: space invaders demo
1125 // FIXME: asteroids demo
1126 
1127 version(OSX) version(DigitalMars) version=OSXCocoa;
1128 
1129 
1130 version(OSXCocoa) {
1131 	version=without_opengl;
1132 	version=allow_unimplemented_features;
1133 	// version=OSXCocoa;
1134 	// pragma(linkerDirective, "-framework Cocoa");
1135 }
1136 
1137 version(without_opengl) {
1138 	enum SdpyIsUsingIVGLBinds = false;
1139 } else /*version(Posix)*/ {
1140 	static if (__traits(compiles, (){import iv.glbinds;})) {
1141 		enum SdpyIsUsingIVGLBinds = true;
1142 		public import iv.glbinds;
1143 		//pragma(msg, "SDPY: using iv.glbinds");
1144 	} else {
1145 		enum SdpyIsUsingIVGLBinds = false;
1146 	}
1147 //} else {
1148 //	enum SdpyIsUsingIVGLBinds = false;
1149 }
1150 
1151 
1152 version(Windows) {
1153 	//import core.sys.windows.windows;
1154 	import core.sys.windows.winnls;
1155 	import core.sys.windows.windef;
1156 	import core.sys.windows.basetyps;
1157 	import core.sys.windows.winbase;
1158 	import core.sys.windows.winuser;
1159 	import core.sys.windows.shellapi;
1160 	import core.sys.windows.wingdi;
1161 	static import gdi = core.sys.windows.wingdi; // so i
1162 
1163 	pragma(lib, "gdi32");
1164 	pragma(lib, "user32");
1165 
1166 	// for AlphaBlend... a breaking change....
1167 	version(CRuntime_DigitalMars) { } else
1168 		pragma(lib, "msimg32");
1169 } else version (linux) {
1170 	//k8: this is hack for rdmd. sorry.
1171 	static import core.sys.linux.epoll;
1172 	static import core.sys.linux.timerfd;
1173 }
1174 
1175 
1176 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1177 
1178 // http://wiki.dlang.org/Simpledisplay.d
1179 
1180 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1181 
1182 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1183 // but can i control the scroll lock led
1184 
1185 
1186 // Note: if you are using Image on X, you might want to do:
1187 /*
1188 	static if(UsingSimpledisplayX11) {
1189 		if(!Image.impl.xshmAvailable) {
1190 			// the images will use the slower XPutImage, you might
1191 			// want to consider an alternative method to get better speed
1192 		}
1193 	}
1194 
1195 	If the shared memory extension is available though, simpledisplay uses it
1196 	for a significant speed boost whenever you draw large Images.
1197 */
1198 
1199 // 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.
1200 
1201 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1202 
1203 /*
1204 	Biggest FIXME:
1205 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1206 
1207 		clean up opengl contexts when their windows close
1208 
1209 		fix resizing the bitmaps/pixmaps
1210 */
1211 
1212 // BTW on Windows:
1213 // -L/SUBSYSTEM:WINDOWS:5.0
1214 // to dmd will make a nice windows binary w/o a console if you want that.
1215 
1216 /*
1217 	Stuff to add:
1218 
1219 	use multibyte functions everywhere we can
1220 
1221 	OpenGL windows
1222 	more event stuff
1223 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1224 
1225 
1226 	resizeEvent
1227 		and make the windows non-resizable by default,
1228 		or perhaps stretched (if I can find something in X like StretchBlt)
1229 
1230 	take a screenshot function!
1231 
1232 	Pens and brushes?
1233 	Maybe a global event loop?
1234 
1235 	Mouse deltas
1236 	Key items
1237 */
1238 
1239 /*
1240 From MSDN:
1241 
1242 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1243 
1244 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.
1245 
1246 */
1247 
1248 version(linux) {
1249 	version = X11;
1250 	version(without_libnotify) {
1251 		// we cool
1252 	}
1253 	else
1254 		version = libnotify;
1255 }
1256 
1257 version(libnotify) {
1258 	pragma(lib, "dl");
1259 	import core.sys.posix.dlfcn;
1260 
1261 	void delegate()[int] libnotify_action_delegates;
1262 	int libnotify_action_delegates_count;
1263 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1264 		auto idx = cast(int) user_data;
1265 		if(auto dgptr = idx in libnotify_action_delegates) {
1266 			(*dgptr)();
1267 			libnotify_action_delegates.remove(idx);
1268 		}
1269 	}
1270 
1271 	struct C_DynamicLibrary {
1272 		void* handle;
1273 		this(string name) {
1274 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1275 			if(handle is null)
1276 				throw new Exception("dlopen");
1277 		}
1278 
1279 		void close() {
1280 			dlclose(handle);
1281 		}
1282 
1283 		~this() {
1284 			// close
1285 		}
1286 
1287 		// FIXME: this looks up by name every time....
1288 		template call(string func, Ret, Args...) {
1289 			extern(C) Ret function(Args) fptr;
1290 			typeof(fptr) call() {
1291 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1292 				return fptr;
1293 			}
1294 		}
1295 	}
1296 
1297 	C_DynamicLibrary* libnotify;
1298 }
1299 
1300 version(OSX) {
1301 	version(OSXCocoa) {}
1302 	else { version = X11; }
1303 }
1304 	//version = OSXCocoa; // this was written by KennyTM
1305 version(FreeBSD)
1306 	version = X11;
1307 version(Solaris)
1308 	version = X11;
1309 
1310 version(X11) {
1311 	version(without_xft) {}
1312 	else version=with_xft;
1313 }
1314 
1315 void featureNotImplemented()() {
1316 	version(allow_unimplemented_features)
1317 		throw new NotYetImplementedException();
1318 	else
1319 		static assert(0);
1320 }
1321 
1322 // these are so the static asserts don't trigger unless you want to
1323 // add support to it for an OS
1324 version(Windows)
1325 	version = with_timer;
1326 version(linux)
1327 	version = with_timer;
1328 version(OSXCocoa)
1329 	version = with_timer;
1330 
1331 version(with_timer)
1332 	enum bool SimpledisplayTimerAvailable = true;
1333 else
1334 	enum bool SimpledisplayTimerAvailable = false;
1335 
1336 /// 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.
1337 version(Windows)
1338 	enum bool UsingSimpledisplayWindows = true;
1339 else
1340 	enum bool UsingSimpledisplayWindows = false;
1341 
1342 /// 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.
1343 version(X11)
1344 	enum bool UsingSimpledisplayX11 = true;
1345 else
1346 	enum bool UsingSimpledisplayX11 = false;
1347 
1348 /// 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.
1349 version(OSXCocoa)
1350 	enum bool UsingSimpledisplayCocoa = true;
1351 else
1352 	enum bool UsingSimpledisplayCocoa = false;
1353 
1354 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1355 version(Windows)
1356 	enum multipleWindowsSupported = true;
1357 else version(X11)
1358 	enum multipleWindowsSupported = true;
1359 else version(OSXCocoa)
1360 	enum multipleWindowsSupported = true;
1361 else
1362 	static assert(0);
1363 
1364 version(without_opengl)
1365 	enum bool OpenGlEnabled = false;
1366 else
1367 	enum bool OpenGlEnabled = true;
1368 
1369 /++
1370 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1371 	If you mix this in above your `main` function, you no longer need to use the linker
1372 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1373 
1374 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1375 
1376 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1377 	stderr writeln. It will fail and throw an exception.
1378 
1379 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1380 
1381 	History:
1382 		Added November 24, 2021 (dub v10.4)
1383 +/
1384 mixin template EnableWindowsSubsystem() {
1385 	version(Windows)
1386 	version(CRuntime_Microsoft) {
1387 		pragma(linkerDirective, "/subsystem:windows");
1388 		version(LDC)
1389 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1390 		else
1391 			pragma(linkerDirective, "/entry:mainCRTStartup");
1392 	}
1393 }
1394 
1395 
1396 /++
1397 	After selecting a type from [WindowTypes], you may further customize
1398 	its behavior by setting one or more of these flags.
1399 
1400 
1401 	The different window types have different meanings of `normal`. If the
1402 	window type already is a good match for what you want to do, you should
1403 	just use [WindowFlags.normal], the default, which will do the right thing
1404 	for your users.
1405 
1406 	The window flags will not always be honored by the operating system
1407 	and window managers; they are hints, not commands.
1408 +/
1409 enum WindowFlags : int {
1410 	normal = 0, ///
1411 	skipTaskbar = 1, ///
1412 	alwaysOnTop = 2, ///
1413 	alwaysOnBottom = 4, ///
1414 	cannotBeActivated = 8, ///
1415 	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.
1416 	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.
1417 	/++
1418 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1419 		it is still a top-level window. This should NOT be set separately for most window types.
1420 
1421 		A transient window will not keep the application open if its main window closes.
1422 
1423 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1424 
1425 
1426 		From the ICCM:
1427 
1428 		$(BLOCKQUOTE
1429 			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.
1430 
1431 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1432 		)
1433 
1434 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1435 
1436 		History:
1437 			Added February 23, 2021 but not yet stabilized.
1438 	+/
1439 	transient = 64,
1440 	/++
1441 		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.
1442 
1443 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1444 
1445 		History:
1446 			Added April 1, 2022
1447 	+/
1448 	managesChildWindowFocus = 128,
1449 
1450 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1451 }
1452 
1453 /++
1454 	When creating a window, you can pass a type to SimpleWindow's constructor,
1455 	then further customize the window by changing `WindowFlags`.
1456 
1457 
1458 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1459 	use. The others are there to build a foundation for a higher level GUI toolkit,
1460 	but are themselves not as high level as you might think from their names.
1461 
1462 	This list is based on the EMWH spec for X11.
1463 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1464 +/
1465 enum WindowTypes : int {
1466 	/// An ordinary application window.
1467 	normal,
1468 	/// 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.
1469 	undecorated,
1470 	/// 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.
1471 	eventOnly,
1472 	/// A drop down menu, such as from a menu bar
1473 	dropdownMenu,
1474 	/// A popup menu, such as from a right click
1475 	popupMenu,
1476 	/// A popup bubble notification
1477 	notification,
1478 	/*
1479 	menu, /// a tearable menu bar
1480 	splashScreen, /// a loading splash screen for your application
1481 	tooltip, /// A tiny window showing temporary help text or something.
1482 	comboBoxDropdown,
1483 	dialog,
1484 	toolbar
1485 	*/
1486 	/// a child nested inside the parent. You must pass a parent window to the ctor
1487 	nestedChild,
1488 
1489 	/++
1490 		The type you get when you pass in an existing browser handle, which means most
1491 		of simpledisplay's fancy things will not be done since they were never set up.
1492 
1493 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1494 		failure; you should use the existing handle constructor.
1495 
1496 		History:
1497 			Added November 17, 2022 (previously it would have type `normal`)
1498 	+/
1499 	minimallyWrapped
1500 }
1501 
1502 
1503 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1504 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1505 private __gshared char* sdpyWindowClassStr = null;
1506 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1507 
1508 /**
1509 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1510 	You may want to change context version if you want to use advanced shaders or
1511 	other modern OpenGL techinques. This setting doesn't affect already created
1512 	windows. You may use version 2.1 as your default, which should be supported
1513 	by any box since 2006, so seems to be a reasonable choice.
1514 
1515 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1516 	old context creation code without any version specified. This is the safest
1517 	way to init OpenGL, but it may not give you access to advanced features.
1518 
1519 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1520 */
1521 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1522 
1523 /**
1524 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1525 	pipeline functions, and without "compatible" mode you won't be able to use
1526 	your old non-shader-based code with such contexts. By default SimpleDisplay
1527 	creates compatible context, so you can gradually upgrade your OpenGL code if
1528 	you want to (or leave it as is, as it should "just work").
1529 */
1530 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1531 
1532 /**
1533 	Set to `true` to allow creating OpenGL context with lower version than requested
1534 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1535 	`openGLContextFallbackActivated()` will return `true`.
1536 	*/
1537 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1538 
1539 /**
1540 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1541 	*/
1542 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1543 
1544 /++
1545 	History:
1546 		Added April 24, 2023  (dub v11.0)
1547 +/
1548 version(without_opengl) {} else
1549 auto openGLCurrentContext() {
1550 	version(Windows)
1551 		return wglGetCurrentContext();
1552 	else
1553 		return glXGetCurrentContext();
1554 }
1555 
1556 
1557 /**
1558 	Set window class name for all following `new SimpleWindow()` calls.
1559 
1560 	WARNING! For Windows, you should set your class name before creating any
1561 	window, and NEVER change it after that!
1562 */
1563 void sdpyWindowClass (const(char)[] v) {
1564 	import core.stdc.stdlib : realloc;
1565 	if (v.length == 0) v = "SimpleDisplayWindow";
1566 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1567 	if (sdpyWindowClassStr is null) return; // oops
1568 	sdpyWindowClassStr[0..v.length+1] = 0;
1569 	sdpyWindowClassStr[0..v.length] = v[];
1570 }
1571 
1572 /**
1573 	Get current window class name.
1574 */
1575 string sdpyWindowClass () {
1576 	if (sdpyWindowClassStr is null) return null;
1577 	foreach (immutable idx; 0..size_t.max-1) {
1578 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1579 	}
1580 	return null;
1581 }
1582 
1583 /++
1584 	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.
1585 
1586 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1587 +/
1588 float[2] getDpi() {
1589 	float[2] dpi;
1590 	version(Windows) {
1591 		HDC screen = GetDC(null);
1592 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1593 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1594 	} else version(X11) {
1595 		auto display = XDisplayConnection.get;
1596 		auto screen = DefaultScreen(display);
1597 
1598 		void fallback() {
1599 			/+
1600 			// 25.4 millimeters in an inch...
1601 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1602 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1603 			+/
1604 
1605 			// the physical size isn't actually as important as the logical size since this is
1606 			// all about scaling really
1607 			dpi[0] = 96;
1608 			dpi[1] = 96;
1609 		}
1610 
1611 		auto xft = getXftDpi();
1612 		if(xft is float.init)
1613 			fallback();
1614 		else {
1615 			dpi[0] = xft;
1616 			dpi[1] = xft;
1617 		}
1618 	}
1619 
1620 	return dpi;
1621 }
1622 
1623 version(X11)
1624 float getXftDpi() {
1625 	auto display = XDisplayConnection.get;
1626 
1627 	char* resourceString = XResourceManagerString(display);
1628 	XrmInitialize();
1629 
1630 	if (resourceString) {
1631 		auto db = XrmGetStringDatabase(resourceString);
1632 		XrmValue value;
1633 		char* type;
1634 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1635 			if (value.addr) {
1636 				import core.stdc.stdlib;
1637 				return atof(cast(char*) value.addr);
1638 			}
1639 		}
1640 	}
1641 
1642 	return float.init;
1643 }
1644 
1645 /++
1646 	Implementation used by [SimpleWindow.takeScreenshot].
1647 
1648 	Params:
1649 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1650 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1651 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1652 		x = the x-offset of the image to capture, from the left.
1653 		y = the y-offset of the image to capture, from the top.
1654 
1655 	History:
1656 		Added on March 14, 2021
1657 
1658 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1659 
1660 +/
1661 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1662 	TrueColorImage got;
1663 	version(X11) {
1664 		auto display = XDisplayConnection.get;
1665 		if(handle == 0)
1666 			handle = RootWindow(display, DefaultScreen(display));
1667 
1668 		if(width == 0 || height == 0) {
1669 			Window root;
1670 			int xpos, ypos;
1671 			uint widthret, heightret, borderret, depthret;
1672 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1673 
1674 			if(width == 0)
1675 				width = widthret;
1676 			if(height == 0)
1677 				height = heightret;
1678 		}
1679 
1680 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1681 
1682 		// https://github.com/adamdruppe/arsd/issues/98
1683 
1684 		auto i = new Image(image);
1685 		got = i.toTrueColorImage();
1686 
1687 		XDestroyImage(image);
1688 	} else version(Windows) {
1689 		auto hdc = GetDC(handle);
1690 		scope(exit) ReleaseDC(handle, hdc);
1691 
1692 		if(width == 0 || height == 0) {
1693 			BITMAP bmHeader;
1694 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1695 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1696 			if(width == 0)
1697 				width = bmHeader.bmWidth;
1698 			if(height == 0)
1699 				height = bmHeader.bmHeight;
1700 		}
1701 
1702 		auto i = new Image(width, height);
1703 		HDC hdcMem = CreateCompatibleDC(hdc);
1704 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1705 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1706 		SelectObject(hdcMem, hbmOld);
1707 		DeleteDC(hdcMem);
1708 
1709 		got = i.toTrueColorImage();
1710 	} else featureNotImplemented();
1711 
1712 	return got;
1713 }
1714 
1715 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1716 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1717 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1718 
1719 version(Windows)
1720 shared static this() {
1721 	auto lib = LoadLibrary("User32.dll");
1722 	if(lib is null)
1723 		return;
1724 	//scope(exit)
1725 		//FreeLibrary(lib);
1726 
1727 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1728 
1729 	if(SetProcessDpiAwarenessContext is null)
1730 		return;
1731 
1732 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1733 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1734 		//writeln(GetLastError());
1735 	}
1736 
1737 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1738 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1739 }
1740 
1741 /++
1742 	Blocking mode for event loop calls associated with a window instance.
1743 
1744 	History:
1745 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1746 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1747 		is, all would block until the application quit.
1748 
1749 		That behavior can still be achieved here with `untilApplicationQuits`,
1750 		or explicitly calling the top-level `EventLoop.get.run` function.
1751 +/
1752 enum BlockingMode {
1753 	/++
1754 		The event loop call will block until the whole application is ready
1755 		to quit if it is the only one running, but if it is nested inside
1756 		another one, it will only block until the window you're calling it on
1757 		closes.
1758 	+/
1759 	automatic             = 0x00,
1760 	/++
1761 		The event loop call will only return when the whole application
1762 		is ready to quit. This usually means all windows have been closed.
1763 
1764 		This is appropriate for your main application event loop.
1765 	+/
1766 	untilApplicationQuits = 0x01,
1767 	/++
1768 		The event loop will return when the window you're calling it on
1769 		closes. If there are other windows still open, they may be destroyed
1770 		unless you have another event loop running later.
1771 
1772 		This might be appropriate for a modal dialog box loop. Remember that
1773 		other windows are still processing input though, so you can end up
1774 		with a lengthy call stack if this happens in a loop, similar to a
1775 		recursive function (well, it literally is a recursive function, just
1776 		not an obvious looking one).
1777 	+/
1778 	untilWindowCloses     = 0x02,
1779 	/++
1780 		If an event loop is already running, this call will immediately
1781 		return, allowing the existing loop to handle it. If not, this call
1782 		will block until the condition you bitwise-or into the flag.
1783 
1784 		The default is to block until the application quits, same as with
1785 		the `automatic` setting (since if it were nested, which triggers until
1786 		window closes in automatic, this flag would instead not block at all),
1787 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1788 		it will only nest until the window closes. You might want that if you are
1789 		going to open two windows simultaneously and want closing just one of them
1790 		to trigger the event loop return.
1791 	+/
1792 	onlyIfNotNested       = 0x10,
1793 }
1794 
1795 /++
1796 	The flagship window class.
1797 
1798 
1799 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1800 	out of more advanced or complex features of the underlying windowing system.
1801 
1802 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1803 	and get a suitable window to work with.
1804 
1805 	From there, you can opt into additional features, like custom resizability and OpenGL support
1806 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1807 	and customization flags with the final two constructor arguments.
1808 
1809 	If none of that works for you, you can also create a window using native function calls, then
1810 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1811 	though, if you do this, managing the window is still your own responsibility! Notably, you
1812 	will need to destroy it yourself.
1813 +/
1814 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1815 
1816 	/++
1817 		Copies the window's current state into a [TrueColorImage].
1818 
1819 		Be warned: this can be a very slow operation
1820 
1821 		History:
1822 			Actually implemented on March 14, 2021
1823 	+/
1824 	TrueColorImage takeScreenshot() {
1825 		version(Windows)
1826 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
1827 		else version(OSXCocoa)
1828 			throw new NotYetImplementedException();
1829 		else
1830 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
1831 	}
1832 
1833 	/++
1834 		Returns the actual logical DPI for the window on its current display monitor. If the window
1835 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1836 
1837 		Please note this function may return zero if it doesn't know the answer!
1838 
1839 
1840 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1841 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1842 
1843 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1844 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1845 		window primarily resides on by checking the center point of the window against the monitor map.
1846 
1847 		Returns:
1848 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1849 			assumes the X and Y dpi are the same.
1850 
1851 		History:
1852 			Added November 26, 2021 (dub v10.4)
1853 
1854 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
1855 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
1856 			that.
1857 
1858 		Bugs:
1859 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
1860 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
1861 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
1862 			and 1.5 on the secondary monitor.
1863 
1864 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
1865 			is a historical misnomer - the real thing of interest is the scale factor and due to
1866 			compatibility concerns the scale would modify dpi values to trick applications. But since
1867 			that's the terminology common out there, I used it too.
1868 
1869 		See_Also:
1870 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1871 			as this since the window many be on a different monitor, but it is a reasonable fallback
1872 			to use if `actualDpi` returns 0.
1873 
1874 			[onDpiChanged] is changed when `actualDpi` has changed.
1875 	+/
1876 	int actualDpi() {
1877 		version(X11) bool useFallbackDpi = false;
1878 		if(!actualDpiLoadAttempted) {
1879 			// FIXME: do the actual monitor we are on
1880 			// and on X this is a good chance to load the monitor map.
1881 			version(Windows) {
1882 				if(GetDpiForWindow)
1883 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1884 			} else version(X11) {
1885 				if(!xRandrInfoLoadAttemped) {
1886 					xRandrInfoLoadAttemped = true;
1887 					if(!XRandrLibrary.attempted) {
1888 						XRandrLibrary.loadDynamicLibrary();
1889 					}
1890 
1891 					if(XRandrLibrary.loadSuccessful) {
1892 						auto display = XDisplayConnection.get;
1893 						int scratch;
1894 						int major, minor;
1895 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1896 							goto fallback;
1897 
1898 						XRRQueryVersion(display, &major, &minor);
1899 						if(major <= 1 && minor < 5)
1900 							goto fallback;
1901 
1902 						int count;
1903 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1904 						if(monitors is null)
1905 							goto fallback;
1906 						scope(exit) XRRFreeMonitors(monitors);
1907 
1908 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1909 						MonitorInfo.info.assumeSafeAppend();
1910 						foreach(idx, monitor; monitors[0 .. count]) {
1911 							MonitorInfo.info ~= MonitorInfo(
1912 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1913 								Size(monitor.mwidth, monitor.mheight),
1914 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
1915 							);
1916 
1917 							/+
1918 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1919 							// unknown physical size, just guess 96 to avoid divide by zero
1920 							MonitorInfo.info ~= MonitorInfo(
1921 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1922 								Size(monitor.mwidth, monitor.mheight),
1923 								96
1924 							);
1925 							else
1926 							// and actual thing
1927 							MonitorInfo.info ~= MonitorInfo(
1928 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1929 								Size(monitor.mwidth, monitor.mheight),
1930 								minInternal(
1931 									// millimeter to int then rounding up.
1932 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1933 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1934 								)
1935 							);
1936 							+/
1937 						}
1938 					// writeln("Here", MonitorInfo.info);
1939 					}
1940 				}
1941 
1942 				if(XRandrLibrary.loadSuccessful) {
1943 					updateActualDpi(true);
1944 					// writeln("updated");
1945 
1946 					if(!requestedInput) {
1947 						// this is what requests live updates should the configuration change
1948 						// each time you select input, it sends an initial event, so very important
1949 						// to not get into a loop of selecting input, getting event, updating data,
1950 						// and reselecting input...
1951 						requestedInput = true;
1952 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1953 						// writeln("requested input");
1954 					}
1955 				} else {
1956 					fallback:
1957 					// make sure we disable events that aren't coming
1958 					xrrEventBase = -1;
1959 					// best guess... respect the custom scaling user command to some extent at least though
1960 					useFallbackDpi = true;
1961 				}
1962 			} else version(OSXCocoa) {
1963 				actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME
1964 			}
1965 			actualDpiLoadAttempted = true;
1966 		} else version(X11) if(MonitorInfo.info.length == 0) {
1967 			useFallbackDpi = true;
1968 		}
1969 
1970 		version(X11)
1971 		if(useFallbackDpi)
1972 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
1973 
1974 		return actualDpi_;
1975 	}
1976 
1977 	private int actualDpi_;
1978 	private bool actualDpiLoadAttempted;
1979 
1980 	version(X11) private {
1981 		bool requestedInput;
1982 		static bool xRandrInfoLoadAttemped;
1983 		struct MonitorInfo {
1984 			Rectangle position;
1985 			Size size;
1986 			int dpi;
1987 
1988 			static MonitorInfo[] info;
1989 		}
1990 		bool screenPositionKnown;
1991 		int screenPositionX;
1992 		int screenPositionY;
1993 		void updateActualDpi(bool loadingNow = false) {
1994 			if(!loadingNow && !actualDpiLoadAttempted)
1995 				actualDpi(); // just to make it do the load
1996 			foreach(idx, m; MonitorInfo.info) {
1997 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1998 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1999 					actualDpi_ = m.dpi;
2000 					// writeln("monitor ", idx);
2001 					if(changed && onDpiChanged)
2002 						onDpiChanged();
2003 					break;
2004 				}
2005 			}
2006 		}
2007 	}
2008 
2009 	/++
2010 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2011 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2012 
2013 		History:
2014 			Added November 26, 2021 (dub v10.4)
2015 
2016 		See_Also:
2017 			[actualDpi]
2018 	+/
2019 	void delegate() onDpiChanged;
2020 
2021 	version(X11) {
2022 		void recreateAfterDisconnect() {
2023 			if(!stateDiscarded) return;
2024 
2025 			if(_parent !is null && _parent.stateDiscarded)
2026 				_parent.recreateAfterDisconnect();
2027 
2028 			bool wasHidden = hidden;
2029 
2030 			activeScreenPainter = null; // should already be done but just to confirm
2031 
2032 			actualDpi_ = 0;
2033 			actualDpiLoadAttempted = false;
2034 			xRandrInfoLoadAttemped = false;
2035 
2036 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2037 
2038 			if(auto dh = dropHandler) {
2039 				dropHandler = null;
2040 				enableDragAndDrop(this, dh);
2041 			}
2042 
2043 			if(recreateAdditionalConnectionState)
2044 				recreateAdditionalConnectionState();
2045 
2046 			hidden = wasHidden;
2047 			stateDiscarded = false;
2048 		}
2049 
2050 		bool stateDiscarded;
2051 		void discardConnectionState() {
2052 			if(XDisplayConnection.display)
2053 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2054 			if(discardAdditionalConnectionState)
2055 				discardAdditionalConnectionState();
2056 			stateDiscarded = true;
2057 		}
2058 
2059 		void delegate() discardAdditionalConnectionState;
2060 		void delegate() recreateAdditionalConnectionState;
2061 
2062 	}
2063 
2064 	private DropHandler dropHandler;
2065 
2066 	SimpleWindow _parent;
2067 	bool beingOpenKeepsAppOpen = true;
2068 	/++
2069 		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.
2070 
2071 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2072 
2073 		Params:
2074 
2075 		width = the width of the window's client area, in pixels
2076 		height = the height of the window's client area, in pixels
2077 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2078 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2079 		resizable = [Resizability] has three options:
2080 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2081 			$(P `fixedSize` will not allow the user to resize the window.)
2082 			$(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.)
2083 		windowType = The type of window you want to make.
2084 		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.
2085 		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".
2086 	+/
2087 	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) {
2088 		claimGuiThread();
2089 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2090 		this._width = this._virtualWidth = width;
2091 		this._height = this._virtualHeight = height;
2092 		this.openglMode = opengl;
2093 		version(X11) {
2094 			// auto scale not implemented except with opengl and even there it is kinda weird
2095 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2096 				resizable = Resizability.fixedSize;
2097 		}
2098 		this.resizability = resizable;
2099 		this.windowType = windowType;
2100 		this.customizationFlags = customizationFlags;
2101 		this._title = (title is null ? "D Application" : title);
2102 		this._parent = parent;
2103 		impl.createWindow(width, height, this._title, opengl, parent);
2104 
2105 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2106 			beingOpenKeepsAppOpen = false;
2107 	}
2108 
2109 	/// ditto
2110 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2111 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2112 	}
2113 
2114 	/// Same as above, except using the `Size` struct instead of separate width and height.
2115 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2116 		this(size.width, size.height, title, opengl, resizable);
2117 	}
2118 
2119 	/// ditto
2120 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2121 		this(size, title, opengl, resizable);
2122 	}
2123 
2124 
2125 	/++
2126 		Creates a window based on the given [Image]. It's client area
2127 		width and height is equal to the image. (A window's client area
2128 		is the drawable space inside; it excludes the title bar, etc.)
2129 
2130 		Windows based on images will not be resizable and do not use OpenGL.
2131 
2132 		It will draw the image in upon creation, but this will be overwritten
2133 		upon any draws, including the initial window visible event.
2134 
2135 		You probably do not want to use this and it may be removed from
2136 		the library eventually, or I might change it to be a "permanent"
2137 		background image; one that is automatically drawn on it before any
2138 		other drawing event. idk.
2139 	+/
2140 	this(Image image, string title = null) {
2141 		this(image.width, image.height, title);
2142 		this.image = image;
2143 	}
2144 
2145 	/++
2146 		Wraps a native window handle with very little additional processing - notably no destruction
2147 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2148 		windows created through the low level API (so you can use platform-specific options and
2149 		other details SimpleWindow does not expose) available to the event loop wrappers.
2150 	+/
2151 	this(NativeWindowHandle nativeWindow) {
2152 		windowType = WindowTypes.minimallyWrapped;
2153 		version(Windows)
2154 			impl.hwnd = nativeWindow;
2155 		else version(X11) {
2156 			impl.window = nativeWindow;
2157 			if(nativeWindow)
2158 				display = XDisplayConnection.get(); // get initial display to not segfault
2159 		} else version(OSXCocoa) {
2160 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2161 		} else featureNotImplemented();
2162 		// FIXME: set the size correctly
2163 		_width = 1;
2164 		_height = 1;
2165 		if(nativeWindow)
2166 			nativeMapping[cast(void*) nativeWindow] = this;
2167 
2168 		beingOpenKeepsAppOpen = false;
2169 
2170 		if(nativeWindow)
2171 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2172 		_suppressDestruction = true; // so it doesn't try to close
2173 	}
2174 
2175 	/++
2176 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2177 		The delegate will be called when the window manager asks you to take focus.
2178 
2179 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2180 
2181 		History:
2182 			Added April 1, 2022 (dub v10.8)
2183 	+/
2184 	SimpleWindow delegate() setRequestedInputFocus;
2185 
2186 	/// Experimental, do not use yet
2187 	/++
2188 		Grabs exclusive input from the user until you release it with
2189 		[releaseInputGrab].
2190 
2191 
2192 		Note: it is extremely rude to do this without good reason.
2193 		Reasons may include doing some kind of mouse drag operation
2194 		or popping up a temporary menu that should get events and will
2195 		be dismissed at ease by the user clicking away.
2196 
2197 		Params:
2198 			keyboard = do you want to grab keyboard input?
2199 			mouse = grab mouse input?
2200 			confine = confine the mouse cursor to inside this window?
2201 
2202 		History:
2203 			Prior to March 11, 2021, grabbing the keyboard would always also
2204 			set the X input focus. Now, it only focuses if it is a non-transient
2205 			window and otherwise manages the input direction internally.
2206 
2207 			This means spurious focus/blur events will no longer be sent and the
2208 			application will not steal focus from other applications (which the
2209 			window manager may have rejected anyway).
2210 	+/
2211 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2212 		static if(UsingSimpledisplayX11) {
2213 			XSync(XDisplayConnection.get, 0);
2214 			if(keyboard) {
2215 				if(isTransient && _parent) {
2216 					/*
2217 					FIXME:
2218 						setting the keyboard focus is not actually that helpful, what I more likely want
2219 						is the events from the parent window to be sent over here if we're transient.
2220 					*/
2221 
2222 					_parent.inputProxy = this;
2223 				} else {
2224 
2225 					SimpleWindow setTo;
2226 					if(setRequestedInputFocus !is null)
2227 						setTo = setRequestedInputFocus();
2228 					if(setTo is null)
2229 						setTo = this;
2230 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2231 				}
2232 			}
2233 			if(mouse) {
2234 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2235 				EventMask.PointerMotionMask // FIXME: not efficient
2236 				| EventMask.ButtonPressMask
2237 				| EventMask.ButtonReleaseMask
2238 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2239 				)
2240 			{
2241 				XSync(XDisplayConnection.get, 0);
2242 				import core.stdc.stdio;
2243 				printf("Grab input failed %d\n", res);
2244 				//throw new Exception("Grab input failed");
2245 			} else {
2246 				// cool
2247 			}
2248 			}
2249 
2250 		} else version(Windows) {
2251 			// FIXME: keyboard?
2252 			SetCapture(impl.hwnd);
2253 			if(confine) {
2254 				RECT rcClip;
2255 				//RECT rcOldClip;
2256 				//GetClipCursor(&rcOldClip);
2257 				GetWindowRect(hwnd, &rcClip);
2258 				ClipCursor(&rcClip);
2259 			}
2260 		} else version(OSXCocoa) {
2261 			// throw new NotYetImplementedException();
2262 		} else static assert(0);
2263 	}
2264 
2265 	private Point imePopupLocation = Point(0, 0);
2266 
2267 	/++
2268 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2269 
2270 		Bugs:
2271 			Not implemented outside X11.
2272 	+/
2273 	void setIMEPopupLocation(Point location) {
2274 		static if(UsingSimpledisplayX11) {
2275 			imePopupLocation = location;
2276 			updateIMEPopupLocation();
2277 		} else {
2278 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2279 			// throw new NotYetImplementedException();
2280 		}
2281 	}
2282 
2283 	/// ditto
2284 	void setIMEPopupLocation(int x, int y) {
2285 		return setIMEPopupLocation(Point(x, y));
2286 	}
2287 
2288 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2289 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2290 	// receives a ConfigureNotify event
2291 	private void updateIMEPopupLocation() {
2292 		static if(UsingSimpledisplayX11) {
2293 			if (xic is null) {
2294 				return;
2295 			}
2296 
2297 			XPoint nspot;
2298 			nspot.x = cast(short) imePopupLocation.x;
2299 			nspot.y = cast(short) imePopupLocation.y;
2300 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2301 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2302 			XFree(preeditAttr);
2303 		}
2304 	}
2305 
2306 	private bool imeFocused = true;
2307 
2308 	/++
2309 		Tells the IME whether or not an input field is currently focused in the window.
2310 
2311 		Bugs:
2312 			Not implemented outside X11.
2313 	+/
2314 	void setIMEFocused(bool value) {
2315 		imeFocused = value;
2316 		updateIMEFocused();
2317 	}
2318 
2319 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2320 	private void updateIMEFocused() {
2321 		static if(UsingSimpledisplayX11) {
2322 			if (xic is null) {
2323 				return;
2324 			}
2325 
2326 			if (focused && imeFocused) {
2327 				XSetICFocus(xic);
2328 			} else {
2329 				XUnsetICFocus(xic);
2330 			}
2331 		}
2332 	}
2333 
2334 	/++
2335 		Returns the native window.
2336 
2337 		History:
2338 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2339 			to access it through the `impl` member (which is semi-supported
2340 			but platform specific and here it is simple enough to offer an accessor).
2341 
2342 		Bugs:
2343 			Not implemented outside Windows or X11.
2344 	+/
2345 	NativeWindowHandle nativeWindowHandle() {
2346 		version(X11)
2347 			return impl.window;
2348 		else version(Windows)
2349 			return impl.hwnd;
2350 		else
2351 			throw new NotYetImplementedException();
2352 	}
2353 
2354 	private bool isTransient() {
2355 		with(WindowTypes)
2356 		final switch(windowType) {
2357 			case normal, undecorated, eventOnly:
2358 			case nestedChild, minimallyWrapped:
2359 				return (customizationFlags & WindowFlags.transient) ? true : false;
2360 			case dropdownMenu, popupMenu, notification:
2361 				return true;
2362 		}
2363 	}
2364 
2365 	private SimpleWindow inputProxy;
2366 
2367 	/++
2368 		Releases the grab acquired by [grabInput].
2369 	+/
2370 	void releaseInputGrab() {
2371 		static if(UsingSimpledisplayX11) {
2372 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2373 			if(_parent)
2374 				_parent.inputProxy = null;
2375 		} else version(Windows) {
2376 			ReleaseCapture();
2377 			ClipCursor(null);
2378 		} else version(OSXCocoa) {
2379 			// throw new NotYetImplementedException();
2380 		} else static assert(0);
2381 	}
2382 
2383 	/++
2384 		Sets the input focus to this window.
2385 
2386 		You shouldn't call this very often - please let the user control the input focus.
2387 	+/
2388 	void focus() {
2389 		static if(UsingSimpledisplayX11) {
2390 			SimpleWindow setTo;
2391 			if(setRequestedInputFocus !is null)
2392 				setTo = setRequestedInputFocus();
2393 			if(setTo is null)
2394 				setTo = this;
2395 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2396 		} else version(Windows) {
2397 			SetFocus(this.impl.hwnd);
2398 		} else version(OSXCocoa) {
2399 			throw new NotYetImplementedException();
2400 		} else static assert(0);
2401 	}
2402 
2403 	/++
2404 		Requests attention from the user for this window.
2405 
2406 
2407 		The typical result of this function is to change the color
2408 		of the taskbar icon, though it may be tweaked on specific
2409 		platforms.
2410 
2411 		It is meant to unobtrusively tell the user that something
2412 		relevant to them happened in the background and they should
2413 		check the window when they get a chance. Upon receiving the
2414 		keyboard focus, the window will automatically return to its
2415 		natural state.
2416 
2417 		If the window already has the keyboard focus, this function
2418 		may do nothing, because the user is presumed to already be
2419 		giving the window attention.
2420 
2421 		Implementation_note:
2422 
2423 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2424 		atom on X11 and the FlashWindow function on Windows.
2425 	+/
2426 	void requestAttention() {
2427 		if(_focused)
2428 			return;
2429 
2430 		version(Windows) {
2431 			FLASHWINFO info;
2432 			info.cbSize = info.sizeof;
2433 			info.hwnd = impl.hwnd;
2434 			info.dwFlags = FLASHW_TRAY;
2435 			info.uCount = 1;
2436 
2437 			FlashWindowEx(&info);
2438 
2439 		} else version(X11) {
2440 			demandingAttention = true;
2441 			demandAttention(this, true);
2442 		} else version(OSXCocoa) {
2443 			throw new NotYetImplementedException();
2444 		} else static assert(0);
2445 	}
2446 
2447 	private bool _focused;
2448 
2449 	version(X11) private bool demandingAttention;
2450 
2451 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2452 	/// You'll have to call `close()` manually if you set this delegate.
2453 	void delegate () closeQuery;
2454 
2455 	/// This will be called when window visibility was changed.
2456 	void delegate (bool becomesVisible) visibilityChanged;
2457 
2458 	/// This will be called when window becomes visible for the first time.
2459 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2460 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2461 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2462 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2463 	private bool _visibleForTheFirstTimeCalled;
2464 	void delegate () visibleForTheFirstTime;
2465 
2466 	/// Returns true if the window has been closed.
2467 	final @property bool closed() { return _closed; }
2468 
2469 	private final @property bool notClosed() { return !_closed; }
2470 
2471 	/// Returns true if the window is focused.
2472 	final @property bool focused() { return _focused; }
2473 
2474 	private bool _visible;
2475 	/// Returns true if the window is visible (mapped).
2476 	final @property bool visible() { return _visible; }
2477 
2478 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2479 	void close() {
2480 		if (!_closed) {
2481 			runInGuiThread( {
2482 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2483 				if (onClosing !is null) onClosing();
2484 				impl.closeWindow();
2485 				_closed = true;
2486 			} );
2487 		}
2488 	}
2489 
2490 	/++
2491 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2492 
2493 		History:
2494 			Overload added on March 7, 2021.
2495 	+/
2496 	void close() shared {
2497 		(cast() this).close();
2498 	}
2499 
2500 	/++
2501 
2502 	+/
2503 	void maximize() {
2504 		version(Windows)
2505 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2506 		else version(X11) {
2507 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2508 
2509 			// also note _NET_WM_STATE_FULLSCREEN
2510 		}
2511 
2512 	}
2513 
2514 	private bool _fullscreen;
2515 	version(Windows)
2516 	private WINDOWPLACEMENT g_wpPrev;
2517 
2518 	/// not fully implemented but planned for a future release
2519 	void fullscreen(bool yes) {
2520 		version(Windows) {
2521 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2522 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2523 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2524 				MONITORINFO mi;
2525 				mi.cbSize = MONITORINFO.sizeof;
2526 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2527 				    GetMonitorInfo(MonitorFromWindow(hwnd,
2528 								     MONITOR_DEFAULTTOPRIMARY), &mi)) {
2529 					SetWindowLong(hwnd, GWL_STYLE,
2530 						      dwStyle & ~WS_OVERLAPPEDWINDOW);
2531 					SetWindowPos(hwnd, HWND_TOP,
2532 						     mi.rcMonitor.left, mi.rcMonitor.top,
2533 						     mi.rcMonitor.right - mi.rcMonitor.left,
2534 						     mi.rcMonitor.bottom - mi.rcMonitor.top,
2535 						     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2536 				}
2537 			} else {
2538 				SetWindowLong(hwnd, GWL_STYLE,
2539 					      dwStyle | WS_OVERLAPPEDWINDOW);
2540 				SetWindowPlacement(hwnd, &g_wpPrev);
2541 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2542 					     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2543 					     SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2544 			}
2545 
2546 		} else version(X11) {
2547 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2548 		}
2549 
2550 		_fullscreen = yes;
2551 
2552 	}
2553 
2554 	bool fullscreen() {
2555 		return _fullscreen;
2556 	}
2557 
2558 	/++
2559 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2560 
2561 	+/
2562 	void minimize() {
2563 		version(Windows)
2564 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2565 		//else version(X11)
2566 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2567 	}
2568 
2569 	/// Alias for `hidden = false`
2570 	void show() {
2571 		hidden = false;
2572 	}
2573 
2574 	/// Alias for `hidden = true`
2575 	void hide() {
2576 		hidden = true;
2577 	}
2578 
2579 	/// Hide cursor when it enters the window.
2580 	void hideCursor() {
2581 		version(OSXCocoa) throw new NotYetImplementedException(); else
2582 		if (!_closed) impl.hideCursor();
2583 	}
2584 
2585 	/// Don't hide cursor when it enters the window.
2586 	void showCursor() {
2587 		version(OSXCocoa) throw new NotYetImplementedException(); else
2588 		if (!_closed) impl.showCursor();
2589 	}
2590 
2591 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2592 	 *
2593 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2594 	 * control. Try to think for other approaches before using this function.
2595 	 *
2596 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2597 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2598 	 *       receive "mouse moved here" event.
2599 	 */
2600 	bool warpMouse (int x, int y) {
2601 		version(X11) {
2602 			if (!_closed) { impl.warpMouse(x, y); return true; }
2603 		} else version(Windows) {
2604 			if (!_closed) {
2605 				POINT point;
2606 				point.x = x;
2607 				point.y = y;
2608 				if(ClientToScreen(impl.hwnd, &point)) {
2609 					SetCursorPos(point.x, point.y);
2610 					return true;
2611 				}
2612 			}
2613 		}
2614 		return false;
2615 	}
2616 
2617 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2618 	void sendDummyEvent () {
2619 		version(X11) {
2620 			if (!_closed) { impl.sendDummyEvent(); }
2621 		}
2622 	}
2623 
2624 	/// Set window minimal size.
2625 	void setMinSize (int minwidth, int minheight) {
2626 		version(OSXCocoa) throw new NotYetImplementedException(); else
2627 		if (!_closed) impl.setMinSize(minwidth, minheight);
2628 	}
2629 
2630 	/// Set window maximal size.
2631 	void setMaxSize (int maxwidth, int maxheight) {
2632 		version(OSXCocoa) throw new NotYetImplementedException(); else
2633 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2634 	}
2635 
2636 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2637 	/// Currently only supported on X11.
2638 	void setResizeGranularity (int granx, int grany) {
2639 		version(OSXCocoa) throw new NotYetImplementedException(); else
2640 		if (!_closed) impl.setResizeGranularity(granx, grany);
2641 	}
2642 
2643 	/// Move window.
2644 	void move(int x, int y) {
2645 		version(OSXCocoa) throw new NotYetImplementedException(); else
2646 		if (!_closed) impl.move(x, y);
2647 	}
2648 
2649 	/// ditto
2650 	void move(Point p) {
2651 		version(OSXCocoa) throw new NotYetImplementedException(); else
2652 		if (!_closed) impl.move(p.x, p.y);
2653 	}
2654 
2655 	/++
2656 		Resize window.
2657 
2658 		Note that the width and height of the window are NOT instantly
2659 		updated - it waits for the window manager to approve the resize
2660 		request, which means you must return to the event loop before the
2661 		width and height are actually changed.
2662 	+/
2663 	void resize(int w, int h) {
2664 		if(!_closed && _fullscreen) fullscreen = false;
2665 		version(OSXCocoa) throw new NotYetImplementedException(); else
2666 		if (!_closed) impl.resize(w, h);
2667 	}
2668 
2669 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2670 	void moveResize (int x, int y, int w, int h) {
2671 		if(!_closed && _fullscreen) fullscreen = false;
2672 		version(OSXCocoa) throw new NotYetImplementedException(); else
2673 		if (!_closed) impl.moveResize(x, y, w, h);
2674 	}
2675 
2676 	private bool _hidden;
2677 
2678 	/// Returns true if the window is hidden.
2679 	final @property bool hidden() {
2680 		return _hidden;
2681 	}
2682 
2683 	/// Shows or hides the window based on the bool argument.
2684 	final @property void hidden(bool b) {
2685 		_hidden = b;
2686 		version(Windows) {
2687 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2688 		} else version(X11) {
2689 			if(b)
2690 				//XUnmapWindow(impl.display, impl.window);
2691 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2692 			else
2693 				XMapWindow(impl.display, impl.window);
2694 		} else version(OSXCocoa) {
2695 			// throw new NotYetImplementedException();
2696 		} else static assert(0);
2697 	}
2698 
2699 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2700 	void opacity(double opacity) @property
2701 	in {
2702 		assert(opacity >= 0 && opacity <= 1);
2703 	} do {
2704 		version (Windows) {
2705 			impl.setOpacity(cast(ubyte)(255 * opacity));
2706 		} else version (X11) {
2707 			impl.setOpacity(cast(uint)(uint.max * opacity));
2708 		} else throw new NotYetImplementedException();
2709 	}
2710 
2711 	/++
2712 		Sets your event handlers, without entering the event loop. Useful if you
2713 		have multiple windows - set the handlers on each window, then only do
2714 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2715 
2716 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2717 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2718 		delegate signatures.
2719 	+/
2720 	void setEventHandlers(T...)(T eventHandlers) {
2721 		// FIXME: add more events
2722 		foreach(handler; eventHandlers) {
2723 			static if(__traits(compiles, handleKeyEvent = handler)) {
2724 				handleKeyEvent = handler;
2725 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2726 				handleCharEvent = handler;
2727 			} else static if(__traits(compiles, handlePulse = handler)) {
2728 				handlePulse = handler;
2729 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2730 				handleMouseEvent = handler;
2731 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2732 		}
2733 	}
2734 
2735 	/++
2736 		The event loop automatically returns when the window is closed
2737 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2738 		pulse timer is created. The event loop will block until an event
2739 		arrives or the pulse timer goes off.
2740 
2741 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2742 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2743 		[handleMouseEvent], based on the signature of delegates you provide.
2744 
2745 		Give one with no parameters to set a timer pulse handler. Give one that
2746 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2747 		and one that takes `dchar` for a char event handler. You can use as many
2748 		or as few handlers as you need for your application.
2749 
2750 		Bugs:
2751 
2752 		$(PITFALL
2753 			You should always have one event loop live for your application.
2754 			If you make two windows in sequence, the second call to eventLoop
2755 			might fail:
2756 
2757 			---
2758 			// don't do this!
2759 			auto window = new SimpleWindow();
2760 			window.eventLoop(0);
2761 
2762 			auto window2 = new SimpleWindow();
2763 			window2.eventLoop(0); // problematic! might crash
2764 			---
2765 
2766 			simpledisplay's current implementation assumes that final cleanup is
2767 			done when the event loop refcount reaches zero. So after the first
2768 			eventLoop returns, when there isn't already another one active, it assumes
2769 			the program will exit soon and cleans up.
2770 
2771 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
2772 			it eventually, but in the mean time, there's an easy solution:
2773 
2774 			---
2775 			// do this
2776 			EventLoop mainEventLoop = EventLoop.get; // just add this line
2777 
2778 			auto window = new SimpleWindow();
2779 			window.eventLoop(0);
2780 
2781 			auto window2 = new SimpleWindow();
2782 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
2783 			---
2784 
2785 			By adding a top-level reference to the event loop, it ensures the final cleanup
2786 			is not performed until it goes out of scope too, letting the individual window loops
2787 			work without trouble despite the bug.
2788 		)
2789 
2790 		History:
2791 			The overload without `pulseTimeout` was added on December 8, 2021.
2792 
2793 			On December 9, 2021, the default blocking mode (which is now configurable
2794 			because [eventLoopWithBlockingMode] was added) switched from
2795 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
2796 			should almost never be noticeable to you since the typical simpledisplay
2797 			paradigm has been (and I still recommend) to have one `eventLoop` call.
2798 
2799 		See_Also:
2800 			[eventLoopWithBlockingMode]
2801 	+/
2802 	final int eventLoop(T...)(
2803 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2804 		T eventHandlers) /// delegate list like std.concurrency.receive
2805 	{
2806 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
2807 	}
2808 
2809 	/// ditto
2810 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
2811 	{
2812 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
2813 	}
2814 
2815 	/++
2816 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
2817 
2818 		History:
2819 			Added December 8, 2021 (dub v10.5)
2820 
2821 			Previously, this implementation was right inside [eventLoop], but when I wanted
2822 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
2823 			just renamed it instead of adding as an overload. Besides, the new name makes it
2824 			easier to remember the order and avoids ambiguity between two int-like params anyway.
2825 
2826 		See_Also:
2827 			[SimpleWindow.eventLoop], [EventLoop]
2828 
2829 		Bugs:
2830 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
2831 	+/
2832 	final int eventLoopWithBlockingMode(T...)(
2833 		BlockingMode blockingMode, /// when you want this function to block until
2834 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2835 		T eventHandlers) /// delegate list like std.concurrency.receive
2836 	{
2837 		setEventHandlers(eventHandlers);
2838 
2839 		version(with_eventloop) {
2840 			// delegates event loop to my other module
2841 			version(X11)
2842 				XFlush(display);
2843 
2844 			import arsd.eventloop;
2845 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2846 			scope(exit) clearInterval(handle);
2847 
2848 			loop();
2849 			return 0;
2850 		} else version(OSXCocoa) {
2851 			// FIXME
2852 			if (handlePulse !is null && pulseTimeout != 0) {
2853 				timer = NSTimer.schedule(pulseTimeout*1e-3,
2854 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
2855 					null, true);
2856 			}
2857 
2858 			view.setNeedsDisplay(true);
2859 
2860 			NSApp.run();
2861             		return 0;
2862         	} else {
2863 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2864 
2865 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
2866 				return 0;
2867 
2868 			return el.run(
2869 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
2870 					null :
2871 					&this.notClosed
2872 			);
2873 		}
2874 	}
2875 
2876 	/++
2877 		This lets you draw on the window (or its backing buffer) using basic
2878 		2D primitives.
2879 
2880 		Be sure to call this in a limited scope because your changes will not
2881 		actually appear on the window until ScreenPainter's destructor runs.
2882 
2883 		Returns: an instance of [ScreenPainter], which has the drawing methods
2884 		on it to draw on this window.
2885 
2886 		Params:
2887 			manualInvalidations = if you set this to true, you will need to
2888 			set the invalid rectangle on the painter yourself. If false, it
2889 			assumes the whole window has been redrawn each time you draw.
2890 
2891 			Only invalidated rectangles are blitted back to the window when
2892 			the destructor runs. Doing this yourself can reduce flickering
2893 			of child windows.
2894 
2895 		History:
2896 			The `manualInvalidations` parameter overload was added on
2897 			December 30, 2021 (dub v10.5)
2898 	+/
2899 	ScreenPainter draw() {
2900 		return draw(false);
2901 	}
2902 	/// ditto
2903 	ScreenPainter draw(bool manualInvalidations) {
2904 		return impl.getPainter(manualInvalidations);
2905 	}
2906 
2907 	// This is here to implement the interface we use for various native handlers.
2908 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2909 
2910 	// maps native window handles to SimpleWindow instances, if there are any
2911 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2912 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
2913 	version(OSXCocoa)
2914 	public __gshared SimpleWindow[void*] nativeMapping;
2915 	else
2916 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2917 
2918 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
2919 	private int _virtualWidth;
2920 	private int _virtualHeight;
2921 
2922 	/// Width of the window's drawable client area, in pixels.
2923 	@scriptable
2924 	final @property int width() const pure nothrow @safe @nogc {
2925 		if(resizability == Resizability.automaticallyScaleIfPossible)
2926 			return _virtualWidth;
2927 		else
2928 			return _width;
2929 	}
2930 
2931 	/// Height of the window's drawable client area, in pixels.
2932 	@scriptable
2933 	final @property int height() const pure nothrow @safe @nogc {
2934 		if(resizability == Resizability.automaticallyScaleIfPossible)
2935 			return _virtualHeight;
2936 		else
2937 			return _height;
2938 	}
2939 
2940 	/++
2941 		Returns the actual size of the window, bypassing the logical
2942 		illusions of [Resizability.automaticallyScaleIfPossible].
2943 
2944 		History:
2945 			Added November 11, 2022 (dub v10.10)
2946 	+/
2947 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
2948 		return Size(_width, _height);
2949 	}
2950 
2951 
2952 	private int _width;
2953 	private int _height;
2954 
2955 	// HACK: making the best of some copy constructor woes with refcounting
2956 	private ScreenPainterImplementation* activeScreenPainter_;
2957 
2958 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2959 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2960 
2961 	private OpenGlOptions openglMode;
2962 	private Resizability resizability;
2963 	private WindowTypes windowType;
2964 	private int customizationFlags;
2965 
2966 	/// `true` if OpenGL was initialized for this window.
2967 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2968 		version(without_opengl)
2969 			return false;
2970 		else
2971 			return (openglMode == OpenGlOptions.yes);
2972 	}
2973 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2974 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2975 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2976 
2977 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2978 	/// to call this, as it's not recommended to share window between threads.
2979 	void mtLock () {
2980 		version(X11) {
2981 			XLockDisplay(this.display);
2982 		}
2983 	}
2984 
2985 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2986 	/// to call this, as it's not recommended to share window between threads.
2987 	void mtUnlock () {
2988 		version(X11) {
2989 			XUnlockDisplay(this.display);
2990 		}
2991 	}
2992 
2993 	/// Emit a beep to get user's attention.
2994 	void beep () {
2995 		version(X11) {
2996 			XBell(this.display, 100);
2997 		} else version(Windows) {
2998 			MessageBeep(0xFFFFFFFF);
2999 		}
3000 	}
3001 
3002 
3003 
3004 	version(without_opengl) {} else {
3005 
3006 		/// 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`.
3007 		void delegate() redrawOpenGlScene;
3008 
3009 		/// This will allow you to change OpenGL vsync state.
3010 		final @property void vsync (bool wait) {
3011 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3012 		  version(X11) {
3013 		    setAsCurrentOpenGlContext();
3014 		    glxSetVSync(display, impl.window, wait);
3015 		  } else version(Windows) {
3016 		    setAsCurrentOpenGlContext();
3017                     wglSetVSync(wait);
3018 		  }
3019 		}
3020 
3021 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3022 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3023 		/// enough without waiting 'em to finish their frame business.
3024 		bool useGLFinish = true;
3025 
3026 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3027 		/// 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.
3028 		void redrawOpenGlSceneNow() {
3029 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3030 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3031 			if(redrawOpenGlScene is null)
3032 				return;
3033 
3034 			this.mtLock();
3035 			scope(exit) this.mtUnlock();
3036 
3037 			this.setAsCurrentOpenGlContext();
3038 
3039 			redrawOpenGlScene();
3040 
3041 			this.swapOpenGlBuffers();
3042 			// 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.
3043 			if (useGLFinish) glFinish();
3044 		}
3045 
3046 		private bool redrawOpenGlSceneSoonSet = false;
3047 		private static class RedrawOpenGlSceneEvent {
3048 			SimpleWindow w;
3049 			this(SimpleWindow w) { this.w = w; }
3050 		}
3051 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3052 		/++
3053 			Queues an opengl redraw as soon as the other pending events are cleared.
3054 		+/
3055 		void redrawOpenGlSceneSoon() {
3056 			if(redrawOpenGlScene is null)
3057 				return;
3058 
3059 			if(!redrawOpenGlSceneSoonSet) {
3060 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3061 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3062 				redrawOpenGlSceneSoonSet = true;
3063 			}
3064 			this.postEvent(redrawOpenGlSceneEvent, true);
3065 		}
3066 
3067 
3068 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3069 		void setAsCurrentOpenGlContext() {
3070 			assert(openglMode == OpenGlOptions.yes);
3071 			version(X11) {
3072 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3073 					throw new Exception("glXMakeCurrent");
3074 			} else version(Windows) {
3075 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3076 				if (!wglMakeCurrent(ghDC, ghRC))
3077 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3078 			}
3079 		}
3080 
3081 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3082 		/// This doesn't throw, returning success flag instead.
3083 		bool setAsCurrentOpenGlContextNT() nothrow {
3084 			assert(openglMode == OpenGlOptions.yes);
3085 			version(X11) {
3086 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3087 			} else version(Windows) {
3088 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3089 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3090 			}
3091 		}
3092 
3093 		/// 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.
3094 		/// This doesn't throw, returning success flag instead.
3095 		bool releaseCurrentOpenGlContext() nothrow {
3096 			assert(openglMode == OpenGlOptions.yes);
3097 			version(X11) {
3098 				return (glXMakeCurrent(display, 0, null) != 0);
3099 			} else version(Windows) {
3100 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3101 				return wglMakeCurrent(ghDC, null) ? true : false;
3102 			}
3103 		}
3104 
3105 		/++
3106 			simpledisplay always uses double buffering, usually automatically. This
3107 			manually swaps the OpenGL buffers.
3108 
3109 
3110 			You should not need to call this yourself because simpledisplay will do it
3111 			for you after calling your `redrawOpenGlScene`.
3112 
3113 			Remember that this may throw an exception, which you can catch in a multithreaded
3114 			application to keep your thread from dying from an unhandled exception.
3115 		+/
3116 		void swapOpenGlBuffers() {
3117 			assert(openglMode == OpenGlOptions.yes);
3118 			version(X11) {
3119 				if (!this._visible) return; // no need to do this if window is invisible
3120 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3121 				glXSwapBuffers(display, impl.window);
3122 			} else version(Windows) {
3123 				SwapBuffers(ghDC);
3124 			}
3125 		}
3126 	}
3127 
3128 	/++
3129 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3130 
3131 
3132 		---
3133 			auto window = new SimpleWindow(100, 100, "First title");
3134 			window.title = "A new title";
3135 		---
3136 
3137 		You may call this function at any time.
3138 	+/
3139 	@property void title(string title) {
3140 		_title = title;
3141 		version(OSXCocoa) throw new NotYetImplementedException(); else
3142 		impl.setTitle(title);
3143 	}
3144 
3145 	private string _title;
3146 
3147 	/// Gets the title
3148 	@property string title() {
3149 		if(_title is null)
3150 			_title = getRealTitle();
3151 		return _title;
3152 	}
3153 
3154 	/++
3155 		Get the title as set by the window manager.
3156 		May not match what you attempted to set.
3157 	+/
3158 	string getRealTitle() {
3159 		static if(is(typeof(impl.getTitle())))
3160 			return impl.getTitle();
3161 		else
3162 			return null;
3163 	}
3164 
3165 	// don't use this generally it is not yet really released
3166 	version(X11)
3167 	@property Image secret_icon() {
3168 		return secret_icon_inner;
3169 	}
3170 	private Image secret_icon_inner;
3171 
3172 
3173 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3174 	@property void icon(MemoryImage icon) {
3175 		if(icon is null)
3176 			return;
3177 		auto tci = icon.getAsTrueColorImage();
3178 		version(Windows) {
3179 			winIcon = new WindowsIcon(icon);
3180 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3181 		} else version(X11) {
3182 			secret_icon_inner = Image.fromMemoryImage(icon);
3183 			// FIXME: ensure this is correct
3184 			auto display = XDisplayConnection.get;
3185 			arch_ulong[] buffer;
3186 			buffer ~= icon.width;
3187 			buffer ~= icon.height;
3188 			foreach(c; tci.imageData.colors) {
3189 				arch_ulong b;
3190 				b |= c.a << 24;
3191 				b |= c.r << 16;
3192 				b |= c.g << 8;
3193 				b |= c.b;
3194 				buffer ~= b;
3195 			}
3196 
3197 			XChangeProperty(
3198 				display,
3199 				impl.window,
3200 				GetAtom!("_NET_WM_ICON", true)(display),
3201 				GetAtom!"CARDINAL"(display),
3202 				32 /* bits */,
3203 				0 /*PropModeReplace*/,
3204 				buffer.ptr,
3205 				cast(int) buffer.length);
3206 		} else version(OSXCocoa) {
3207 			throw new NotYetImplementedException();
3208 		} else static assert(0);
3209 	}
3210 
3211 	version(Windows)
3212 		private WindowsIcon winIcon;
3213 
3214 	bool _suppressDestruction;
3215 
3216 	~this() {
3217 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3218 		if(_suppressDestruction)
3219 			return;
3220 		impl.dispose();
3221 	}
3222 
3223 	private bool _closed;
3224 
3225 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3226 	/*
3227 	ScreenPainter drawTransiently() {
3228 		return impl.getPainter();
3229 	}
3230 	*/
3231 
3232 	/// Draws an image on the window. This is meant to provide quick look
3233 	/// of a static image generated elsewhere.
3234 	@property void image(Image i) {
3235 	/+
3236 		version(Windows) {
3237 			BITMAP bm;
3238 			HDC hdc = GetDC(hwnd);
3239 			HDC hdcMem = CreateCompatibleDC(hdc);
3240 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3241 
3242 			GetObject(i.handle, bm.sizeof, &bm);
3243 
3244 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3245 
3246 			SelectObject(hdcMem, hbmOld);
3247 			DeleteDC(hdcMem);
3248 			ReleaseDC(hwnd, hdc);
3249 
3250 			/*
3251 			RECT r;
3252 			r.right = i.width;
3253 			r.bottom = i.height;
3254 			InvalidateRect(hwnd, &r, false);
3255 			*/
3256 		} else
3257 		version(X11) {
3258 			if(!destroyed) {
3259 				if(i.usingXshm)
3260 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3261 				else
3262 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3263 			}
3264 		} else
3265 		version(OSXCocoa) {
3266 			draw().drawImage(Point(0, 0), i);
3267 			setNeedsDisplay(view, true);
3268 		} else static assert(0);
3269 	+/
3270 		auto painter = this.draw;
3271 		painter.drawImage(Point(0, 0), i);
3272 	}
3273 
3274 	/++
3275 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3276 
3277 		---
3278 		window.cursor = GenericCursor.Help;
3279 		// now the window mouse cursor is set to a generic help
3280 		---
3281 
3282 	+/
3283 	@property void cursor(MouseCursor cursor) {
3284 		version(OSXCocoa)
3285 			{} // featureNotImplemented();
3286 		else
3287 		if(this.impl.curHidden <= 0) {
3288 			static if(UsingSimpledisplayX11) {
3289 				auto ch = cursor.cursorHandle;
3290 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3291 			} else version(Windows) {
3292 				auto ch = cursor.cursorHandle;
3293 				impl.currentCursor = ch;
3294 				SetCursor(ch); // redraw without waiting for mouse movement to update
3295 			} else featureNotImplemented();
3296 		}
3297 
3298 	}
3299 
3300 	/// What follows are the event handlers. These are set automatically
3301 	/// by the eventLoop function, but are still public so you can change
3302 	/// them later. wasPressed == true means key down. false == key up.
3303 
3304 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3305 	void delegate(KeyEvent ke) handleKeyEvent;
3306 
3307 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3308 	void delegate(dchar c) handleCharEvent;
3309 
3310 	/// Handles a timer pulse. Settable through setEventHandlers.
3311 	void delegate() handlePulse;
3312 
3313 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3314 	void delegate(bool) onFocusChange;
3315 
3316 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3317 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3318 	void delegate() onClosing;
3319 
3320 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3321 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3322 	 * last minute cleanup. */
3323 	void delegate() onDestroyed;
3324 
3325 	static if (UsingSimpledisplayX11)
3326 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3327 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3328 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3329 	 *
3330 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3331 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3332 
3333 	//version(Windows)
3334 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3335 
3336 	private {
3337 		int lastMouseX = int.min;
3338 		int lastMouseY = int.min;
3339 		void mdx(ref MouseEvent ev) {
3340 			if(lastMouseX == int.min || lastMouseY == int.min) {
3341 				ev.dx = 0;
3342 				ev.dy = 0;
3343 			} else {
3344 				ev.dx = ev.x - lastMouseX;
3345 				ev.dy = ev.y - lastMouseY;
3346 			}
3347 
3348 			lastMouseX = ev.x;
3349 			lastMouseY = ev.y;
3350 		}
3351 	}
3352 
3353 	/// Mouse event handler. Settable through setEventHandlers.
3354 	void delegate(MouseEvent) handleMouseEvent;
3355 
3356 	/// use to redraw child widgets if you use system apis to add stuff
3357 	void delegate() paintingFinished;
3358 
3359 	void delegate() paintingFinishedDg() {
3360 		return paintingFinished;
3361 	}
3362 
3363 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3364 	/// for this to ever happen.
3365 	void delegate(int width, int height) windowResized;
3366 
3367 	/++
3368 		Platform specific - handle any native message this window gets.
3369 
3370 		Note: this is called *in addition to* other event handlers, unless you either:
3371 
3372 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3373 
3374 		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.
3375 
3376 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3377 
3378 		On X, it takes the form of `int delegate(XEvent)`.
3379 
3380 		History:
3381 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3382 
3383 			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.
3384 	+/
3385 	NativeEventHandler handleNativeEvent_;
3386 
3387 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3388 		return handleNativeEvent_;
3389 	}
3390 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3391 		handleNativeEvent_ = neh;
3392 	}
3393 
3394 	version(Windows)
3395 	// compatibility shim with the old deprecated way
3396 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3397 	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) {
3398 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3399 			auto ret = dg(h, m, w, l);
3400 			if(ret == 0)
3401 				r = 1;
3402 			return ret;
3403 		};
3404 	}
3405 
3406 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3407 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3408 	/// this instead and it will work the same way.
3409 	__gshared NativeEventHandler handleNativeGlobalEvent;
3410 
3411 //  private:
3412 	/// The native implementation is available, but you shouldn't use it unless you are
3413 	/// familiar with the underlying operating system, don't mind depending on it, and
3414 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3415 	/// do what you need to do with handleNativeEvent instead.
3416 	///
3417 	/// This is likely to eventually change to be just a struct holding platform-specific
3418 	/// handles instead of a template mixin at some point because I'm not happy with the
3419 	/// code duplication here (ironically).
3420 	mixin NativeSimpleWindowImplementation!() impl;
3421 
3422 	/**
3423 		This is in-process one-way (from anything to window) event sending mechanics.
3424 		It is thread-safe, so it can be used in multi-threaded applications to send,
3425 		for example, "wake up and repaint" events when thread completed some operation.
3426 		This will allow to avoid using timer pulse to check events with synchronization,
3427 		'cause event handler will be called in UI thread. You can stop guessing which
3428 		pulse frequency will be enough for your app.
3429 		Note that events handlers may be called in arbitrary order, i.e. last registered
3430 		handler can be called first, and vice versa.
3431 	*/
3432 public:
3433 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3434 	 * "spamming" window with events it can't cope with.
3435 	 * It is safe to call this from non-UI threads.
3436 	 */
3437 	@property bool eventQueueEmpty() () {
3438 		synchronized(this) {
3439 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3440 		}
3441 		return true;
3442 	}
3443 
3444 	/** Does our custom event queue contains at least one with the given type?
3445 	 * Can be used in simple cases to prevent "spamming" window with events
3446 	 * it can't cope with.
3447 	 * It is safe to call this from non-UI threads.
3448 	 */
3449 	@property bool eventQueued(ET:Object) () {
3450 		synchronized(this) {
3451 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3452 				if (!o.doProcess) {
3453 					if (cast(ET)(o.evt)) return true;
3454 				}
3455 			}
3456 		}
3457 		return false;
3458 	}
3459 
3460 	/++
3461 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3462 
3463 		History:
3464 			Added May 12, 2021
3465 	+/
3466 	void delegate(Exception e) nothrow eventUncaughtException;
3467 
3468 	/** Add listener for custom event. Can be used like this:
3469 	 *
3470 	 * ---------------------
3471 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3472 	 *   ...
3473 	 *   win.removeEventListener(eid);
3474 	 * ---------------------
3475 	 *
3476 	 * Returns: 0 on failure (should never happen, so ignore it)
3477 	 *
3478 	 * $(WARNING Don't use this method in object destructors!)
3479 	 *
3480 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3481 	 *           'cause if event handler id counter will overflow, you won't be able
3482 	 *           to register any more events.)
3483 	 */
3484 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3485 		if (dg is null) return 0; // ignore empty handlers
3486 		synchronized(this) {
3487 			//FIXME: abort on overflow?
3488 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3489 			EventHandlerEntry e;
3490 			e.dg = delegate (Object o) {
3491 				if (auto co = cast(ET)o) {
3492 					try {
3493 						dg(co);
3494 					} catch (Exception e) {
3495 						// sorry!
3496 						if(eventUncaughtException)
3497 							eventUncaughtException(e);
3498 					}
3499 					return true;
3500 				}
3501 				return false;
3502 			};
3503 			e.id = lastUsedHandlerId;
3504 			auto optr = eventHandlers.ptr;
3505 			eventHandlers ~= e;
3506 			if (eventHandlers.ptr !is optr) {
3507 				import core.memory : GC;
3508 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3509 			}
3510 			return lastUsedHandlerId;
3511 		}
3512 	}
3513 
3514 	/// Remove event listener. It is safe to pass invalid event id here.
3515 	/// $(WARNING Don't use this method in object destructors!)
3516 	void removeEventListener() (uint id) {
3517 		if (id == 0 || id > lastUsedHandlerId) return;
3518 		synchronized(this) {
3519 			foreach (immutable idx; 0..eventHandlers.length) {
3520 				if (eventHandlers[idx].id == id) {
3521 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3522 					eventHandlers[$-1].dg = null;
3523 					eventHandlers.length -= 1;
3524 					eventHandlers.assumeSafeAppend;
3525 					return;
3526 				}
3527 			}
3528 		}
3529 	}
3530 
3531 	/// Post event to queue. It is safe to call this from non-UI threads.
3532 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3533 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3534 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3535 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3536 		if (this.closed) return false; // closed windows can't handle events
3537 
3538 		// remove all events of type `ET`
3539 		void removeAllET () {
3540 			uint eidx = 0, ec = eventQueueUsed;
3541 			auto eptr = eventQueue.ptr;
3542 			while (eidx < ec) {
3543 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3544 				if (cast(ET)eptr.evt !is null) {
3545 					// i found her!
3546 					if (inCustomEventProcessor) {
3547 						// if we're in custom event processing loop, processor will clear it for us
3548 						eptr.evt = null;
3549 						++eidx;
3550 						++eptr;
3551 					} else {
3552 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3553 						ec = --eventQueueUsed;
3554 						// clear last event (it is already copied)
3555 						eventQueue.ptr[ec].evt = null;
3556 					}
3557 				} else {
3558 					++eidx;
3559 					++eptr;
3560 				}
3561 			}
3562 		}
3563 
3564 		if (evt is null) {
3565 			if (replace) { synchronized(this) removeAllET(); }
3566 			// ignore empty events, they can't be handled anyway
3567 			return false;
3568 		}
3569 
3570 		// add events even if no event FD/event object created yet
3571 		synchronized(this) {
3572 			if (replace) removeAllET();
3573 			if (eventQueueUsed == uint.max) return false; // just in case
3574 			if (eventQueueUsed < eventQueue.length) {
3575 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3576 			} else {
3577 				if (eventQueue.capacity == eventQueue.length) {
3578 					// need to reallocate; do a trick to ensure that old array is cleared
3579 					auto oarr = eventQueue;
3580 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3581 					// just in case, do yet another check
3582 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3583 					import core.memory : GC;
3584 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3585 				} else {
3586 					auto optr = eventQueue.ptr;
3587 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3588 					assert(eventQueue.ptr is optr);
3589 				}
3590 				++eventQueueUsed;
3591 				assert(eventQueueUsed == eventQueue.length);
3592 			}
3593 			if (!eventWakeUp()) {
3594 				// can't wake up event processor, so there is no reason to keep the event
3595 				assert(eventQueueUsed > 0);
3596 				eventQueue[--eventQueueUsed].evt = null;
3597 				return false;
3598 			}
3599 			return true;
3600 		}
3601 	}
3602 
3603 	/// Post event to queue. It is safe to call this from non-UI threads.
3604 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3605 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3606 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3607 		return postTimeout!ET(evt, 0, replace);
3608 	}
3609 
3610 private:
3611 	private import core.time : MonoTime;
3612 
3613 	version(Posix) {
3614 		__gshared int customEventFDRead = -1;
3615 		__gshared int customEventFDWrite = -1;
3616 		__gshared int customSignalFD = -1;
3617 	} else version(Windows) {
3618 		__gshared HANDLE customEventH = null;
3619 	}
3620 
3621 	// wake up event processor
3622 	static bool eventWakeUp () {
3623 		version(X11) {
3624 			import core.sys.posix.unistd : write;
3625 			ulong n = 1;
3626 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3627 			return true;
3628 		} else version(Windows) {
3629 			if (customEventH !is null) SetEvent(customEventH);
3630 			return true;
3631 		} else version(OSXCocoa) {
3632 			if(globalAppDelegate)
3633 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3634 			return true;
3635 		} else {
3636 			// not implemented for other OSes
3637 			return false;
3638 		}
3639 	}
3640 
3641 	static struct QueuedEvent {
3642 		Object evt;
3643 		bool timed = false;
3644 		MonoTime hittime = MonoTime.zero;
3645 		bool doProcess = false; // process event at the current iteration (internal flag)
3646 
3647 		this (Object aevt, uint toutmsecs) {
3648 			evt = aevt;
3649 			if (toutmsecs > 0) {
3650 				import core.time : msecs;
3651 				timed = true;
3652 				hittime = MonoTime.currTime+toutmsecs.msecs;
3653 			}
3654 		}
3655 	}
3656 
3657 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3658 	static struct EventHandlerEntry {
3659 		CustomEventHandler dg;
3660 		uint id;
3661 	}
3662 
3663 	uint lastUsedHandlerId;
3664 	EventHandlerEntry[] eventHandlers;
3665 	QueuedEvent[] eventQueue = null;
3666 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3667 	bool inCustomEventProcessor = false; // required to properly remove events
3668 
3669 	// process queued events and call custom event handlers
3670 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3671 	void processCustomEvents () {
3672 		bool hasSomethingToDo = false;
3673 		uint ecount;
3674 		bool ocep;
3675 		synchronized(this) {
3676 			ocep = inCustomEventProcessor;
3677 			inCustomEventProcessor = true;
3678 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3679 			auto ctt = MonoTime.currTime;
3680 			bool hasEmpty = false;
3681 			// mark events to process (this is required for `eventQueued()`)
3682 			foreach (ref qe; eventQueue[0..ecount]) {
3683 				if (qe.evt is null) { hasEmpty = true; continue; }
3684 				if (qe.timed) {
3685 					qe.doProcess = (qe.hittime <= ctt);
3686 				} else {
3687 					qe.doProcess = true;
3688 				}
3689 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3690 			}
3691 			if (!hasSomethingToDo) {
3692 				// remove empty events
3693 				if (hasEmpty) {
3694 					uint eidx = 0, ec = eventQueueUsed;
3695 					auto eptr = eventQueue.ptr;
3696 					while (eidx < ec) {
3697 						if (eptr.evt is null) {
3698 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3699 							ec = --eventQueueUsed;
3700 							eventQueue.ptr[ec].evt = null; // make GC life easier
3701 						} else {
3702 							++eidx;
3703 							++eptr;
3704 						}
3705 					}
3706 				}
3707 				inCustomEventProcessor = ocep;
3708 				return;
3709 			}
3710 		}
3711 		// process marked events
3712 		uint efree = 0; // non-processed events will be put at this index
3713 		EventHandlerEntry[] eh;
3714 		Object evt;
3715 		foreach (immutable eidx; 0..ecount) {
3716 			synchronized(this) {
3717 				if (!eventQueue[eidx].doProcess) {
3718 					// skip this event
3719 					assert(efree <= eidx);
3720 					if (efree != eidx) {
3721 						// copy this event to queue start
3722 						eventQueue[efree] = eventQueue[eidx];
3723 						eventQueue[eidx].evt = null; // just in case
3724 					}
3725 					++efree;
3726 					continue;
3727 				}
3728 				evt = eventQueue[eidx].evt;
3729 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3730 				if (evt is null) continue; // just in case
3731 				// try all handlers; this can be slow, but meh...
3732 				eh = eventHandlers;
3733 			}
3734 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3735 			evt = null;
3736 			eh = null;
3737 		}
3738 		synchronized(this) {
3739 			// move all unprocessed events to queue top; efree holds first "free index"
3740 			foreach (immutable eidx; ecount..eventQueueUsed) {
3741 				assert(efree <= eidx);
3742 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3743 				++efree;
3744 			}
3745 			eventQueueUsed = efree;
3746 			// wake up event processor on next event loop iteration if we have more queued events
3747 			// also, remove empty events
3748 			bool awaken = false;
3749 			uint eidx = 0, ec = eventQueueUsed;
3750 			auto eptr = eventQueue.ptr;
3751 			while (eidx < ec) {
3752 				if (eptr.evt is null) {
3753 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3754 					ec = --eventQueueUsed;
3755 					eventQueue.ptr[ec].evt = null; // make GC life easier
3756 				} else {
3757 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3758 					++eidx;
3759 					++eptr;
3760 				}
3761 			}
3762 			inCustomEventProcessor = ocep;
3763 		}
3764 	}
3765 
3766 	// for all windows in nativeMapping
3767 	package static void processAllCustomEvents () {
3768 
3769 		cleanupQueue.process();
3770 
3771 		justCommunication.processCustomEvents();
3772 
3773 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3774 			if (sw is null || sw.closed) continue;
3775 			sw.processCustomEvents();
3776 		}
3777 
3778 		runPendingRunInGuiThreadDelegates();
3779 	}
3780 
3781 	// 0: infinite (i.e. no scheduled events in queue)
3782 	uint eventQueueTimeoutMSecs () {
3783 		synchronized(this) {
3784 			if (eventQueueUsed == 0) return 0;
3785 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3786 			uint res = int.max;
3787 			auto ctt = MonoTime.currTime;
3788 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3789 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3790 				if (qe.doProcess) continue; // just in case
3791 				if (!qe.timed) return 1; // minimal
3792 				if (qe.hittime <= ctt) return 1; // minimal
3793 				auto tms = (qe.hittime-ctt).total!"msecs";
3794 				if (tms < 1) tms = 1; // safety net
3795 				if (tms >= int.max) tms = int.max-1; // and another safety net
3796 				if (res > tms) res = cast(uint)tms;
3797 			}
3798 			return (res >= int.max ? 0 : res);
3799 		}
3800 	}
3801 
3802 	// for all windows in nativeMapping
3803 	static uint eventAllQueueTimeoutMSecs () {
3804 		uint res = uint.max;
3805 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3806 			if (sw is null || sw.closed) continue;
3807 			uint to = sw.eventQueueTimeoutMSecs();
3808 			if (to && to < res) {
3809 				res = to;
3810 				if (to == 1) break; // can't have less than this
3811 			}
3812 		}
3813 		return (res >= int.max ? 0 : res);
3814 	}
3815 
3816 	version(X11) {
3817 		ResizeEvent pendingResizeEvent;
3818 	}
3819 
3820 	/++
3821 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
3822 
3823 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
3824 		worth so you can disable it by setting this to `true`.
3825 
3826 		History:
3827 			Added November 13, 2022.
3828 	+/
3829 	public bool suppressAutoOpenglViewport = false;
3830 	private void updateOpenglViewportIfNeeded(int width, int height) {
3831 		if(suppressAutoOpenglViewport) return;
3832 
3833 		version(without_opengl) {} else
3834 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
3835 		// writeln(width, " ", height);
3836 			setAsCurrentOpenGlContextNT();
3837 			glViewport(0, 0, width, height);
3838 		}
3839 	}
3840 }
3841 
3842 version(OSXCocoa)
3843 	enum NSWindow NullWindow = null;
3844 else
3845 	enum NullWindow = NativeWindowHandle.init;
3846 
3847 /++
3848 	Magic pseudo-window for just posting events to a global queue.
3849 
3850 	Not entirely supported, I might delete it at any time.
3851 
3852 	Added Nov 5, 2021.
3853 +/
3854 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
3855 
3856 /* Drag and drop support { */
3857 version(X11) {
3858 
3859 } else version(Windows) {
3860 	import core.sys.windows.uuid;
3861 	import core.sys.windows.ole2;
3862 	import core.sys.windows.oleidl;
3863 	import core.sys.windows.objidl;
3864 	import core.sys.windows.wtypes;
3865 
3866 	pragma(lib, "ole32");
3867 	void initDnd() {
3868 		auto err = OleInitialize(null);
3869 		if(err != S_OK && err != S_FALSE)
3870 			throw new Exception("init");//err);
3871 	}
3872 }
3873 /* } End drag and drop support */
3874 
3875 
3876 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3877 /// See [GenericCursor].
3878 class MouseCursor {
3879 	int osId;
3880 	bool isStockCursor;
3881 	private this(int osId) {
3882 		this.osId = osId;
3883 		this.isStockCursor = true;
3884 	}
3885 
3886 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3887 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3888 
3889 	version(Windows) {
3890 		HCURSOR cursor_;
3891 		HCURSOR cursorHandle() {
3892 			if(cursor_ is null)
3893 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3894 			return cursor_;
3895 		}
3896 
3897 	} else static if(UsingSimpledisplayX11) {
3898 		Cursor cursor_ = None;
3899 		int xDisplaySequence;
3900 
3901 		Cursor cursorHandle() {
3902 			if(this.osId == None)
3903 				return None;
3904 
3905 			// we need to reload if we on a new X connection
3906 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3907 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3908 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3909 			}
3910 			return cursor_;
3911 		}
3912 	}
3913 }
3914 
3915 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3916 // https://tronche.com/gui/x/xlib/appendix/b/
3917 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3918 /// 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.
3919 enum GenericCursorType {
3920 	Default, /// The default arrow pointer.
3921 	Wait, /// A cursor indicating something is loading and the user must wait.
3922 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3923 	Help, /// A cursor indicating the user can get help about the pointer location.
3924 	Cross, /// A crosshair.
3925 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3926 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3927 	UpArrow, /// An arrow pointing straight up.
3928 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3929 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3930 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3931 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3932 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3933 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3934 
3935 }
3936 
3937 /*
3938 	X_plus == css cell == Windows ?
3939 */
3940 
3941 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3942 static struct GenericCursor {
3943 	static:
3944 	///
3945 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3946 		static MouseCursor mc;
3947 
3948 		auto type = __traits(getMember, GenericCursorType, str);
3949 
3950 		if(mc is null) {
3951 
3952 			version(Windows) {
3953 				int osId;
3954 				final switch(type) {
3955 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3956 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3957 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3958 					case GenericCursorType.Help: osId = IDC_HELP; break;
3959 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3960 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3961 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3962 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3963 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3964 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3965 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3966 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3967 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3968 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3969 				}
3970 			} else static if(UsingSimpledisplayX11) {
3971 				int osId;
3972 				final switch(type) {
3973 					case GenericCursorType.Default: osId = None; break;
3974 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3975 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3976 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3977 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3978 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3979 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3980 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3981 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3982 
3983 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3984 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3985 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3986 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3987 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3988 				}
3989 
3990 			} else {
3991 				int osId;
3992 				// featureNotImplemented();
3993 			}
3994 
3995 			mc = new MouseCursor(osId);
3996 		}
3997 		return mc;
3998 	}
3999 }
4000 
4001 
4002 /++
4003 	If you want to get more control over the event loop, you can use this.
4004 
4005 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4006 	to `EventLoop.get.run`.
4007 +/
4008 struct EventLoop {
4009 	@disable this();
4010 
4011 	/// Gets a reference to an existing event loop
4012 	static EventLoop get() {
4013 		return EventLoop(0, null);
4014 	}
4015 
4016 	static void quitApplication() {
4017 		EventLoop.get().exit();
4018 	}
4019 
4020 	private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
4021 
4022 	/// Construct an application-global event loop for yourself
4023 	/// See_Also: [SimpleWindow.setEventHandlers]
4024 	this(long pulseTimeout, void delegate() handlePulse) {
4025 		synchronized(monitor) {
4026 			if(impl is null) {
4027 				claimGuiThread();
4028 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4029 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4030 			} else {
4031 				if(pulseTimeout) {
4032 					impl.pulseTimeout = pulseTimeout;
4033 					impl.handlePulse = handlePulse;
4034 				}
4035 			}
4036 			impl.refcount++;
4037 		}
4038 	}
4039 
4040 	~this() {
4041 		if(impl is null)
4042 			return;
4043 		impl.refcount--;
4044 		if(impl.refcount == 0) {
4045 			impl.dispose();
4046 			if(thisIsGuiThread)
4047 				guiThreadFinalize();
4048 		}
4049 
4050 	}
4051 
4052 	this(this) {
4053 		if(impl is null)
4054 			return;
4055 		impl.refcount++;
4056 	}
4057 
4058 	/// Runs the event loop until the whileCondition, if present, returns false
4059 	int run(bool delegate() whileCondition = null) {
4060 		assert(impl !is null);
4061 		impl.notExited = true;
4062 		return impl.run(whileCondition);
4063 	}
4064 
4065 	/// Exits the event loop
4066 	void exit() {
4067 		assert(impl !is null);
4068 		impl.notExited = false;
4069 	}
4070 
4071 	version(linux)
4072 	ref void delegate(int) signalHandler() {
4073 		assert(impl !is null);
4074 		return impl.signalHandler;
4075 	}
4076 
4077 	__gshared static EventLoopImpl* impl;
4078 }
4079 
4080 version(linux)
4081 	void delegate(int, int) globalHupHandler;
4082 
4083 version(Posix)
4084 	void makeNonBlocking(int fd) {
4085 		import fcntl = core.sys.posix.fcntl;
4086 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4087 		if(flags == -1)
4088 			throw new Exception("fcntl get");
4089 		flags |= fcntl.O_NONBLOCK;
4090 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4091 		if(s == -1)
4092 			throw new Exception("fcntl set");
4093 	}
4094 
4095 struct EventLoopImpl {
4096 	int refcount;
4097 
4098 	bool notExited = true;
4099 
4100 	version(linux) {
4101 		static import ep = core.sys.linux.epoll;
4102 		static import unix = core.sys.posix.unistd;
4103 		static import err = core.stdc.errno;
4104 		import core.sys.linux.timerfd;
4105 
4106 		void delegate(int) signalHandler;
4107 	}
4108 
4109 	version(X11) {
4110 		int pulseFd = -1;
4111 		version(linux) ep.epoll_event[16] events = void;
4112 	} else version(Windows) {
4113 		Timer pulser;
4114 		HANDLE[] handles;
4115 	}
4116 
4117 
4118 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4119 	/// to call this, as it's not recommended to share window between threads.
4120 	void mtLock () {
4121 		version(X11) {
4122 			XLockDisplay(this.display);
4123 		}
4124 	}
4125 
4126 	version(X11)
4127 	auto display() { return XDisplayConnection.get; }
4128 
4129 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4130 	/// to call this, as it's not recommended to share window between threads.
4131 	void mtUnlock () {
4132 		version(X11) {
4133 			XUnlockDisplay(this.display);
4134 		}
4135 	}
4136 
4137 	version(with_eventloop)
4138 	void initialize(long pulseTimeout) {}
4139 	else
4140 	void initialize(long pulseTimeout) {
4141 		version(Windows) {
4142 			if(pulseTimeout && handlePulse !is null)
4143 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4144 
4145 			if (customEventH is null) {
4146 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4147 				if (customEventH !is null) {
4148 					handles ~= customEventH;
4149 				} else {
4150 					// this is something that should not be; better be safe than sorry
4151 					throw new Exception("can't create eventfd for custom event processing");
4152 				}
4153 			}
4154 
4155 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4156 		}
4157 
4158 		version(linux) {
4159 			prepareEventLoop();
4160 			{
4161 				auto display = XDisplayConnection.get;
4162 				// adding Xlib file
4163 				ep.epoll_event ev = void;
4164 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4165 				ev.events = ep.EPOLLIN;
4166 				ev.data.fd = display.fd;
4167 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4168 					throw new Exception("add x fd");// ~ to!string(epollFd));
4169 				displayFd = display.fd;
4170 			}
4171 
4172 			if(pulseTimeout && handlePulse !is null) {
4173 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4174 				if(pulseFd == -1)
4175 					throw new Exception("pulse timer create failed");
4176 
4177 				itimerspec value;
4178 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4179 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4180 
4181 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4182 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4183 
4184 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4185 					throw new Exception("couldn't make pulse timer");
4186 
4187 				ep.epoll_event ev = void;
4188 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4189 				ev.events = ep.EPOLLIN;
4190 				ev.data.fd = pulseFd;
4191 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4192 			}
4193 
4194 			// eventfd for custom events
4195 			if (customEventFDWrite == -1) {
4196 				customEventFDWrite = eventfd(0, 0);
4197 				customEventFDRead = customEventFDWrite;
4198 				if (customEventFDRead >= 0) {
4199 					ep.epoll_event ev = void;
4200 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4201 					ev.events = ep.EPOLLIN;
4202 					ev.data.fd = customEventFDRead;
4203 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4204 				} else {
4205 					// this is something that should not be; better be safe than sorry
4206 					throw new Exception("can't create eventfd for custom event processing");
4207 				}
4208 			}
4209 
4210 			if (customSignalFD == -1) {
4211 				import core.sys.linux.sys.signalfd;
4212 
4213 				sigset_t sigset;
4214 				auto err = sigemptyset(&sigset);
4215 				assert(!err);
4216 				err = sigaddset(&sigset, SIGINT);
4217 				assert(!err);
4218 				err = sigaddset(&sigset, SIGHUP);
4219 				assert(!err);
4220 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4221 				assert(!err);
4222 
4223 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4224 				assert(customSignalFD != -1);
4225 
4226 				ep.epoll_event ev = void;
4227 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4228 				ev.events = ep.EPOLLIN;
4229 				ev.data.fd = customSignalFD;
4230 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4231 			}
4232 		} else version(Posix) {
4233 			prepareEventLoop();
4234 			if (customEventFDRead == -1) {
4235 				int[2] bfr;
4236 				import core.sys.posix.unistd;
4237 				auto ret = pipe(bfr);
4238 				if(ret == -1) throw new Exception("pipe");
4239 				customEventFDRead = bfr[0];
4240 				customEventFDWrite = bfr[1];
4241 			}
4242 
4243 		}
4244 
4245 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4246 
4247 		version(linux) {
4248 			this.mtLock();
4249 			scope(exit) this.mtUnlock();
4250 			XPending(display); // no, really
4251 		}
4252 
4253 		disposed = false;
4254 	}
4255 
4256 	bool disposed = true;
4257 	version(X11)
4258 		int displayFd = -1;
4259 
4260 	version(with_eventloop)
4261 	void dispose() {}
4262 	else
4263 	void dispose() {
4264 		disposed = true;
4265 		version(X11) {
4266 			if(pulseFd != -1) {
4267 				import unix = core.sys.posix.unistd;
4268 				unix.close(pulseFd);
4269 				pulseFd = -1;
4270 			}
4271 
4272 				version(linux)
4273 				if(displayFd != -1) {
4274 					// 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
4275 					ep.epoll_event ev = void;
4276 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4277 					ev.events = ep.EPOLLIN;
4278 					ev.data.fd = displayFd;
4279 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4280 					displayFd = -1;
4281 				}
4282 
4283 		} else version(Windows) {
4284 			if(pulser !is null) {
4285 				pulser.destroy();
4286 				pulser = null;
4287 			}
4288 			if (customEventH !is null) {
4289 				CloseHandle(customEventH);
4290 				customEventH = null;
4291 			}
4292 		}
4293 	}
4294 
4295 	this(long pulseTimeout, void delegate() handlePulse) {
4296 		this.pulseTimeout = pulseTimeout;
4297 		this.handlePulse = handlePulse;
4298 		initialize(pulseTimeout);
4299 	}
4300 
4301 	private long pulseTimeout;
4302 	void delegate() handlePulse;
4303 
4304 	~this() {
4305 		dispose();
4306 	}
4307 
4308 	version(Posix)
4309 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4310 	version(Posix)
4311 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4312 	version(linux)
4313 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4314 	version(Windows)
4315 	ref auto customEventH() { return SimpleWindow.customEventH; }
4316 
4317 	version(with_eventloop) {
4318 		int loopHelper(bool delegate() whileCondition) {
4319 			// FIXME: whileCondition
4320 			import arsd.eventloop;
4321 			loop();
4322 			return 0;
4323 		}
4324 	} else
4325 	int loopHelper(bool delegate() whileCondition) {
4326 		version(X11) {
4327 			bool done = false;
4328 
4329 			XFlush(display);
4330 			insideXEventLoop = true;
4331 			scope(exit) insideXEventLoop = false;
4332 
4333 			version(linux) {
4334 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
4335 					bool forceXPending = false;
4336 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4337 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
4338 					{
4339 						this.mtLock();
4340 						scope(exit) this.mtUnlock();
4341 						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
4342 					}
4343 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
4344 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
4345 					if(nfds == -1) {
4346 						if(err.errno == err.EINTR) {
4347 							//if(forceXPending) goto xpending;
4348 							continue; // interrupted by signal, just try again
4349 						}
4350 						throw new Exception("epoll wait failure");
4351 					}
4352 					// writeln(nfds, " ", events[0].data.fd);
4353 
4354 					SimpleWindow.processAllCustomEvents(); // anyway
4355 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
4356 					foreach(idx; 0 .. nfds) {
4357 						if(done) break;
4358 						auto fd = events[idx].data.fd;
4359 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
4360 						auto flags = events[idx].events;
4361 						if(flags & ep.EPOLLIN) {
4362 							if (fd == customSignalFD) {
4363 								version(linux) {
4364 									import core.sys.linux.sys.signalfd;
4365 									import core.sys.posix.unistd : read;
4366 									signalfd_siginfo info;
4367 									read(customSignalFD, &info, info.sizeof);
4368 
4369 									auto sig = info.ssi_signo;
4370 
4371 									if(EventLoop.get.signalHandler !is null) {
4372 										EventLoop.get.signalHandler()(sig);
4373 									} else {
4374 										EventLoop.get.exit();
4375 									}
4376 								}
4377 							} else if(fd == display.fd) {
4378 								version(sdddd) { writeln("X EVENT PENDING!"); }
4379 								this.mtLock();
4380 								scope(exit) this.mtUnlock();
4381 								while(!done && XPending(display)) {
4382 									done = doXNextEvent(this.display);
4383 								}
4384 								forceXPending = false;
4385 							} else if(fd == pulseFd) {
4386 								long expirationCount;
4387 								// 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...
4388 
4389 								handlePulse();
4390 
4391 								// read just to clear the buffer so poll doesn't trigger again
4392 								// BTW I read AFTER the pulse because if the pulse handler takes
4393 								// a lot of time to execute, we don't want the app to get stuck
4394 								// in a loop of timer hits without a chance to do anything else
4395 								//
4396 								// IOW handlePulse happens at most once per pulse interval.
4397 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
4398 								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
4399 							} else if (fd == customEventFDRead) {
4400 								// we have some custom events; process 'em
4401 								import core.sys.posix.unistd : read;
4402 								ulong n;
4403 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
4404 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
4405 								//SimpleWindow.processAllCustomEvents();
4406 
4407 								forceXPending = true;
4408 							} else {
4409 								// some other timer
4410 								version(sdddd) { writeln("unknown fd: ", fd); }
4411 
4412 								if(Timer* t = fd in Timer.mapping)
4413 									(*t).trigger();
4414 
4415 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4416 									(*pfr).ready(flags);
4417 
4418 								// we don't know what the user did in this timer, so we need to assume that
4419 								// there's X data to be flushed and potentially processed
4420 								forceXPending = true;
4421 
4422 								// or i might add support for other FDs too
4423 								// but for now it is just timer
4424 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
4425 							}
4426 						}
4427 						if(flags & ep.EPOLLHUP) {
4428 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
4429 								(*pfr).hup(flags);
4430 							if(globalHupHandler)
4431 								globalHupHandler(fd, flags);
4432 						}
4433 						/+
4434 						} else {
4435 							// not interested in OUT, we are just reading here.
4436 							//
4437 							// error or hup might also be reported
4438 							// but it shouldn't here since we are only
4439 							// using a few types of FD and Xlib will report
4440 							// if it dies.
4441 							// so instead of thoughtfully handling it, I'll
4442 							// just throw. for now at least
4443 
4444 							throw new Exception("epoll did something else");
4445 						}
4446 						+/
4447 					}
4448 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
4449 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
4450 					xpending:
4451 					if (!done && forceXPending) {
4452 						this.mtLock();
4453 						scope(exit) this.mtUnlock();
4454 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4455 						while(!done && XPending(display)) {
4456 							done = doXNextEvent(this.display);
4457 						}
4458 					}
4459 				}
4460 			} else {
4461 				// Generic fallback: yes to simple pulse support,
4462 				// but NO timer support!
4463 
4464 				// FIXME: we could probably support the POSIX timer_create
4465 				// signal-based option, but I'm in no rush to write it since
4466 				// I prefer the fd-based functions.
4467 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
4468 
4469 					import core.sys.posix.poll;
4470 
4471 					pollfd[] pfds;
4472 					pollfd[32] pfdsBuffer;
4473 					auto len = PosixFdReader.mapping.length + 2;
4474 					// FIXME: i should just reuse the buffer
4475 					if(len < pfdsBuffer.length)
4476 						pfds = pfdsBuffer[0 .. len];
4477 					else
4478 						pfds = new pollfd[](len);
4479 
4480 					pfds[0].fd = display.fd;
4481 					pfds[0].events = POLLIN;
4482 					pfds[0].revents = 0;
4483 
4484 					int slot = 1;
4485 
4486 					if(customEventFDRead != -1) {
4487 						pfds[slot].fd = customEventFDRead;
4488 						pfds[slot].events = POLLIN;
4489 						pfds[slot].revents = 0;
4490 
4491 						slot++;
4492 					}
4493 
4494 					foreach(fd, obj; PosixFdReader.mapping) {
4495 						if(!obj.enabled) continue;
4496 						pfds[slot].fd = fd;
4497 						pfds[slot].events = POLLIN;
4498 						pfds[slot].revents = 0;
4499 
4500 						slot++;
4501 					}
4502 
4503 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
4504 					if(ret == -1) throw new Exception("poll");
4505 
4506 					if(ret == 0) {
4507 						// FIXME it may not necessarily time out if events keep coming
4508 						if(handlePulse !is null)
4509 							handlePulse();
4510 					} else {
4511 						foreach(s; 0 .. slot) {
4512 							if(pfds[s].revents == 0) continue;
4513 
4514 							if(pfds[s].fd == display.fd) {
4515 								while(!done && XPending(display)) {
4516 									this.mtLock();
4517 									scope(exit) this.mtUnlock();
4518 									done = doXNextEvent(this.display);
4519 								}
4520 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4521 
4522 								import core.sys.posix.unistd : read;
4523 								ulong n;
4524 								read(customEventFDRead, &n, n.sizeof);
4525 								SimpleWindow.processAllCustomEvents();
4526 							} else {
4527 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4528 								if(pfds[s].revents & POLLNVAL) {
4529 									obj.dispose();
4530 								} else {
4531 									obj.ready(pfds[s].revents);
4532 								}
4533 							}
4534 
4535 							ret--;
4536 							if(ret == 0) break;
4537 						}
4538 					}
4539 				}
4540 			}
4541 		}
4542 
4543 		version(Windows) {
4544 			int ret = -1;
4545 			MSG message;
4546 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4547 				eventLoopRound++;
4548 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4549 				auto waitResult = MsgWaitForMultipleObjectsEx(
4550 					cast(int) handles.length, handles.ptr,
4551 					(wto == 0 ? INFINITE : wto), /* timeout */
4552 					0x04FF, /* QS_ALLINPUT */
4553 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4554 
4555 				SimpleWindow.processAllCustomEvents(); // anyway
4556 				enum WAIT_OBJECT_0 = 0;
4557 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4558 					auto h = handles[waitResult - WAIT_OBJECT_0];
4559 					if(auto e = h in WindowsHandleReader.mapping) {
4560 						(*e).ready();
4561 					}
4562 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4563 					// message ready
4564 					int count;
4565 					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
4566 						ret = GetMessage(&message, null, 0, 0);
4567 						if(ret == -1)
4568 							throw new WindowsApiException("GetMessage", GetLastError());
4569 						TranslateMessage(&message);
4570 						DispatchMessage(&message);
4571 
4572 						count++;
4573 						if(count > 10)
4574 							break; // take the opportunity to catch up on other events
4575 
4576 						if(ret == 0) { // WM_QUIT
4577 							EventLoop.quitApplication();
4578 							break;
4579 						}
4580 					}
4581 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4582 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4583 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4584 					// timeout, should never happen since we aren't using it
4585 				} else if(waitResult == 0xFFFFFFFF) {
4586 						// failed
4587 						throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
4588 				} else {
4589 					// idk....
4590 				}
4591 			}
4592 
4593 			// return message.wParam;
4594 			return 0;
4595 		} else {
4596 			return 0;
4597 		}
4598 	}
4599 
4600 	int run(bool delegate() whileCondition = null) {
4601 		if(disposed)
4602 			initialize(this.pulseTimeout);
4603 
4604 		version(X11) {
4605 			try {
4606 				return loopHelper(whileCondition);
4607 			} catch(XDisconnectException e) {
4608 				if(e.userRequested) {
4609 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4610 						item.discardConnectionState();
4611 					XCloseDisplay(XDisplayConnection.display);
4612 				}
4613 
4614 				XDisplayConnection.display = null;
4615 
4616 				this.dispose();
4617 
4618 				throw e;
4619 			}
4620 		} else {
4621 			return loopHelper(whileCondition);
4622 		}
4623 	}
4624 }
4625 
4626 
4627 /++
4628 	Provides an icon on the system notification area (also known as the system tray).
4629 
4630 
4631 	If a notification area is not available with the NotificationIcon object is created,
4632 	it will silently succeed and simply attempt to create one when an area becomes available.
4633 
4634 
4635 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
4636 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
4637 	with true color was added at that time. I was just too lazy to write the fallback.
4638 
4639 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
4640 	you use arsd 10.x when targeting Windows XP.
4641 +/
4642 version(OSXCocoa) {} else // NotYetImplementedException
4643 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4644 
4645 	version(X11) {
4646 		void recreateAfterDisconnect() {
4647 			stateDiscarded = false;
4648 			clippixmap = None;
4649 			throw new Exception("NOT IMPLEMENTED");
4650 		}
4651 
4652 		bool stateDiscarded;
4653 		void discardConnectionState() {
4654 			stateDiscarded = true;
4655 		}
4656 	}
4657 
4658 
4659 	version(X11) {
4660 		Image img;
4661 
4662 		NativeEventHandler getNativeEventHandler() {
4663 			return delegate int(XEvent e) {
4664 				switch(e.type) {
4665 					case EventType.Expose:
4666 					//case EventType.VisibilityNotify:
4667 						redraw();
4668 					break;
4669 					case EventType.ClientMessage:
4670 						version(sddddd) {
4671 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4672 						writeln("\t", e.xclient.format);
4673 						writeln("\t", e.xclient.data.l);
4674 						}
4675 					break;
4676 					case EventType.ButtonPress:
4677 						auto event = e.xbutton;
4678 						if (onClick !is null || onClickEx !is null) {
4679 							MouseButton mb = cast(MouseButton)0;
4680 							switch (event.button) {
4681 								case 1: mb = MouseButton.left; break; // left
4682 								case 2: mb = MouseButton.middle; break; // middle
4683 								case 3: mb = MouseButton.right; break; // right
4684 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4685 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4686 								case 6: break; // scroll left...
4687 								case 7: break; // scroll right...
4688 								case 8: mb = MouseButton.backButton; break;
4689 								case 9: mb = MouseButton.forwardButton; break;
4690 								default:
4691 							}
4692 							if (mb) {
4693 								try { onClick()(mb); } catch (Exception) {}
4694 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4695 							}
4696 						}
4697 					break;
4698 					case EventType.EnterNotify:
4699 						if (onEnter !is null) {
4700 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4701 						}
4702 						break;
4703 					case EventType.LeaveNotify:
4704 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4705 						break;
4706 					case EventType.DestroyNotify:
4707 						active = false;
4708 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4709 					break;
4710 					case EventType.ConfigureNotify:
4711 						auto event = e.xconfigure;
4712 						this.width = event.width;
4713 						this.height = event.height;
4714 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4715 						redraw();
4716 					break;
4717 					default: return 1;
4718 				}
4719 				return 1;
4720 			};
4721 		}
4722 
4723 		/* private */ void hideBalloon() {
4724 			balloon.close();
4725 			version(with_timer)
4726 				timer.destroy();
4727 			balloon = null;
4728 			version(with_timer)
4729 				timer = null;
4730 		}
4731 
4732 		void redraw() {
4733 			if (!active) return;
4734 
4735 			auto display = XDisplayConnection.get;
4736 			GC gc;
4737 
4738 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
4739 
4740 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
4741 				Visual *visual;
4742 				XVisualInfo vis_info;
4743 				XSetWindowAttributes win_attr;
4744 				c_ulong win_mask;
4745 
4746 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
4747 					assert(0);
4748 					// return 1;
4749 				}
4750 
4751 				visual = vis_info.visual;
4752 
4753 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
4754 				win_attr.background_pixel = 0;
4755 				win_attr.border_pixel = 0;
4756 
4757 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
4758 
4759 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
4760 
4761 				return 0;
4762 			}
4763 
4764 			if(useAlpha)
4765 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
4766 			else
4767 				gc = DefaultGC(display, DefaultScreen(display));
4768 
4769 			XClearWindow(display, nativeHandle);
4770 
4771 			if(!useAlpha && img !is null)
4772 				XSetClipMask(display, gc, clippixmap);
4773 
4774 			/+
4775 			XSetForeground(display, gc,
4776 				cast(uint) 0 << 16 |
4777 				cast(uint) 0 << 8 |
4778 				cast(uint) 0);
4779 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4780 			+/
4781 
4782 			if (img is null) {
4783 				XSetForeground(display, gc,
4784 					cast(uint) 0 << 16 |
4785 					cast(uint) 127 << 8 |
4786 					cast(uint) 0);
4787 				XFillArc(display, nativeHandle,
4788 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4789 			} else {
4790 				int dx = 0;
4791 				int dy = 0;
4792 				if(width > img.width)
4793 					dx = (width - img.width) / 2;
4794 				if(height > img.height)
4795 					dy = (height - img.height) / 2;
4796 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
4797 				XSetClipOrigin(display, gc, dx, dy);
4798 
4799 				int max(int a, int b) {
4800 					if(a > b) return a; else return b;
4801 				}
4802 
4803 				if (img.usingXshm)
4804 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
4805 				else
4806 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
4807 			}
4808 			XSetClipMask(display, gc, None);
4809 			flushGui();
4810 		}
4811 
4812 		static Window getTrayOwner() {
4813 			auto display = XDisplayConnection.get;
4814 			auto i = cast(int) DefaultScreen(display);
4815 			if(i < 10 && i >= 0) {
4816 				static Atom atom;
4817 				if(atom == None)
4818 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4819 				return XGetSelectionOwner(display, atom);
4820 			}
4821 			return None;
4822 		}
4823 
4824 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4825 			auto to = getTrayOwner();
4826 			auto display = XDisplayConnection.get;
4827 			XEvent ev;
4828 			ev.xclient.type = EventType.ClientMessage;
4829 			ev.xclient.window = to;
4830 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4831 			ev.xclient.format = 32;
4832 			ev.xclient.data.l[0] = CurrentTime;
4833 			ev.xclient.data.l[1] = message;
4834 			ev.xclient.data.l[2] = d1;
4835 			ev.xclient.data.l[3] = d2;
4836 			ev.xclient.data.l[4] = d3;
4837 
4838 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4839 		}
4840 
4841 		private static NotificationAreaIcon[] activeIcons;
4842 
4843 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4844 		private void newManager() {
4845 			close();
4846 			createXWin();
4847 
4848 			if(this.clippixmap)
4849 				XFreePixmap(XDisplayConnection.get, clippixmap);
4850 			if(this.originalMemoryImage)
4851 				this.icon = this.originalMemoryImage;
4852 			else if(this.img)
4853 				this.icon = this.img;
4854 		}
4855 
4856 		private bool useAlpha = false;
4857 
4858 		private void createXWin () {
4859 			// create window
4860 			auto display = XDisplayConnection.get;
4861 
4862 			// to check for MANAGER on root window to catch new/changed tray owners
4863 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4864 			// so if a thing does appear, we can handle it
4865 			foreach(ai; activeIcons)
4866 				if(ai is this)
4867 					goto alreadythere;
4868 			activeIcons ~= this;
4869 			alreadythere:
4870 
4871 			// and check for an existing tray
4872 			auto trayOwner = getTrayOwner();
4873 			if(trayOwner == None)
4874 				return;
4875 				//throw new Exception("No notification area found");
4876 
4877 			Visual* v = cast(Visual*) CopyFromParent;
4878 
4879 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
4880 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
4881 			// a resize event later.
4882 			width = 22;
4883 			height = 22;
4884 
4885 			// if they system gave us a 32 bit visual we need to switch to it too
4886 			int depth = 24;
4887 
4888 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4889 			if(visualProp !is null) {
4890 				c_ulong[] info = cast(c_ulong[]) visualProp;
4891 				if(info.length == 1) {
4892 					auto vid = info[0];
4893 					int returned;
4894 					XVisualInfo t;
4895 					t.visualid = vid;
4896 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4897 					if(got !is null) {
4898 						if(returned == 1) {
4899 							v = got.visual;
4900 							depth = got.depth;
4901 							// writeln("using special visual ", got.depth);
4902 							// writeln(depth);
4903 						}
4904 						XFree(got);
4905 					}
4906 				}
4907 			}
4908 
4909 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
4910 			XSetWindowAttributes attr;
4911 			attr.background_pixel = 0;
4912 			attr.border_pixel = 0;
4913 			attr.override_redirect = 0;
4914 			if(v !is cast(Visual*) CopyFromParent) {
4915 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
4916 				CWFlags |= CWColormap;
4917 				if(depth == 32)
4918 					useAlpha = true;
4919 				else
4920 					goto plain;
4921 			} else {
4922 				plain:
4923 				attr.background_pixmap = 1 /* ParentRelative */;
4924 				CWFlags |= CWBackPixmap;
4925 			}
4926 
4927 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
4928 
4929 			assert(nativeWindow);
4930 
4931 			if(!useAlpha)
4932 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4933 
4934 			nativeHandle = nativeWindow;
4935 
4936 			///+
4937 			arch_ulong[2] info;
4938 			info[0] = 0;
4939 			info[1] = 1;
4940 
4941 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4942 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4943 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4944 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4945 
4946 			XChangeProperty(
4947 				display,
4948 				nativeWindow,
4949 				GetAtom!("_XEMBED_INFO", true)(display),
4950 				GetAtom!("_XEMBED_INFO", true)(display),
4951 				32 /* bits */,
4952 				0 /*PropModeReplace*/,
4953 				info.ptr,
4954 				2);
4955 
4956 			import core.sys.posix.unistd;
4957 			arch_ulong pid = getpid();
4958 
4959 			XChangeProperty(
4960 				display,
4961 				nativeWindow,
4962 				GetAtom!("_NET_WM_PID", true)(display),
4963 				XA_CARDINAL,
4964 				32 /* bits */,
4965 				0 /*PropModeReplace*/,
4966 				&pid,
4967 				1);
4968 
4969 			updateNetWmIcon();
4970 
4971 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4972 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4973 				XClassHint klass;
4974 				XWMHints wh;
4975 				XSizeHints size;
4976 				klass.res_name = sdpyWindowClassStr;
4977 				klass.res_class = sdpyWindowClassStr;
4978 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4979 			}
4980 
4981 				// believe it or not, THIS is what xfce needed for the 9999 issue
4982 				XSizeHints sh;
4983 				c_long spr;
4984 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4985 				sh.flags |= PMaxSize | PMinSize;
4986 				// FIXME maybe nicer resizing
4987 				sh.min_width = 16;
4988 				sh.min_height = 16;
4989 				sh.max_width = 22;
4990 				sh.max_height = 22;
4991 				XSetWMNormalHints(display, nativeWindow, &sh);
4992 
4993 
4994 			//+/
4995 
4996 
4997 			XSelectInput(display, nativeWindow,
4998 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4999 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
5000 
5001 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5002 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5003 
5004 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5005 			active = true;
5006 		}
5007 
5008 		void updateNetWmIcon() {
5009 			if(img is null) return;
5010 			auto display = XDisplayConnection.get;
5011 			// FIXME: ensure this is correct
5012 			arch_ulong[] buffer;
5013 			auto imgMi = img.toTrueColorImage;
5014 			buffer ~= imgMi.width;
5015 			buffer ~= imgMi.height;
5016 			foreach(c; imgMi.imageData.colors) {
5017 				arch_ulong b;
5018 				b |= c.a << 24;
5019 				b |= c.r << 16;
5020 				b |= c.g << 8;
5021 				b |= c.b;
5022 				buffer ~= b;
5023 			}
5024 
5025 			XChangeProperty(
5026 				display,
5027 				nativeHandle,
5028 				GetAtom!"_NET_WM_ICON"(display),
5029 				GetAtom!"CARDINAL"(display),
5030 				32 /* bits */,
5031 				0 /*PropModeReplace*/,
5032 				buffer.ptr,
5033 				cast(int) buffer.length);
5034 		}
5035 
5036 
5037 
5038 		private SimpleWindow balloon;
5039 		version(with_timer)
5040 		private Timer timer;
5041 
5042 		private Window nativeHandle;
5043 		private Pixmap clippixmap = None;
5044 		private int width = 16;
5045 		private int height = 16;
5046 		private bool active = false;
5047 
5048 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5049 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5050 		void delegate () onLeave; /// X11 only.
5051 
5052 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5053 
5054 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5055 		void getWindowRect (out int x, out int y, out int width, out int height) {
5056 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5057 			Window dummyw;
5058 			auto dpy = XDisplayConnection.get;
5059 			//XWindowAttributes xwa;
5060 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5061 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5062 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5063 			width = this.width;
5064 			height = this.height;
5065 		}
5066 	}
5067 
5068 	/+
5069 		What I actually want from this:
5070 
5071 		* set / change: icon, tooltip
5072 		* handle: mouse click, right click
5073 		* show: notification bubble.
5074 	+/
5075 
5076 	version(Windows) {
5077 		WindowsIcon win32Icon;
5078 		HWND hwnd;
5079 
5080 		NOTIFYICONDATAW data;
5081 
5082 		NativeEventHandler getNativeEventHandler() {
5083 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5084 				if(msg == WM_USER) {
5085 					auto event = LOWORD(lParam);
5086 					auto iconId = HIWORD(lParam);
5087 					//auto x = GET_X_LPARAM(wParam);
5088 					//auto y = GET_Y_LPARAM(wParam);
5089 					switch(event) {
5090 						case WM_LBUTTONDOWN:
5091 							onClick()(MouseButton.left);
5092 						break;
5093 						case WM_RBUTTONDOWN:
5094 							onClick()(MouseButton.right);
5095 						break;
5096 						case WM_MBUTTONDOWN:
5097 							onClick()(MouseButton.middle);
5098 						break;
5099 						case WM_MOUSEMOVE:
5100 							// sent, we could use it.
5101 						break;
5102 						case WM_MOUSEWHEEL:
5103 							// NOT SENT
5104 						break;
5105 						//case NIN_KEYSELECT:
5106 						//case NIN_SELECT:
5107 						//break;
5108 						default: {}
5109 					}
5110 				}
5111 				return 0;
5112 			};
5113 		}
5114 
5115 		enum NIF_SHOWTIP = 0x00000080;
5116 
5117 		private static struct NOTIFYICONDATAW {
5118 			DWORD cbSize;
5119 			HWND  hWnd;
5120 			UINT  uID;
5121 			UINT  uFlags;
5122 			UINT  uCallbackMessage;
5123 			HICON hIcon;
5124 			WCHAR[128] szTip;
5125 			DWORD dwState;
5126 			DWORD dwStateMask;
5127 			WCHAR[256] szInfo;
5128 			union {
5129 				UINT uTimeout;
5130 				UINT uVersion;
5131 			}
5132 			WCHAR[64] szInfoTitle;
5133 			DWORD dwInfoFlags;
5134 			GUID  guidItem;
5135 			HICON hBalloonIcon;
5136 		}
5137 
5138 	}
5139 
5140 	/++
5141 		Note that on Windows, only left, right, and middle buttons are sent.
5142 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5143 		program is meant to be used on Windows too.
5144 	+/
5145 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5146 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5147 		// but on X, we need an Image, so its canonical ctor is there. They should
5148 		// forward to each other though.
5149 		version(X11) {
5150 			this.name = name;
5151 			this.onClick = onClick;
5152 			createXWin();
5153 			this.icon = icon;
5154 		} else version(Windows) {
5155 			this.onClick = onClick;
5156 			this.win32Icon = new WindowsIcon(icon);
5157 
5158 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5159 
5160 			static bool registered = false;
5161 			if(!registered) {
5162 				WNDCLASSEX wc;
5163 				wc.cbSize = wc.sizeof;
5164 				wc.hInstance = hInstance;
5165 				wc.lpfnWndProc = &WndProc;
5166 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5167 				if(!RegisterClassExW(&wc))
5168 					throw new WindowsApiException("RegisterClass", GetLastError());
5169 				registered = true;
5170 			}
5171 
5172 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5173 			if(hwnd is null)
5174 				throw new WindowsApiException("CreateWindow", GetLastError());
5175 
5176 			data.cbSize = data.sizeof;
5177 			data.hWnd = hwnd;
5178 			data.uID = cast(uint) cast(void*) this;
5179 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5180 				// NIF_INFO means show balloon
5181 			data.uCallbackMessage = WM_USER;
5182 			data.hIcon = this.win32Icon.hIcon;
5183 			data.szTip = ""; // FIXME
5184 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5185 			data.dwStateMask = NIS_HIDDEN; // windows vista
5186 
5187 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5188 
5189 
5190 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5191 
5192 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5193 		} else version(OSXCocoa) {
5194 			throw new NotYetImplementedException();
5195 		} else static assert(0);
5196 	}
5197 
5198 	/// ditto
5199 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5200 		version(X11) {
5201 			this.onClick = onClick;
5202 			this.name = name;
5203 			createXWin();
5204 			this.icon = icon;
5205 		} else version(Windows) {
5206 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5207 		} else version(OSXCocoa) {
5208 			throw new NotYetImplementedException();
5209 		} else static assert(0);
5210 	}
5211 
5212 	version(X11) {
5213 		/++
5214 			X-specific extension (for now at least)
5215 		+/
5216 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5217 			this.onClickEx = onClickEx;
5218 			createXWin();
5219 			if (icon !is null) this.icon = icon;
5220 		}
5221 
5222 		/// ditto
5223 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5224 			this.onClickEx = onClickEx;
5225 			createXWin();
5226 			this.icon = icon;
5227 		}
5228 	}
5229 
5230 	private void delegate (MouseButton button) onClick_;
5231 
5232 	///
5233 	@property final void delegate(MouseButton) onClick() {
5234 		if(onClick_ is null)
5235 			onClick_ = delegate void(MouseButton) {};
5236 		return onClick_;
5237 	}
5238 
5239 	/// ditto
5240 	@property final void onClick(void delegate(MouseButton) handler) {
5241 		// I made this a property setter so we can wrap smaller arg
5242 		// delegates and just forward all to onClickEx or something.
5243 		onClick_ = handler;
5244 	}
5245 
5246 
5247 	string name_;
5248 	@property void name(string n) {
5249 		name_ = n;
5250 	}
5251 
5252 	@property string name() {
5253 		return name_;
5254 	}
5255 
5256 	private MemoryImage originalMemoryImage;
5257 
5258 	///
5259 	@property void icon(MemoryImage i) {
5260 		version(X11) {
5261 			this.originalMemoryImage = i;
5262 			if (!active) return;
5263 			if (i !is null) {
5264 				this.img = Image.fromMemoryImage(i, useAlpha, false);
5265 				if(!useAlpha)
5266 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
5267 				// writeln("using pixmap ", clippixmap);
5268 				updateNetWmIcon();
5269 				redraw();
5270 			} else {
5271 				if (this.img !is null) {
5272 					this.img = null;
5273 					redraw();
5274 				}
5275 			}
5276 		} else version(Windows) {
5277 			this.win32Icon = new WindowsIcon(i);
5278 
5279 			data.uFlags = NIF_ICON;
5280 			data.hIcon = this.win32Icon.hIcon;
5281 
5282 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5283 		} else version(OSXCocoa) {
5284 			throw new NotYetImplementedException();
5285 		} else static assert(0);
5286 	}
5287 
5288 	/// ditto
5289 	@property void icon (Image i) {
5290 		version(X11) {
5291 			if (!active) return;
5292 			if (i !is img) {
5293 				originalMemoryImage = null;
5294 				img = i;
5295 				redraw();
5296 			}
5297 		} else version(Windows) {
5298 			this.icon(i is null ? null : i.toTrueColorImage());
5299 		} else version(OSXCocoa) {
5300 			throw new NotYetImplementedException();
5301 		} else static assert(0);
5302 	}
5303 
5304 	/++
5305 		Shows a balloon notification. You can only show one balloon at a time, if you call
5306 		it twice while one is already up, the first balloon will be replaced.
5307 
5308 
5309 		The user is free to block notifications and they will automatically disappear after
5310 		a timeout period.
5311 
5312 		Params:
5313 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
5314 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
5315 			icon = the icon to display with the notification. If null, it uses your existing icon.
5316 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
5317 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
5318 	+/
5319 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
5320 		bool useCustom = true;
5321 		version(libnotify) {
5322 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
5323 			try {
5324 				if(!active) return;
5325 
5326 				if(libnotify is null) {
5327 					libnotify = new C_DynamicLibrary("libnotify.so");
5328 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
5329 				}
5330 
5331 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
5332 
5333 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
5334 
5335 				if(onclick) {
5336 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
5337 					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);
5338 					libnotify_action_delegates_count++;
5339 				}
5340 
5341 				// FIXME icon
5342 
5343 				// set hint image-data
5344 				// set default action for onclick
5345 
5346 				void* error;
5347 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
5348 
5349 				useCustom = false;
5350 			} catch(Exception e) {
5351 
5352 			}
5353 		}
5354 
5355 		version(X11) {
5356 		if(useCustom) {
5357 			if(!active) return;
5358 			if(balloon) {
5359 				hideBalloon();
5360 			}
5361 			// I know there are two specs for this, but one is never
5362 			// implemented by any window manager I have ever seen, and
5363 			// the other is a bloated mess and too complicated for simpledisplay...
5364 			// so doing my own little window instead.
5365 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
5366 
5367 			int x, y, width, height;
5368 			getWindowRect(x, y, width, height);
5369 
5370 			int bx = x - balloon.width;
5371 			int by = y - balloon.height;
5372 			if(bx < 0)
5373 				bx = x + width + balloon.width;
5374 			if(by < 0)
5375 				by = y + height;
5376 
5377 			// just in case, make sure it is actually on scren
5378 			if(bx < 0)
5379 				bx = 0;
5380 			if(by < 0)
5381 				by = 0;
5382 
5383 			balloon.move(bx, by);
5384 			auto painter = balloon.draw();
5385 			painter.fillColor = Color(220, 220, 220);
5386 			painter.outlineColor = Color.black;
5387 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
5388 			auto iconWidth = icon is null ? 0 : icon.width;
5389 			if(icon)
5390 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
5391 			iconWidth += 6; // margin around the icon
5392 
5393 			// draw a close button
5394 			painter.outlineColor = Color(44, 44, 44);
5395 			painter.fillColor = Color(255, 255, 255);
5396 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
5397 			painter.pen = Pen(Color.black, 3);
5398 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
5399 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
5400 			painter.pen = Pen(Color.black, 1);
5401 			painter.fillColor = Color(220, 220, 220);
5402 
5403 			// Draw the title and message
5404 			painter.drawText(Point(4 + iconWidth, 4), title);
5405 			painter.drawLine(
5406 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
5407 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
5408 			);
5409 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
5410 
5411 			balloon.setEventHandlers(
5412 				(MouseEvent ev) {
5413 					if(ev.type == MouseEventType.buttonPressed) {
5414 						if(ev.x > balloon.width - 16 && ev.y < 16)
5415 							hideBalloon();
5416 						else if(onclick)
5417 							onclick();
5418 					}
5419 				}
5420 			);
5421 			balloon.show();
5422 
5423 			version(with_timer)
5424 			timer = new Timer(timeout, &hideBalloon);
5425 			else {} // FIXME
5426 		}
5427 		} else version(Windows) {
5428 			enum NIF_INFO = 0x00000010;
5429 
5430 			data.uFlags = NIF_INFO;
5431 
5432 			// FIXME: go back to the last valid unicode code point
5433 			if(title.length > 40)
5434 				title = title[0 .. 40];
5435 			if(message.length > 220)
5436 				message = message[0 .. 220];
5437 
5438 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
5439 			enum NIIF_LARGE_ICON  = 0x00000020;
5440 			enum NIIF_NOSOUND = 0x00000010;
5441 			enum NIIF_USER = 0x00000004;
5442 			enum NIIF_ERROR = 0x00000003;
5443 			enum NIIF_WARNING = 0x00000002;
5444 			enum NIIF_INFO = 0x00000001;
5445 			enum NIIF_NONE = 0;
5446 
5447 			WCharzBuffer t = WCharzBuffer(title);
5448 			WCharzBuffer m = WCharzBuffer(message);
5449 
5450 			t.copyInto(data.szInfoTitle);
5451 			m.copyInto(data.szInfo);
5452 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
5453 
5454 			if(icon !is null) {
5455 				auto i = new WindowsIcon(icon);
5456 				data.hBalloonIcon = i.hIcon;
5457 				data.dwInfoFlags |= NIIF_USER;
5458 			}
5459 
5460 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5461 		} else version(OSXCocoa) {
5462 			throw new NotYetImplementedException();
5463 		} else static assert(0);
5464 	}
5465 
5466 	///
5467 	//version(Windows)
5468 	void show() {
5469 		version(X11) {
5470 			if(!hidden)
5471 				return;
5472 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
5473 			hidden = false;
5474 		} else version(Windows) {
5475 			data.uFlags = NIF_STATE;
5476 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5477 			data.dwStateMask = NIS_HIDDEN; // windows vista
5478 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5479 		} else version(OSXCocoa) {
5480 			throw new NotYetImplementedException();
5481 		} else static assert(0);
5482 	}
5483 
5484 	version(X11)
5485 		bool hidden = false;
5486 
5487 	///
5488 	//version(Windows)
5489 	void hide() {
5490 		version(X11) {
5491 			if(hidden)
5492 				return;
5493 			hidden = true;
5494 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
5495 		} else version(Windows) {
5496 			data.uFlags = NIF_STATE;
5497 			data.dwState = NIS_HIDDEN; // windows vista
5498 			data.dwStateMask = NIS_HIDDEN; // windows vista
5499 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
5500 		} else version(OSXCocoa) {
5501 			throw new NotYetImplementedException();
5502 		} else static assert(0);
5503 	}
5504 
5505 	///
5506 	void close () {
5507 		version(X11) {
5508 			if (active) {
5509 				active = false; // event handler will set this too, but meh
5510 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
5511 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
5512 				flushGui();
5513 			}
5514 		} else version(Windows) {
5515 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
5516 		} else version(OSXCocoa) {
5517 			throw new NotYetImplementedException();
5518 		} else static assert(0);
5519 	}
5520 
5521 	~this() {
5522 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
5523 		version(X11)
5524 			if(clippixmap != None)
5525 				XFreePixmap(XDisplayConnection.get, clippixmap);
5526 		close();
5527 	}
5528 }
5529 
5530 version(X11)
5531 /// Call `XFreePixmap` on the return value.
5532 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
5533 	char[] data = new char[](i.width * i.height / 8 + 2);
5534 	data[] = 0;
5535 
5536 	int bitOffset = 0;
5537 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
5538 		ubyte v = c.a > 128 ? 1 : 0;
5539 		data[bitOffset / 8] |= v << (bitOffset%8);
5540 		bitOffset++;
5541 	}
5542 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
5543 	return handle;
5544 }
5545 
5546 
5547 // basic functions to make timers
5548 /**
5549 	A timer that will trigger your function on a given interval.
5550 
5551 
5552 	You create a timer with an interval and a callback. It will continue
5553 	to fire on the interval until it is destroyed.
5554 
5555 	There are currently no one-off timers (instead, just create one and
5556 	destroy it when it is triggered) nor are there pause/resume functions -
5557 	the timer must again be destroyed and recreated if you want to pause it.
5558 
5559 	---
5560 	auto timer = new Timer(50, { it happened!; });
5561 	timer.destroy();
5562 	---
5563 
5564 	Timers can only be expected to fire when the event loop is running and only
5565 	once per iteration through the event loop.
5566 
5567 	History:
5568 		Prior to December 9, 2020, a timer pulse set too high with a handler too
5569 		slow could lock up the event loop. It now guarantees other things will
5570 		get a chance to run between timer calls, even if that means not keeping up
5571 		with the requested interval.
5572 */
5573 version(with_timer) {
5574 class Timer {
5575 // FIXME: needs pause and unpause
5576 	// FIXME: I might add overloads for ones that take a count of
5577 	// how many elapsed since last time (on Windows, it will divide
5578 	// the ticks thing given, on Linux it is just available) and
5579 	// maybe one that takes an instance of the Timer itself too
5580 	/// Create a timer with a callback when it triggers.
5581 	this(int intervalInMilliseconds, void delegate() onPulse) {
5582 		assert(onPulse !is null);
5583 
5584 		this.intervalInMilliseconds = intervalInMilliseconds;
5585 		this.onPulse = onPulse;
5586 
5587 		version(Windows) {
5588 			/*
5589 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5590 			if(handle == 0)
5591 				throw new WindowsApiException("SetTimer", GetLastError());
5592 			*/
5593 
5594 			// thanks to Archival 998 for the WaitableTimer blocks
5595 			handle = CreateWaitableTimer(null, false, null);
5596 			long initialTime = -intervalInMilliseconds;
5597 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5598 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
5599 
5600 			mapping[handle] = this;
5601 
5602 		} else version(linux) {
5603 			static import ep = core.sys.linux.epoll;
5604 
5605 			import core.sys.linux.timerfd;
5606 
5607 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5608 			if(fd == -1)
5609 				throw new Exception("timer create failed");
5610 
5611 			mapping[fd] = this;
5612 
5613 			itimerspec value = makeItimerspec(intervalInMilliseconds);
5614 
5615 			if(timerfd_settime(fd, 0, &value, null) == -1)
5616 				throw new Exception("couldn't make pulse timer");
5617 
5618 			version(with_eventloop) {
5619 				import arsd.eventloop;
5620 				addFileEventListeners(fd, &trigger, null, null);
5621 			} else {
5622 				prepareEventLoop();
5623 
5624 				ep.epoll_event ev = void;
5625 				ev.events = ep.EPOLLIN;
5626 				ev.data.fd = fd;
5627 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5628 			}
5629 		} else featureNotImplemented();
5630 	}
5631 
5632 	private int intervalInMilliseconds;
5633 
5634 	// just cuz I sometimes call it this.
5635 	alias dispose = destroy;
5636 
5637 	/// Stop and destroy the timer object.
5638 	void destroy() {
5639 		version(Windows) {
5640 			staticDestroy(handle);
5641 			handle = null;
5642 		} else version(linux) {
5643 			staticDestroy(fd);
5644 			fd = -1;
5645 		} else featureNotImplemented();
5646 	}
5647 
5648 	version(Windows)
5649 	static void staticDestroy(HANDLE handle) {
5650 		if(handle) {
5651 			// KillTimer(null, handle);
5652 			CancelWaitableTimer(cast(void*)handle);
5653 			mapping.remove(handle);
5654 			CloseHandle(handle);
5655 		}
5656 	}
5657 	else version(linux)
5658 	static void staticDestroy(int fd) {
5659 		if(fd != -1) {
5660 			import unix = core.sys.posix.unistd;
5661 			static import ep = core.sys.linux.epoll;
5662 
5663 			version(with_eventloop) {
5664 				import arsd.eventloop;
5665 				removeFileEventListeners(fd);
5666 			} else {
5667 				ep.epoll_event ev = void;
5668 				ev.events = ep.EPOLLIN;
5669 				ev.data.fd = fd;
5670 
5671 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5672 			}
5673 			unix.close(fd);
5674 			mapping.remove(fd);
5675 		}
5676 	}
5677 
5678 	~this() {
5679 		version(Windows) { if(handle)
5680 			cleanupQueue.queue!staticDestroy(handle);
5681 		} else version(linux) { if(fd != -1)
5682 			cleanupQueue.queue!staticDestroy(fd);
5683 		}
5684 	}
5685 
5686 	void changeTime(int intervalInMilliseconds)
5687 	{
5688 		this.intervalInMilliseconds = intervalInMilliseconds;
5689 		version(Windows)
5690 		{
5691 			if(handle)
5692 			{
5693 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5694 				long initialTime = -intervalInMilliseconds;
5695 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5696 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
5697 			}
5698 		} else version(linux) {
5699 			import core.sys.linux.timerfd;
5700 
5701 			itimerspec value = makeItimerspec(intervalInMilliseconds);
5702 			if(timerfd_settime(fd, 0, &value, null) == -1) {
5703 				throw new Exception("couldn't change pulse timer");
5704 			}
5705 		} else {
5706 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
5707 		}
5708 	}
5709 
5710 
5711 	private:
5712 
5713 	void delegate() onPulse;
5714 
5715 	int lastEventLoopRoundTriggered;
5716 
5717 	version(linux) {
5718 		static auto makeItimerspec(int intervalInMilliseconds) {
5719 			import core.sys.linux.timerfd;
5720 
5721 			itimerspec value;
5722 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5723 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5724 
5725 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5726 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5727 
5728 			return value;
5729 		}
5730 	}
5731 
5732 	void trigger() {
5733 		version(linux) {
5734 			import unix = core.sys.posix.unistd;
5735 			long val;
5736 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5737 		} else version(Windows) {
5738 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5739 				return; // never try to actually run faster than the event loop
5740 			lastEventLoopRoundTriggered = eventLoopRound;
5741 		} else featureNotImplemented();
5742 
5743 		onPulse();
5744 	}
5745 
5746 	version(Windows)
5747 	void rearm() {
5748 
5749 	}
5750 
5751 	version(Windows)
5752 		extern(Windows)
5753 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5754 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5755 			if(Timer* t = timer in mapping) {
5756 				try
5757 				(*t).trigger();
5758 				catch(Exception e) { sdpy_abort(e); assert(0); }
5759 			}
5760 		}
5761 
5762 	version(Windows) {
5763 		//UINT_PTR handle;
5764 		//static Timer[UINT_PTR] mapping;
5765 		HANDLE handle;
5766 		__gshared Timer[HANDLE] mapping;
5767 	} else version(linux) {
5768 		int fd = -1;
5769 		__gshared Timer[int] mapping;
5770 	} else version(OSXCocoa) {
5771 	} else static assert(0, "timer not supported");
5772 }
5773 }
5774 
5775 version(Windows)
5776 private int eventLoopRound;
5777 
5778 version(Windows)
5779 /// 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
5780 class WindowsHandleReader {
5781 	///
5782 	this(void delegate() onReady, HANDLE handle) {
5783 		this.onReady = onReady;
5784 		this.handle = handle;
5785 
5786 		mapping[handle] = this;
5787 
5788 		enable();
5789 	}
5790 
5791 	///
5792 	void enable() {
5793 		auto el = EventLoop.get().impl;
5794 		el.handles ~= handle;
5795 	}
5796 
5797 	///
5798 	void disable() {
5799 		auto el = EventLoop.get().impl;
5800 		for(int i = 0; i < el.handles.length; i++) {
5801 			if(el.handles[i] is handle) {
5802 				el.handles[i] = el.handles[$-1];
5803 				el.handles = el.handles[0 .. $-1];
5804 				return;
5805 			}
5806 		}
5807 	}
5808 
5809 	void dispose() {
5810 		disable();
5811 		if(handle)
5812 			mapping.remove(handle);
5813 		handle = null;
5814 	}
5815 
5816 	void ready() {
5817 		if(onReady)
5818 			onReady();
5819 	}
5820 
5821 	HANDLE handle;
5822 	void delegate() onReady;
5823 
5824 	__gshared WindowsHandleReader[HANDLE] mapping;
5825 }
5826 
5827 version(Posix)
5828 /// Lets you add files to the event loop for reading. Use at your own risk.
5829 class PosixFdReader {
5830 	///
5831 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5832 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5833 	}
5834 
5835 	///
5836 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5837 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5838 	}
5839 
5840 	///
5841 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5842 		this.onReady = onReady;
5843 		this.fd = fd;
5844 		this.captureWrites = captureWrites;
5845 		this.captureReads = captureReads;
5846 
5847 		mapping[fd] = this;
5848 
5849 		version(with_eventloop) {
5850 			import arsd.eventloop;
5851 			addFileEventListeners(fd, &readyel);
5852 		} else {
5853 			enable();
5854 		}
5855 	}
5856 
5857 	bool captureReads;
5858 	bool captureWrites;
5859 
5860 	version(with_eventloop) {} else
5861 	///
5862 	void enable() {
5863 		prepareEventLoop();
5864 
5865 		enabled = true;
5866 
5867 		version(linux) {
5868 			static import ep = core.sys.linux.epoll;
5869 			ep.epoll_event ev = void;
5870 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5871 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5872 			ev.data.fd = fd;
5873 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5874 		} else {
5875 
5876 		}
5877 	}
5878 
5879 	version(with_eventloop) {} else
5880 	///
5881 	void disable() {
5882 		prepareEventLoop();
5883 
5884 		enabled = false;
5885 
5886 		version(linux) {
5887 			static import ep = core.sys.linux.epoll;
5888 			ep.epoll_event ev = void;
5889 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5890 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5891 			ev.data.fd = fd;
5892 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5893 		}
5894 	}
5895 
5896 	version(with_eventloop) {} else
5897 	///
5898 	void dispose() {
5899 		if(enabled)
5900 			disable();
5901 		if(fd != -1)
5902 			mapping.remove(fd);
5903 		fd = -1;
5904 	}
5905 
5906 	void delegate(int, bool, bool) onReady;
5907 
5908 	version(with_eventloop)
5909 	void readyel() {
5910 		onReady(fd, true, true);
5911 	}
5912 
5913 	void ready(uint flags) {
5914 		version(linux) {
5915 			static import ep = core.sys.linux.epoll;
5916 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5917 		} else {
5918 			import core.sys.posix.poll;
5919 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5920 		}
5921 	}
5922 
5923 	void hup(uint flags) {
5924 		if(onHup)
5925 			onHup();
5926 	}
5927 
5928 	void delegate() onHup;
5929 
5930 	int fd = -1;
5931 	private bool enabled;
5932 	__gshared PosixFdReader[int] mapping;
5933 }
5934 
5935 // basic functions to access the clipboard
5936 /+
5937 
5938 
5939 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5940 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5941 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5942 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5943 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5944 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5945 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5946 
5947 +/
5948 
5949 /++
5950 	this does a delegate because it is actually an async call on X...
5951 	the receiver may never be called if the clipboard is empty or unavailable
5952 	gets plain text from the clipboard.
5953 +/
5954 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5955 	version(Windows) {
5956 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5957 		if(OpenClipboard(hwndOwner) == 0)
5958 			throw new WindowsApiException("OpenClipboard", GetLastError());
5959 		scope(exit)
5960 			CloseClipboard();
5961 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5962 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5963 
5964 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5965 				scope(exit)
5966 					GlobalUnlock(dataHandle);
5967 
5968 				// FIXME: CR/LF conversions
5969 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5970 				int len = 0;
5971 				auto d = data;
5972 				while(*d) {
5973 					d++;
5974 					len++;
5975 				}
5976 				string s;
5977 				s.reserve(len);
5978 				foreach(dchar ch; data[0 .. len]) {
5979 					s ~= ch;
5980 				}
5981 				receiver(s);
5982 			}
5983 		}
5984 	} else version(X11) {
5985 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5986 	} else version(OSXCocoa) {
5987 		throw new NotYetImplementedException();
5988 	} else static assert(0);
5989 }
5990 
5991 // FIXME: a clipboard listener might be cool btw
5992 
5993 /++
5994 	this does a delegate because it is actually an async call on X...
5995 	the receiver may never be called if the clipboard is empty or unavailable
5996 	gets image from the clipboard.
5997 
5998 	templated because it introduces an optional dependency on arsd.bmp
5999 +/
6000 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6001 	version(Windows) {
6002 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6003 		if(OpenClipboard(hwndOwner) == 0)
6004 			throw new WindowsApiException("OpenClipboard", GetLastError());
6005 		scope(exit)
6006 			CloseClipboard();
6007 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6008 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6009 				scope(exit)
6010 					GlobalUnlock(dataHandle);
6011 
6012 				auto len = GlobalSize(dataHandle);
6013 
6014 				import arsd.bmp;
6015 				auto img = readBmp(data[0 .. len], false);
6016 				receiver(img);
6017 			}
6018 		}
6019 	} else version(X11) {
6020 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6021 	} else version(OSXCocoa) {
6022 		throw new NotYetImplementedException();
6023 	} else static assert(0);
6024 }
6025 
6026 /// Copies some text to the clipboard.
6027 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6028 	assert(clipboardOwner !is null);
6029 	version(Windows) {
6030 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6031 			throw new WindowsApiException("OpenClipboard", GetLastError());
6032 		scope(exit)
6033 			CloseClipboard();
6034 		EmptyClipboard();
6035 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6036 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6037 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6038 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6039 			auto slice = data[0 .. sz];
6040 			scope(failure)
6041 				GlobalUnlock(handle);
6042 
6043 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6044 
6045 			GlobalUnlock(handle);
6046 			SetClipboardData(CF_UNICODETEXT, handle);
6047 		}
6048 	} else version(X11) {
6049 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6050 	} else version(OSXCocoa) {
6051 		throw new NotYetImplementedException();
6052 	} else static assert(0);
6053 }
6054 
6055 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6056 	assert(clipboardOwner !is null);
6057 	version(Windows) {
6058 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6059 			throw new WindowsApiException("OpenClipboard", GetLastError());
6060 		scope(exit)
6061 			CloseClipboard();
6062 		EmptyClipboard();
6063 
6064 
6065 		import arsd.bmp;
6066 		ubyte[] mdata;
6067 		mdata.reserve(img.width * img.height);
6068 		void sink(ubyte b) {
6069 			mdata ~= b;
6070 		}
6071 		writeBmpIndirect(img, &sink, false);
6072 
6073 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6074 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6075 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6076 			auto slice = data[0 .. mdata.length];
6077 			scope(failure)
6078 				GlobalUnlock(handle);
6079 
6080 			slice[] = mdata[];
6081 
6082 			GlobalUnlock(handle);
6083 			SetClipboardData(CF_DIB, handle);
6084 		}
6085 	} else version(X11) {
6086 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6087 			mixin X11SetSelectionHandler_Basics;
6088 			private const(ubyte)[] mdata;
6089 			private const(ubyte)[] mdata_original;
6090 			this(MemoryImage img) {
6091 				import arsd.bmp;
6092 
6093 				mdata.reserve(img.width * img.height);
6094 				void sink(ubyte b) {
6095 					mdata ~= b;
6096 				}
6097 				writeBmpIndirect(img, &sink, true);
6098 
6099 				mdata_original = mdata;
6100 			}
6101 
6102 			Atom[] availableFormats() {
6103 				auto display = XDisplayConnection.get;
6104 				return [
6105 					GetAtom!"image/bmp"(display),
6106 					GetAtom!"TARGETS"(display)
6107 				];
6108 			}
6109 
6110 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6111 				if(mdata.length < data.length) {
6112 					data[0 .. mdata.length] = mdata[];
6113 					auto ret = data[0 .. mdata.length];
6114 					mdata = mdata[$..$];
6115 					return ret;
6116 				} else {
6117 					data[] = mdata[0 .. data.length];
6118 					mdata = mdata[data.length .. $];
6119 					return data[];
6120 				}
6121 			}
6122 
6123 			void done() {
6124 				mdata = mdata_original;
6125 			}
6126 		}
6127 
6128 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
6129 	} else version(OSXCocoa) {
6130 		throw new NotYetImplementedException();
6131 	} else static assert(0);
6132 }
6133 
6134 
6135 version(X11) {
6136 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6137 
6138 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6139 
6140 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6141 	/// Platform-specific for X11.
6142 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6143 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6144 		__gshared static Atom a;
6145 		if(!a) {
6146 			a = XInternAtom(display, name, !create);
6147 			// FIXME: might need to synchronize this and attach it to the actual object
6148 			interredAtoms ~= &a;
6149 		}
6150 		if(a == None)
6151 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6152 		return a;
6153 	}
6154 
6155 	/// Platform-specific for X11 - gets atom names as a string.
6156 	string getAtomName(Atom atom, Display* display) {
6157 		auto got = XGetAtomName(display, atom);
6158 		scope(exit) XFree(got);
6159 		import core.stdc.string;
6160 		string s = got[0 .. strlen(got)].idup;
6161 		return s;
6162 	}
6163 
6164 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6165 	void setPrimarySelection(SimpleWindow window, string text) {
6166 		setX11Selection!"PRIMARY"(window, text);
6167 	}
6168 
6169 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6170 	void setSecondarySelection(SimpleWindow window, string text) {
6171 		setX11Selection!"SECONDARY"(window, text);
6172 	}
6173 
6174 	interface X11SetSelectionHandler {
6175 		// should include TARGETS right now
6176 		Atom[] availableFormats();
6177 		// Return the slice of data you filled, empty slice if done.
6178 		// this is to support the incremental thing
6179 		ubyte[] getData(Atom format, return scope ubyte[] data);
6180 
6181 		void done();
6182 
6183 		void handleRequest(XEvent);
6184 
6185 		bool matchesIncr(Window, Atom);
6186 		void sendMoreIncr(XPropertyEvent*);
6187 	}
6188 
6189 	mixin template X11SetSelectionHandler_Basics() {
6190 		Window incrWindow;
6191 		Atom incrAtom;
6192 		Atom selectionAtom;
6193 		Atom formatAtom;
6194 		ubyte[] toSend;
6195 		bool matchesIncr(Window w, Atom a) {
6196 			return incrAtom && incrAtom == a && w == incrWindow;
6197 		}
6198 		void sendMoreIncr(XPropertyEvent* event) {
6199 			auto display = XDisplayConnection.get;
6200 
6201 			XChangeProperty (display,
6202 				incrWindow,
6203 				incrAtom,
6204 				formatAtom,
6205 				8 /* bits */, PropModeReplace,
6206 				toSend.ptr, cast(int) toSend.length);
6207 
6208 			if(toSend.length != 0) {
6209 				toSend = this.getData(formatAtom, toSend[]);
6210 			} else {
6211 				this.done();
6212 				incrWindow = None;
6213 				incrAtom = None;
6214 				selectionAtom = None;
6215 				formatAtom = None;
6216 				toSend = null;
6217 			}
6218 		}
6219 		void handleRequest(XEvent ev) {
6220 
6221 			auto display = XDisplayConnection.get;
6222 
6223 			XSelectionRequestEvent* event = &ev.xselectionrequest;
6224 			XSelectionEvent selectionEvent;
6225 			selectionEvent.type = EventType.SelectionNotify;
6226 			selectionEvent.display = event.display;
6227 			selectionEvent.requestor = event.requestor;
6228 			selectionEvent.selection = event.selection;
6229 			selectionEvent.time = event.time;
6230 			selectionEvent.target = event.target;
6231 
6232 			bool supportedType() {
6233 				foreach(t; this.availableFormats())
6234 					if(t == event.target)
6235 						return true;
6236 				return false;
6237 			}
6238 
6239 			if(event.property == None) {
6240 				selectionEvent.property = event.target;
6241 
6242 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6243 				XFlush(display);
6244 			} if(event.target == GetAtom!"TARGETS"(display)) {
6245 				/* respond with the supported types */
6246 				auto tlist = this.availableFormats();
6247 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
6248 				selectionEvent.property = event.property;
6249 
6250 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6251 				XFlush(display);
6252 			} else if(supportedType()) {
6253 				auto buffer = new ubyte[](1024 * 64);
6254 				auto toSend = this.getData(event.target, buffer[]);
6255 
6256 				if(toSend.length < 32 * 1024) {
6257 					// small enough to send directly...
6258 					selectionEvent.property = event.property;
6259 					XChangeProperty (display,
6260 						selectionEvent.requestor,
6261 						selectionEvent.property,
6262 						event.target,
6263 						8 /* bits */, 0 /* PropModeReplace */,
6264 						toSend.ptr, cast(int) toSend.length);
6265 
6266 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6267 					XFlush(display);
6268 				} else {
6269 					// large, let's send incrementally
6270 					arch_ulong l = toSend.length;
6271 
6272 					// if I wanted other events from this window don't want to clear that out....
6273 					XWindowAttributes xwa;
6274 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
6275 
6276 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
6277 
6278 					incrWindow = event.requestor;
6279 					incrAtom = event.property;
6280 					formatAtom = event.target;
6281 					selectionAtom = event.selection;
6282 					this.toSend = toSend;
6283 
6284 					selectionEvent.property = event.property;
6285 					XChangeProperty (display,
6286 						selectionEvent.requestor,
6287 						selectionEvent.property,
6288 						GetAtom!"INCR"(display),
6289 						32 /* bits */, PropModeReplace,
6290 						&l, 1);
6291 
6292 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
6293 					XFlush(display);
6294 				}
6295 				//if(after)
6296 					//after();
6297 			} else {
6298 				debug(sdpy_clip) {
6299 					writeln("Unsupported data ", getAtomName(event.target, display));
6300 				}
6301 				selectionEvent.property = None; // I don't know how to handle this type...
6302 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
6303 				XFlush(display);
6304 			}
6305 		}
6306 	}
6307 
6308 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
6309 		mixin X11SetSelectionHandler_Basics;
6310 		private const(ubyte)[] text;
6311 		private const(ubyte)[] text_original;
6312 		this(string text) {
6313 			this.text = cast(const ubyte[]) text;
6314 			this.text_original = this.text;
6315 		}
6316 		Atom[] availableFormats() {
6317 			auto display = XDisplayConnection.get;
6318 			return [
6319 				GetAtom!"UTF8_STRING"(display),
6320 				GetAtom!"text/plain"(display),
6321 				XA_STRING,
6322 				GetAtom!"TARGETS"(display)
6323 			];
6324 		}
6325 
6326 		ubyte[] getData(Atom format, return scope ubyte[] data) {
6327 			if(text.length < data.length) {
6328 				data[0 .. text.length] = text[];
6329 				return data[0 .. text.length];
6330 			} else {
6331 				data[] = text[0 .. data.length];
6332 				text = text[data.length .. $];
6333 				return data[];
6334 			}
6335 		}
6336 
6337 		void done() {
6338 			text = text_original;
6339 		}
6340 	}
6341 
6342 	/// 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?!)
6343 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
6344 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
6345 	}
6346 
6347 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
6348 		assert(window !is null);
6349 
6350 		auto display = XDisplayConnection.get();
6351 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
6352 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
6353 		else Atom a = GetAtom!atomName(display);
6354 
6355 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
6356 
6357 		window.impl.setSelectionHandlers[a] = data;
6358 	}
6359 
6360 	///
6361 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
6362 		getX11Selection!"PRIMARY"(window, handler);
6363 	}
6364 
6365 	// added July 28, 2020
6366 	// undocumented as experimental tho
6367 	interface X11GetSelectionHandler {
6368 		void handleData(Atom target, in ubyte[] data);
6369 		Atom findBestFormat(Atom[] answer);
6370 
6371 		void prepareIncremental(Window, Atom);
6372 		bool matchesIncr(Window, Atom);
6373 		void handleIncrData(Atom, in ubyte[] data);
6374 	}
6375 
6376 	mixin template X11GetSelectionHandler_Basics() {
6377 		Window incrWindow;
6378 		Atom incrAtom;
6379 
6380 		void prepareIncremental(Window w, Atom a) {
6381 			incrWindow = w;
6382 			incrAtom = a;
6383 		}
6384 		bool matchesIncr(Window w, Atom a) {
6385 			return incrWindow == w && incrAtom == a;
6386 		}
6387 
6388 		Atom incrFormatAtom;
6389 		ubyte[] incrData;
6390 		void handleIncrData(Atom format, in ubyte[] data) {
6391 			incrFormatAtom = format;
6392 
6393 			if(data.length)
6394 				incrData ~= data;
6395 			else
6396 				handleData(incrFormatAtom, incrData);
6397 
6398 		}
6399 	}
6400 
6401 	///
6402 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
6403 		assert(window !is null);
6404 
6405 		auto display = XDisplayConnection.get();
6406 		auto atom = GetAtom!atomName(display);
6407 
6408 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6409 			this(void delegate(in char[]) handler) {
6410 				this.handler = handler;
6411 			}
6412 
6413 			mixin X11GetSelectionHandler_Basics;
6414 
6415 			void delegate(in char[]) handler;
6416 
6417 			void handleData(Atom target, in ubyte[] data) {
6418 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6419 					handler(cast(const char[]) data);
6420 			}
6421 
6422 			Atom findBestFormat(Atom[] answer) {
6423 				Atom best = None;
6424 				foreach(option; answer) {
6425 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6426 						best = option;
6427 						break;
6428 					} else if(option == XA_STRING) {
6429 						best = option;
6430 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6431 						best = option;
6432 					}
6433 				}
6434 				return best;
6435 			}
6436 		}
6437 
6438 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6439 
6440 		auto target = GetAtom!"TARGETS"(display);
6441 
6442 		// SDD_DATA is "simpledisplay.d data"
6443 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6444 	}
6445 
6446 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6447 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6448 		assert(window !is null);
6449 
6450 		auto display = XDisplayConnection.get();
6451 		auto atom = GetAtom!atomName(display);
6452 
6453 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6454 			this(void delegate(MemoryImage) handler) {
6455 				this.handler = handler;
6456 			}
6457 
6458 			mixin X11GetSelectionHandler_Basics;
6459 
6460 			void delegate(MemoryImage) handler;
6461 
6462 			void handleData(Atom target, in ubyte[] data) {
6463 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6464 					import arsd.bmp;
6465 					handler(readBmp(data));
6466 				}
6467 			}
6468 
6469 			Atom findBestFormat(Atom[] answer) {
6470 				Atom best = None;
6471 				foreach(option; answer) {
6472 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6473 						best = option;
6474 					}
6475 				}
6476 				return best;
6477 			}
6478 
6479 		}
6480 
6481 
6482 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6483 
6484 		auto target = GetAtom!"TARGETS"(display);
6485 
6486 		// SDD_DATA is "simpledisplay.d data"
6487 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6488 	}
6489 
6490 
6491 	///
6492 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6493 		Atom actualType;
6494 		int actualFormat;
6495 		arch_ulong actualItems;
6496 		arch_ulong bytesRemaining;
6497 		void* data;
6498 
6499 		auto display = XDisplayConnection.get();
6500 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6501 			if(actualFormat == 0)
6502 				return null;
6503 			else {
6504 				int byteLength;
6505 				if(actualFormat == 32) {
6506 					// 32 means it is a C long... which is variable length
6507 					actualFormat = cast(int) arch_long.sizeof * 8;
6508 				}
6509 
6510 				// then it is just a bit count
6511 				byteLength = cast(int) (actualItems * actualFormat / 8);
6512 
6513 				auto d = new ubyte[](byteLength);
6514 				d[] = cast(ubyte[]) data[0 .. byteLength];
6515 				XFree(data);
6516 				return d;
6517 			}
6518 		}
6519 		return null;
6520 	}
6521 
6522 	/* defined in the systray spec */
6523 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6524 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6525 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6526 
6527 
6528 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6529 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6530 	public class GlobalHotkey {
6531 		KeyEvent key;
6532 		void delegate () handler;
6533 
6534 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6535 
6536 		/// Create from initialzed KeyEvent object
6537 		this (KeyEvent akey, void delegate () ahandler=null) {
6538 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6539 			key = akey;
6540 			handler = ahandler;
6541 		}
6542 
6543 		/// Create from emacs-like key name ("C-M-Y", etc.)
6544 		this (const(char)[] akey, void delegate () ahandler=null) {
6545 			key = KeyEvent.parse(akey);
6546 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6547 			handler = ahandler;
6548 		}
6549 
6550 	}
6551 
6552 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6553 		//conwriteln("failed to grab key");
6554 		GlobalHotkeyManager.ghfailed = true;
6555 		return 0;
6556 	}
6557 
6558 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6559 		Image.impl.xshmfailed = true;
6560 		return 0;
6561 	}
6562 
6563 	private __gshared int errorHappened;
6564 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6565 		import core.stdc.stdio;
6566 		char[265] buffer;
6567 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6568 		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);
6569 		errorHappened = true;
6570 		return 0;
6571 	}
6572 
6573 	/++
6574 		Global hotkey manager. It contains static methods to manage global hotkeys.
6575 
6576 		---
6577 		 try {
6578 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6579 		} catch (Exception e) {
6580 			conwriteln("ERROR registering hotkey!");
6581 		}
6582 		EventLoop.get.run();
6583 		---
6584 
6585 		The key strings are based on Emacs. In practical terms,
6586 		`M` means `alt` and `H` means the Windows logo key. `C`
6587 		is `ctrl`.
6588 
6589 		$(WARNING
6590 			This is X-specific right now. If you are on
6591 			Windows, try [registerHotKey] instead.
6592 
6593 			We will probably merge these into a single
6594 			interface later.
6595 		)
6596 	+/
6597 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6598 		version(X11) {
6599 			void recreateAfterDisconnect() {
6600 				throw new Exception("NOT IMPLEMENTED");
6601 			}
6602 			void discardConnectionState() {
6603 				throw new Exception("NOT IMPLEMENTED");
6604 			}
6605 		}
6606 
6607 		private static immutable uint[8] masklist = [ 0,
6608 			KeyOrButtonMask.LockMask,
6609 			KeyOrButtonMask.Mod2Mask,
6610 			KeyOrButtonMask.Mod3Mask,
6611 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6612 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6613 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6614 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6615 		];
6616 		private __gshared GlobalHotkeyManager ghmanager;
6617 		private __gshared bool ghfailed = false;
6618 
6619 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6620 			if (modmask == 0) return false;
6621 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6622 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6623 			return true;
6624 		}
6625 
6626 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6627 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6628 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6629 			return modmask;
6630 		}
6631 
6632 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
6633 			uint keycode = cast(uint)ke.key;
6634 			auto dpy = XDisplayConnection.get;
6635 			return XKeysymToKeycode(dpy, keycode);
6636 		}
6637 
6638 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6639 
6640 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6641 
6642 		NativeEventHandler getNativeEventHandler () {
6643 			return delegate int (XEvent e) {
6644 				if (e.type != EventType.KeyPress) return 1;
6645 				auto kev = cast(const(XKeyEvent)*)&e;
6646 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6647 				if (auto ghkp = hash in globalHotkeyList) {
6648 					try {
6649 						ghkp.doHandle();
6650 					} catch (Exception e) {
6651 						import core.stdc.stdio : stderr, fprintf;
6652 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6653 					}
6654 				}
6655 				return 1;
6656 			};
6657 		}
6658 
6659 		private this () {
6660 			auto dpy = XDisplayConnection.get;
6661 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6662 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6663 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6664 		}
6665 
6666 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6667 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6668 		static void register (GlobalHotkey gh) {
6669 			if (gh is null) return;
6670 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6671 
6672 			auto dpy = XDisplayConnection.get;
6673 			immutable keycode = keyEvent2KeyCode(gh.key);
6674 
6675 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6676 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6677 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6678 			XSync(dpy, 0/*False*/);
6679 
6680 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6681 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6682 			ghfailed = false;
6683 			foreach (immutable uint ormask; masklist[]) {
6684 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6685 			}
6686 			XSync(dpy, 0/*False*/);
6687 			XSetErrorHandler(savedErrorHandler);
6688 
6689 			if (ghfailed) {
6690 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6691 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6692 				XSync(dpy, 0/*False*/);
6693 				XSetErrorHandler(savedErrorHandler);
6694 				throw new Exception("cannot register global hotkey");
6695 			}
6696 
6697 			globalHotkeyList[hash] = gh;
6698 		}
6699 
6700 		/// Ditto
6701 		static void register (const(char)[] akey, void delegate () ahandler) {
6702 			register(new GlobalHotkey(akey, ahandler));
6703 		}
6704 
6705 		private static void removeByHash (ulong hash) {
6706 			if (auto ghp = hash in globalHotkeyList) {
6707 				auto dpy = XDisplayConnection.get;
6708 				immutable keycode = keyEvent2KeyCode(ghp.key);
6709 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6710 				XSync(dpy, 0/*False*/);
6711 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6712 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6713 				XSync(dpy, 0/*False*/);
6714 				XSetErrorHandler(savedErrorHandler);
6715 				globalHotkeyList.remove(hash);
6716 			}
6717 		}
6718 
6719 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6720 		/// It is safe to unregister unknown or invalid hotkey.
6721 		static void unregister (GlobalHotkey gh) {
6722 			//TODO: add second AA for faster search? prolly doesn't worth it.
6723 			if (gh is null) return;
6724 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6725 				if (kv.value is gh) {
6726 					removeByHash(kv.key);
6727 					return;
6728 				}
6729 			}
6730 		}
6731 
6732 		/// Ditto.
6733 		static void unregister (const(char)[] key) {
6734 			auto kev = KeyEvent.parse(key);
6735 			immutable keycode = keyEvent2KeyCode(kev);
6736 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6737 		}
6738 	}
6739 }
6740 
6741 version(Windows) {
6742 	/++
6743 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
6744 
6745 		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).
6746 	+/
6747 	void sendSyntheticInput(wstring s) {
6748 			INPUT[] inputs;
6749 			inputs.reserve(s.length * 2);
6750 
6751 			foreach(wchar c; s) {
6752 				INPUT input;
6753 				input.type = INPUT_KEYBOARD;
6754 				input.ki.wScan = c;
6755 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6756 				inputs ~= input;
6757 
6758 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6759 				inputs ~= input;
6760 			}
6761 
6762 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6763 				throw new WindowsApiException("SendInput", GetLastError());
6764 			}
6765 
6766 	}
6767 
6768 
6769 	// global hotkey helper function
6770 
6771 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
6772 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6773 		__gshared int hotkeyId = 0;
6774 		int id = ++hotkeyId;
6775 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6776 			throw new Exception("RegisterHotKey");
6777 
6778 		__gshared void delegate()[WPARAM][HWND] handlers;
6779 
6780 		handlers[window.impl.hwnd][id] = handler;
6781 
6782 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6783 
6784 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6785 			switch(msg) {
6786 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6787 				case WM_HOTKEY:
6788 					if(auto list = hwnd in handlers) {
6789 						if(auto h = wParam in *list) {
6790 							(*h)();
6791 							return 0;
6792 						}
6793 					}
6794 				goto default;
6795 				default:
6796 			}
6797 			if(oldHandler)
6798 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6799 			return 1; // pass it on
6800 		};
6801 
6802 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6803 			oldHandler = window.handleNativeEvent;
6804 			window.handleNativeEvent = nativeEventHandler;
6805 		}
6806 
6807 		return id;
6808 	}
6809 
6810 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6811 	void unregisterHotKey(SimpleWindow window, int id) {
6812 		if(!UnregisterHotKey(window.impl.hwnd, id))
6813 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
6814 	}
6815 }
6816 
6817 version (X11) {
6818 	pragma(lib, "dl");
6819 	import core.sys.posix.dlfcn;
6820 }
6821 
6822 /++
6823 	Allows for sending synthetic input to the X server via the Xtst
6824 	extension or on Windows using SendInput.
6825 
6826 	Please remember user input is meant to be user - don't use this
6827 	if you have some other alternative!
6828 
6829 	History:
6830 		Added May 17, 2020 with the X implementation.
6831 
6832 		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.)
6833 	Bugs:
6834 		All methods on OSX Cocoa will throw not yet implemented exceptions.
6835 +/
6836 struct SyntheticInput {
6837 	@disable this();
6838 
6839 	private int* refcount;
6840 
6841 	version(X11) {
6842 		private void* lib;
6843 
6844 		private extern(C) {
6845 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6846 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6847 		}
6848 	}
6849 
6850 	/// The dummy param must be 0.
6851 	this(int dummy) {
6852 		version(X11) {
6853 			lib = dlopen("libXtst.so", RTLD_NOW);
6854 			if(lib is null)
6855 				throw new Exception("cannot load xtest lib extension");
6856 			scope(failure)
6857 				dlclose(lib);
6858 
6859 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6860 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6861 
6862 			if(XTestFakeKeyEvent is null)
6863 				throw new Exception("No XTestFakeKeyEvent");
6864 			if(XTestFakeButtonEvent is null)
6865 				throw new Exception("No XTestFakeButtonEvent");
6866 		}
6867 
6868 		refcount = new int;
6869 		*refcount = 1;
6870 	}
6871 
6872 	this(this) {
6873 		if(refcount)
6874 			*refcount += 1;
6875 	}
6876 
6877 	~this() {
6878 		if(refcount) {
6879 			*refcount -= 1;
6880 			if(*refcount == 0)
6881 				// I commented this because if I close the lib before
6882 				// XCloseDisplay, it is liable to segfault... so just
6883 				// gonna keep it loaded if it is loaded, no big deal
6884 				// anyway.
6885 				{} // dlclose(lib);
6886 		}
6887 	}
6888 
6889 	/++
6890 		Simulates typing a string into the keyboard.
6891 
6892 		Bugs:
6893 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
6894 
6895 			Not implemented except on Windows and X11.
6896 	+/
6897 	void sendSyntheticInput(string s) {
6898 		version(Windows) {
6899 			INPUT[] inputs;
6900 			inputs.reserve(s.length * 2);
6901 
6902 			auto ei = GetMessageExtraInfo();
6903 
6904 			foreach(wchar c; s) {
6905 				INPUT input;
6906 				input.type = INPUT_KEYBOARD;
6907 				input.ki.wScan = c;
6908 				input.ki.dwFlags = KEYEVENTF_UNICODE;
6909 				input.ki.dwExtraInfo = ei;
6910 				inputs ~= input;
6911 
6912 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
6913 				inputs ~= input;
6914 			}
6915 
6916 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6917 				throw new WindowsApiException("SendInput", GetLastError());
6918 			}
6919 		} else version(X11) {
6920 			int delay = 0;
6921 			foreach(ch; s) {
6922 				pressKey(cast(Key) ch, true, delay);
6923 				pressKey(cast(Key) ch, false, delay);
6924 				delay += 5;
6925 			}
6926 		} else throw new NotYetImplementedException();
6927 	}
6928 
6929 	/++
6930 		Sends a fake press or release key event.
6931 
6932 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6933 
6934 		Bugs:
6935 			The `delay` parameter is not implemented yet on Windows.
6936 
6937 			Not implemented except on Windows and X11.
6938 	+/
6939 	void pressKey(Key key, bool pressed, int delay = 0) {
6940 		version(Windows) {
6941 			INPUT input;
6942 			input.type = INPUT_KEYBOARD;
6943 			input.ki.wVk = cast(ushort) key;
6944 
6945 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
6946 			input.ki.dwExtraInfo = GetMessageExtraInfo();
6947 
6948 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6949 				throw new WindowsApiException("SendInput", GetLastError());
6950 			}
6951 		} else version(X11) {
6952 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6953 		} else throw new NotYetImplementedException();
6954 	}
6955 
6956 	/++
6957 		Sends a fake mouse button press or release event.
6958 
6959 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
6960 
6961 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
6962 
6963 		Bugs:
6964 			The `delay` parameter is not implemented yet on Windows.
6965 
6966 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
6967 
6968 			All arguments will throw NotYetImplementedException on OSX Cocoa.
6969 	+/
6970 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6971 		version(Windows) {
6972 			INPUT input;
6973 			input.type = INPUT_MOUSE;
6974 			input.mi.dwExtraInfo = GetMessageExtraInfo();
6975 
6976 			// input.mi.mouseData for a wheel event
6977 
6978 			switch(button) {
6979 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
6980 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
6981 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
6982 				case MouseButton.wheelUp:
6983 				case MouseButton.wheelDown:
6984 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
6985 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
6986 				break;
6987 				case MouseButton.backButton: throw new NotYetImplementedException();
6988 				case MouseButton.forwardButton: throw new NotYetImplementedException();
6989 				default:
6990 			}
6991 
6992 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
6993 				throw new WindowsApiException("SendInput", GetLastError());
6994 			}
6995 		} else version(X11) {
6996 			int btn;
6997 
6998 			switch(button) {
6999 				case MouseButton.left: btn = 1; break;
7000 				case MouseButton.middle: btn = 2; break;
7001 				case MouseButton.right: btn = 3; break;
7002 				case MouseButton.wheelUp: btn = 4; break;
7003 				case MouseButton.wheelDown: btn = 5; break;
7004 				case MouseButton.backButton: btn = 8; break;
7005 				case MouseButton.forwardButton: btn = 9; break;
7006 				default:
7007 			}
7008 
7009 			assert(btn);
7010 
7011 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7012 		} else throw new NotYetImplementedException();
7013 	}
7014 
7015 	///
7016 	static void moveMouseArrowBy(int dx, int dy) {
7017 		version(Windows) {
7018 			INPUT input;
7019 			input.type = INPUT_MOUSE;
7020 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7021 			input.mi.dx = dx;
7022 			input.mi.dy = dy;
7023 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7024 
7025 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7026 				throw new WindowsApiException("SendInput", GetLastError());
7027 			}
7028 		} else version(X11) {
7029 			auto disp = XDisplayConnection.get();
7030 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7031 			XFlush(disp);
7032 		} else throw new NotYetImplementedException();
7033 	}
7034 
7035 	///
7036 	static void moveMouseArrowTo(int x, int y) {
7037 		version(Windows) {
7038 			INPUT input;
7039 			input.type = INPUT_MOUSE;
7040 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7041 			input.mi.dx = x;
7042 			input.mi.dy = y;
7043 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7044 
7045 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7046 				throw new WindowsApiException("SendInput", GetLastError());
7047 			}
7048 		} else version(X11) {
7049 			auto disp = XDisplayConnection.get();
7050 			auto root = RootWindow(disp, DefaultScreen(disp));
7051 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7052 			XFlush(disp);
7053 		} else throw new NotYetImplementedException();
7054 	}
7055 }
7056 
7057 
7058 
7059 /++
7060 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7061 
7062 	See_Also:
7063 	$(LIST
7064 		*[ScreenPainter]
7065 		*[ScreenPainter.rasterOp]
7066 	)
7067 +/
7068 enum RasterOp {
7069 	normal, /// Replaces the pixel.
7070 	xor, /// Uses bitwise xor to draw.
7071 }
7072 
7073 // being phobos-free keeps the size WAY down
7074 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7075 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7076 package(arsd) const(wchar)* toWStringz(string s) {
7077 	wstring r;
7078 	foreach(dchar c; s)
7079 		r ~= c;
7080 	r ~= '\0';
7081 	return r.ptr;
7082 }
7083 private string[] split(in void[] a, char c) {
7084 		string[] ret;
7085 		size_t previous = 0;
7086 		foreach(i, char ch; cast(ubyte[]) a) {
7087 			if(ch == c) {
7088 				ret ~= cast(string) a[previous .. i];
7089 				previous = i + 1;
7090 			}
7091 		}
7092 		if(previous != a.length)
7093 			ret ~= cast(string) a[previous .. $];
7094 		return ret;
7095 	}
7096 
7097 version(without_opengl) {
7098 	enum OpenGlOptions {
7099 		no,
7100 	}
7101 } else {
7102 	/++
7103 		Determines if you want an OpenGL context created on the new window.
7104 
7105 
7106 		See more: [#topics-3d|in the 3d topic].
7107 
7108 		---
7109 		import arsd.simpledisplay;
7110 		void main() {
7111 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7112 
7113 			// Set up the matrix
7114 			window.setAsCurrentOpenGlContext(); // make this window active
7115 
7116 			// This is called on each frame, we will draw our scene
7117 			window.redrawOpenGlScene = delegate() {
7118 
7119 			};
7120 
7121 			window.eventLoop(0);
7122 		}
7123 		---
7124 	+/
7125 	enum OpenGlOptions {
7126 		no, /// No OpenGL context is created
7127 		yes, /// Yes, create an OpenGL context
7128 	}
7129 
7130 	version(X11) {
7131 		static if (!SdpyIsUsingIVGLBinds) {
7132 
7133 
7134 			struct __GLXFBConfigRec {}
7135 			alias GLXFBConfig = __GLXFBConfigRec*;
7136 
7137 			//pragma(lib, "GL");
7138 			//pragma(lib, "GLU");
7139 			interface GLX {
7140 			extern(C) nothrow @nogc {
7141 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7142 						const int *attrib_list);
7143 
7144 				 void glXCopyContext(Display *dpy, GLXContext src,
7145 						GLXContext dst, arch_ulong mask);
7146 
7147 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7148 						GLXContext share_list, Bool direct);
7149 
7150 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7151 						Pixmap pixmap);
7152 
7153 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7154 
7155 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7156 
7157 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7158 						int attrib, int *value);
7159 
7160 				 GLXContext glXGetCurrentContext();
7161 
7162 				 GLXDrawable glXGetCurrentDrawable();
7163 
7164 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
7165 
7166 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
7167 						GLXContext ctx);
7168 
7169 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
7170 
7171 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
7172 
7173 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
7174 
7175 				 void glXUseXFont(Font font, int first, int count, int list_base);
7176 
7177 				 void glXWaitGL();
7178 
7179 				 void glXWaitX();
7180 
7181 
7182 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
7183 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
7184 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
7185 
7186 				char* glXQueryExtensionsString (Display*, int);
7187 				void* glXGetProcAddress (const(char)*);
7188 
7189 			}
7190 			}
7191 
7192 			version(OSX)
7193 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
7194 			else
7195 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
7196 			shared static this() {
7197 				glx.loadDynamicLibrary();
7198 			}
7199 
7200 			alias glbindGetProcAddress = glXGetProcAddress;
7201 		}
7202 	} else version(Windows) {
7203 		/* it is done below by interface GL */
7204 	} else
7205 		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.");
7206 }
7207 
7208 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
7209 alias Resizablity = Resizability;
7210 
7211 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
7212 enum Resizability {
7213 	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.
7214 	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.
7215 	/++
7216 		$(PITFALL
7217 			Planned for the future but not implemented.
7218 		)
7219 
7220 		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.
7221 
7222 		History:
7223 			Added November 11, 2022, but not yet implemented and may not be for some time.
7224 	+/
7225 	/*@__future*/ allowResizingMaintainingAspectRatio,
7226 	/++
7227 		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.
7228 
7229 		History:
7230 			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.
7231 
7232 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
7233 	+/
7234 	automaticallyScaleIfPossible,
7235 }
7236 /// ditto
7237 alias Resizeability = Resizability;
7238 
7239 
7240 /++
7241 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
7242 +/
7243 enum TextAlignment : uint {
7244 	Left = 0, ///
7245 	Center = 1, ///
7246 	Right = 2, ///
7247 
7248 	VerticalTop = 0, ///
7249 	VerticalCenter = 4, ///
7250 	VerticalBottom = 8, ///
7251 }
7252 
7253 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
7254 alias Rectangle = arsd.color.Rectangle;
7255 
7256 
7257 /++
7258 	Keyboard press and release events.
7259 +/
7260 struct KeyEvent {
7261 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
7262 	Key key;
7263 	ubyte hardwareCode; /// A platform and hardware specific code for the key
7264 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
7265 
7266 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
7267 
7268 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
7269 
7270 	SimpleWindow window; /// associated Window
7271 
7272 	/++
7273 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
7274 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
7275 		to predict if char events are actually coming..
7276 
7277 		Only available on X systems since this information is not given ahead of time elsewhere.
7278 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
7279 
7280 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
7281 		and potential quirks I'd recommend avoiding it.
7282 
7283 		History:
7284 			Added April 26, 2021 (dub v9.5)
7285 	+/
7286 	version(X11)
7287 		dchar[] charsPossible;
7288 
7289 	// convert key event to simplified string representation a-la emacs
7290 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
7291 		uint dpos = 0;
7292 		void put (const(char)[] s...) nothrow @trusted {
7293 			static if (growdest) {
7294 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
7295 			} else {
7296 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
7297 			}
7298 		}
7299 
7300 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
7301 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
7302 		}
7303 
7304 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
7305 
7306 		// put modifiers
7307 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
7308 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
7309 		putMod(ModifierState.alt, Key.Alt, "Alt+");
7310 		putMod(ModifierState.windows, Key.Shift, "Windows+");
7311 		putMod(ModifierState.shift, Key.Shift, "Shift+");
7312 
7313 		if (this.key) {
7314 			foreach (string kn; __traits(allMembers, Key)) {
7315 				if (this.key == __traits(getMember, Key, kn)) {
7316 					// HACK!
7317 					static if (kn == "N0") put("0");
7318 					else static if (kn == "N1") put("1");
7319 					else static if (kn == "N2") put("2");
7320 					else static if (kn == "N3") put("3");
7321 					else static if (kn == "N4") put("4");
7322 					else static if (kn == "N5") put("5");
7323 					else static if (kn == "N6") put("6");
7324 					else static if (kn == "N7") put("7");
7325 					else static if (kn == "N8") put("8");
7326 					else static if (kn == "N9") put("9");
7327 					else put(kn);
7328 					return dest[0..dpos];
7329 				}
7330 			}
7331 			put("Unknown");
7332 		} else {
7333 			if (dpos && dest[dpos-1] == '+') --dpos;
7334 		}
7335 		return dest[0..dpos];
7336 	}
7337 
7338 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
7339 
7340 	/** Parse string into key name with modifiers. It accepts things like:
7341 	 *
7342 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
7343 	 *
7344 	 * Ctrl+Win+1 -- windows style
7345 	 *
7346 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
7347 	 *
7348 	 * Ctrl Win 1 -- and space
7349 	 *
7350 	 * and even "Win + 1 + Ctrl".
7351 	 */
7352 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
7353 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
7354 
7355 		// remove trailing spaces
7356 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
7357 
7358 		// tokens delimited by blank, '+', or '-'
7359 		// null on eol
7360 		const(char)[] getToken () nothrow @trusted @nogc {
7361 			// remove leading spaces and delimiters
7362 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
7363 			if (name.length == 0) return null; // oops, no more tokens
7364 			// get token
7365 			size_t epos = 0;
7366 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
7367 			assert(epos > 0 && epos <= name.length);
7368 			auto res = name[0..epos];
7369 			name = name[epos..$];
7370 			return res;
7371 		}
7372 
7373 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
7374 			if (s0.length != s1.length) return false;
7375 			foreach (immutable ci, char c0; s0) {
7376 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
7377 				char c1 = s1[ci];
7378 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
7379 				if (c0 != c1) return false;
7380 			}
7381 			return true;
7382 		}
7383 
7384 		if (ignoreModsOut !is null) *ignoreModsOut = false;
7385 		if (updown !is null) *updown = -1;
7386 		KeyEvent res;
7387 		res.key = cast(Key)0; // just in case
7388 		const(char)[] tk, tkn; // last token
7389 		bool allowEmascStyle = true;
7390 		bool ignoreModifiers = false;
7391 		tokenloop: for (;;) {
7392 			tk = tkn;
7393 			tkn = getToken();
7394 			//k8: yay, i took "Bloody Mess" trait from Fallout!
7395 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
7396 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
7397 			if (allowEmascStyle && tkn.length != 0) {
7398 				if (tk.length == 1) {
7399 					char mdc = tk[0];
7400 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
7401 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7402 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7403 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7404 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7405 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
7406 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
7407 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
7408 				}
7409 			}
7410 			allowEmascStyle = false;
7411 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
7412 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
7413 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
7414 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
7415 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
7416 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
7417 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
7418 			if (tk.length == 0) continue;
7419 			// try key name
7420 			if (res.key == 0) {
7421 				// little hack
7422 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
7423 					final switch (tk[0]) {
7424 						case '0': tk = "N0"; break;
7425 						case '1': tk = "N1"; break;
7426 						case '2': tk = "N2"; break;
7427 						case '3': tk = "N3"; break;
7428 						case '4': tk = "N4"; break;
7429 						case '5': tk = "N5"; break;
7430 						case '6': tk = "N6"; break;
7431 						case '7': tk = "N7"; break;
7432 						case '8': tk = "N8"; break;
7433 						case '9': tk = "N9"; break;
7434 					}
7435 				}
7436 				foreach (string kn; __traits(allMembers, Key)) {
7437 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
7438 				}
7439 			}
7440 			// unknown or duplicate key name, get out of here
7441 			break;
7442 		}
7443 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
7444 		return res; // something
7445 	}
7446 
7447 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
7448 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
7449 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
7450 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
7451 		}
7452 		bool ignoreMods;
7453 		int updown;
7454 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
7455 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
7456 		if (this.key != ke.key) {
7457 			// things like "ctrl+alt" are complicated
7458 			uint tkm = this.modifierState&modmask;
7459 			uint kkm = ke.modifierState&modmask;
7460 			Key tk = this.key;
7461 			// ke
7462 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
7463 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
7464 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
7465 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
7466 			// this
7467 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
7468 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
7469 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
7470 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
7471 			return (tk == ke.key && tkm == kkm);
7472 		}
7473 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
7474 	}
7475 }
7476 
7477 /// Sets the application name.
7478 @property string ApplicationName(string name) {
7479 	return _applicationName = name;
7480 }
7481 
7482 string _applicationName;
7483 
7484 /// ditto
7485 @property string ApplicationName() {
7486 	if(_applicationName is null) {
7487 		import core.runtime;
7488 		return Runtime.args[0];
7489 	}
7490 	return _applicationName;
7491 }
7492 
7493 
7494 /// Type of a [MouseEvent].
7495 enum MouseEventType : int {
7496 	motion = 0, /// The mouse moved inside the window
7497 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
7498 	buttonReleased = 2, /// A mouse button was released
7499 }
7500 
7501 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
7502 /++
7503 	Listen for this on your event listeners if you are interested in mouse action.
7504 
7505 	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.
7506 
7507 	Examples:
7508 
7509 	This will draw boxes on the window with the mouse as you hold the left button.
7510 	---
7511 	import arsd.simpledisplay;
7512 
7513 	void main() {
7514 		auto window = new SimpleWindow();
7515 
7516 		window.eventLoop(0,
7517 			(MouseEvent ev) {
7518 				if(ev.modifierState & ModifierState.leftButtonDown) {
7519 					auto painter = window.draw();
7520 					painter.fillColor = Color.red;
7521 					painter.outlineColor = Color.black;
7522 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
7523 				}
7524 			}
7525 		);
7526 	}
7527 	---
7528 +/
7529 struct MouseEvent {
7530 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
7531 
7532 	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.
7533 	int y; /// Current Y position of the cursor when the event fired.
7534 
7535 	int dx; /// Change in X position since last report
7536 	int dy; /// Change in Y position since last report
7537 
7538 	MouseButton button; /// See [MouseButton]
7539 	int modifierState; /// See [ModifierState]
7540 
7541 	version(X11)
7542 		private Time timestamp;
7543 
7544 	/// Returns a linear representation of mouse button,
7545 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
7546 	///
7547 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
7548 	@property ubyte buttonLinear() const {
7549 		import core.bitop;
7550 		if(button == 0)
7551 			return 0;
7552 		return (bsf(button) + 1) & 0b1111;
7553 	}
7554 
7555 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7556 
7557 	SimpleWindow window; /// The window in which the event happened.
7558 
7559 	Point globalCoordinates() {
7560 		Point p;
7561 		if(window is null)
7562 			throw new Exception("wtf");
7563 		static if(UsingSimpledisplayX11) {
7564 			Window child;
7565 			XTranslateCoordinates(
7566 				XDisplayConnection.get,
7567 				window.impl.window,
7568 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7569 				x, y, &p.x, &p.y, &child);
7570 			return p;
7571 		} else version(Windows) {
7572 			POINT[1] points;
7573 			points[0].x = x;
7574 			points[0].y = y;
7575 			MapWindowPoints(
7576 				window.impl.hwnd,
7577 				null,
7578 				points.ptr,
7579 				points.length
7580 			);
7581 			p.x = points[0].x;
7582 			p.y = points[0].y;
7583 
7584 			return p;
7585 		} else version(OSXCocoa) {
7586 			throw new NotYetImplementedException();
7587 		} else static assert(0);
7588 	}
7589 
7590 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7591 
7592 	/**
7593 	can contain emacs-like modifier prefix
7594 	case-insensitive names:
7595 		lmbX/leftX
7596 		rmbX/rightX
7597 		mmbX/middleX
7598 		wheelX
7599 		motion (no prefix allowed)
7600 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7601 	*/
7602 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7603 		if (str.length == 0) return false; // just in case
7604 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7605 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7606 		auto anchor = str;
7607 		uint mods = 0; // uint.max == any
7608 		// interesting bits in kmod
7609 		uint kmodmask =
7610 			ModifierState.shift|
7611 			ModifierState.ctrl|
7612 			ModifierState.alt|
7613 			ModifierState.windows|
7614 			ModifierState.leftButtonDown|
7615 			ModifierState.middleButtonDown|
7616 			ModifierState.rightButtonDown|
7617 			0;
7618 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7619 		bool wasButtons = false;
7620 		while (str.length) {
7621 			if (str.ptr[0] <= ' ') {
7622 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7623 				continue;
7624 			}
7625 			// one-letter modifier?
7626 			if (str.length >= 2 && str.ptr[1] == '-') {
7627 				switch (str.ptr[0]) {
7628 					case '*': // "any" modifier (cannot be undone)
7629 						mods = mods.max;
7630 						break;
7631 					case 'C': case 'c': // emacs "ctrl"
7632 						if (mods != mods.max) mods |= ModifierState.ctrl;
7633 						break;
7634 					case 'M': case 'm': // emacs "meta"
7635 						if (mods != mods.max) mods |= ModifierState.alt;
7636 						break;
7637 					case 'S': case 's': // emacs "shift"
7638 						if (mods != mods.max) mods |= ModifierState.shift;
7639 						break;
7640 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7641 						if (mods != mods.max) mods |= ModifierState.windows;
7642 						break;
7643 					default:
7644 						return false; // unknown modifier
7645 				}
7646 				str = str[2..$];
7647 				continue;
7648 			}
7649 			// word
7650 			char[16] buf = void; // locased
7651 			auto wep = 0;
7652 			while (str.length) {
7653 				immutable char ch = str.ptr[0];
7654 				if (ch <= ' ' || ch == '-') break;
7655 				str = str[1..$];
7656 				if (wep > buf.length) return false; // too long
7657 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7658 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7659 				else return false; // invalid char
7660 			}
7661 			if (wep == 0) return false; // just in case
7662 			uint bnum;
7663 			enum UpDown { None = -1, Up, Down, Any }
7664 			auto updown = UpDown.None; // 0: up; 1: down
7665 			switch (buf[0..wep]) {
7666 				// left button
7667 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7668 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7669 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7670 				case "lmb": case "left": bnum = 0; break;
7671 				// middle button
7672 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7673 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7674 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7675 				case "mmb": case "middle": bnum = 1; break;
7676 				// right button
7677 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7678 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7679 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7680 				case "rmb": case "right": bnum = 2; break;
7681 				// wheel
7682 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7683 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7684 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7685 				case "wheel": bnum = 3; break;
7686 				// motion
7687 				case "motion": bnum = 7; break;
7688 				// unknown
7689 				default: return false;
7690 			}
7691 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7692 			// parse possible "-up" or "-down"
7693 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7694 				wep = 0;
7695 				foreach (immutable idx, immutable char ch; str[1..$]) {
7696 					if (ch <= ' ' || ch == '-') break;
7697 					assert(idx == wep); // for now; trick
7698 					if (wep > buf.length) { wep = 0; break; } // too long
7699 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7700 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7701 					else { wep = 0; break; } // invalid char
7702 				}
7703 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7704 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7705 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7706 				// remove parsed part
7707 				if (updown != UpDown.None) str = str[wep+1..$];
7708 			}
7709 			if (updown == UpDown.None) {
7710 				updown = UpDown.Down;
7711 			}
7712 			wasButtons = wasButtons || (bnum <= 2);
7713 			//assert(updown != UpDown.None);
7714 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7715 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7716 			if (lastButt != lastButt.max) {
7717 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7718 				if (mods != mods.max) {
7719 					uint butbit = 0;
7720 					final switch (lastButt&0x03) {
7721 						case 0: butbit = ModifierState.leftButtonDown; break;
7722 						case 1: butbit = ModifierState.middleButtonDown; break;
7723 						case 2: butbit = ModifierState.rightButtonDown; break;
7724 					}
7725 					     if (lastButt&Flag.Down) mods |= butbit;
7726 					else if (lastButt&Flag.Up) mods &= ~butbit;
7727 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7728 				}
7729 			}
7730 			// remember last button
7731 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7732 		}
7733 		// no button -- nothing to do
7734 		if (lastButt == lastButt.max) return false;
7735 		// done parsing, check if something's left
7736 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7737 		// remove action button from mask
7738 		if ((lastButt&0xff) < 3) {
7739 			final switch (lastButt&0x03) {
7740 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7741 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7742 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7743 			}
7744 		}
7745 		// special case: "Motion" means "ignore buttons"
7746 		if ((lastButt&0xff) == 7 && !wasButtons) {
7747 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7748 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7749 		}
7750 		uint kmod = event.modifierState&kmodmask;
7751 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7752 		// check modifier state
7753 		if (mods != mods.max) {
7754 			if (kmod != mods) return false;
7755 		}
7756 		// now check type
7757 		if ((lastButt&0xff) == 7) {
7758 			// motion
7759 			if (event.type != MouseEventType.motion) return false;
7760 		} else if ((lastButt&0xff) == 3) {
7761 			// wheel
7762 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7763 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7764 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7765 			return false;
7766 		} else {
7767 			// buttons
7768 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7769 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7770 			{
7771 				return false;
7772 			}
7773 			// button number
7774 			switch (lastButt&0x03) {
7775 				case 0: if (event.button != MouseButton.left) return false; break;
7776 				case 1: if (event.button != MouseButton.middle) return false; break;
7777 				case 2: if (event.button != MouseButton.right) return false; break;
7778 				default: return false;
7779 			}
7780 		}
7781 		return true;
7782 	}
7783 }
7784 
7785 version(arsd_mevent_strcmp_test) unittest {
7786 	MouseEvent event;
7787 	event.type = MouseEventType.buttonPressed;
7788 	event.button = MouseButton.left;
7789 	event.modifierState = ModifierState.ctrl;
7790 	assert(event == "C-LMB");
7791 	assert(event != "C-LMBUP");
7792 	assert(event != "C-LMB-UP");
7793 	assert(event != "C-S-LMB");
7794 	assert(event == "*-LMB");
7795 	assert(event != "*-LMB-UP");
7796 
7797 	event.type = MouseEventType.buttonReleased;
7798 	assert(event != "C-LMB");
7799 	assert(event == "C-LMBUP");
7800 	assert(event == "C-LMB-UP");
7801 	assert(event != "C-S-LMB");
7802 	assert(event != "*-LMB");
7803 	assert(event == "*-LMB-UP");
7804 
7805 	event.button = MouseButton.right;
7806 	event.modifierState |= ModifierState.shift;
7807 	event.type = MouseEventType.buttonPressed;
7808 	assert(event != "C-LMB");
7809 	assert(event != "C-LMBUP");
7810 	assert(event != "C-LMB-UP");
7811 	assert(event != "C-S-LMB");
7812 	assert(event != "*-LMB");
7813 	assert(event != "*-LMB-UP");
7814 
7815 	assert(event != "C-RMB");
7816 	assert(event != "C-RMBUP");
7817 	assert(event != "C-RMB-UP");
7818 	assert(event == "C-S-RMB");
7819 	assert(event == "*-RMB");
7820 	assert(event != "*-RMB-UP");
7821 }
7822 
7823 /// This gives a few more options to drawing lines and such
7824 struct Pen {
7825 	Color color; /// the foreground color
7826 	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.
7827 	Style style; /// See [Style]
7828 /+
7829 // From X.h
7830 
7831 #define LineSolid		0
7832 #define LineOnOffDash		1
7833 #define LineDoubleDash		2
7834        LineDou-        The full path of the line is drawn, but the
7835        bleDash         even dashes are filled differently from the
7836                        odd dashes (see fill-style) with CapButt
7837                        style used where even and odd dashes meet.
7838 
7839 
7840 
7841 /* capStyle */
7842 
7843 #define CapNotLast		0
7844 #define CapButt			1
7845 #define CapRound		2
7846 #define CapProjecting		3
7847 
7848 /* joinStyle */
7849 
7850 #define JoinMiter		0
7851 #define JoinRound		1
7852 #define JoinBevel		2
7853 
7854 /* fillStyle */
7855 
7856 #define FillSolid		0
7857 #define FillTiled		1
7858 #define FillStippled		2
7859 #define FillOpaqueStippled	3
7860 
7861 
7862 +/
7863 	/// Style of lines drawn
7864 	enum Style {
7865 		Solid, /// a solid line
7866 		Dashed, /// a dashed line
7867 		Dotted, /// a dotted line
7868 	}
7869 }
7870 
7871 
7872 /++
7873 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7874 
7875 
7876 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7877 
7878 	$(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.)
7879 
7880 	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.
7881 
7882 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7883 
7884 	$(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.
7885 
7886 	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!
7887 
7888 	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!)
7889 
7890 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7891 
7892 	---
7893 		auto image = new Image(256, 256);
7894 		scope(exit) destroy(image);
7895 	---
7896 
7897 	As long as you don't hold on to it outside the scope.
7898 
7899 	I might change it to be an owned pointer at some point in the future.
7900 
7901 	)
7902 
7903 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7904 	you can also often get a fair amount of speedup by getting the raw data format and
7905 	writing some custom code.
7906 
7907 	FIXME INSERT EXAMPLES HERE
7908 
7909 
7910 +/
7911 final class Image {
7912 	///
7913 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7914 		this.width = width;
7915 		this.height = height;
7916 		this.enableAlpha = enableAlpha;
7917 
7918 		impl.createImage(width, height, forcexshm, enableAlpha);
7919 	}
7920 
7921 	///
7922 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7923 		this(size.width, size.height, forcexshm, enableAlpha);
7924 	}
7925 
7926 	private bool suppressDestruction;
7927 
7928 	version(X11)
7929 	this(XImage* handle) {
7930 		this.handle = handle;
7931 		this.rawData = cast(ubyte*) handle.data;
7932 		this.width = handle.width;
7933 		this.height = handle.height;
7934 		this.enableAlpha = handle.depth == 32;
7935 		suppressDestruction = true;
7936 	}
7937 
7938 	~this() {
7939 		if(suppressDestruction) return;
7940 		impl.dispose();
7941 	}
7942 
7943 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7944 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7945 	pure const @system nothrow {
7946 		/*
7947 			To use these to draw a blue rectangle with size WxH at position X,Y...
7948 
7949 			// make certain that it will fit before we proceed
7950 			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!
7951 
7952 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7953 			// (though calculating them isn't really that expensive).
7954 			auto nextLineAdjustment = img.adjustmentForNextLine();
7955 			auto offR = img.redByteOffset();
7956 			auto offB = img.blueByteOffset();
7957 			auto offG = img.greenByteOffset();
7958 			auto bpp = img.bytesPerPixel();
7959 
7960 			auto data = img.getDataPointer();
7961 
7962 			// figure out the starting byte offset
7963 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7964 
7965 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7966 
7967 			// and now our drawing loop for the rectangle
7968 			foreach(y; 0 .. H) {
7969 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7970 				foreach(x; 0 .. W) {
7971 					// write our color
7972 					data[offR] = 0;
7973 					data[offG] = 0;
7974 					data[offB] = 255;
7975 
7976 					data += bpp; // moving to the next pixel is just an addition...
7977 				}
7978 				startOfLine += nextLineAdjustment;
7979 			}
7980 
7981 
7982 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7983 
7984 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7985 			can be made into a bitmask or something so we can write them as *uint...
7986 		*/
7987 
7988 		///
7989 		int offsetForTopLeftPixel() {
7990 			version(X11) {
7991 				return 0;
7992 			} else version(Windows) {
7993 				if(enableAlpha) {
7994 					return (width * 4) * (height - 1);
7995 				} else {
7996 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7997 				}
7998 			} else version(OSXCocoa) {
7999 				return 0 ; //throw new NotYetImplementedException();
8000 			} else static assert(0, "fill in this info for other OSes");
8001 		}
8002 
8003 		///
8004 		int offsetForPixel(int x, int y) {
8005 			version(X11) {
8006 				auto offset = (y * width + x) * 4;
8007 				return offset;
8008 			} else version(Windows) {
8009 				if(enableAlpha) {
8010 					auto itemsPerLine = width * 4;
8011 					// remember, bmps are upside down
8012 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8013 					return offset;
8014 				} else {
8015 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8016 					// remember, bmps are upside down
8017 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8018 					return offset;
8019 				}
8020 			} else version(OSXCocoa) {
8021 				return 0 ; //throw new NotYetImplementedException();
8022 			} else static assert(0, "fill in this info for other OSes");
8023 		}
8024 
8025 		///
8026 		int adjustmentForNextLine() {
8027 			version(X11) {
8028 				return width * 4;
8029 			} else version(Windows) {
8030 				// windows bmps are upside down, so the adjustment is actually negative
8031 				if(enableAlpha)
8032 					return - (cast(int) width * 4);
8033 				else
8034 					return -((cast(int) width * 3 + 3) / 4) * 4;
8035 			} else version(OSXCocoa) {
8036 				return 0 ; //throw new NotYetImplementedException();
8037 			} else static assert(0, "fill in this info for other OSes");
8038 		}
8039 
8040 		/// once you have the position of a pixel, use these to get to the proper color
8041 		int redByteOffset() {
8042 			version(X11) {
8043 				return 2;
8044 			} else version(Windows) {
8045 				return 2;
8046 			} else version(OSXCocoa) {
8047 				return 0 ; //throw new NotYetImplementedException();
8048 			} else static assert(0, "fill in this info for other OSes");
8049 		}
8050 
8051 		///
8052 		int greenByteOffset() {
8053 			version(X11) {
8054 				return 1;
8055 			} else version(Windows) {
8056 				return 1;
8057 			} else version(OSXCocoa) {
8058 				return 0 ; //throw new NotYetImplementedException();
8059 			} else static assert(0, "fill in this info for other OSes");
8060 		}
8061 
8062 		///
8063 		int blueByteOffset() {
8064 			version(X11) {
8065 				return 0;
8066 			} else version(Windows) {
8067 				return 0;
8068 			} else version(OSXCocoa) {
8069 				return 0 ; //throw new NotYetImplementedException();
8070 			} else static assert(0, "fill in this info for other OSes");
8071 		}
8072 
8073 		/// Only valid if [enableAlpha] is true
8074 		int alphaByteOffset() {
8075 			version(X11) {
8076 				return 3;
8077 			} else version(Windows) {
8078 				return 3;
8079 			} else version(OSXCocoa) {
8080 				return 3; //throw new NotYetImplementedException();
8081 			} else static assert(0, "fill in this info for other OSes");
8082 		}
8083 	}
8084 
8085 	///
8086 	final void putPixel(int x, int y, Color c) {
8087 		if(x < 0 || x >= width)
8088 			return;
8089 		if(y < 0 || y >= height)
8090 			return;
8091 
8092 		impl.setPixel(x, y, c);
8093 	}
8094 
8095 	///
8096 	final Color getPixel(int x, int y) {
8097 		if(x < 0 || x >= width)
8098 			return Color.transparent;
8099 		if(y < 0 || y >= height)
8100 			return Color.transparent;
8101 
8102 		version(OSXCocoa) throw new NotYetImplementedException(); else
8103 		return impl.getPixel(x, y);
8104 	}
8105 
8106 	///
8107 	final void opIndexAssign(Color c, int x, int y) {
8108 		putPixel(x, y, c);
8109 	}
8110 
8111 	///
8112 	TrueColorImage toTrueColorImage() {
8113 		auto tci = new TrueColorImage(width, height);
8114 		convertToRgbaBytes(tci.imageData.bytes);
8115 		return tci;
8116 	}
8117 
8118 	///
8119 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8120 		auto tci = i.getAsTrueColorImage();
8121 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8122 		static if(UsingSimpledisplayX11)
8123 			img.premultiply = premultiply;
8124 		img.setRgbaBytes(tci.imageData.bytes);
8125 		return img;
8126 	}
8127 
8128 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8129 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8130 	/// if you pass null, it will allocate a new one.
8131 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8132 		if(where is null)
8133 			where = new ubyte[this.width*this.height*4];
8134 		convertToRgbaBytes(where);
8135 		return where;
8136 	}
8137 
8138 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
8139 	void setRgbaBytes(in ubyte[] from ) {
8140 		assert(from.length == this.width * this.height * 4);
8141 		setFromRgbaBytes(from);
8142 	}
8143 
8144 	// FIXME: make properly cross platform by getting rgba right
8145 
8146 	/// warning: this is not portable across platforms because the data format can change
8147 	ubyte* getDataPointer() {
8148 		return impl.rawData;
8149 	}
8150 
8151 	/// for use with getDataPointer
8152 	final int bytesPerLine() const pure @safe nothrow {
8153 		version(Windows)
8154 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
8155 		else version(X11)
8156 			return 4 * width;
8157 		else version(OSXCocoa)
8158 			return 4 * width;
8159 		else static assert(0);
8160 	}
8161 
8162 	/// for use with getDataPointer
8163 	final int bytesPerPixel() const pure @safe nothrow {
8164 		version(Windows)
8165 			return enableAlpha ? 4 : 3;
8166 		else version(X11)
8167 			return 4;
8168 		else version(OSXCocoa)
8169 			return 4;
8170 		else static assert(0);
8171 	}
8172 
8173 	///
8174 	immutable int width;
8175 
8176 	///
8177 	immutable int height;
8178 
8179 	///
8180 	immutable bool enableAlpha;
8181     //private:
8182 	mixin NativeImageImplementation!() impl;
8183 }
8184 
8185 /++
8186 	A convenience function to pop up a window displaying the image.
8187 	If you pass a win, it will draw the image in it. Otherwise, it will
8188 	create a window with the size of the image and run its event loop, closing
8189 	when a key is pressed.
8190 
8191 	History:
8192 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
8193 		always block until the application quit which could cause bizarre behavior
8194 		inside a more complex application. Now, the default is to block until
8195 		this window closes if it is the only event loop running, and otherwise,
8196 		not to block at all and just pop up the display window asynchronously.
8197 +/
8198 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
8199 	if(win is null) {
8200 		win = new SimpleWindow(image);
8201 		{
8202 			auto p = win.draw;
8203 			p.drawImage(Point(0, 0), image);
8204 		}
8205 		win.eventLoopWithBlockingMode(
8206 			bm, 0,
8207 			(KeyEvent ev) {
8208 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
8209 			} );
8210 	} else {
8211 		win.image = image;
8212 	}
8213 }
8214 
8215 enum FontWeight : int {
8216 	dontcare = 0,
8217 	thin = 100,
8218 	extralight = 200,
8219 	light = 300,
8220 	regular = 400,
8221 	medium = 500,
8222 	semibold = 600,
8223 	bold = 700,
8224 	extrabold = 800,
8225 	heavy = 900
8226 }
8227 
8228 /++
8229 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
8230 
8231 	History:
8232 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
8233 +/
8234 interface MeasurableFont {
8235 	/++
8236 		Returns true if it is a monospace font, meaning each of the
8237 		glyphs (at least the ascii characters) have matching width
8238 		and no kerning, so you can determine the display width of some
8239 		strings by simply multiplying the string width by [averageWidth].
8240 
8241 		(Please note that multiply doesn't $(I actually) work in general,
8242 		consider characters like tab and newline, but it does sometimes.)
8243 	+/
8244 	bool isMonospace();
8245 
8246 	/++
8247 		The average width of glyphs in the font, traditionally equal to the
8248 		width of the lowercase x. Can be used to estimate bounding boxes,
8249 		especially if the font [isMonospace].
8250 
8251 		Given in pixels.
8252 	+/
8253 	int averageWidth();
8254 	/++
8255 		The height of the bounding box of a line.
8256 	+/
8257 	int height();
8258 	/++
8259 		The maximum ascent of a glyph above the baseline.
8260 
8261 		Given in pixels.
8262 	+/
8263 	int ascent();
8264 	/++
8265 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
8266 
8267 		Given in pixels.
8268 	+/
8269 	int descent();
8270 	/++
8271 		The display width of the given string, and if you provide a window, it will use it to
8272 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
8273 
8274 		Given in pixels.
8275 	+/
8276 	int stringWidth(scope const(char)[] s, SimpleWindow window = null);
8277 
8278 }
8279 
8280 // FIXME: i need a font cache and it needs to handle disconnects.
8281 
8282 /++
8283 	Represents a font loaded off the operating system or the X server.
8284 
8285 
8286 	While the api here is unified cross platform, the fonts are not necessarily
8287 	available, even across machines of the same platform, so be sure to always check
8288 	for null (using [isNull]) and have a fallback plan.
8289 
8290 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
8291 
8292 	Worst case, a null font will automatically fall back to the default font loaded
8293 	for your system.
8294 +/
8295 class OperatingSystemFont : MeasurableFont {
8296 	// FIXME: when the X Connection is lost, these need to be invalidated!
8297 	// that means I need to store the original stuff again to reconstruct it too.
8298 
8299 	version(X11) {
8300 		XFontStruct* font;
8301 		XFontSet fontset;
8302 
8303 		version(with_xft) {
8304 			XftFont* xftFont;
8305 			bool isXft;
8306 		}
8307 	} else version(Windows) {
8308 		HFONT font;
8309 		int width_;
8310 		int height_;
8311 	} else version(OSXCocoa) {
8312 		NSFont font;
8313 	} else static assert(0);
8314 
8315 	/++
8316 		Constructs the class and immediately calls [load].
8317 	+/
8318 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8319 		load(name, size, weight, italic);
8320 	}
8321 
8322 	/++
8323 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
8324 
8325 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
8326 
8327 		History:
8328 			Added January 24, 2021.
8329 	+/
8330 	this() {
8331 		// this space intentionally left blank
8332 	}
8333 
8334 	/++
8335 		Constructs a copy of the given font object.
8336 
8337 		History:
8338 			Added January 7, 2023.
8339 	+/
8340 	this(OperatingSystemFont font) {
8341 		if(font is null || font.loadedInfo is LoadedInfo.init)
8342 			loadDefault();
8343 		else
8344 			load(font.loadedInfo.tupleof);
8345 	}
8346 
8347 	/++
8348 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
8349 
8350 		History:
8351 			Added November 13, 2020.
8352 	+/
8353 	version(with_xft)
8354 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8355 		unload();
8356 
8357 		if(!XftLibrary.attempted) {
8358 			XftLibrary.loadDynamicLibrary();
8359 		}
8360 
8361 		if(!XftLibrary.loadSuccessful)
8362 			return false;
8363 
8364 		auto display = XDisplayConnection.get;
8365 
8366 		char[256] nameBuffer = void;
8367 		int nbp = 0;
8368 
8369 		void add(in char[] a) {
8370 			nameBuffer[nbp .. nbp + a.length] = a[];
8371 			nbp += a.length;
8372 		}
8373 		add(name);
8374 
8375 		if(size) {
8376 			add(":size=");
8377 			add(toInternal!string(size));
8378 		}
8379 		if(weight != FontWeight.dontcare) {
8380 			add(":weight=");
8381 			add(weightToString(weight));
8382 		}
8383 		if(italic)
8384 			add(":slant=100");
8385 
8386 		nameBuffer[nbp] = 0;
8387 
8388 		this.xftFont = XftFontOpenName(
8389 			display,
8390 			DefaultScreen(display),
8391 			nameBuffer.ptr
8392 		);
8393 
8394 		this.isXft = true;
8395 
8396 		if(xftFont !is null) {
8397 			isMonospace_ = stringWidth("x") == stringWidth("M");
8398 			ascent_ = xftFont.ascent;
8399 			descent_ = xftFont.descent;
8400 		}
8401 
8402 		return !isNull();
8403 	}
8404 
8405 	/++
8406 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
8407 
8408 
8409 		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.
8410 
8411 		If `pattern` is null, it returns all available font families.
8412 
8413 		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.
8414 
8415 		The format of the pattern is platform-specific.
8416 
8417 		History:
8418 			Added May 1, 2021 (dub v9.5)
8419 	+/
8420 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
8421 		version(Windows) {
8422 			auto hdc = GetDC(null);
8423 			scope(exit) ReleaseDC(null, hdc);
8424 			LOGFONT logfont;
8425 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
8426 				auto localHandler = *(cast(typeof(handler)*) p);
8427 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
8428 			}
8429 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
8430 		} else version(X11) {
8431 			//import core.stdc.stdio;
8432 			bool done = false;
8433 			version(with_xft) {
8434 				if(!XftLibrary.attempted) {
8435 					XftLibrary.loadDynamicLibrary();
8436 				}
8437 
8438 				if(!XftLibrary.loadSuccessful)
8439 					goto skipXft;
8440 
8441 				if(!FontConfigLibrary.attempted)
8442 					FontConfigLibrary.loadDynamicLibrary();
8443 				if(!FontConfigLibrary.loadSuccessful)
8444 					goto skipXft;
8445 
8446 				{
8447 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
8448 					if(got is null)
8449 						goto skipXft;
8450 					scope(exit) FcFontSetDestroy(got);
8451 
8452 					auto fontPatterns = got.fonts[0 .. got.nfont];
8453 					foreach(candidate; fontPatterns) {
8454 						char* where, whereStyle;
8455 
8456 						char* pmg = FcNameUnparse(candidate);
8457 
8458 						//FcPatternGetString(candidate, "family", 0, &where);
8459 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
8460 						//if(where && whereStyle) {
8461 						if(pmg) {
8462 							if(!handler(pmg.sliceCString))
8463 								return;
8464 							//printf("%s || %s %s\n", pmg, where, whereStyle);
8465 						}
8466 					}
8467 				}
8468 			}
8469 
8470 			skipXft:
8471 
8472 			if(pattern is null)
8473 				pattern = "*";
8474 
8475 			int count;
8476 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
8477 			scope(exit) XFreeFontNames(coreFontsRaw);
8478 
8479 			auto coreFonts = coreFontsRaw[0 .. count];
8480 
8481 			foreach(font; coreFonts) {
8482 				char[128] tmp;
8483 				tmp[0 ..5] = "core:";
8484 				auto cf = font.sliceCString;
8485 				if(5 + cf.length > tmp.length)
8486 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
8487 				tmp[5 .. 5 + cf.length] = cf;
8488 				if(!handler(tmp[0 .. 5 + cf.length]))
8489 					return;
8490 			}
8491 		}
8492 	}
8493 
8494 	/++
8495 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
8496 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
8497 
8498 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
8499 		underlying system doesn't support returning the raw bytes.
8500 
8501 		History:
8502 			Added September 10, 2021 (dub v10.3)
8503 	+/
8504 	ubyte[] getTtfBytes() {
8505 		if(isNull)
8506 			return null;
8507 
8508 		version(Windows) {
8509 			auto dc = GetDC(null);
8510 			auto orig = SelectObject(dc, font);
8511 
8512 			scope(exit) {
8513 				SelectObject(dc, orig);
8514 				ReleaseDC(null, dc);
8515 			}
8516 
8517 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
8518 			if(res == GDI_ERROR)
8519 				return null;
8520 
8521 			ubyte[] buffer = new ubyte[](res);
8522 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
8523 			if(res == GDI_ERROR)
8524 				return null; // wtf really tbh
8525 
8526 			return buffer;
8527 		} else version(with_xft) {
8528 			if(isXft && xftFont) {
8529 				if(!FontConfigLibrary.attempted)
8530 					FontConfigLibrary.loadDynamicLibrary();
8531 				if(!FontConfigLibrary.loadSuccessful)
8532 					return null;
8533 
8534 				char* file;
8535 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
8536 					if (file !is null && file[0]) {
8537 						import core.stdc.stdio;
8538 						auto fp = fopen(file, "rb");
8539 						if(fp is null)
8540 							return null;
8541 						scope(exit)
8542 							fclose(fp);
8543 						fseek(fp, 0, SEEK_END);
8544 						ubyte[] buffer = new ubyte[](ftell(fp));
8545 						fseek(fp, 0, SEEK_SET);
8546 
8547 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
8548 						if(got != buffer.length)
8549 							return null;
8550 
8551 						return buffer;
8552 					}
8553 				}
8554 			}
8555 			return null;
8556 		} else throw new NotYetImplementedException();
8557 	}
8558 
8559 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
8560 
8561 	private string weightToString(FontWeight weight) {
8562 		with(FontWeight)
8563 		final switch(weight) {
8564 			case dontcare: return "*";
8565 			case thin: return "extralight";
8566 			case extralight: return "extralight";
8567 			case light: return "light";
8568 			case regular: return "regular";
8569 			case medium: return "medium";
8570 			case semibold: return "demibold";
8571 			case bold: return "bold";
8572 			case extrabold: return "demibold";
8573 			case heavy: return "black";
8574 		}
8575 	}
8576 
8577 	/++
8578 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
8579 
8580 		History:
8581 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8582 	+/
8583 	version(X11)
8584 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8585 		unload();
8586 
8587 		string xfontstr;
8588 
8589 		if(name.length > 3 && name[0 .. 3] == "-*-") {
8590 			// this is kinda a disgusting hack but if the user sends an exact
8591 			// string I'd like to honor it...
8592 			xfontstr = name;
8593 		} else {
8594 			string weightstr = weightToString(weight);
8595 			string sizestr;
8596 			if(size == 0)
8597 				sizestr = "*";
8598 			else
8599 				sizestr = toInternal!string(size);
8600 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
8601 		}
8602 
8603 		// writeln(xfontstr);
8604 
8605 		auto display = XDisplayConnection.get;
8606 
8607 		font = XLoadQueryFont(display, xfontstr.ptr);
8608 		if(font is null)
8609 			return false;
8610 
8611 		char** lol;
8612 		int lol2;
8613 		char* lol3;
8614 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8615 
8616 		prepareFontInfo();
8617 
8618 		return !isNull();
8619 	}
8620 
8621 	version(X11)
8622 	private void prepareFontInfo() {
8623 		if(font !is null) {
8624 			isMonospace_ = stringWidth("l") == stringWidth("M");
8625 			ascent_ = font.max_bounds.ascent;
8626 			descent_ = font.max_bounds.descent;
8627 		}
8628 	}
8629 
8630 	version(OSXCocoa)
8631 	private void prepareFontInfo() {
8632 		if(font !is null) {
8633 			isMonospace_ = font.isFixedPitch;
8634 			ascent_ = cast(int) font.ascender;
8635 			descent_ = cast(int) - font.descender;
8636 		}
8637 	}
8638 
8639 
8640 	/++
8641 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8642 
8643 		History:
8644 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8645 	+/
8646 	version(Windows)
8647 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8648 		unload();
8649 
8650 		WCharzBuffer buffer = WCharzBuffer(name);
8651 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8652 
8653 		prepareFontInfo(hdc);
8654 
8655 		return !isNull();
8656 	}
8657 
8658 	version(Windows)
8659 	void prepareFontInfo(HDC hdc = null) {
8660 		if(font is null)
8661 			return;
8662 
8663 		TEXTMETRIC tm;
8664 		auto dc = hdc ? hdc : GetDC(null);
8665 		auto orig = SelectObject(dc, font);
8666 		GetTextMetrics(dc, &tm);
8667 		SelectObject(dc, orig);
8668 		if(hdc is null)
8669 			ReleaseDC(null, dc);
8670 
8671 		width_ = tm.tmAveCharWidth;
8672 		height_ = tm.tmHeight;
8673 		ascent_ = tm.tmAscent;
8674 		descent_ = tm.tmDescent;
8675 		// 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.
8676 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8677 	}
8678 
8679 
8680 	/++
8681 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8682 
8683 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8684 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8685 
8686 		On Windows, it forwards directly to [loadWin32].
8687 
8688 		Params:
8689 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8690 			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.
8691 			weight = approximate boldness, results may vary.
8692 			italic = try to get a slanted version of the given font.
8693 
8694 		History:
8695 			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.
8696 	+/
8697 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8698 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
8699 		version(X11) {
8700 			version(with_xft) {
8701 				if(name.length > 5 && name[0 .. 5] == "core:") {
8702 					goto core;
8703 				}
8704 
8705 				if(loadXft(name, size, weight, italic))
8706 					return true;
8707 				// if xft fails, fallback to core to avoid breaking
8708 				// code that already depended on this.
8709 			}
8710 
8711 			core:
8712 
8713 			if(name.length > 5 && name[0 .. 5] == "core:") {
8714 				name = name[5 .. $];
8715 			}
8716 
8717 			return loadCoreX(name, size, weight, italic);
8718 		} else version(Windows) {
8719 			return loadWin32(name, size, weight, italic);
8720 		} else version(OSXCocoa) {
8721 			return loadCocoa(name, size, weight, italic);
8722 		} else static assert(0);
8723 	}
8724 
8725 	version(OSXCocoa)
8726 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
8727 		unload();
8728 
8729 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
8730 		prepareFontInfo();
8731 
8732 		return !isNull();
8733 	}
8734 
8735 	private struct LoadedInfo {
8736 		string name;
8737 		int size;
8738 		FontWeight weight;
8739 		bool italic;
8740 	}
8741 	private LoadedInfo loadedInfo;
8742 
8743 	///
8744 	void unload() {
8745 		if(isNull())
8746 			return;
8747 
8748 		version(X11) {
8749 			auto display = XDisplayConnection.display;
8750 
8751 			if(display is null)
8752 				return;
8753 
8754 			version(with_xft) {
8755 				if(isXft) {
8756 					if(xftFont)
8757 						XftFontClose(display, xftFont);
8758 					isXft = false;
8759 					xftFont = null;
8760 					return;
8761 				}
8762 			}
8763 
8764 			if(font && font !is ScreenPainterImplementation.defaultfont)
8765 				XFreeFont(display, font);
8766 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8767 				XFreeFontSet(display, fontset);
8768 
8769 			font = null;
8770 			fontset = null;
8771 		} else version(Windows) {
8772 			DeleteObject(font);
8773 			font = null;
8774 		} else version(OSXCocoa) {
8775 			font.release();
8776 			font = null;
8777 		} else static assert(0);
8778 	}
8779 
8780 	private bool isMonospace_;
8781 
8782 	/++
8783 		History:
8784 			Added January 16, 2021
8785 	+/
8786 	bool isMonospace() {
8787 		return isMonospace_;
8788 	}
8789 
8790 	/++
8791 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8792 
8793 		History:
8794 			Added March 26, 2020
8795 			Documented January 16, 2021
8796 	+/
8797 	int averageWidth() {
8798 		version(X11) {
8799 			return stringWidth("x");
8800 		} version(OSXCocoa) {
8801 			return stringWidth("x");
8802 		} else version(Windows)
8803 			return width_;
8804 		else assert(0);
8805 	}
8806 
8807 	/++
8808 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8809 
8810 		History:
8811 			Added January 16, 2021
8812 	+/
8813 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8814 	// FIXME: what about tab?
8815 		if(isNull)
8816 			return 0;
8817 
8818 		version(X11) {
8819 			version(with_xft)
8820 				if(isXft && xftFont !is null) {
8821 					//return xftFont.max_advance_width;
8822 					XGlyphInfo extents;
8823 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8824 					// writeln(extents);
8825 					return extents.xOff;
8826 				}
8827 			if(font is null)
8828 				return 0;
8829 			else if(fontset) {
8830 				XRectangle rect;
8831 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8832 
8833 				return rect.width;
8834 			} else {
8835 				return XTextWidth(font, s.ptr, cast(int) s.length);
8836 			}
8837 		} else version(Windows) {
8838 			WCharzBuffer buffer = WCharzBuffer(s);
8839 
8840 			return stringWidth(buffer.slice, window);
8841 		} else version(OSXCocoa) {
8842 			/+
8843 			int charCount = [string length];
8844 			CGGlyph glyphs[charCount];
8845 			CGRect rects[charCount];
8846 
8847 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
8848 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
8849 
8850 			int totalwidth = 0, maxheight = 0;
8851 			for (int i=0; i < charCount; i++)
8852 			{
8853 				totalwidth += rects[i].size.width;
8854 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
8855 			}
8856 
8857 			dim = CGSizeMake(totalwidth, maxheight);
8858 			+/
8859 
8860 			return 16; // FIXME
8861 		}
8862 		else assert(0);
8863 	}
8864 
8865 	version(Windows)
8866 	/// ditto
8867 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8868 		if(isNull)
8869 			return 0;
8870 		version(Windows) {
8871 			SIZE size;
8872 
8873 			prepareContext(window);
8874 			scope(exit) releaseContext();
8875 
8876 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8877 
8878 			return size.cx;
8879 		} else {
8880 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8881 			static assert(0, "not implemented yet");
8882 			//return stringWidth(s, window);
8883 		}
8884 	}
8885 
8886 	private {
8887 		int prepRefcount;
8888 
8889 		version(Windows) {
8890 			HDC dc;
8891 			HANDLE orig;
8892 			HWND hwnd;
8893 		}
8894 	}
8895 	/++
8896 		[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.
8897 
8898 		History:
8899 			Added January 23, 2021
8900 	+/
8901 	void prepareContext(SimpleWindow window = null) {
8902 		prepRefcount++;
8903 		if(prepRefcount == 1) {
8904 			version(Windows) {
8905 				hwnd = window is null ? null : window.impl.hwnd;
8906 				dc = GetDC(hwnd);
8907 				orig = SelectObject(dc, font);
8908 			}
8909 		}
8910 	}
8911 	/// ditto
8912 	void releaseContext() {
8913 		prepRefcount--;
8914 		if(prepRefcount == 0) {
8915 			version(Windows) {
8916 				SelectObject(dc, orig);
8917 				ReleaseDC(hwnd, dc);
8918 				hwnd = null;
8919 				dc = null;
8920 				orig = null;
8921 			}
8922 		}
8923 	}
8924 
8925 	/+
8926 		FIXME: I think I need advance and kerning pair
8927 
8928 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8929 	+/
8930 
8931 	/++
8932 		Returns the height of the font.
8933 
8934 		History:
8935 			Added March 26, 2020
8936 			Documented January 16, 2021
8937 	+/
8938 	int height() {
8939 		version(X11) {
8940 			version(with_xft)
8941 				if(isXft && xftFont !is null) {
8942 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8943 				}
8944 			if(font is null)
8945 				return 0;
8946 			return font.max_bounds.ascent + font.max_bounds.descent;
8947 		} else version(Windows) {
8948 			return height_;
8949 		} else version(OSXCocoa) {
8950 			if(font is null)
8951 				return 0;
8952 			return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight
8953 		}
8954 		else assert(0);
8955 	}
8956 
8957 	private int ascent_;
8958 	private int descent_;
8959 
8960 	/++
8961 		Max ascent above the baseline.
8962 
8963 		History:
8964 			Added January 22, 2021
8965 	+/
8966 	int ascent() {
8967 		return ascent_;
8968 	}
8969 
8970 	/++
8971 		Max descent below the baseline.
8972 
8973 		History:
8974 			Added January 22, 2021
8975 	+/
8976 	int descent() {
8977 		return descent_;
8978 	}
8979 
8980 	/++
8981 		Loads the default font used by [ScreenPainter] if none others are loaded.
8982 
8983 		Returns:
8984 			This method mutates the `this` object, but then returns `this` for
8985 			easy chaining like:
8986 
8987 			---
8988 			auto font = foo.isNull ? foo : foo.loadDefault
8989 			---
8990 
8991 		History:
8992 			Added previously, but left unimplemented until January 24, 2021.
8993 	+/
8994 	OperatingSystemFont loadDefault() {
8995 		unload();
8996 
8997 		loadedInfo = LoadedInfo.init;
8998 
8999 		version(X11) {
9000 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9001 			// but meh since sdpy does its own thing, this should be ok too
9002 
9003 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9004 			this.font = ScreenPainterImplementation.defaultfont;
9005 			this.fontset = ScreenPainterImplementation.defaultfontset;
9006 
9007 			prepareFontInfo();
9008 			return this;
9009 		} else version(Windows) {
9010 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9011 			this.font = ScreenPainterImplementation.defaultGuiFont;
9012 
9013 			prepareFontInfo();
9014 			return this;
9015 		} else version(OSXCocoa) {
9016 			this.font = NSFont.systemFontOfSize(15);
9017 
9018 			prepareFontInfo();
9019 
9020 			// import std.stdio; writeln("Load default: ", this.height());
9021 			return this;
9022 		} else throw new NotYetImplementedException();
9023 	}
9024 
9025 	///
9026 	bool isNull() {
9027 		version(with_xft)
9028 			if(isXft)
9029 				return xftFont is null;
9030 		return font is null;
9031 	}
9032 
9033 	/* Metrics */
9034 	/+
9035 		GetABCWidth
9036 		GetKerningPairs
9037 
9038 		if I do it right, I can size it all here, and match
9039 		what happens when I draw the full string with the OS functions.
9040 
9041 		subclasses might do the same thing while getting the glyphs on images
9042 	struct GlyphInfo {
9043 		int glyph;
9044 
9045 		size_t stringIdxStart;
9046 		size_t stringIdxEnd;
9047 
9048 		Rectangle boundingBox;
9049 	}
9050 	GlyphInfo[] getCharBoxes() {
9051 		// XftTextExtentsUtf8
9052 		return null;
9053 
9054 	}
9055 	+/
9056 
9057 	~this() {
9058 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9059 		unload();
9060 	}
9061 }
9062 
9063 version(Windows)
9064 private string sliceCString(const(wchar)[] w) {
9065 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9066 }
9067 
9068 private inout(char)[] sliceCString(inout(char)* s) {
9069 	import core.stdc.string;
9070 	auto len = strlen(s);
9071 	return s[0 .. len];
9072 }
9073 
9074 version(OSXCocoa)
9075 	alias PaintingHandle = NSObject;
9076 else
9077 	alias PaintingHandle = NativeWindowHandle;
9078 
9079 /**
9080 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9081 	than constructing it directly. Then, it is reference counted so you can pass it
9082 	at around and when the last ref goes out of scope, the buffered drawing activities
9083 	are all carried out.
9084 
9085 
9086 	Most functions use the outlineColor instead of taking a color themselves.
9087 	ScreenPainter is reference counted and draws its buffer to the screen when its
9088 	final reference goes out of scope.
9089 */
9090 struct ScreenPainter {
9091 	CapableOfBeingDrawnUpon window;
9092 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
9093 		this.window = window;
9094 		if(window.closed)
9095 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
9096 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
9097 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
9098 		if(window.activeScreenPainter !is null) {
9099 			impl = window.activeScreenPainter;
9100 			if(impl.referenceCount == 0) {
9101 				impl.window = window;
9102 				impl.create(handle);
9103 			}
9104 			impl.manualInvalidations = manualInvalidations;
9105 			impl.referenceCount++;
9106 		//	writeln("refcount ++ ", impl.referenceCount);
9107 		} else {
9108 			impl = new ScreenPainterImplementation;
9109 			impl.window = window;
9110 			impl.create(handle);
9111 			impl.referenceCount = 1;
9112 			impl.manualInvalidations = manualInvalidations;
9113 			window.activeScreenPainter = impl;
9114 			// writeln("constructed");
9115 		}
9116 
9117 		copyActiveOriginals();
9118 	}
9119 
9120 	/++
9121 		EXPERIMENTAL. subject to change.
9122 
9123 		When you draw a cursor, you can draw this to notify your window of where it is,
9124 		for IME systems to use.
9125 	+/
9126 	void notifyCursorPosition(int x, int y, int width, int height) {
9127 		if(auto w = cast(SimpleWindow) window) {
9128 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
9129 		}
9130 	}
9131 
9132 	/++
9133 		If you are using manual invalidations, this informs the
9134 		window system that a section needs to be redrawn.
9135 
9136 		If you didn't opt into manual invalidation, you don't
9137 		have to call this.
9138 
9139 		History:
9140 			Added December 30, 2021 (dub v10.5)
9141 	+/
9142 	void invalidateRect(Rectangle rect) {
9143 		if(impl is null) return;
9144 
9145 		// transform(rect)
9146 		rect.left += _originX;
9147 		rect.right += _originX;
9148 		rect.top += _originY;
9149 		rect.bottom += _originY;
9150 
9151 		impl.invalidateRect(rect);
9152 	}
9153 
9154 	private Pen originalPen;
9155 	private Color originalFillColor;
9156 	private arsd.color.Rectangle originalClipRectangle;
9157 	private OperatingSystemFont originalFont;
9158 	void copyActiveOriginals() {
9159 		if(impl is null) return;
9160 		originalPen = impl._activePen;
9161 		originalFillColor = impl._fillColor;
9162 		originalClipRectangle = impl._clipRectangle;
9163 		version(OSXCocoa) {} else
9164 		originalFont = impl._activeFont;
9165 	}
9166 
9167 	~this() {
9168 		if(impl is null) return;
9169 		impl.referenceCount--;
9170 		//writeln("refcount -- ", impl.referenceCount);
9171 		if(impl.referenceCount == 0) {
9172 			// writeln("destructed");
9173 			impl.dispose();
9174 			*window.activeScreenPainter = ScreenPainterImplementation.init;
9175 			// writeln("paint finished");
9176 		} else {
9177 			// there is still an active reference, reset stuff so the
9178 			// next user doesn't get weirdness via the reference
9179 			this.rasterOp = RasterOp.normal;
9180 			pen = originalPen;
9181 			fillColor = originalFillColor;
9182 			if(originalFont)
9183 				setFont(originalFont);
9184 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
9185 		}
9186 	}
9187 
9188 	this(this) {
9189 		if(impl is null) return;
9190 		impl.referenceCount++;
9191 		//writeln("refcount ++ ", impl.referenceCount);
9192 
9193 		copyActiveOriginals();
9194 	}
9195 
9196 	private int _originX;
9197 	private int _originY;
9198 	@property int originX() { return _originX; }
9199 	@property int originY() { return _originY; }
9200 	@property int originX(int a) {
9201 		_originX = a;
9202 		return _originX;
9203 	}
9204 	@property int originY(int a) {
9205 		_originY = a;
9206 		return _originY;
9207 	}
9208 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
9209 	private void transform(ref Point p) {
9210 		if(impl is null) return;
9211 		p.x += _originX;
9212 		p.y += _originY;
9213 	}
9214 
9215 	// this needs to be checked BEFORE the originX/Y transformation
9216 	private bool isClipped(Point p) {
9217 		return !currentClipRectangle.contains(p);
9218 	}
9219 	private bool isClipped(Point p, int width, int height) {
9220 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
9221 	}
9222 	private bool isClipped(Point p, Size s) {
9223 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
9224 	}
9225 	private bool isClipped(Point p, Point p2) {
9226 		// need to ensure the end points are actually included inside, so the +1 does that
9227 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
9228 	}
9229 
9230 
9231 	/++
9232 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
9233 
9234 		Returns:
9235 			The old clip rectangle.
9236 
9237 		History:
9238 			Return value was `void` prior to May 10, 2021.
9239 
9240 	+/
9241 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
9242 		if(impl is null) return currentClipRectangle;
9243 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
9244 			return currentClipRectangle; // no need to do anything
9245 		auto old = currentClipRectangle;
9246 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
9247 		transform(pt);
9248 
9249 		impl.setClipRectangle(pt.x, pt.y, width, height);
9250 
9251 		return old;
9252 	}
9253 
9254 	/// ditto
9255 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
9256 		if(impl is null) return currentClipRectangle;
9257 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
9258 	}
9259 
9260 	///
9261 	void setFont(OperatingSystemFont font) {
9262 		if(impl is null) return;
9263 		impl.setFont(font);
9264 	}
9265 
9266 	///
9267 	int fontHeight() {
9268 		if(impl is null) return 0;
9269 		return impl.fontHeight();
9270 	}
9271 
9272 	private Pen activePen;
9273 
9274 	///
9275 	@property void pen(Pen p) {
9276 		if(impl is null) return;
9277 		activePen = p;
9278 		impl.pen(p);
9279 	}
9280 
9281 	///
9282 	@scriptable
9283 	@property void outlineColor(Color c) {
9284 		if(impl is null) return;
9285 		if(activePen.color == c)
9286 			return;
9287 		activePen.color = c;
9288 		impl.pen(activePen);
9289 	}
9290 
9291 	///
9292 	@scriptable
9293 	@property void fillColor(Color c) {
9294 		if(impl is null) return;
9295 		impl.fillColor(c);
9296 	}
9297 
9298 	///
9299 	@property void rasterOp(RasterOp op) {
9300 		if(impl is null) return;
9301 		impl.rasterOp(op);
9302 	}
9303 
9304 
9305 	void updateDisplay() {
9306 		// FIXME this should do what the dtor does
9307 	}
9308 
9309 	/// 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)
9310 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
9311 		if(impl is null) return;
9312 		if(isClipped(upperLeft, width, height)) return;
9313 		transform(upperLeft);
9314 		version(Windows) {
9315 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
9316 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
9317 			RECT clip = scroll;
9318 			RECT uncovered;
9319 			HRGN hrgn;
9320 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
9321 				throw new WindowsApiException("ScrollDC", GetLastError());
9322 
9323 		} else version(X11) {
9324 			// FIXME: clip stuff outside this rectangle
9325 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
9326 		} else version(OSXCocoa) {
9327 			throw new NotYetImplementedException();
9328 		} else static assert(0);
9329 	}
9330 
9331 	///
9332 	void clear(Color color = Color.white()) {
9333 		if(impl is null) return;
9334 		fillColor = color;
9335 		outlineColor = color;
9336 		drawRectangle(Point(0, 0), window.width, window.height);
9337 	}
9338 
9339 	/++
9340 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
9341 
9342 		Params:
9343 			upperLeft = point on the window where the upper left corner of the image will be drawn
9344 			imageUpperLeft = point on the image to start the slice to draw
9345 			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.
9346 		History:
9347 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9348 	+/
9349 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9350 		if(impl is null) return;
9351 		if(isClipped(upperLeft, s.width, s.height)) return;
9352 		transform(upperLeft);
9353 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
9354 	}
9355 
9356 	///
9357 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
9358 		if(impl is null) return;
9359 		//if(isClipped(upperLeft, w, h)) return; // FIXME
9360 		transform(upperLeft);
9361 		if(w == 0 || w > i.width)
9362 			w = i.width;
9363 		if(h == 0 || h > i.height)
9364 			h = i.height;
9365 		if(upperLeftOfImage.x < 0)
9366 			upperLeftOfImage.x = 0;
9367 		if(upperLeftOfImage.y < 0)
9368 			upperLeftOfImage.y = 0;
9369 
9370 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
9371 	}
9372 
9373 	///
9374 	Size textSize(in char[] text) {
9375 		if(impl is null) return Size(0, 0);
9376 		return impl.textSize(text);
9377 	}
9378 
9379 	/++
9380 		Draws a string in the window with the set font (see [setFont] to change it).
9381 
9382 		Params:
9383 			upperLeft = the upper left point of the bounding box of the text
9384 			text = the string to draw
9385 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
9386 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
9387 	+/
9388 	@scriptable
9389 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
9390 		if(impl is null) return;
9391 		if(lowerRight.x != 0 || lowerRight.y != 0) {
9392 			if(isClipped(upperLeft, lowerRight)) return;
9393 			transform(lowerRight);
9394 		} else {
9395 			if(isClipped(upperLeft, textSize(text))) return;
9396 		}
9397 		transform(upperLeft);
9398 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
9399 	}
9400 
9401 	/++
9402 		Draws text using a custom font.
9403 
9404 		This is still MAJOR work in progress.
9405 
9406 		Creating a [DrawableFont] can be tricky and require additional dependencies.
9407 	+/
9408 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
9409 		if(impl is null) return;
9410 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9411 		transform(upperLeft);
9412 		font.drawString(this, upperLeft, text);
9413 	}
9414 
9415 	version(Windows)
9416 	void drawText(Point upperLeft, scope const(wchar)[] text) {
9417 		if(impl is null) return;
9418 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
9419 		transform(upperLeft);
9420 
9421 		if(text.length && text[$-1] == '\n')
9422 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
9423 
9424 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
9425 	}
9426 
9427 	static struct TextDrawingContext {
9428 		Point boundingBoxUpperLeft;
9429 		Point boundingBoxLowerRight;
9430 
9431 		Point currentLocation;
9432 
9433 		Point lastDrewUpperLeft;
9434 		Point lastDrewLowerRight;
9435 
9436 		// how do i do right aligned rich text?
9437 		// i kinda want to do a pre-made drawing then right align
9438 		// draw the whole block.
9439 		//
9440 		// That's exactly the diff: inline vs block stuff.
9441 
9442 		// I need to get coordinates of an inline section out too,
9443 		// not just a bounding box, but a series of bounding boxes
9444 		// should be ok. Consider what's needed to detect a click
9445 		// on a link in the middle of a paragraph breaking a line.
9446 		//
9447 		// Generally, we should be able to get the rectangles of
9448 		// any portion we draw.
9449 		//
9450 		// It also needs to tell what text is left if it overflows
9451 		// out of the box, so we can do stuff like float images around
9452 		// it. It should not attempt to draw a letter that would be
9453 		// clipped.
9454 		//
9455 		// I might also turn off word wrap stuff.
9456 	}
9457 
9458 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
9459 		if(impl is null) return;
9460 		// FIXME
9461 	}
9462 
9463 	/// Drawing an individual pixel is slow. Avoid it if possible.
9464 	void drawPixel(Point where) {
9465 		if(impl is null) return;
9466 		if(isClipped(where)) return;
9467 		transform(where);
9468 		impl.drawPixel(where.x, where.y);
9469 	}
9470 
9471 
9472 	/// Draws a pen using the current pen / outlineColor
9473 	@scriptable
9474 	void drawLine(Point starting, Point ending) {
9475 		if(impl is null) return;
9476 		if(isClipped(starting, ending)) return;
9477 		transform(starting);
9478 		transform(ending);
9479 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
9480 	}
9481 
9482 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
9483 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
9484 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
9485 	@scriptable
9486 	void drawRectangle(Point upperLeft, int width, int height) {
9487 		if(impl is null) return;
9488 		if(isClipped(upperLeft, width, height)) return;
9489 		transform(upperLeft);
9490 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
9491 	}
9492 
9493 	/// ditto
9494 	void drawRectangle(Point upperLeft, Size size) {
9495 		if(impl is null) return;
9496 		if(isClipped(upperLeft, size.width, size.height)) return;
9497 		transform(upperLeft);
9498 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
9499 	}
9500 
9501 	/// ditto
9502 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
9503 		if(impl is null) return;
9504 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
9505 		transform(upperLeft);
9506 		transform(lowerRightInclusive);
9507 		impl.drawRectangle(upperLeft.x, upperLeft.y,
9508 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
9509 	}
9510 
9511 	// overload added on May 12, 2021
9512 	/// ditto
9513 	void drawRectangle(Rectangle rect) {
9514 		drawRectangle(rect.upperLeft, rect.size);
9515 	}
9516 
9517 	/// Arguments are the points of the bounding rectangle
9518 	void drawEllipse(Point upperLeft, Point lowerRight) {
9519 		if(impl is null) return;
9520 		if(isClipped(upperLeft, lowerRight)) return;
9521 		transform(upperLeft);
9522 		transform(lowerRight);
9523 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
9524 	}
9525 
9526 	/++
9527 		start and finish are units of degrees * 64
9528 
9529 		History:
9530 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
9531 
9532 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
9533 	+/
9534 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
9535 		if(impl is null) return;
9536 		// FIXME: not actually implemented
9537 		if(isClipped(upperLeft, width, height)) return;
9538 		transform(upperLeft);
9539 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
9540 	}
9541 
9542 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
9543 	void drawCircle(Point upperLeft, int diameter) {
9544 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
9545 	}
9546 
9547 	/// .
9548 	void drawPolygon(Point[] vertexes) {
9549 		if(impl is null) return;
9550 		assert(vertexes.length);
9551 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
9552 		foreach(ref vertex; vertexes) {
9553 			if(vertex.x < minX)
9554 				minX = vertex.x;
9555 			if(vertex.y < minY)
9556 				minY = vertex.y;
9557 			if(vertex.x > maxX)
9558 				maxX = vertex.x;
9559 			if(vertex.y > maxY)
9560 				maxY = vertex.y;
9561 			transform(vertex);
9562 		}
9563 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
9564 		impl.drawPolygon(vertexes);
9565 	}
9566 
9567 	/// ditto
9568 	void drawPolygon(Point[] vertexes...) {
9569 		if(impl is null) return;
9570 		drawPolygon(vertexes);
9571 	}
9572 
9573 
9574 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
9575 
9576 	//mixin NativeScreenPainterImplementation!() impl;
9577 
9578 
9579 	// HACK: if I mixin the impl directly, it won't let me override the copy
9580 	// constructor! The linker complains about there being multiple definitions.
9581 	// I'll make the best of it and reference count it though.
9582 	ScreenPainterImplementation* impl;
9583 }
9584 
9585 	// HACK: I need a pointer to the implementation so it's separate
9586 	struct ScreenPainterImplementation {
9587 		CapableOfBeingDrawnUpon window;
9588 		int referenceCount;
9589 		mixin NativeScreenPainterImplementation!();
9590 	}
9591 
9592 // FIXME: i haven't actually tested the sprite class on MS Windows
9593 
9594 /**
9595 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
9596 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
9597 
9598 
9599 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
9600 	though I'm not sure that's ideal and the implementation might change.
9601 
9602 	You create one by giving a window and an image. It optimizes for that window,
9603 	and copies the image into it to use as the initial picture. Creating a sprite
9604 	can be quite slow (especially over a network connection) so you should do it
9605 	as little as possible and just hold on to your sprite handles after making them.
9606 	simpledisplay does try to do its best though, using the XSHM extension if available,
9607 	but you should still write your code as if it will always be slow.
9608 
9609 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
9610 	a fast operation - much faster than drawing the Image itself every time.
9611 
9612 	`Sprite` represents a scarce resource which should be freed when you
9613 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
9614 	after it has been disposed. If you are unsure about this, don't take chances,
9615 	just let the garbage collector do it for you. But ideally, you can manage its
9616 	lifetime more efficiently.
9617 
9618 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
9619 	support alpha blending in its drawing at this time. That might change in the
9620 	future, but if you need alpha blending right now, use OpenGL instead. See
9621 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
9622 
9623 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
9624 	in by setting the enableAlpha = true in the constructor.
9625 */
9626 class Sprite : CapableOfBeingDrawnUpon {
9627 
9628 	///
9629 	ScreenPainter draw() {
9630 		return ScreenPainter(this, handle, false);
9631 	}
9632 
9633 	/++
9634 		Copies the sprite's current state into a [TrueColorImage].
9635 
9636 		Be warned: this can be a very slow operation
9637 
9638 		History:
9639 			Actually implemented on March 14, 2021
9640 	+/
9641 	TrueColorImage takeScreenshot() {
9642 		return trueColorImageFromNativeHandle(handle, width, height);
9643 	}
9644 
9645 	void delegate() paintingFinishedDg() { return null; }
9646 	bool closed() { return false; }
9647 	ScreenPainterImplementation* activeScreenPainter_;
9648 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
9649 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
9650 
9651 	version(Windows)
9652 		private ubyte* rawData;
9653 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
9654 	// ditto on the XPicture stuff
9655 
9656 	version(X11) {
9657 		private static XRenderPictFormat* RGB24;
9658 		private static XRenderPictFormat* ARGB32;
9659 
9660 		private Picture xrenderPicture;
9661 	}
9662 
9663 	version(X11)
9664 	private static void requireXRender() {
9665 		if(!XRenderLibrary.loadAttempted) {
9666 			XRenderLibrary.loadDynamicLibrary();
9667 		}
9668 
9669 		if(!XRenderLibrary.loadSuccessful)
9670 			throw new Exception("XRender library load failure");
9671 
9672 		auto display = XDisplayConnection.get;
9673 
9674 		// FIXME: if we migrate X displays, these need to be changed
9675 		if(RGB24 is null)
9676 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
9677 		if(ARGB32 is null)
9678 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
9679 	}
9680 
9681 	protected this() {}
9682 
9683 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
9684 		this._width = width;
9685 		this._height = height;
9686 		this.enableAlpha = enableAlpha;
9687 
9688 		version(X11) {
9689 			auto display = XDisplayConnection.get();
9690 
9691 			if(enableAlpha) {
9692 				requireXRender();
9693 			}
9694 
9695 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
9696 
9697 			if(enableAlpha) {
9698 				XRenderPictureAttributes attrs;
9699 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
9700 			}
9701 		} else version(Windows) {
9702 			version(CRuntime_DigitalMars) {
9703 				//if(enableAlpha)
9704 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
9705 			}
9706 
9707 			BITMAPINFO infoheader;
9708 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9709 			infoheader.bmiHeader.biWidth = width;
9710 			infoheader.bmiHeader.biHeight = height;
9711 			infoheader.bmiHeader.biPlanes = 1;
9712 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
9713 			infoheader.bmiHeader.biCompression = BI_RGB;
9714 
9715 			// FIXME: this should prolly be a device dependent bitmap...
9716 			handle = CreateDIBSection(
9717 				null,
9718 				&infoheader,
9719 				DIB_RGB_COLORS,
9720 				cast(void**) &rawData,
9721 				null,
9722 				0);
9723 
9724 			if(handle is null)
9725 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
9726 		}
9727 	}
9728 
9729 	/// Makes a sprite based on the image with the initial contents from the Image
9730 	this(SimpleWindow win, Image i) {
9731 		this(win, i.width, i.height, i.enableAlpha);
9732 
9733 		version(X11) {
9734 			auto display = XDisplayConnection.get();
9735 			auto gc = XCreateGC(display, this.handle, 0, null);
9736 			scope(exit) XFreeGC(display, gc);
9737 			if(i.usingXshm)
9738 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9739 			else
9740 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9741 		} else version(Windows) {
9742 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9743 			auto arrLength = itemsPerLine * height;
9744 			rawData[0..arrLength] = i.rawData[0..arrLength];
9745 		} else version(OSXCocoa) {
9746 			// FIXME: I have no idea if this is even any good
9747 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9748 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
9749 				colorSpace,
9750 				kCGImageAlphaPremultipliedLast
9751 				|kCGBitmapByteOrder32Big);
9752 			CGColorSpaceRelease(colorSpace);
9753 			auto rawData = CGBitmapContextGetData(handle);
9754 
9755 			auto rdl = (width * height * 4);
9756 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9757 		} else static assert(0);
9758 	}
9759 
9760 	/++
9761 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9762 
9763 		Params:
9764 			where = point on the window where the upper left corner of the image will be drawn
9765 			imageUpperLeft = point on the image to start the slice to draw
9766 			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.
9767 		History:
9768 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9769 	+/
9770 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9771 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9772 	}
9773 
9774 	/// Call this when you're ready to get rid of it
9775 	void dispose() {
9776 		version(X11) {
9777 			staticDispose(xrenderPicture, handle);
9778 			xrenderPicture = None;
9779 			handle = None;
9780 		} else version(Windows) {
9781 			staticDispose(handle);
9782 			handle = null;
9783 		} else version(OSXCocoa) {
9784 			staticDispose(handle);
9785 			handle = null;
9786 		} else static assert(0);
9787 
9788 	}
9789 
9790 	version(X11)
9791 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
9792 		if(xrenderPicture)
9793 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9794 		if(handle)
9795 			XFreePixmap(XDisplayConnection.get(), handle);
9796 	}
9797 	else version(Windows)
9798 	static void staticDispose(HBITMAP handle) {
9799 		if(handle)
9800 			DeleteObject(handle);
9801 	}
9802 	else version(OSXCocoa)
9803 	static void staticDispose(CGContextRef context) {
9804 		if(context)
9805 			CGContextRelease(context);
9806 	}
9807 
9808 	~this() {
9809 		version(X11) { if(xrenderPicture || handle)
9810 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
9811 		} else version(Windows) { if(handle)
9812 			cleanupQueue.queue!staticDispose(handle);
9813 		} else version(OSXCocoa) { if(handle)
9814 			cleanupQueue.queue!staticDispose(handle);
9815 		} else static assert(0);
9816 	}
9817 
9818 	///
9819 	final @property int width() { return _width; }
9820 
9821 	///
9822 	final @property int height() { return _height; }
9823 
9824 	///
9825 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9826 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9827 	}
9828 
9829 	auto nativeHandle() {
9830 		return handle;
9831 	}
9832 
9833 	private:
9834 
9835 	int _width;
9836 	int _height;
9837 	bool enableAlpha;
9838 	version(X11)
9839 		Pixmap handle;
9840 	else version(Windows)
9841 		HBITMAP handle;
9842 	else version(OSXCocoa)
9843 		CGContextRef handle;
9844 	else static assert(0);
9845 }
9846 
9847 /++
9848 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9849 
9850 	History:
9851 		Added November 20, 2021 (dub v10.4)
9852 +/
9853 version(OSXCocoa) {} else // NotYetImplementedException
9854 abstract class Gradient : Sprite {
9855 	protected this(int w, int h) {
9856 		version(X11) {
9857 			Sprite.requireXRender();
9858 
9859 			super();
9860 			enableAlpha = true;
9861 			_width = w;
9862 			_height = h;
9863 		} else version(Windows) {
9864 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9865 		}
9866 	}
9867 
9868 	version(Windows)
9869 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9870 		auto ptr = rawData;
9871 		foreach(j; 0 .. _height)
9872 		foreach(i; 0 .. _width) {
9873 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9874 			*rawData = (color.a * color.b) / 255; rawData++;
9875 			*rawData = (color.a * color.g) / 255; rawData++;
9876 			*rawData = (color.a * color.r) / 255; rawData++;
9877 			*rawData = color.a; rawData++;
9878 		}
9879 	}
9880 
9881 	version(X11)
9882 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9883 		assert(stops.length > 0);
9884 		assert(stops.length <= 16, "I got lazy with buffers");
9885 
9886 		XFixed[16] stopsPositions = void;
9887 		XRenderColor[16] colors = void;
9888 
9889 		foreach(idx, stop; stops) {
9890 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9891 			auto c = stop.c;
9892 			colors[idx] = XRenderColor(
9893 				cast(ushort)(c.r * ushort.max / 255),
9894 				cast(ushort)(c.g * ushort.max / 255),
9895 				cast(ushort)(c.b * ushort.max / 255),
9896 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9897 			);
9898 		}
9899 
9900 		xrenderPicture = dg(stopsPositions, colors);
9901 	}
9902 
9903 	///
9904 	static struct Stop {
9905 		float percentage; /// between 0 and 1.0
9906 		Color c;
9907 	}
9908 }
9909 
9910 /++
9911 	Creates a linear gradient between p1 and p2.
9912 
9913 	X ONLY RIGHT NOW
9914 
9915 	History:
9916 		Added November 20, 2021 (dub v10.4)
9917 
9918 	Bugs:
9919 		Not yet implemented on Windows.
9920 +/
9921 version(OSXCocoa) {} else // NotYetImplementedException
9922 class LinearGradient : Gradient {
9923 	/++
9924 
9925 	+/
9926 	this(Point p1, Point p2, Stop[] stops...) {
9927 		super(p2.x, p2.y);
9928 
9929 		version(X11) {
9930 			XLinearGradient gradient;
9931 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9932 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9933 
9934 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9935 				return XRenderCreateLinearGradient(
9936 					XDisplayConnection.get,
9937 					&gradient,
9938 					stopsPositions.ptr,
9939 					colors.ptr,
9940 					cast(int) stops.length);
9941 			});
9942 		} else version(Windows) {
9943 			// FIXME
9944 			forEachPixel((int x, int y) {
9945 				import core.stdc.math;
9946 
9947 				//sqrtf(
9948 
9949 				return Color.transparent;
9950 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9951 			});
9952 		}
9953 	}
9954 }
9955 
9956 /++
9957 	A conical gradient goes from color to color around a circumference from a center point.
9958 
9959 	X ONLY RIGHT NOW
9960 
9961 	History:
9962 		Added November 20, 2021 (dub v10.4)
9963 
9964 	Bugs:
9965 		Not yet implemented on Windows.
9966 +/
9967 version(OSXCocoa) {} else // NotYetImplementedException
9968 class ConicalGradient : Gradient {
9969 	/++
9970 
9971 	+/
9972 	this(Point center, float angleInDegrees, Stop[] stops...) {
9973 		super(center.x * 2, center.y * 2);
9974 
9975 		version(X11) {
9976 			XConicalGradient gradient;
9977 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9978 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9979 
9980 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9981 				return XRenderCreateConicalGradient(
9982 					XDisplayConnection.get,
9983 					&gradient,
9984 					stopsPositions.ptr,
9985 					colors.ptr,
9986 					cast(int) stops.length);
9987 			});
9988 		} else version(Windows) {
9989 			// FIXME
9990 			forEachPixel((int x, int y) {
9991 				import core.stdc.math;
9992 
9993 				//sqrtf(
9994 
9995 				return Color.transparent;
9996 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9997 			});
9998 
9999 		}
10000 	}
10001 }
10002 
10003 /++
10004 	A radial gradient goes from color to color based on distance from the center.
10005 	It is like rings of color.
10006 
10007 	X ONLY RIGHT NOW
10008 
10009 
10010 	More specifically, you create two circles: an inner circle and an outer circle.
10011 	The gradient is only drawn in the area outside the inner circle but inside the outer
10012 	circle. The closest line between those two circles forms the line for the gradient
10013 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10014 
10015 	History:
10016 		Added November 20, 2021 (dub v10.4)
10017 
10018 	Bugs:
10019 		Not yet implemented on Windows.
10020 +/
10021 version(OSXCocoa) {} else // NotYetImplementedException
10022 class RadialGradient : Gradient {
10023 	/++
10024 
10025 	+/
10026 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10027 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10028 
10029 		version(X11) {
10030 			XRadialGradient gradient;
10031 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10032 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10033 
10034 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10035 				return XRenderCreateRadialGradient(
10036 					XDisplayConnection.get,
10037 					&gradient,
10038 					stopsPositions.ptr,
10039 					colors.ptr,
10040 					cast(int) stops.length);
10041 			});
10042 		} else version(Windows) {
10043 			// FIXME
10044 			forEachPixel((int x, int y) {
10045 				import core.stdc.math;
10046 
10047 				//sqrtf(
10048 
10049 				return Color.transparent;
10050 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10051 			});
10052 		}
10053 	}
10054 }
10055 
10056 
10057 
10058 /+
10059 	NOT IMPLEMENTED
10060 
10061 	A display-stored image optimized for relatively quick drawing, like
10062 	[Sprite], but this one supports alpha channel blending and does NOT
10063 	support direct drawing upon it with a [ScreenPainter].
10064 
10065 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
10066 	plain [ScreenPainter]... sort of.
10067 
10068 	On X11, it requires the Xrender extension and library. This is available
10069 	almost everywhere though.
10070 
10071 	History:
10072 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
10073 +/
10074 version(none)
10075 class AlphaSprite {
10076 	/++
10077 		Copies the given image into it.
10078 	+/
10079 	this(MemoryImage img) {
10080 
10081 		if(!XRenderLibrary.loadAttempted) {
10082 			XRenderLibrary.loadDynamicLibrary();
10083 
10084 			// FIXME: this needs to be reconstructed when the X server changes
10085 			repopulateX();
10086 		}
10087 		if(!XRenderLibrary.loadSuccessful)
10088 			throw new Exception("XRender library load failure");
10089 
10090 		// I probably need to put the alpha mask in a separate Picture
10091 		// ugh
10092 		// maybe the Sprite itself can have an alpha bitmask anyway
10093 
10094 
10095 		auto display = XDisplayConnection.get();
10096 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10097 
10098 
10099 		XRenderPictureAttributes attrs;
10100 
10101 		handle = XRenderCreatePicture(
10102 			XDisplayConnection.get,
10103 			pixmap,
10104 			RGBA,
10105 			0,
10106 			&attrs
10107 		);
10108 
10109 	}
10110 
10111 	// maybe i'll use the create gradient functions too with static factories..
10112 
10113 	void drawAt(ScreenPainter painter, Point where) {
10114 		//painter.drawPixmap(this, where);
10115 
10116 		XRenderPictureAttributes attrs;
10117 
10118 		auto pic = XRenderCreatePicture(
10119 			XDisplayConnection.get,
10120 			painter.impl.d,
10121 			RGB,
10122 			0,
10123 			&attrs
10124 		);
10125 
10126 		XRenderComposite(
10127 			XDisplayConnection.get,
10128 			3, // PictOpOver
10129 			handle,
10130 			None,
10131 			pic,
10132 			0, // src
10133 			0,
10134 			0, // mask
10135 			0,
10136 			10, // dest
10137 			10,
10138 			100, // width
10139 			100
10140 		);
10141 
10142 		/+
10143 		XRenderFreePicture(
10144 			XDisplayConnection.get,
10145 			pic
10146 		);
10147 
10148 		XRenderFreePicture(
10149 			XDisplayConnection.get,
10150 			fill
10151 		);
10152 		+/
10153 		// on Windows you can stretch but Xrender still can't :(
10154 	}
10155 
10156 	static XRenderPictFormat* RGB;
10157 	static XRenderPictFormat* RGBA;
10158 	static void repopulateX() {
10159 		auto display = XDisplayConnection.get;
10160 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
10161 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
10162 	}
10163 
10164 	XPixmap pixmap;
10165 	Picture handle;
10166 }
10167 
10168 ///
10169 interface CapableOfBeingDrawnUpon {
10170 	///
10171 	ScreenPainter draw();
10172 	///
10173 	int width();
10174 	///
10175 	int height();
10176 	protected ScreenPainterImplementation* activeScreenPainter();
10177 	protected void activeScreenPainter(ScreenPainterImplementation*);
10178 	bool closed();
10179 
10180 	void delegate() paintingFinishedDg();
10181 
10182 	/// Be warned: this can be a very slow operation
10183 	TrueColorImage takeScreenshot();
10184 }
10185 
10186 /// 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].
10187 void flushGui() {
10188 	version(X11) {
10189 		auto dpy = XDisplayConnection.get();
10190 		XLockDisplay(dpy);
10191 		scope(exit) XUnlockDisplay(dpy);
10192 		XFlush(dpy);
10193 	}
10194 }
10195 
10196 /++
10197 	Runs the given code in the GUI thread when its event loop
10198 	is available, blocking until it completes. This allows you
10199 	to create and manipulate windows from another thread without
10200 	invoking undefined behavior.
10201 
10202 	If this is the gui thread, it runs the code immediately.
10203 
10204 	If no gui thread exists yet, the current thread is assumed
10205 	to be it. Attempting to create windows or run the event loop
10206 	in any other thread will cause an assertion failure.
10207 
10208 
10209 	$(TIP
10210 		Did you know you can use UFCS on delegate literals?
10211 
10212 		() {
10213 			// code here
10214 		}.runInGuiThread;
10215 	)
10216 
10217 	Returns:
10218 		`true` if the function was called, `false` if it was not.
10219 		The function may not be called because the gui thread had
10220 		already terminated by the time you called this.
10221 
10222 	History:
10223 		Added April 10, 2020 (v7.2.0)
10224 
10225 		Return value added and implementation tweaked to avoid locking
10226 		at program termination on February 24, 2021 (v9.2.1).
10227 +/
10228 bool runInGuiThread(scope void delegate() dg) @trusted {
10229 	claimGuiThread();
10230 
10231 	if(thisIsGuiThread) {
10232 		dg();
10233 		return true;
10234 	}
10235 
10236 	if(guiThreadTerminating)
10237 		return false;
10238 
10239 	import core.sync.semaphore;
10240 	static Semaphore sc;
10241 	if(sc is null)
10242 		sc = new Semaphore();
10243 
10244 	static RunQueueMember* rqm;
10245 	if(rqm is null)
10246 		rqm = new RunQueueMember;
10247 	rqm.dg = cast(typeof(rqm.dg)) dg;
10248 	rqm.signal = sc;
10249 	rqm.thrown = null;
10250 
10251 	synchronized(runInGuiThreadLock) {
10252 		runInGuiThreadQueue ~= rqm;
10253 	}
10254 
10255 	if(!SimpleWindow.eventWakeUp())
10256 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
10257 
10258 	rqm.signal.wait();
10259 	auto t = rqm.thrown;
10260 
10261 	if(t)
10262 		throw t;
10263 
10264 	return true;
10265 }
10266 
10267 // note it runs sync if this is the gui thread....
10268 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
10269 	claimGuiThread();
10270 
10271 	try {
10272 
10273 		if(thisIsGuiThread) {
10274 			dg();
10275 			return;
10276 		}
10277 
10278 		if(guiThreadTerminating)
10279 			return;
10280 
10281 		RunQueueMember* rqm = new RunQueueMember;
10282 		rqm.dg = cast(typeof(rqm.dg)) dg;
10283 		rqm.signal = null;
10284 		rqm.thrown = null;
10285 
10286 		synchronized(runInGuiThreadLock) {
10287 			runInGuiThreadQueue ~= rqm;
10288 		}
10289 
10290 		if(!SimpleWindow.eventWakeUp())
10291 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
10292 	} catch(Exception e) {
10293 		if(handleError)
10294 			handleError(e);
10295 	}
10296 }
10297 
10298 private void runPendingRunInGuiThreadDelegates() {
10299 	more:
10300 	RunQueueMember* next;
10301 	synchronized(runInGuiThreadLock) {
10302 		if(runInGuiThreadQueue.length) {
10303 			next = runInGuiThreadQueue[0];
10304 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
10305 		} else {
10306 			next = null;
10307 		}
10308 	}
10309 
10310 	if(next) {
10311 		try {
10312 			next.dg();
10313 			next.thrown = null;
10314 		} catch(Throwable t) {
10315 			next.thrown = t;
10316 		}
10317 
10318 		if(next.signal)
10319 			next.signal.notify();
10320 
10321 		goto more;
10322 	}
10323 }
10324 
10325 private void claimGuiThread() nothrow {
10326 	import core.atomic;
10327 	if(cas(&guiThreadExists_, false, true))
10328 		thisIsGuiThread = true;
10329 }
10330 
10331 private struct RunQueueMember {
10332 	void delegate() dg;
10333 	import core.sync.semaphore;
10334 	Semaphore signal;
10335 	Throwable thrown;
10336 }
10337 
10338 private __gshared RunQueueMember*[] runInGuiThreadQueue;
10339 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
10340 private bool thisIsGuiThread = false;
10341 private shared bool guiThreadExists_ = false;
10342 private shared bool guiThreadTerminating = false;
10343 
10344 /++
10345 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
10346 	event loop. All windows must be exclusively created and managed by a single thread.
10347 
10348 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
10349 	when you call one of its constructors.
10350 
10351 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
10352 	one. If so, you can run gui functions on it. If not, don't. The helper functions
10353 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
10354 
10355 	The reason this function is available is in case you want to message pass between a gui
10356 	thread and your current thread. If no gui thread exists or if this is the gui thread,
10357 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
10358 
10359 	History:
10360 		Added December 3, 2021 (dub v10.5)
10361 +/
10362 public bool guiThreadExists() {
10363 	return guiThreadExists_;
10364 }
10365 
10366 /++
10367 	Returns `true` if this thread is either running or set to be running the
10368 	simpledisplay.d gui core event loop because it owns windows.
10369 
10370 	It is important to keep gui-related functionality in the right thread, so you will
10371 	want to `runInGuiThread` when you call them (with some specific exceptions called
10372 	out in those specific functions' documentation). Notably, all windows must be
10373 	created and managed only from the gui thread.
10374 
10375 	Will return false if simpledisplay's other functions haven't been called
10376 	yet; check [guiThreadExists] in addition to this.
10377 
10378 	History:
10379 		Added December 3, 2021 (dub v10.5)
10380 +/
10381 public bool thisThreadRunningGui() {
10382 	return thisIsGuiThread;
10383 }
10384 
10385 /++
10386 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
10387 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
10388 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
10389 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
10390 	file instead if you are in one of those situations).
10391 
10392 	It does not support outputting very many types; just strings and ints are likely to actually work.
10393 
10394 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
10395 	is unspecified meaning I can change it at any time. The only point of this function is to help
10396 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
10397 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
10398 	in those contexts.
10399 
10400 	$(WARNING
10401 		I reserve the right to change this function at any time. You can use it if it helps you
10402 		but do not rely on it for anything permanent.
10403 	)
10404 
10405 	History:
10406 		Added December 3, 2021. Not formally supported under any stable tag.
10407 +/
10408 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
10409 	try {
10410 		version(Windows) {
10411 			import core.sys.windows.wincon;
10412 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
10413 				AllocConsole();
10414 			const(char)* fn = "CONOUT$";
10415 		} else version(Posix) {
10416 			const(char)* fn = "/dev/tty";
10417 		} else static assert(0, "Function not implemented for your system");
10418 
10419 		if(fileOverride.length)
10420 			fn = fileOverride.ptr;
10421 
10422 		import core.stdc.stdio;
10423 		auto fp = fopen(fn, "wt");
10424 		if(fp is null) return;
10425 		scope(exit) fclose(fp);
10426 
10427 		string str;
10428 		foreach(item; t) {
10429 			static if(is(typeof(item) : const(char)[]))
10430 				str ~= item;
10431 			else
10432 				str ~= toInternal!string(item);
10433 			str ~= " ";
10434 		}
10435 		str ~= "\n";
10436 
10437 		fwrite(str.ptr, 1, str.length, fp);
10438 		fflush(fp);
10439 	} catch(Exception e) {
10440 		// sorry no hope
10441 	}
10442 }
10443 
10444 private void guiThreadFinalize() {
10445 	assert(thisIsGuiThread);
10446 
10447 	guiThreadTerminating = true; // don't add any more from this point on
10448 	runPendingRunInGuiThreadDelegates();
10449 }
10450 
10451 /+
10452 interface IPromise {
10453 	void reportProgress(int current, int max, string message);
10454 
10455 	/+ // not formally in cuz of templates but still
10456 	IPromise Then();
10457 	IPromise Catch();
10458 	IPromise Finally();
10459 	+/
10460 }
10461 
10462 /+
10463 	auto promise = async({ ... });
10464 	promise.Then(whatever).
10465 		Then(whateverelse).
10466 		Catch((exception) { });
10467 
10468 
10469 	A promise is run inside a fiber and it looks something like:
10470 
10471 	try {
10472 		auto res = whatever();
10473 		auto res2 = whateverelse(res);
10474 	} catch(Exception e) {
10475 		{ }(e);
10476 	}
10477 
10478 	When a thing succeeds, it is passed as an arg to the next
10479 +/
10480 class Promise(T) : IPromise {
10481 	auto Then() { return null; }
10482 	auto Catch() { return null; }
10483 	auto Finally() { return null; }
10484 
10485 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
10486 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
10487 	T await();
10488 }
10489 
10490 interface Task {
10491 }
10492 
10493 interface Resolvable(T) : Task {
10494 	void run();
10495 
10496 	void resolve(T);
10497 
10498 	Resolvable!T then(void delegate(T)); // returns a new promise
10499 	Resolvable!T error(Throwable); // js catch
10500 	Resolvable!T completed(); // js finally
10501 
10502 }
10503 
10504 /++
10505 	Runs `work` in a helper thread and sends its return value back to the main gui
10506 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
10507 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
10508 	kill the program.
10509 
10510 	You can call reportProgress(position, max, message) to update your parent window
10511 	on your progress.
10512 
10513 	I should also use `shared` methods. FIXME
10514 
10515 	History:
10516 		Added March 6, 2021 (dub version 9.3).
10517 +/
10518 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
10519 	uponCompletion(work(null));
10520 }
10521 
10522 +/
10523 
10524 /// Used internal to dispatch events to various classes.
10525 interface CapableOfHandlingNativeEvent {
10526 	NativeEventHandler getNativeEventHandler();
10527 
10528 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
10529 
10530 	version(X11) {
10531 		// if this is impossible, you are allowed to just throw from it
10532 		// Note: if you call it from another object, set a flag cuz the manger will call you again
10533 		void recreateAfterDisconnect();
10534 		// discard any *connection specific* state, but keep enough that you
10535 		// can be recreated if possible. discardConnectionState() is always called immediately
10536 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
10537 		// you need initialization order
10538 		void discardConnectionState();
10539 	}
10540 }
10541 
10542 version(X11)
10543 /++
10544 	State of keys on mouse events, especially motion.
10545 
10546 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
10547 +/
10548 enum ModifierState : uint {
10549 	shift = 1, ///
10550 	capsLock = 2, ///
10551 	ctrl = 4, ///
10552 	alt = 8, /// Not always available on Windows
10553 	windows = 64, /// ditto
10554 	numLock = 16, ///
10555 
10556 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10557 	middleButtonDown = 512, /// ditto
10558 	rightButtonDown = 1024, /// ditto
10559 }
10560 else version(Windows)
10561 /// ditto
10562 enum ModifierState : uint {
10563 	shift = 4, ///
10564 	ctrl = 8, ///
10565 
10566 	// i'm not sure if the next two are available
10567 	alt = 256, /// not always available on Windows
10568 	windows = 512, /// ditto
10569 
10570 	capsLock = 1024, ///
10571 	numLock = 2048, ///
10572 
10573 	leftButtonDown = 1, /// not available on key events
10574 	middleButtonDown = 16, /// ditto
10575 	rightButtonDown = 2, /// ditto
10576 
10577 	backButtonDown = 0x20, /// not available on X
10578 	forwardButtonDown = 0x40, /// ditto
10579 }
10580 else version(OSXCocoa)
10581 // FIXME FIXME NotYetImplementedException
10582 enum ModifierState : uint {
10583 	shift = 1, ///
10584 	capsLock = 2, ///
10585 	ctrl = 4, ///
10586 	alt = 8, /// Not always available on Windows
10587 	windows = 64, /// ditto
10588 	numLock = 16, ///
10589 
10590 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
10591 	middleButtonDown = 512, /// ditto
10592 	rightButtonDown = 1024, /// ditto
10593 }
10594 
10595 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
10596 enum MouseButton : int {
10597 	none = 0,
10598 	left = 1, ///
10599 	right = 2, ///
10600 	middle = 4, ///
10601 	wheelUp = 8, ///
10602 	wheelDown = 16, ///
10603 	backButton = 32, /// often found on the thumb and used for back in browsers
10604 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
10605 }
10606 
10607 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
10608 enum MouseButtonLinear : ubyte {
10609 	left = 1, ///
10610 	right, ///
10611 	middle, ///
10612 	wheelUp, ///
10613 	wheelDown, ///
10614 	backButton, /// often found on the thumb and used for back in browsers
10615 	forwardButton, /// often found on the thumb and used for forward in browsers
10616 }
10617 
10618 version(X11) {
10619 	// FIXME: match ASCII whenever we can. Most of it is already there,
10620 	// but there's a few exceptions and mismatches with Windows
10621 
10622 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
10623 	enum Key {
10624 		Escape = 0xff1b, ///
10625 		F1 = 0xffbe, ///
10626 		F2 = 0xffbf, ///
10627 		F3 = 0xffc0, ///
10628 		F4 = 0xffc1, ///
10629 		F5 = 0xffc2, ///
10630 		F6 = 0xffc3, ///
10631 		F7 = 0xffc4, ///
10632 		F8 = 0xffc5, ///
10633 		F9 = 0xffc6, ///
10634 		F10 = 0xffc7, ///
10635 		F11 = 0xffc8, ///
10636 		F12 = 0xffc9, ///
10637 		PrintScreen = 0xff61, ///
10638 		ScrollLock = 0xff14, ///
10639 		Pause = 0xff13, ///
10640 		Grave = 0x60, /// The $(BACKTICK) ~ key
10641 		// number keys across the top of the keyboard
10642 		N1 = 0x31, /// Number key atop the keyboard
10643 		N2 = 0x32, ///
10644 		N3 = 0x33, ///
10645 		N4 = 0x34, ///
10646 		N5 = 0x35, ///
10647 		N6 = 0x36, ///
10648 		N7 = 0x37, ///
10649 		N8 = 0x38, ///
10650 		N9 = 0x39, ///
10651 		N0 = 0x30, ///
10652 		Dash = 0x2d, ///
10653 		Equals = 0x3d, ///
10654 		Backslash = 0x5c, /// The \ | key
10655 		Backspace = 0xff08, ///
10656 		Insert = 0xff63, ///
10657 		Home = 0xff50, ///
10658 		PageUp = 0xff55, ///
10659 		Delete = 0xffff, ///
10660 		End = 0xff57, ///
10661 		PageDown = 0xff56, ///
10662 		Up = 0xff52, ///
10663 		Down = 0xff54, ///
10664 		Left = 0xff51, ///
10665 		Right = 0xff53, ///
10666 
10667 		Tab = 0xff09, ///
10668 		Q = 0x71, ///
10669 		W = 0x77, ///
10670 		E = 0x65, ///
10671 		R = 0x72, ///
10672 		T = 0x74, ///
10673 		Y = 0x79, ///
10674 		U = 0x75, ///
10675 		I = 0x69, ///
10676 		O = 0x6f, ///
10677 		P = 0x70, ///
10678 		LeftBracket = 0x5b, /// the [ { key
10679 		RightBracket = 0x5d, /// the ] } key
10680 		CapsLock = 0xffe5, ///
10681 		A = 0x61, ///
10682 		S = 0x73, ///
10683 		D = 0x64, ///
10684 		F = 0x66, ///
10685 		G = 0x67, ///
10686 		H = 0x68, ///
10687 		J = 0x6a, ///
10688 		K = 0x6b, ///
10689 		L = 0x6c, ///
10690 		Semicolon = 0x3b, ///
10691 		Apostrophe = 0x27, ///
10692 		Enter = 0xff0d, ///
10693 		Shift = 0xffe1, ///
10694 		Z = 0x7a, ///
10695 		X = 0x78, ///
10696 		C = 0x63, ///
10697 		V = 0x76, ///
10698 		B = 0x62, ///
10699 		N = 0x6e, ///
10700 		M = 0x6d, ///
10701 		Comma = 0x2c, ///
10702 		Period = 0x2e, ///
10703 		Slash = 0x2f, /// the / ? key
10704 		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
10705 		Ctrl = 0xffe3, ///
10706 		Windows = 0xffeb, ///
10707 		Alt = 0xffe9, ///
10708 		Space = 0x20, ///
10709 		Alt_r = 0xffea, /// ditto of shift_r
10710 		Windows_r = 0xffec, ///
10711 		Menu = 0xff67, ///
10712 		Ctrl_r = 0xffe4, ///
10713 
10714 		NumLock = 0xff7f, ///
10715 		Divide = 0xffaf, /// The / key on the number pad
10716 		Multiply = 0xffaa, /// The * key on the number pad
10717 		Minus = 0xffad, /// The - key on the number pad
10718 		Plus = 0xffab, /// The + key on the number pad
10719 		PadEnter = 0xff8d, /// Numberpad enter key
10720 		Pad1 = 0xff9c, /// Numberpad keys
10721 		Pad2 = 0xff99, ///
10722 		Pad3 = 0xff9b, ///
10723 		Pad4 = 0xff96, ///
10724 		Pad5 = 0xff9d, ///
10725 		Pad6 = 0xff98, ///
10726 		Pad7 = 0xff95, ///
10727 		Pad8 = 0xff97, ///
10728 		Pad9 = 0xff9a, ///
10729 		Pad0 = 0xff9e, ///
10730 		PadDot = 0xff9f, ///
10731 	}
10732 } else version(Windows) {
10733 	// the character here is for en-us layouts and for illustration only
10734 	// if you actually want to get characters, wait for character events
10735 	// (the argument to your event handler is simply a dchar)
10736 	// those will be converted by the OS for the right locale.
10737 
10738 	enum Key {
10739 		Escape = 0x1b,
10740 		F1 = 0x70,
10741 		F2 = 0x71,
10742 		F3 = 0x72,
10743 		F4 = 0x73,
10744 		F5 = 0x74,
10745 		F6 = 0x75,
10746 		F7 = 0x76,
10747 		F8 = 0x77,
10748 		F9 = 0x78,
10749 		F10 = 0x79,
10750 		F11 = 0x7a,
10751 		F12 = 0x7b,
10752 		PrintScreen = 0x2c,
10753 		ScrollLock = 0x91,
10754 		Pause = 0x13,
10755 		Grave = 0xc0,
10756 		// number keys across the top of the keyboard
10757 		N1 = 0x31,
10758 		N2 = 0x32,
10759 		N3 = 0x33,
10760 		N4 = 0x34,
10761 		N5 = 0x35,
10762 		N6 = 0x36,
10763 		N7 = 0x37,
10764 		N8 = 0x38,
10765 		N9 = 0x39,
10766 		N0 = 0x30,
10767 		Dash = 0xbd,
10768 		Equals = 0xbb,
10769 		Backslash = 0xdc,
10770 		Backspace = 0x08,
10771 		Insert = 0x2d,
10772 		Home = 0x24,
10773 		PageUp = 0x21,
10774 		Delete = 0x2e,
10775 		End = 0x23,
10776 		PageDown = 0x22,
10777 		Up = 0x26,
10778 		Down = 0x28,
10779 		Left = 0x25,
10780 		Right = 0x27,
10781 
10782 		Tab = 0x09,
10783 		Q = 0x51,
10784 		W = 0x57,
10785 		E = 0x45,
10786 		R = 0x52,
10787 		T = 0x54,
10788 		Y = 0x59,
10789 		U = 0x55,
10790 		I = 0x49,
10791 		O = 0x4f,
10792 		P = 0x50,
10793 		LeftBracket = 0xdb,
10794 		RightBracket = 0xdd,
10795 		CapsLock = 0x14,
10796 		A = 0x41,
10797 		S = 0x53,
10798 		D = 0x44,
10799 		F = 0x46,
10800 		G = 0x47,
10801 		H = 0x48,
10802 		J = 0x4a,
10803 		K = 0x4b,
10804 		L = 0x4c,
10805 		Semicolon = 0xba,
10806 		Apostrophe = 0xde,
10807 		Enter = 0x0d,
10808 		Shift = 0x10,
10809 		Z = 0x5a,
10810 		X = 0x58,
10811 		C = 0x43,
10812 		V = 0x56,
10813 		B = 0x42,
10814 		N = 0x4e,
10815 		M = 0x4d,
10816 		Comma = 0xbc,
10817 		Period = 0xbe,
10818 		Slash = 0xbf,
10819 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10820 		Ctrl = 0x11,
10821 		Windows = 0x5b,
10822 		Alt = -5, // FIXME
10823 		Space = 0x20,
10824 		Alt_r = 0xffea, // ditto of shift_r
10825 		Windows_r = 0x5c, // ditto of shift_r
10826 		Menu = 0x5d,
10827 		Ctrl_r = 0xa3, // ditto of shift_r
10828 
10829 		NumLock = 0x90,
10830 		Divide = 0x6f,
10831 		Multiply = 0x6a,
10832 		Minus = 0x6d,
10833 		Plus = 0x6b,
10834 		PadEnter = -8, // FIXME
10835 		Pad1 = 0x61,
10836 		Pad2 = 0x62,
10837 		Pad3 = 0x63,
10838 		Pad4 = 0x64,
10839 		Pad5 = 0x65,
10840 		Pad6 = 0x66,
10841 		Pad7 = 0x67,
10842 		Pad8 = 0x68,
10843 		Pad9 = 0x69,
10844 		Pad0 = 0x60,
10845 		PadDot = 0x6e,
10846 	}
10847 
10848 	// I'm keeping this around for reference purposes
10849 	// ideally all these buttons will be listed for all platforms,
10850 	// but now now I'm just focusing on my US keyboard
10851 	version(none)
10852 	enum Key {
10853 		LBUTTON = 0x01,
10854 		RBUTTON = 0x02,
10855 		CANCEL = 0x03,
10856 		MBUTTON = 0x04,
10857 		//static if (_WIN32_WINNT > =  0x500) {
10858 		XBUTTON1 = 0x05,
10859 		XBUTTON2 = 0x06,
10860 		//}
10861 		BACK = 0x08,
10862 		TAB = 0x09,
10863 		CLEAR = 0x0C,
10864 		RETURN = 0x0D,
10865 		SHIFT = 0x10,
10866 		CONTROL = 0x11,
10867 		MENU = 0x12,
10868 		PAUSE = 0x13,
10869 		CAPITAL = 0x14,
10870 		KANA = 0x15,
10871 		HANGEUL = 0x15,
10872 		HANGUL = 0x15,
10873 		JUNJA = 0x17,
10874 		FINAL = 0x18,
10875 		HANJA = 0x19,
10876 		KANJI = 0x19,
10877 		ESCAPE = 0x1B,
10878 		CONVERT = 0x1C,
10879 		NONCONVERT = 0x1D,
10880 		ACCEPT = 0x1E,
10881 		MODECHANGE = 0x1F,
10882 		SPACE = 0x20,
10883 		PRIOR = 0x21,
10884 		NEXT = 0x22,
10885 		END = 0x23,
10886 		HOME = 0x24,
10887 		LEFT = 0x25,
10888 		UP = 0x26,
10889 		RIGHT = 0x27,
10890 		DOWN = 0x28,
10891 		SELECT = 0x29,
10892 		PRINT = 0x2A,
10893 		EXECUTE = 0x2B,
10894 		SNAPSHOT = 0x2C,
10895 		INSERT = 0x2D,
10896 		DELETE = 0x2E,
10897 		HELP = 0x2F,
10898 		LWIN = 0x5B,
10899 		RWIN = 0x5C,
10900 		APPS = 0x5D,
10901 		SLEEP = 0x5F,
10902 		NUMPAD0 = 0x60,
10903 		NUMPAD1 = 0x61,
10904 		NUMPAD2 = 0x62,
10905 		NUMPAD3 = 0x63,
10906 		NUMPAD4 = 0x64,
10907 		NUMPAD5 = 0x65,
10908 		NUMPAD6 = 0x66,
10909 		NUMPAD7 = 0x67,
10910 		NUMPAD8 = 0x68,
10911 		NUMPAD9 = 0x69,
10912 		MULTIPLY = 0x6A,
10913 		ADD = 0x6B,
10914 		SEPARATOR = 0x6C,
10915 		SUBTRACT = 0x6D,
10916 		DECIMAL = 0x6E,
10917 		DIVIDE = 0x6F,
10918 		F1 = 0x70,
10919 		F2 = 0x71,
10920 		F3 = 0x72,
10921 		F4 = 0x73,
10922 		F5 = 0x74,
10923 		F6 = 0x75,
10924 		F7 = 0x76,
10925 		F8 = 0x77,
10926 		F9 = 0x78,
10927 		F10 = 0x79,
10928 		F11 = 0x7A,
10929 		F12 = 0x7B,
10930 		F13 = 0x7C,
10931 		F14 = 0x7D,
10932 		F15 = 0x7E,
10933 		F16 = 0x7F,
10934 		F17 = 0x80,
10935 		F18 = 0x81,
10936 		F19 = 0x82,
10937 		F20 = 0x83,
10938 		F21 = 0x84,
10939 		F22 = 0x85,
10940 		F23 = 0x86,
10941 		F24 = 0x87,
10942 		NUMLOCK = 0x90,
10943 		SCROLL = 0x91,
10944 		LSHIFT = 0xA0,
10945 		RSHIFT = 0xA1,
10946 		LCONTROL = 0xA2,
10947 		RCONTROL = 0xA3,
10948 		LMENU = 0xA4,
10949 		RMENU = 0xA5,
10950 		//static if (_WIN32_WINNT > =  0x500) {
10951 		BROWSER_BACK = 0xA6,
10952 		BROWSER_FORWARD = 0xA7,
10953 		BROWSER_REFRESH = 0xA8,
10954 		BROWSER_STOP = 0xA9,
10955 		BROWSER_SEARCH = 0xAA,
10956 		BROWSER_FAVORITES = 0xAB,
10957 		BROWSER_HOME = 0xAC,
10958 		VOLUME_MUTE = 0xAD,
10959 		VOLUME_DOWN = 0xAE,
10960 		VOLUME_UP = 0xAF,
10961 		MEDIA_NEXT_TRACK = 0xB0,
10962 		MEDIA_PREV_TRACK = 0xB1,
10963 		MEDIA_STOP = 0xB2,
10964 		MEDIA_PLAY_PAUSE = 0xB3,
10965 		LAUNCH_MAIL = 0xB4,
10966 		LAUNCH_MEDIA_SELECT = 0xB5,
10967 		LAUNCH_APP1 = 0xB6,
10968 		LAUNCH_APP2 = 0xB7,
10969 		//}
10970 		OEM_1 = 0xBA,
10971 		//static if (_WIN32_WINNT > =  0x500) {
10972 		OEM_PLUS = 0xBB,
10973 		OEM_COMMA = 0xBC,
10974 		OEM_MINUS = 0xBD,
10975 		OEM_PERIOD = 0xBE,
10976 		//}
10977 		OEM_2 = 0xBF,
10978 		OEM_3 = 0xC0,
10979 		OEM_4 = 0xDB,
10980 		OEM_5 = 0xDC,
10981 		OEM_6 = 0xDD,
10982 		OEM_7 = 0xDE,
10983 		OEM_8 = 0xDF,
10984 		//static if (_WIN32_WINNT > =  0x500) {
10985 		OEM_102 = 0xE2,
10986 		//}
10987 		PROCESSKEY = 0xE5,
10988 		//static if (_WIN32_WINNT > =  0x500) {
10989 		PACKET = 0xE7,
10990 		//}
10991 		ATTN = 0xF6,
10992 		CRSEL = 0xF7,
10993 		EXSEL = 0xF8,
10994 		EREOF = 0xF9,
10995 		PLAY = 0xFA,
10996 		ZOOM = 0xFB,
10997 		NONAME = 0xFC,
10998 		PA1 = 0xFD,
10999 		OEM_CLEAR = 0xFE,
11000 	}
11001 
11002 } else version(OSXCocoa) {
11003 	enum Key {
11004 		Escape = 53,
11005 		F1 = 122,
11006 		F2 = 120,
11007 		F3 = 99,
11008 		F4 = 118,
11009 		F5 = 96,
11010 		F6 = 97,
11011 		F7 = 98,
11012 		F8 = 100,
11013 		F9 = 101,
11014 		F10 = 109,
11015 		F11 = 103,
11016 		F12 = 111,
11017 		PrintScreen = 105,
11018 		ScrollLock = 107,
11019 		Pause = 113,
11020 		Grave = 50,
11021 		// number keys across the top of the keyboard
11022 		N1 = 18,
11023 		N2 = 19,
11024 		N3 = 20,
11025 		N4 = 21,
11026 		N5 = 23,
11027 		N6 = 22,
11028 		N7 = 26,
11029 		N8 = 28,
11030 		N9 = 25,
11031 		N0 = 29,
11032 		Dash = 27,
11033 		Equals = 24,
11034 		Backslash = 42,
11035 		Backspace = 51,
11036 		Insert = 114,
11037 		Home = 115,
11038 		PageUp = 116,
11039 		Delete = 117,
11040 		End = 119,
11041 		PageDown = 121,
11042 		Up = 126,
11043 		Down = 125,
11044 		Left = 123,
11045 		Right = 124,
11046 
11047 		Tab = 48,
11048 		Q = 12,
11049 		W = 13,
11050 		E = 14,
11051 		R = 15,
11052 		T = 17,
11053 		Y = 16,
11054 		U = 32,
11055 		I = 34,
11056 		O = 31,
11057 		P = 35,
11058 		LeftBracket = 33,
11059 		RightBracket = 30,
11060 		CapsLock = 57,
11061 		A = 0,
11062 		S = 1,
11063 		D = 2,
11064 		F = 3,
11065 		G = 5,
11066 		H = 4,
11067 		J = 38,
11068 		K = 40,
11069 		L = 37,
11070 		Semicolon = 41,
11071 		Apostrophe = 39,
11072 		Enter = 36,
11073 		Shift = 56,
11074 		Z = 6,
11075 		X = 7,
11076 		C = 8,
11077 		V = 9,
11078 		B = 11,
11079 		N = 45,
11080 		M = 46,
11081 		Comma = 43,
11082 		Period = 47,
11083 		Slash = 44,
11084 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11085 		Ctrl = 59,
11086 		Windows = 55,
11087 		Alt = 58,
11088 		Space = 49,
11089 		Alt_r = -3, // ditto of shift_r
11090 		Windows_r = -2,
11091 		Menu = 110,
11092 		Ctrl_r = -1,
11093 
11094 		NumLock = 1,
11095 		Divide = 75,
11096 		Multiply = 67,
11097 		Minus = 78,
11098 		Plus = 69,
11099 		PadEnter = 76,
11100 		Pad1 = 83,
11101 		Pad2 = 84,
11102 		Pad3 = 85,
11103 		Pad4 = 86,
11104 		Pad5 = 87,
11105 		Pad6 = 88,
11106 		Pad7 = 89,
11107 		Pad8 = 91,
11108 		Pad9 = 92,
11109 		Pad0 = 82,
11110 		PadDot = 65,
11111 	}
11112 
11113 }
11114 
11115 /* Additional utilities */
11116 
11117 
11118 Color fromHsl(real h, real s, real l) {
11119 	return arsd.color.fromHsl([h,s,l]);
11120 }
11121 
11122 
11123 
11124 /* ********** What follows is the system-specific implementations *********/
11125 version(Windows) {
11126 
11127 
11128 	// helpers for making HICONs from MemoryImages
11129 	class WindowsIcon {
11130 		struct Win32Icon {
11131 			align(1):
11132 			uint biSize;
11133 			int biWidth;
11134 			int biHeight;
11135 			ushort biPlanes;
11136 			ushort biBitCount;
11137 			uint biCompression;
11138 			uint biSizeImage;
11139 			int biXPelsPerMeter;
11140 			int biYPelsPerMeter;
11141 			uint biClrUsed;
11142 			uint biClrImportant;
11143 			// RGBQUAD[colorCount] biColors;
11144 			/* Pixels:
11145 			Uint8 pixels[]
11146 			*/
11147 			/* Mask:
11148 			Uint8 mask[]
11149 			*/
11150 		}
11151 
11152 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
11153 
11154 			assert(mi.width <= 256, "image too wide");
11155 			assert(mi.height <= 256, "image too tall");
11156 			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
11157 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
11158 
11159 			int icon_plen = mi.width * mi.height * 4;
11160 			int icon_mlen = mi.width * mi.height / 8;
11161 
11162 			int colorCount = 0;
11163 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
11164 
11165 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
11166 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
11167 
11168 			auto data = memory[Win32Icon.sizeof .. $];
11169 
11170 			width = mi.width;
11171 			height = mi.height;
11172 
11173 			auto trueColorImage = mi.getAsTrueColorImage();
11174 
11175 			icon_win32.biSize = 40;
11176 			icon_win32.biWidth = mi.width;
11177 			icon_win32.biHeight = mi.height*2;
11178 			icon_win32.biPlanes = 1;
11179 			icon_win32.biBitCount = 32;
11180 			icon_win32.biSizeImage = icon_plen + icon_mlen;
11181 
11182 			int offset = 0;
11183 			int andOff = icon_plen * 8; // the and offset is in bits
11184 
11185 			// leaving the and mask as the default 0 so the rgba alpha blend
11186 			// does its thing instead
11187 			for(int y = height - 1; y >= 0; y--) {
11188 				int off2 = y * width * 4;
11189 				foreach(x; 0 .. width) {
11190 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
11191 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
11192 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
11193 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
11194 
11195 					offset += 4;
11196 					off2 += 4;
11197 				}
11198 			}
11199 
11200 			return memory;
11201 		}
11202 
11203 		this(MemoryImage mi) {
11204 			int icon_len, width, height;
11205 
11206 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
11207 
11208 			/*
11209 			PNG* png = readPnpngData);
11210 			PNGHeader pngh = getHeader(png);
11211 			void* icon_win32;
11212 			if(pngh.depth == 4) {
11213 				auto i = new Win32Icon!(16);
11214 				i.fromPNG(png, pngh, icon_len, width, height);
11215 				icon_win32 = i;
11216 			}
11217 			else if(pngh.depth == 8) {
11218 				auto i = new Win32Icon!(256);
11219 				i.fromPNG(png, pngh, icon_len, width, height);
11220 				icon_win32 = i;
11221 			} else assert(0);
11222 			*/
11223 
11224 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
11225 
11226 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
11227 		}
11228 
11229 		~this() {
11230 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
11231 			DestroyIcon(hIcon);
11232 		}
11233 
11234 		HICON hIcon;
11235 	}
11236 
11237 
11238 
11239 
11240 
11241 
11242 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
11243 	alias HWND NativeWindowHandle;
11244 
11245 	extern(Windows)
11246 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
11247 		try {
11248 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
11249 				// it returns zero if the message is handled, so we won't do anything more there
11250 				// do I like that though?
11251 				int mustReturn;
11252 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
11253 				if(mustReturn)
11254 					return ret;
11255 			}
11256 
11257 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
11258 				if(window.getNativeEventHandler !is null) {
11259 					int mustReturn;
11260 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
11261 					if(mustReturn)
11262 						return ret;
11263 				}
11264 				if(auto w = cast(SimpleWindow) (*window))
11265 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
11266 				else
11267 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
11268 			} else {
11269 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
11270 			}
11271 		} catch (Exception e) {
11272 			try {
11273 				sdpy_abort(e);
11274 				return 0;
11275 			} catch(Exception e) { assert(0); }
11276 		}
11277 	}
11278 
11279 	void sdpy_abort(Throwable e) nothrow {
11280 		try
11281 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
11282 		catch(Exception e)
11283 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
11284 		ExitProcess(1);
11285 	}
11286 
11287 	mixin template NativeScreenPainterImplementation() {
11288 		HDC hdc;
11289 		HWND hwnd;
11290 		//HDC windowHdc;
11291 		HBITMAP oldBmp;
11292 
11293 		void create(PaintingHandle window) {
11294 			hwnd = window;
11295 
11296 			if(auto sw = cast(SimpleWindow) this.window) {
11297 				// drawing on a window, double buffer
11298 				auto windowHdc = GetDC(hwnd);
11299 
11300 				auto buffer = sw.impl.buffer;
11301 				if(buffer is null) {
11302 					hdc = windowHdc;
11303 					windowDc = true;
11304 				} else {
11305 					hdc = CreateCompatibleDC(windowHdc);
11306 
11307 					ReleaseDC(hwnd, windowHdc);
11308 
11309 					oldBmp = SelectObject(hdc, buffer);
11310 				}
11311 			} else {
11312 				// drawing on something else, draw directly
11313 				hdc = CreateCompatibleDC(null);
11314 				SelectObject(hdc, window);
11315 			}
11316 
11317 			// X doesn't draw a text background, so neither should we
11318 			SetBkMode(hdc, TRANSPARENT);
11319 
11320 			ensureDefaultFontLoaded();
11321 
11322 			if(defaultGuiFont) {
11323 				SelectObject(hdc, defaultGuiFont);
11324 				// DeleteObject(defaultGuiFont);
11325 			}
11326 		}
11327 
11328 		static HFONT defaultGuiFont;
11329 		static void ensureDefaultFontLoaded() {
11330 			static bool triedDefaultGuiFont = false;
11331 			if(!triedDefaultGuiFont) {
11332 				NONCLIENTMETRICS params;
11333 				params.cbSize = params.sizeof;
11334 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
11335 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
11336 				}
11337 				triedDefaultGuiFont = true;
11338 			}
11339 		}
11340 
11341 		private OperatingSystemFont _activeFont;
11342 
11343 		void setFont(OperatingSystemFont font) {
11344 			_activeFont = font;
11345 			if(font && font.font) {
11346 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
11347 					// error... how to handle tho?
11348 				} else {
11349 
11350 				}
11351 			}
11352 			else if(defaultGuiFont)
11353 				SelectObject(hdc, defaultGuiFont);
11354 		}
11355 
11356 		arsd.color.Rectangle _clipRectangle;
11357 
11358 		void setClipRectangle(int x, int y, int width, int height) {
11359 			auto old = _clipRectangle;
11360 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11361 			if(old == _clipRectangle)
11362 				return;
11363 
11364 			if(width == 0 || height == 0) {
11365 				SelectClipRgn(hdc, null);
11366 			} else {
11367 				auto region = CreateRectRgn(x, y, x + width, y + height);
11368 				SelectClipRgn(hdc, region);
11369 				DeleteObject(region);
11370 			}
11371 		}
11372 
11373 
11374 		// just because we can on Windows...
11375 		//void create(Image image);
11376 
11377 		void invalidateRect(Rectangle invalidRect) {
11378 			RECT rect;
11379 			rect.left = invalidRect.left;
11380 			rect.right = invalidRect.right;
11381 			rect.top = invalidRect.top;
11382 			rect.bottom = invalidRect.bottom;
11383 			InvalidateRect(hwnd, &rect, false);
11384 		}
11385 		bool manualInvalidations;
11386 
11387 		void dispose() {
11388 			// FIXME: this.window.width/height is probably wrong
11389 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
11390 			// ReleaseDC(hwnd, windowHdc);
11391 
11392 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
11393 			if(cast(SimpleWindow) this.window) {
11394 				if(!manualInvalidations)
11395 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
11396 			}
11397 
11398 			if(originalPen !is null)
11399 				SelectObject(hdc, originalPen);
11400 			if(currentPen !is null)
11401 				DeleteObject(currentPen);
11402 			if(originalBrush !is null)
11403 				SelectObject(hdc, originalBrush);
11404 			if(currentBrush !is null)
11405 				DeleteObject(currentBrush);
11406 
11407 			SelectObject(hdc, oldBmp);
11408 
11409 			if(windowDc)
11410 				ReleaseDC(hwnd, hdc);
11411 			else
11412 				DeleteDC(hdc);
11413 
11414 			if(window.paintingFinishedDg !is null)
11415 				window.paintingFinishedDg()();
11416 		}
11417 
11418 		bool windowDc;
11419 		HPEN originalPen;
11420 		HPEN currentPen;
11421 
11422 		Pen _activePen;
11423 
11424 		Color _outlineColor;
11425 
11426 		@property void pen(Pen p) {
11427 			_activePen = p;
11428 			_outlineColor = p.color;
11429 
11430 			HPEN pen;
11431 			if(p.color.a == 0) {
11432 				pen = GetStockObject(NULL_PEN);
11433 			} else {
11434 				int style = PS_SOLID;
11435 				final switch(p.style) {
11436 					case Pen.Style.Solid:
11437 						style = PS_SOLID;
11438 					break;
11439 					case Pen.Style.Dashed:
11440 						style = PS_DASH;
11441 					break;
11442 					case Pen.Style.Dotted:
11443 						style = PS_DOT;
11444 					break;
11445 				}
11446 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
11447 			}
11448 			auto orig = SelectObject(hdc, pen);
11449 			if(originalPen is null)
11450 				originalPen = orig;
11451 
11452 			if(currentPen !is null)
11453 				DeleteObject(currentPen);
11454 
11455 			currentPen = pen;
11456 
11457 			// the outline is like a foreground since it's done that way on X
11458 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
11459 
11460 		}
11461 
11462 		@property void rasterOp(RasterOp op) {
11463 			int mode;
11464 			final switch(op) {
11465 				case RasterOp.normal:
11466 					mode = R2_COPYPEN;
11467 				break;
11468 				case RasterOp.xor:
11469 					mode = R2_XORPEN;
11470 				break;
11471 			}
11472 			SetROP2(hdc, mode);
11473 		}
11474 
11475 		HBRUSH originalBrush;
11476 		HBRUSH currentBrush;
11477 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
11478 		@property void fillColor(Color c) {
11479 			if(c == _fillColor)
11480 				return;
11481 			_fillColor = c;
11482 			HBRUSH brush;
11483 			if(c.a == 0) {
11484 				brush = GetStockObject(HOLLOW_BRUSH);
11485 			} else {
11486 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
11487 			}
11488 			auto orig = SelectObject(hdc, brush);
11489 			if(originalBrush is null)
11490 				originalBrush = orig;
11491 
11492 			if(currentBrush !is null)
11493 				DeleteObject(currentBrush);
11494 
11495 			currentBrush = brush;
11496 
11497 			// background color is NOT set because X doesn't draw text backgrounds
11498 			//   SetBkColor(hdc, RGB(255, 255, 255));
11499 		}
11500 
11501 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11502 			BITMAP bm;
11503 
11504 			HDC hdcMem = CreateCompatibleDC(hdc);
11505 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
11506 
11507 			GetObject(i.handle, bm.sizeof, &bm);
11508 
11509 			// or should I AlphaBlend!??!?!
11510 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
11511 
11512 			SelectObject(hdcMem, hbmOld);
11513 			DeleteDC(hdcMem);
11514 		}
11515 
11516 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
11517 			BITMAP bm;
11518 
11519 			HDC hdcMem = CreateCompatibleDC(hdc);
11520 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
11521 
11522 			GetObject(s.handle, bm.sizeof, &bm);
11523 
11524 			version(CRuntime_DigitalMars) goto noalpha;
11525 
11526 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
11527 			if(s.enableAlpha) {
11528 				auto dw = w ? w : bm.bmWidth;
11529 				auto dh = h ? h : bm.bmHeight;
11530 				BLENDFUNCTION bf;
11531 				bf.BlendOp = AC_SRC_OVER;
11532 				bf.SourceConstantAlpha = 255;
11533 				bf.AlphaFormat = AC_SRC_ALPHA;
11534 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
11535 			} else {
11536 				noalpha:
11537 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
11538 			}
11539 
11540 			SelectObject(hdcMem, hbmOld);
11541 			DeleteDC(hdcMem);
11542 		}
11543 
11544 		Size textSize(scope const(char)[] text) {
11545 			bool dummyX;
11546 			if(text.length == 0) {
11547 				text = " ";
11548 				dummyX = true;
11549 			}
11550 			RECT rect;
11551 			WCharzBuffer buffer = WCharzBuffer(text);
11552 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
11553 			return Size(dummyX ? 0 : rect.right, rect.bottom);
11554 		}
11555 
11556 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
11557 			if(text.length && text[$-1] == '\n')
11558 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
11559 			if(text.length && text[$-1] == '\r')
11560 				text = text[0 .. $-1];
11561 
11562 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
11563 			if(x2 == 0 && y2 == 0) {
11564 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
11565 			} else {
11566 				RECT rect;
11567 				rect.left = x;
11568 				rect.top = y;
11569 				rect.right = x2;
11570 				rect.bottom = y2;
11571 
11572 				uint mode = DT_LEFT;
11573 				if(alignment & TextAlignment.Right)
11574 					mode = DT_RIGHT;
11575 				else if(alignment & TextAlignment.Center)
11576 					mode = DT_CENTER;
11577 
11578 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
11579 				if(alignment & TextAlignment.VerticalCenter)
11580 					mode |= DT_VCENTER | DT_SINGLELINE;
11581 
11582 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
11583 			}
11584 
11585 			/*
11586 			uint mode;
11587 
11588 			if(alignment & TextAlignment.Center)
11589 				mode = TA_CENTER;
11590 
11591 			SetTextAlign(hdc, mode);
11592 			*/
11593 		}
11594 
11595 		int fontHeight() {
11596 			TEXTMETRIC metric;
11597 			if(GetTextMetricsW(hdc, &metric)) {
11598 				return metric.tmHeight;
11599 			}
11600 
11601 			return 16; // idk just guessing here, maybe we should throw
11602 		}
11603 
11604 		void drawPixel(int x, int y) {
11605 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
11606 		}
11607 
11608 		// The basic shapes, outlined
11609 
11610 		void drawLine(int x1, int y1, int x2, int y2) {
11611 			MoveToEx(hdc, x1, y1, null);
11612 			LineTo(hdc, x2, y2);
11613 		}
11614 
11615 		void drawRectangle(int x, int y, int width, int height) {
11616 			// FIXME: with a wider pen this might not draw quite right. im not sure.
11617 			gdi.Rectangle(hdc, x, y, x + width, y + height);
11618 		}
11619 
11620 		/// Arguments are the points of the bounding rectangle
11621 		void drawEllipse(int x1, int y1, int x2, int y2) {
11622 			Ellipse(hdc, x1, y1, x2, y2);
11623 		}
11624 
11625 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11626 			if((start % (360*64)) == (finish % (360*64)))
11627 				drawEllipse(x1, y1, x1 + width, y1 + height);
11628 			else {
11629 				import core.stdc.math;
11630 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
11631 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
11632 
11633 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
11634 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
11635 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
11636 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
11637 
11638 				if(_activePen.color.a)
11639 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11640 				if(_fillColor.a)
11641 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
11642 			}
11643 		}
11644 
11645 		void drawPolygon(Point[] vertexes) {
11646 			POINT[] points;
11647 			points.length = vertexes.length;
11648 
11649 			foreach(i, p; vertexes) {
11650 				points[i].x = p.x;
11651 				points[i].y = p.y;
11652 			}
11653 
11654 			Polygon(hdc, points.ptr, cast(int) points.length);
11655 		}
11656 	}
11657 
11658 
11659 	// Mix this into the SimpleWindow class
11660 	mixin template NativeSimpleWindowImplementation() {
11661 		int curHidden = 0; // counter
11662 		__gshared static bool[string] knownWinClasses;
11663 		static bool altPressed = false;
11664 
11665 		HANDLE oldCursor;
11666 
11667 		void hideCursor () {
11668 			if(curHidden == 0)
11669 				oldCursor = SetCursor(null);
11670 			++curHidden;
11671 		}
11672 
11673 		void showCursor () {
11674 			--curHidden;
11675 			if(curHidden == 0) {
11676 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
11677 			}
11678 		}
11679 
11680 
11681 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
11682 
11683 		void setMinSize (int minwidth, int minheight) {
11684 			minWidth = minwidth;
11685 			minHeight = minheight;
11686 		}
11687 		void setMaxSize (int maxwidth, int maxheight) {
11688 			maxWidth = maxwidth;
11689 			maxHeight = maxheight;
11690 		}
11691 
11692 		// FIXME i'm not sure that Windows has this functionality
11693 		// though it is nonessential anyway.
11694 		void setResizeGranularity (int granx, int grany) {}
11695 
11696 		ScreenPainter getPainter(bool manualInvalidations) {
11697 			return ScreenPainter(this, hwnd, manualInvalidations);
11698 		}
11699 
11700 		HBITMAP buffer;
11701 
11702 		void setTitle(string title) {
11703 			WCharzBuffer bfr = WCharzBuffer(title);
11704 			SetWindowTextW(hwnd, bfr.ptr);
11705 		}
11706 
11707 		string getTitle() {
11708 			auto len = GetWindowTextLengthW(hwnd);
11709 			if (!len)
11710 				return null;
11711 			wchar[256] tmpBuffer;
11712 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
11713 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11714 			auto str = buffer[0 .. len2];
11715 			return makeUtf8StringFromWindowsString(str);
11716 		}
11717 
11718 		void move(int x, int y) {
11719 			RECT rect;
11720 			GetWindowRect(hwnd, &rect);
11721 			// move it while maintaining the same size...
11722 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
11723 		}
11724 
11725 		void resize(int w, int h) {
11726 			RECT rect;
11727 			GetWindowRect(hwnd, &rect);
11728 
11729 			RECT client;
11730 			GetClientRect(hwnd, &client);
11731 
11732 			rect.right = rect.right - client.right + w;
11733 			rect.bottom = rect.bottom - client.bottom + h;
11734 
11735 			// same position, new size for the client rectangle
11736 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
11737 
11738 			updateOpenglViewportIfNeeded(w, h);
11739 		}
11740 
11741 		void moveResize (int x, int y, int w, int h) {
11742 			// what's given is the client rectangle, we need to adjust
11743 
11744 			RECT rect;
11745 			rect.left = x;
11746 			rect.top = y;
11747 			rect.right = w + x;
11748 			rect.bottom = h + y;
11749 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
11750 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
11751 
11752 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
11753 			updateOpenglViewportIfNeeded(w, h);
11754 			if (windowResized !is null) windowResized(w, h);
11755 		}
11756 
11757 		version(without_opengl) {} else {
11758 			HGLRC ghRC;
11759 			HDC ghDC;
11760 		}
11761 
11762 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
11763 			string cnamec;
11764 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
11765 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
11766 				cnamec = "DSimpleWindow";
11767 			} else {
11768 				cnamec = sdpyWindowClass;
11769 			}
11770 
11771 			WCharzBuffer cn = WCharzBuffer(cnamec);
11772 
11773 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
11774 
11775 			if(cnamec !in knownWinClasses) {
11776 				WNDCLASSEX wc;
11777 
11778 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
11779 				// to the object. Maybe.
11780 				wc.cbSize = wc.sizeof;
11781 				wc.cbClsExtra = 0;
11782 				wc.cbWndExtra = 0;
11783 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
11784 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
11785 				wc.hIcon = LoadIcon(hInstance, null);
11786 				wc.hInstance = hInstance;
11787 				wc.lpfnWndProc = &WndProc;
11788 				wc.lpszClassName = cn.ptr;
11789 				wc.hIconSm = null;
11790 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
11791 				if(!RegisterClassExW(&wc))
11792 					throw new WindowsApiException("RegisterClassExW", GetLastError());
11793 				knownWinClasses[cnamec] = true;
11794 			}
11795 
11796 			int style;
11797 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
11798 
11799 			// FIXME: windowType and customizationFlags
11800 			final switch(windowType) {
11801 				case WindowTypes.normal:
11802 					if(resizability == Resizability.fixedSize) {
11803 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
11804 					} else {
11805 						style = WS_OVERLAPPEDWINDOW;
11806 					}
11807 				break;
11808 				case WindowTypes.undecorated:
11809 					style = WS_POPUP | WS_SYSMENU;
11810 				break;
11811 				case WindowTypes.eventOnly:
11812 					_hidden = true;
11813 				break;
11814 				case WindowTypes.dropdownMenu:
11815 				case WindowTypes.popupMenu:
11816 				case WindowTypes.notification:
11817 					style = WS_POPUP;
11818 					flags |= WS_EX_NOACTIVATE;
11819 				break;
11820 				case WindowTypes.nestedChild:
11821 					style = WS_CHILD;
11822 				break;
11823 				case WindowTypes.minimallyWrapped:
11824 					assert(0, "construct minimally wrapped through the other ctor overlad");
11825 			}
11826 
11827 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11828 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
11829 
11830 			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
11831 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
11832 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
11833 
11834 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
11835 				setOpacity(255);
11836 
11837 			SimpleWindow.nativeMapping[hwnd] = this;
11838 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
11839 
11840 			if(windowType == WindowTypes.eventOnly)
11841 				return;
11842 
11843 			HDC hdc = GetDC(hwnd);
11844 
11845 
11846 			version(without_opengl) {}
11847 			else {
11848 				if(opengl == OpenGlOptions.yes) {
11849 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
11850 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
11851 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
11852 					ghDC = hdc;
11853 					PIXELFORMATDESCRIPTOR pfd;
11854 
11855 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
11856 					pfd.nVersion = 1;
11857 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
11858 					pfd.dwLayerMask = PFD_MAIN_PLANE;
11859 					pfd.iPixelType = PFD_TYPE_RGBA;
11860 					pfd.cColorBits = 24;
11861 					pfd.cDepthBits = 24;
11862 					pfd.cAccumBits = 0;
11863 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
11864 
11865 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
11866 
11867 					if (pixelformat == 0)
11868 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
11869 
11870 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
11871 						throw new WindowsApiException("SetPixelFormat", GetLastError());
11872 
11873 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
11874 						// windoze is idiotic: we have to have OpenGL context to get function addresses
11875 						// so we will create fake context to get that stupid address
11876 						auto tmpcc = wglCreateContext(ghDC);
11877 						if (tmpcc !is null) {
11878 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11879 							wglMakeCurrent(ghDC, tmpcc);
11880 							wglInitOtherFunctions();
11881 						}
11882 					}
11883 
11884 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11885 						int[9] contextAttribs = [
11886 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11887 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11888 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11889 							// for modern context, set "forward compatibility" flag too
11890 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11891 							0/*None*/,
11892 						];
11893 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11894 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11895 							// activate fallback mode
11896 							// 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;
11897 							ghRC = wglCreateContext(ghDC);
11898 						}
11899 						if (ghRC is null)
11900 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
11901 					} else {
11902 						// try to do at least something
11903 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11904 							sdpyOpenGLContextVersion = 0;
11905 							ghRC = wglCreateContext(ghDC);
11906 						}
11907 						if (ghRC is null)
11908 							throw new WindowsApiException("wglCreateContext", GetLastError());
11909 					}
11910 				}
11911 			}
11912 
11913 			if(opengl == OpenGlOptions.no) {
11914 				buffer = CreateCompatibleBitmap(hdc, width, height);
11915 
11916 				auto hdcBmp = CreateCompatibleDC(hdc);
11917 				// make sure it's filled with a blank slate
11918 				auto oldBmp = SelectObject(hdcBmp, buffer);
11919 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11920 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11921 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11922 				SelectObject(hdcBmp, oldBmp);
11923 				SelectObject(hdcBmp, oldBrush);
11924 				SelectObject(hdcBmp, oldPen);
11925 				DeleteDC(hdcBmp);
11926 
11927 				bmpWidth = width;
11928 				bmpHeight = height;
11929 
11930 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11931 			}
11932 
11933 			// We want the window's client area to match the image size
11934 			RECT rcClient, rcWindow;
11935 			POINT ptDiff;
11936 			GetClientRect(hwnd, &rcClient);
11937 			GetWindowRect(hwnd, &rcWindow);
11938 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11939 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11940 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11941 
11942 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11943 				ShowWindow(hwnd, SW_SHOWNORMAL);
11944 			} else {
11945 				_hidden = true;
11946 			}
11947 			this._visibleForTheFirstTimeCalled = false; // hack!
11948 		}
11949 
11950 
11951 		void dispose() {
11952 			if(buffer)
11953 				DeleteObject(buffer);
11954 		}
11955 
11956 		void closeWindow() {
11957 			if(ghRC) {
11958 				wglDeleteContext(ghRC);
11959 				ghRC = null;
11960 			}
11961 			DestroyWindow(hwnd);
11962 		}
11963 
11964 		bool setOpacity(ubyte alpha) {
11965 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11966 		}
11967 
11968 		HANDLE currentCursor;
11969 
11970 		// returns zero if it recognized the event
11971 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11972 			MouseEvent mouse;
11973 
11974 			void mouseEvent(bool isScreen, ulong mods) {
11975 				auto x = LOWORD(lParam);
11976 				auto y = HIWORD(lParam);
11977 				if(isScreen) {
11978 					POINT p;
11979 					p.x = x;
11980 					p.y = y;
11981 					ScreenToClient(hwnd, &p);
11982 					x = cast(ushort) p.x;
11983 					y = cast(ushort) p.y;
11984 				}
11985 
11986 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
11987 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
11988 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
11989 				}
11990 
11991 				mouse.x = x + offsetX;
11992 				mouse.y = y + offsetY;
11993 
11994 				wind.mdx(mouse);
11995 				mouse.modifierState = cast(int) mods;
11996 				mouse.window = wind;
11997 
11998 				if(wind.handleMouseEvent)
11999 					wind.handleMouseEvent(mouse);
12000 			}
12001 
12002 			switch(msg) {
12003 				case WM_GETMINMAXINFO:
12004 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
12005 
12006 					if(wind.minWidth > 0) {
12007 						RECT rect;
12008 						rect.left = 100;
12009 						rect.top = 100;
12010 						rect.right = wind.minWidth + 100;
12011 						rect.bottom = wind.minHeight + 100;
12012 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12013 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12014 
12015 						mmi.ptMinTrackSize.x = rect.right - rect.left;
12016 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
12017 					}
12018 
12019 					if(wind.maxWidth < int.max) {
12020 						RECT rect;
12021 						rect.left = 100;
12022 						rect.top = 100;
12023 						rect.right = wind.maxWidth + 100;
12024 						rect.bottom = wind.maxHeight + 100;
12025 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
12026 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
12027 
12028 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
12029 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
12030 					}
12031 				break;
12032 				case WM_CHAR:
12033 					wchar c = cast(wchar) wParam;
12034 					if(wind.handleCharEvent)
12035 						wind.handleCharEvent(cast(dchar) c);
12036 				break;
12037 				  case WM_SETFOCUS:
12038 				  case WM_KILLFOCUS:
12039 					wind._focused = (msg == WM_SETFOCUS);
12040 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
12041 					if(wind.onFocusChange)
12042 						wind.onFocusChange(msg == WM_SETFOCUS);
12043 				  break;
12044 
12045 				case WM_SYSKEYDOWN:
12046 					goto case;
12047 				case WM_SYSKEYUP:
12048 					if(lParam & (1 << 29)) {
12049 						goto case;
12050 					} else {
12051 						// no window has keyboard focus
12052 						goto default;
12053 					}
12054 				case WM_KEYDOWN:
12055 				case WM_KEYUP:
12056 					KeyEvent ev;
12057 					ev.key = cast(Key) wParam;
12058 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
12059 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
12060 
12061 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
12062 
12063 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
12064 						ev.modifierState |= ModifierState.shift;
12065 					//k8: this doesn't work; thanks for nothing, windows
12066 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
12067 						ev.modifierState |= ModifierState.alt;*/
12068 					// this never seems to actually be set
12069 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12070 
12071 					if (wParam == 0x12) {
12072 						altPressed = (msg == WM_SYSKEYDOWN);
12073 					}
12074 
12075 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
12076 						altPressed = false;
12077 					}
12078 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
12079 
12080 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
12081 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
12082 						ev.modifierState |= ModifierState.ctrl;
12083 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
12084 						ev.modifierState |= ModifierState.windows;
12085 					if(GetKeyState(Key.NumLock))
12086 						ev.modifierState |= ModifierState.numLock;
12087 					if(GetKeyState(Key.CapsLock))
12088 						ev.modifierState |= ModifierState.capsLock;
12089 
12090 					/+
12091 					// we always want to send the character too, so let's convert it
12092 					ubyte[256] state;
12093 					wchar[16] buffer;
12094 					GetKeyboardState(state.ptr);
12095 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
12096 
12097 					foreach(dchar d; buffer) {
12098 						ev.character = d;
12099 						break;
12100 					}
12101 					+/
12102 
12103 					ev.window = wind;
12104 					if(wind.handleKeyEvent)
12105 						wind.handleKeyEvent(ev);
12106 				break;
12107 				case 0x020a /*WM_MOUSEWHEEL*/:
12108 					// send click
12109 					mouse.type = cast(MouseEventType) 1;
12110 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12111 					mouseEvent(true, LOWORD(wParam));
12112 
12113 					// also send release
12114 					mouse.type = cast(MouseEventType) 2;
12115 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
12116 					mouseEvent(true, LOWORD(wParam));
12117 				break;
12118 				case WM_MOUSEMOVE:
12119 					mouse.type = cast(MouseEventType) 0;
12120 					mouseEvent(false, wParam);
12121 				break;
12122 				case WM_LBUTTONDOWN:
12123 				case WM_LBUTTONDBLCLK:
12124 					mouse.type = cast(MouseEventType) 1;
12125 					mouse.button = MouseButton.left;
12126 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
12127 					mouseEvent(false, wParam);
12128 				break;
12129 				case WM_LBUTTONUP:
12130 					mouse.type = cast(MouseEventType) 2;
12131 					mouse.button = MouseButton.left;
12132 					mouseEvent(false, wParam);
12133 				break;
12134 				case WM_RBUTTONDOWN:
12135 				case WM_RBUTTONDBLCLK:
12136 					mouse.type = cast(MouseEventType) 1;
12137 					mouse.button = MouseButton.right;
12138 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
12139 					mouseEvent(false, wParam);
12140 				break;
12141 				case WM_RBUTTONUP:
12142 					mouse.type = cast(MouseEventType) 2;
12143 					mouse.button = MouseButton.right;
12144 					mouseEvent(false, wParam);
12145 				break;
12146 				case WM_MBUTTONDOWN:
12147 				case WM_MBUTTONDBLCLK:
12148 					mouse.type = cast(MouseEventType) 1;
12149 					mouse.button = MouseButton.middle;
12150 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
12151 					mouseEvent(false, wParam);
12152 				break;
12153 				case WM_MBUTTONUP:
12154 					mouse.type = cast(MouseEventType) 2;
12155 					mouse.button = MouseButton.middle;
12156 					mouseEvent(false, wParam);
12157 				break;
12158 				case WM_XBUTTONDOWN:
12159 				case WM_XBUTTONDBLCLK:
12160 					mouse.type = cast(MouseEventType) 1;
12161 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12162 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
12163 					mouseEvent(false, wParam);
12164 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
12165 				case WM_XBUTTONUP:
12166 					mouse.type = cast(MouseEventType) 2;
12167 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
12168 					mouseEvent(false, wParam);
12169 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
12170 
12171 				default: return 1;
12172 			}
12173 			return 0;
12174 		}
12175 
12176 		HWND hwnd;
12177 		private int oldWidth;
12178 		private int oldHeight;
12179 		private bool inSizeMove;
12180 
12181 		/++
12182 			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.
12183 
12184 			History:
12185 				Added November 23, 2021
12186 
12187 				Not fully stable, may be moved out of the impl struct.
12188 
12189 				Default value changed to `true` on February 15, 2021
12190 		+/
12191 		bool doLiveResizing = true;
12192 
12193 		package int bmpWidth;
12194 		package int bmpHeight;
12195 
12196 		// the extern(Windows) wndproc should just forward to this
12197 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
12198 		try {
12199 			assert(hwnd is this.hwnd);
12200 
12201 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
12202 			switch(msg) {
12203 				case WM_MENUCHAR: // menu active but key not associated with a thing.
12204 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
12205 					// The main things we can do are select, execute, close, or ignore
12206 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
12207 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
12208 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
12209 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
12210 
12211 					// returns the value in the *high order word* of the return value
12212 					// hence the << 16
12213 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
12214 				case WM_SETCURSOR:
12215 					if(cast(HWND) wParam !is hwnd)
12216 						return 0; // further processing elsewhere
12217 
12218 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
12219 						SetCursor(this.curHidden > 0 ? null : currentCursor);
12220 						return 1;
12221 					} else {
12222 						return DefWindowProc(hwnd, msg, wParam, lParam);
12223 					}
12224 				//break;
12225 
12226 				case WM_CLOSE:
12227 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
12228 				break;
12229 				case WM_DESTROY:
12230 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
12231 					SimpleWindow.nativeMapping.remove(hwnd);
12232 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
12233 
12234 					bool anyImportant = false;
12235 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
12236 						if(w.beingOpenKeepsAppOpen) {
12237 							anyImportant = true;
12238 							break;
12239 						}
12240 					if(!anyImportant) {
12241 						PostQuitMessage(0);
12242 					}
12243 				break;
12244 				case 0x02E0 /*WM_DPICHANGED*/:
12245 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
12246 
12247 					RECT* prcNewWindow = cast(RECT*)lParam;
12248 					// docs say this is the recommended position and we should honor it
12249 					SetWindowPos(hwnd,
12250 							null,
12251 							prcNewWindow.left,
12252 							prcNewWindow.top,
12253 							prcNewWindow.right - prcNewWindow.left,
12254 							prcNewWindow.bottom - prcNewWindow.top,
12255 							SWP_NOZORDER | SWP_NOACTIVATE);
12256 
12257 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
12258 					// im not sure it is completely correct
12259 					// but without it the tabs and such do look weird as things change.
12260 					if(SystemParametersInfoForDpi) {
12261 						LOGFONT lfText;
12262 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
12263 						HFONT hFontNew = CreateFontIndirect(&lfText);
12264 						if (hFontNew)
12265 						{
12266 							//DeleteObject(hFontOld);
12267 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
12268 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
12269 								return TRUE;
12270 							}
12271 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
12272 						}
12273 					}
12274 
12275 					if(this.onDpiChanged)
12276 						this.onDpiChanged();
12277 				break;
12278 				case WM_ENTERIDLE:
12279 					// when a menu is up, it stops normal event processing (modal message loop)
12280 					// but this at least gives us a chance to SOMETIMES catch up
12281 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
12282 					SimpleWindow.processAllCustomEvents;
12283 					SimpleWindow.processAllCustomEvents;
12284 					SleepEx(0, true);
12285 					break;
12286 				case WM_SIZE:
12287 					if(wParam == 1 /* SIZE_MINIMIZED */)
12288 						break;
12289 					_width = LOWORD(lParam);
12290 					_height = HIWORD(lParam);
12291 
12292 					// I want to avoid tearing in the windows (my code is inefficient
12293 					// so this is a hack around that) so while sizing, we don't trigger,
12294 					// but we do want to trigger on events like mazimize.
12295 					if(!inSizeMove || doLiveResizing)
12296 						goto size_changed;
12297 				break;
12298 				/+
12299 				case WM_SIZING:
12300 					writeln("size");
12301 				break;
12302 				+/
12303 				// I don't like the tearing I get when redrawing on WM_SIZE
12304 				// (I know there's other ways to fix that but I don't like that behavior anyway)
12305 				// so instead it is going to redraw only at the end of a size.
12306 				case 0x0231: /* WM_ENTERSIZEMOVE */
12307 					inSizeMove = true;
12308 				break;
12309 				case 0x0232: /* WM_EXITSIZEMOVE */
12310 					inSizeMove = false;
12311 
12312 					size_changed:
12313 
12314 					// nothing relevant changed, don't bother redrawing
12315 					if(oldWidth == _width && oldHeight == _height) {
12316 						if(msg == 0x0232)
12317 							goto finalize_resize;
12318 						break;
12319 					}
12320 
12321 					// note: OpenGL windows don't use a backing bmp, so no need to change them
12322 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
12323 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
12324 						// gotta get the double buffer bmp to match the window
12325 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
12326 
12327 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
12328 						if(resizability != Resizability.automaticallyScaleIfPossible)
12329 						if(_width > bmpWidth || _height > bmpHeight) {
12330 							auto hdc = GetDC(hwnd);
12331 							auto oldBuffer = buffer;
12332 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
12333 
12334 							auto hdcBmp = CreateCompatibleDC(hdc);
12335 							auto oldBmp = SelectObject(hdcBmp, buffer);
12336 
12337 							auto hdcOldBmp = CreateCompatibleDC(hdc);
12338 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
12339 
12340 							/+
12341 							RECT r;
12342 							r.left = 0;
12343 							r.top = 0;
12344 							r.right = width;
12345 							r.bottom = height;
12346 							auto c = Color.green;
12347 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12348 							FillRect(hdcBmp, &r, brush);
12349 							DeleteObject(brush);
12350 							+/
12351 
12352 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
12353 
12354 							bmpWidth = _width;
12355 							bmpHeight = _height;
12356 
12357 							SelectObject(hdcOldBmp, oldOldBmp);
12358 							DeleteDC(hdcOldBmp);
12359 
12360 							SelectObject(hdcBmp, oldBmp);
12361 							DeleteDC(hdcBmp);
12362 
12363 							ReleaseDC(hwnd, hdc);
12364 
12365 							DeleteObject(oldBuffer);
12366 						}
12367 					}
12368 
12369 					updateOpenglViewportIfNeeded(_width, _height);
12370 
12371 					if(resizability != Resizability.automaticallyScaleIfPossible)
12372 					if(windowResized !is null)
12373 						windowResized(_width, _height);
12374 
12375 					/+
12376 					if(inSizeMove) {
12377 						// SimpleWindow.processAllCustomEvents();
12378 						// SimpleWindow.processAllCustomEvents();
12379 
12380 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12381 						//sdpyPrintDebugString("redraw b");
12382 					} else {
12383 					+/ {
12384 						finalize_resize:
12385 						// when it is all done, make sure everything is freshly drawn or there might be
12386 						// weird bugs left.
12387 						SimpleWindow.processAllCustomEvents();
12388 						SimpleWindow.processAllCustomEvents();
12389 
12390 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
12391 						// sdpyPrintDebugString("redraw");
12392 					}
12393 
12394 					oldWidth = this._width;
12395 					oldHeight = this._height;
12396 				break;
12397 				case WM_ERASEBKGND:
12398 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
12399 					if (!this._visibleForTheFirstTimeCalled) {
12400 						this._visibleForTheFirstTimeCalled = true;
12401 						if (this.visibleForTheFirstTime !is null) {
12402 							this.visibleForTheFirstTime();
12403 						}
12404 					}
12405 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
12406 					version(without_opengl) {} else {
12407 						if (openglMode == OpenGlOptions.yes) return 1;
12408 					}
12409 					// call windows default handler, so it can paint standard controls
12410 					goto default;
12411 				case WM_CTLCOLORBTN:
12412 				case WM_CTLCOLORSTATIC:
12413 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
12414 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
12415 					GetSysColorBrush(COLOR_3DFACE);
12416 				//break;
12417 				case WM_SHOWWINDOW:
12418 					this._visible = (wParam != 0);
12419 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
12420 						this._visibleForTheFirstTimeCalled = true;
12421 						if (this.visibleForTheFirstTime !is null) {
12422 							this.visibleForTheFirstTime();
12423 						}
12424 					}
12425 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
12426 					break;
12427 				case WM_PAINT: {
12428 					if (!this._visibleForTheFirstTimeCalled) {
12429 						this._visibleForTheFirstTimeCalled = true;
12430 						if (this.visibleForTheFirstTime !is null) {
12431 							this.visibleForTheFirstTime();
12432 						}
12433 					}
12434 
12435 					BITMAP bm;
12436 					PAINTSTRUCT ps;
12437 
12438 					HDC hdc = BeginPaint(hwnd, &ps);
12439 
12440 					if(openglMode == OpenGlOptions.no) {
12441 
12442 						HDC hdcMem = CreateCompatibleDC(hdc);
12443 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
12444 
12445 						GetObject(buffer, bm.sizeof, &bm);
12446 
12447 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
12448 						if(resizability == Resizability.automaticallyScaleIfPossible)
12449 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
12450 						else
12451 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
12452 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
12453 
12454 						SelectObject(hdcMem, hbmOld);
12455 						DeleteDC(hdcMem);
12456 						EndPaint(hwnd, &ps);
12457 					} else {
12458 						EndPaint(hwnd, &ps);
12459 						version(without_opengl) {} else
12460 							redrawOpenGlSceneSoon();
12461 					}
12462 				} break;
12463 				  default:
12464 					return DefWindowProc(hwnd, msg, wParam, lParam);
12465 			}
12466 			 return 0;
12467 
12468 		}
12469 		catch(Throwable t) {
12470 			sdpyPrintDebugString(t.toString);
12471 			return 0;
12472 		}
12473 		}
12474 	}
12475 
12476 	mixin template NativeImageImplementation() {
12477 		HBITMAP handle;
12478 		ubyte* rawData;
12479 
12480 	final:
12481 
12482 		Color getPixel(int x, int y) {
12483 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12484 			// remember, bmps are upside down
12485 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12486 
12487 			Color c;
12488 			if(enableAlpha)
12489 				c.a = rawData[offset + 3];
12490 			else
12491 				c.a = 255;
12492 			c.b = rawData[offset + 0];
12493 			c.g = rawData[offset + 1];
12494 			c.r = rawData[offset + 2];
12495 			c.unPremultiply();
12496 			return c;
12497 		}
12498 
12499 		void setPixel(int x, int y, Color c) {
12500 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12501 			// remember, bmps are upside down
12502 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
12503 
12504 			if(enableAlpha)
12505 				c.premultiply();
12506 
12507 			rawData[offset + 0] = c.b;
12508 			rawData[offset + 1] = c.g;
12509 			rawData[offset + 2] = c.r;
12510 			if(enableAlpha)
12511 				rawData[offset + 3] = c.a;
12512 		}
12513 
12514 		void convertToRgbaBytes(ubyte[] where) {
12515 			assert(where.length == this.width * this.height * 4);
12516 
12517 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
12518 			int idx = 0;
12519 			int offset = itemsPerLine * (height - 1);
12520 			// remember, bmps are upside down
12521 			for(int y = height - 1; y >= 0; y--) {
12522 				auto offsetStart = offset;
12523 				for(int x = 0; x < width; x++) {
12524 					where[idx + 0] = rawData[offset + 2]; // r
12525 					where[idx + 1] = rawData[offset + 1]; // g
12526 					where[idx + 2] = rawData[offset + 0]; // b
12527 					if(enableAlpha) {
12528 						where[idx + 3] = rawData[offset + 3]; // a
12529 						unPremultiplyRgba(where[idx .. idx + 4]);
12530 						offset++;
12531 					} else
12532 						where[idx + 3] = 255; // a
12533 					idx += 4;
12534 					offset += 3;
12535 				}
12536 
12537 				offset = offsetStart - itemsPerLine;
12538 			}
12539 		}
12540 
12541 		void setFromRgbaBytes(in ubyte[] what) {
12542 			assert(what.length == this.width * this.height * 4);
12543 
12544 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
12545 			int idx = 0;
12546 			int offset = itemsPerLine * (height - 1);
12547 			// remember, bmps are upside down
12548 			for(int y = height - 1; y >= 0; y--) {
12549 				auto offsetStart = offset;
12550 				for(int x = 0; x < width; x++) {
12551 					if(enableAlpha) {
12552 						auto a = what[idx + 3];
12553 
12554 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
12555 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
12556 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
12557 						rawData[offset + 3] = a; // a
12558 						//premultiplyBgra(rawData[offset .. offset + 4]);
12559 						offset++;
12560 					} else {
12561 						rawData[offset + 2] = what[idx + 0]; // r
12562 						rawData[offset + 1] = what[idx + 1]; // g
12563 						rawData[offset + 0] = what[idx + 2]; // b
12564 					}
12565 					idx += 4;
12566 					offset += 3;
12567 				}
12568 
12569 				offset = offsetStart - itemsPerLine;
12570 			}
12571 		}
12572 
12573 
12574 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
12575 			BITMAPINFO infoheader;
12576 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
12577 			infoheader.bmiHeader.biWidth = width;
12578 			infoheader.bmiHeader.biHeight = height;
12579 			infoheader.bmiHeader.biPlanes = 1;
12580 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
12581 			infoheader.bmiHeader.biCompression = BI_RGB;
12582 
12583 			handle = CreateDIBSection(
12584 				null,
12585 				&infoheader,
12586 				DIB_RGB_COLORS,
12587 				cast(void**) &rawData,
12588 				null,
12589 				0);
12590 			if(handle is null)
12591 				throw new WindowsApiException("create image failed", GetLastError());
12592 
12593 		}
12594 
12595 		void dispose() {
12596 			DeleteObject(handle);
12597 		}
12598 	}
12599 
12600 	enum KEY_ESCAPE = 27;
12601 }
12602 version(X11) {
12603 	/// This is the default font used. You might change this before doing anything else with
12604 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
12605 	/// for cross-platform compatibility.
12606 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12607 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
12608 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
12609 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
12610 
12611 	alias int delegate(XEvent) NativeEventHandler;
12612 	alias Window NativeWindowHandle;
12613 
12614 	enum KEY_ESCAPE = 9;
12615 
12616 	mixin template NativeScreenPainterImplementation() {
12617 		Display* display;
12618 		Drawable d;
12619 		Drawable destiny;
12620 
12621 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
12622 		GC gc;
12623 
12624 		__gshared bool fontAttempted;
12625 
12626 		__gshared XFontStruct* defaultfont;
12627 		__gshared XFontSet defaultfontset;
12628 
12629 		XFontStruct* font;
12630 		XFontSet fontset;
12631 
12632 		void create(PaintingHandle window) {
12633 			this.display = XDisplayConnection.get();
12634 
12635 			Drawable buffer = None;
12636 			if(auto sw = cast(SimpleWindow) this.window) {
12637 				buffer = sw.impl.buffer;
12638 				this.destiny = cast(Drawable) window;
12639 			} else {
12640 				buffer = cast(Drawable) window;
12641 				this.destiny = None;
12642 			}
12643 
12644 			this.d = cast(Drawable) buffer;
12645 
12646 			auto dgc = DefaultGC(display, DefaultScreen(display));
12647 
12648 			this.gc = XCreateGC(display, d, 0, null);
12649 
12650 			XCopyGC(display, dgc, 0xffffffff, this.gc);
12651 
12652 			ensureDefaultFontLoaded();
12653 
12654 			font = defaultfont;
12655 			fontset = defaultfontset;
12656 
12657 			if(font) {
12658 				XSetFont(display, gc, font.fid);
12659 			}
12660 		}
12661 
12662 		static void ensureDefaultFontLoaded() {
12663 			if(!fontAttempted) {
12664 				auto display = XDisplayConnection.get;
12665 				auto font = XLoadQueryFont(display, xfontstr.ptr);
12666 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
12667 				if(font is null) {
12668 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
12669 					font = XLoadQueryFont(display, xfontstr.ptr);
12670 				}
12671 
12672 				char** lol;
12673 				int lol2;
12674 				char* lol3;
12675 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
12676 
12677 				fontAttempted = true;
12678 
12679 				defaultfont = font;
12680 				defaultfontset = fontset;
12681 			}
12682 		}
12683 
12684 		arsd.color.Rectangle _clipRectangle;
12685 		void setClipRectangle(int x, int y, int width, int height) {
12686 			auto old = _clipRectangle;
12687 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12688 			if(old == _clipRectangle)
12689 				return;
12690 
12691 			if(width == 0 || height == 0) {
12692 				XSetClipMask(display, gc, None);
12693 
12694 				if(xrenderPicturePainter) {
12695 
12696 					XRectangle[1] rects;
12697 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
12698 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12699 				}
12700 
12701 				version(with_xft) {
12702 					if(xftFont is null || xftDraw is null)
12703 						return;
12704 					XftDrawSetClip(xftDraw, null);
12705 				}
12706 			} else {
12707 				XRectangle[1] rects;
12708 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
12709 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
12710 
12711 				if(xrenderPicturePainter)
12712 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12713 
12714 				version(with_xft) {
12715 					if(xftFont is null || xftDraw is null)
12716 						return;
12717 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
12718 				}
12719 			}
12720 		}
12721 
12722 		version(with_xft) {
12723 			XftFont* xftFont;
12724 			XftDraw* xftDraw;
12725 
12726 			XftColor xftColor;
12727 
12728 			void updateXftColor() {
12729 				if(xftFont is null)
12730 					return;
12731 
12732 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
12733 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
12734 
12735 				XftColorAllocValue(
12736 					display,
12737 					DefaultVisual(display, DefaultScreen(display)),
12738 					DefaultColormap(display, 0),
12739 					&colorIn,
12740 					&xftColor
12741 				);
12742 			}
12743 		}
12744 
12745 		private OperatingSystemFont _activeFont;
12746 		void setFont(OperatingSystemFont font) {
12747 			_activeFont = font;
12748 			version(with_xft) {
12749 				if(font && font.isXft && font.xftFont)
12750 					this.xftFont = font.xftFont;
12751 				else
12752 					this.xftFont = null;
12753 
12754 				if(this.xftFont) {
12755 					if(xftDraw is null) {
12756 						xftDraw = XftDrawCreate(
12757 							display,
12758 							d,
12759 							DefaultVisual(display, DefaultScreen(display)),
12760 							DefaultColormap(display, 0)
12761 						);
12762 
12763 						updateXftColor();
12764 					}
12765 
12766 					return;
12767 				}
12768 			}
12769 
12770 			if(font && font.font) {
12771 				this.font = font.font;
12772 				this.fontset = font.fontset;
12773 				XSetFont(display, gc, font.font.fid);
12774 			} else {
12775 				this.font = defaultfont;
12776 				this.fontset = defaultfontset;
12777 			}
12778 
12779 		}
12780 
12781 		private Picture xrenderPicturePainter;
12782 
12783 		bool manualInvalidations;
12784 		void invalidateRect(Rectangle invalidRect) {
12785 			// FIXME if manualInvalidations
12786 		}
12787 
12788 		void dispose() {
12789 			this.rasterOp = RasterOp.normal;
12790 
12791 			if(xrenderPicturePainter) {
12792 				XRenderFreePicture(display, xrenderPicturePainter);
12793 				xrenderPicturePainter = None;
12794 			}
12795 
12796 			// FIXME: this.window.width/height is probably wrong
12797 
12798 			// src x,y     then dest x, y
12799 			if(destiny != None) {
12800 				// FIXME: if manual invalidations we can actually only copy some of the area.
12801 				// if(manualInvalidations)
12802 				XSetClipMask(display, gc, None);
12803 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
12804 			}
12805 
12806 			XFreeGC(display, gc);
12807 
12808 			version(with_xft)
12809 			if(xftDraw) {
12810 				XftDrawDestroy(xftDraw);
12811 				xftDraw = null;
12812 			}
12813 
12814 			/+
12815 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
12816 			if(font && font !is defaultfont) {
12817 				XFreeFont(display, font);
12818 				font = null;
12819 			}
12820 			if(fontset && fontset !is defaultfontset) {
12821 				XFreeFontSet(display, fontset);
12822 				fontset = null;
12823 			}
12824 			+/
12825 			XFlush(display);
12826 
12827 			if(window.paintingFinishedDg !is null)
12828 				window.paintingFinishedDg()();
12829 		}
12830 
12831 		bool backgroundIsNotTransparent = true;
12832 		bool foregroundIsNotTransparent = true;
12833 
12834 		bool _penInitialized = false;
12835 		Pen _activePen;
12836 
12837 		Color _outlineColor;
12838 		Color _fillColor;
12839 
12840 		@property void pen(Pen p) {
12841 			if(_penInitialized && p == _activePen) {
12842 				return;
12843 			}
12844 			_penInitialized = true;
12845 			_activePen = p;
12846 			_outlineColor = p.color;
12847 
12848 			int style;
12849 
12850 			byte dashLength;
12851 
12852 			final switch(p.style) {
12853 				case Pen.Style.Solid:
12854 					style = 0 /*LineSolid*/;
12855 				break;
12856 				case Pen.Style.Dashed:
12857 					style = 1 /*LineOnOffDash*/;
12858 					dashLength = 4;
12859 				break;
12860 				case Pen.Style.Dotted:
12861 					style = 1 /*LineOnOffDash*/;
12862 					dashLength = 1;
12863 				break;
12864 			}
12865 
12866 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
12867 			if(dashLength)
12868 				XSetDashes(display, gc, 0, &dashLength, 1);
12869 
12870 			if(p.color.a == 0) {
12871 				foregroundIsNotTransparent = false;
12872 				return;
12873 			}
12874 
12875 			foregroundIsNotTransparent = true;
12876 
12877 			XSetForeground(display, gc, colorToX(p.color, display));
12878 
12879 			version(with_xft)
12880 				updateXftColor();
12881 		}
12882 
12883 		RasterOp _currentRasterOp;
12884 		bool _currentRasterOpInitialized = false;
12885 		@property void rasterOp(RasterOp op) {
12886 			if(_currentRasterOpInitialized && _currentRasterOp == op)
12887 				return;
12888 			_currentRasterOp = op;
12889 			_currentRasterOpInitialized = true;
12890 			int mode;
12891 			final switch(op) {
12892 				case RasterOp.normal:
12893 					mode = GXcopy;
12894 				break;
12895 				case RasterOp.xor:
12896 					mode = GXxor;
12897 				break;
12898 			}
12899 			XSetFunction(display, gc, mode);
12900 		}
12901 
12902 
12903 		bool _fillColorInitialized = false;
12904 
12905 		@property void fillColor(Color c) {
12906 			if(_fillColorInitialized && _fillColor == c)
12907 				return; // already good, no need to waste time calling it
12908 			_fillColor = c;
12909 			_fillColorInitialized = true;
12910 			if(c.a == 0) {
12911 				backgroundIsNotTransparent = false;
12912 				return;
12913 			}
12914 
12915 			backgroundIsNotTransparent = true;
12916 
12917 			XSetBackground(display, gc, colorToX(c, display));
12918 
12919 		}
12920 
12921 		void swapColors() {
12922 			auto tmp = _fillColor;
12923 			fillColor = _outlineColor;
12924 			auto newPen = _activePen;
12925 			newPen.color = tmp;
12926 			pen(newPen);
12927 		}
12928 
12929 		uint colorToX(Color c, Display* display) {
12930 			auto visual = DefaultVisual(display, DefaultScreen(display));
12931 			import core.bitop;
12932 			uint color = 0;
12933 			{
12934 			auto startBit = bsf(visual.red_mask);
12935 			auto lastBit = bsr(visual.red_mask);
12936 			auto r = cast(uint) c.r;
12937 			r >>= 7 - (lastBit - startBit);
12938 			r <<= startBit;
12939 			color |= r;
12940 			}
12941 			{
12942 			auto startBit = bsf(visual.green_mask);
12943 			auto lastBit = bsr(visual.green_mask);
12944 			auto g = cast(uint) c.g;
12945 			g >>= 7 - (lastBit - startBit);
12946 			g <<= startBit;
12947 			color |= g;
12948 			}
12949 			{
12950 			auto startBit = bsf(visual.blue_mask);
12951 			auto lastBit = bsr(visual.blue_mask);
12952 			auto b = cast(uint) c.b;
12953 			b >>= 7 - (lastBit - startBit);
12954 			b <<= startBit;
12955 			color |= b;
12956 			}
12957 
12958 
12959 
12960 			return color;
12961 		}
12962 
12963 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12964 			// source x, source y
12965 			if(ix >= i.width) return;
12966 			if(iy >= i.height) return;
12967 			if(ix + w > i.width) w = i.width - ix;
12968 			if(iy + h > i.height) h = i.height - iy;
12969 			if(i.usingXshm)
12970 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12971 			else
12972 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12973 		}
12974 
12975 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12976 			if(s.enableAlpha) {
12977 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12978 				if(this.xrenderPicturePainter == None) {
12979 					XRenderPictureAttributes attrs;
12980 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12981 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12982 
12983 					// need to initialize the clip
12984 					XRectangle[1] rects;
12985 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
12986 
12987 					if(_clipRectangle != Rectangle.init)
12988 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
12989 				}
12990 
12991 				XRenderComposite(
12992 					display,
12993 					3, // PicOpOver
12994 					s.xrenderPicture,
12995 					None,
12996 					this.xrenderPicturePainter,
12997 					ix,
12998 					iy,
12999 					0,
13000 					0,
13001 					x,
13002 					y,
13003 					w ? w : s.width,
13004 					h ? h : s.height
13005 				);
13006 			} else {
13007 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
13008 			}
13009 		}
13010 
13011 		int fontHeight() {
13012 			version(with_xft)
13013 				if(xftFont !is null)
13014 					return xftFont.height;
13015 			if(font)
13016 				return font.max_bounds.ascent + font.max_bounds.descent;
13017 			return 12; // pretty common default...
13018 		}
13019 
13020 		int textWidth(in char[] line) {
13021 			version(with_xft)
13022 			if(xftFont) {
13023 				if(line.length == 0)
13024 					return 0;
13025 				XGlyphInfo extents;
13026 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
13027 				return extents.width;
13028 			}
13029 
13030 			if(fontset) {
13031 				if(line.length == 0)
13032 					return 0;
13033 				XRectangle rect;
13034 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
13035 
13036 				return rect.width;
13037 			}
13038 
13039 			if(font)
13040 				// FIXME: unicode
13041 				return XTextWidth( font, line.ptr, cast(int) line.length);
13042 			else
13043 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
13044 		}
13045 
13046 		Size textSize(in char[] text) {
13047 			auto maxWidth = 0;
13048 			auto lineHeight = fontHeight;
13049 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
13050 			foreach(line; text.split('\n')) {
13051 				int textWidth = this.textWidth(line);
13052 				if(textWidth > maxWidth)
13053 					maxWidth = textWidth;
13054 				h += lineHeight + 4;
13055 			}
13056 			return Size(maxWidth, h);
13057 		}
13058 
13059 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
13060 			const(char)[] text;
13061 			version(with_xft)
13062 			if(xftFont) {
13063 				text = originalText;
13064 				goto loaded;
13065 			}
13066 
13067 			if(fontset)
13068 				text = originalText;
13069 			else {
13070 				text.reserve(originalText.length);
13071 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
13072 				// then strip the rest so there isn't garbage
13073 				foreach(dchar ch; originalText)
13074 					if(ch < 256)
13075 						text ~= cast(ubyte) ch;
13076 					else
13077 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
13078 			}
13079 			loaded:
13080 			if(text.length == 0)
13081 				return;
13082 
13083 			// FIXME: should we clip it to the bounding box?
13084 			int textHeight = fontHeight;
13085 
13086 			auto lines = text.split('\n');
13087 
13088 			const lineHeight = textHeight;
13089 			textHeight *= lines.length;
13090 
13091 			int cy = y;
13092 
13093 			if(alignment & TextAlignment.VerticalBottom) {
13094 				if(y2 <= 0)
13095 					return;
13096 				auto h = y2 - y;
13097 				if(h > textHeight) {
13098 					cy += h - textHeight;
13099 					cy -= lineHeight / 2;
13100 				}
13101 			} else if(alignment & TextAlignment.VerticalCenter) {
13102 				if(y2 <= 0)
13103 					return;
13104 				auto h = y2 - y;
13105 				if(textHeight < h) {
13106 					cy += (h - textHeight) / 2;
13107 					//cy -= lineHeight / 4;
13108 				}
13109 			}
13110 
13111 			foreach(line; text.split('\n')) {
13112 				int textWidth = this.textWidth(line);
13113 
13114 				int px = x, py = cy;
13115 
13116 				if(alignment & TextAlignment.Center) {
13117 					if(x2 <= 0)
13118 						return;
13119 					auto w = x2 - x;
13120 					if(w > textWidth)
13121 						px += (w - textWidth) / 2;
13122 				} else if(alignment & TextAlignment.Right) {
13123 					if(x2 <= 0)
13124 						return;
13125 					auto pos = x2 - textWidth;
13126 					if(pos > x)
13127 						px = pos;
13128 				}
13129 
13130 				version(with_xft)
13131 				if(xftFont) {
13132 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
13133 
13134 					goto carry_on;
13135 				}
13136 
13137 				if(fontset)
13138 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13139 				else
13140 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
13141 				carry_on:
13142 				cy += lineHeight + 4;
13143 			}
13144 		}
13145 
13146 		void drawPixel(int x, int y) {
13147 			XDrawPoint(display, d, gc, x, y);
13148 		}
13149 
13150 		// The basic shapes, outlined
13151 
13152 		void drawLine(int x1, int y1, int x2, int y2) {
13153 			if(foregroundIsNotTransparent)
13154 				XDrawLine(display, d, gc, x1, y1, x2, y2);
13155 		}
13156 
13157 		void drawRectangle(int x, int y, int width, int height) {
13158 			if(backgroundIsNotTransparent) {
13159 				swapColors();
13160 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
13161 				swapColors();
13162 			}
13163 			// 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
13164 			if(foregroundIsNotTransparent)
13165 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
13166 		}
13167 
13168 		/// Arguments are the points of the bounding rectangle
13169 		void drawEllipse(int x1, int y1, int x2, int y2) {
13170 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
13171 		}
13172 
13173 		// NOTE: start and finish are in units of degrees * 64
13174 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
13175 			if(backgroundIsNotTransparent) {
13176 				swapColors();
13177 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
13178 				swapColors();
13179 			}
13180 			if(foregroundIsNotTransparent) {
13181 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
13182 
13183 				// Windows draws the straight lines on the edges too so FIXME sort of
13184 			}
13185 		}
13186 
13187 		void drawPolygon(Point[] vertexes) {
13188 			XPoint[16] pointsBuffer;
13189 			XPoint[] points;
13190 			if(vertexes.length <= pointsBuffer.length)
13191 				points = pointsBuffer[0 .. vertexes.length];
13192 			else
13193 				points.length = vertexes.length;
13194 
13195 			foreach(i, p; vertexes) {
13196 				points[i].x = cast(short) p.x;
13197 				points[i].y = cast(short) p.y;
13198 			}
13199 
13200 			if(backgroundIsNotTransparent) {
13201 				swapColors();
13202 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
13203 				swapColors();
13204 			}
13205 			if(foregroundIsNotTransparent) {
13206 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
13207 			}
13208 		}
13209 	}
13210 
13211 	/* XRender { */
13212 
13213 	struct XRenderColor {
13214 		ushort red;
13215 		ushort green;
13216 		ushort blue;
13217 		ushort alpha;
13218 	}
13219 
13220 	alias Picture = XID;
13221 	alias PictFormat = XID;
13222 
13223 	struct XGlyphInfo {
13224 		ushort width;
13225 		ushort height;
13226 		short x;
13227 		short y;
13228 		short xOff;
13229 		short yOff;
13230 	}
13231 
13232 struct XRenderDirectFormat {
13233     short   red;
13234     short   redMask;
13235     short   green;
13236     short   greenMask;
13237     short   blue;
13238     short   blueMask;
13239     short   alpha;
13240     short   alphaMask;
13241 }
13242 
13243 struct XRenderPictFormat {
13244     PictFormat		id;
13245     int			type;
13246     int			depth;
13247     XRenderDirectFormat	direct;
13248     Colormap		colormap;
13249 }
13250 
13251 enum PictFormatID	=   (1 << 0);
13252 enum PictFormatType	=   (1 << 1);
13253 enum PictFormatDepth	=   (1 << 2);
13254 enum PictFormatRed	=   (1 << 3);
13255 enum PictFormatRedMask  =(1 << 4);
13256 enum PictFormatGreen	=   (1 << 5);
13257 enum PictFormatGreenMask=(1 << 6);
13258 enum PictFormatBlue	=   (1 << 7);
13259 enum PictFormatBlueMask =(1 << 8);
13260 enum PictFormatAlpha	=   (1 << 9);
13261 enum PictFormatAlphaMask=(1 << 10);
13262 enum PictFormatColormap =(1 << 11);
13263 
13264 struct XRenderPictureAttributes {
13265 	int 		repeat;
13266 	Picture		alpha_map;
13267 	int			alpha_x_origin;
13268 	int			alpha_y_origin;
13269 	int			clip_x_origin;
13270 	int			clip_y_origin;
13271 	Pixmap		clip_mask;
13272 	Bool		graphics_exposures;
13273 	int			subwindow_mode;
13274 	int			poly_edge;
13275 	int			poly_mode;
13276 	Atom		dither;
13277 	Bool		component_alpha;
13278 }
13279 
13280 alias int XFixed;
13281 
13282 struct XPointFixed {
13283     XFixed  x, y;
13284 }
13285 
13286 struct XCircle {
13287     XFixed x;
13288     XFixed y;
13289     XFixed radius;
13290 }
13291 
13292 struct XTransform {
13293     XFixed[3][3]  matrix;
13294 }
13295 
13296 struct XFilters {
13297     int	    nfilter;
13298     char    **filter;
13299     int	    nalias;
13300     short   *alias_;
13301 }
13302 
13303 struct XIndexValue {
13304     c_ulong    pixel;
13305     ushort   red, green, blue, alpha;
13306 }
13307 
13308 struct XAnimCursor {
13309     Cursor	    cursor;
13310     c_ulong   delay;
13311 }
13312 
13313 struct XLinearGradient {
13314     XPointFixed p1;
13315     XPointFixed p2;
13316 }
13317 
13318 struct XRadialGradient {
13319     XCircle inner;
13320     XCircle outer;
13321 }
13322 
13323 struct XConicalGradient {
13324     XPointFixed center;
13325     XFixed angle; /* in degrees */
13326 }
13327 
13328 enum PictStandardARGB32  = 0;
13329 enum PictStandardRGB24   = 1;
13330 enum PictStandardA8	 =  2;
13331 enum PictStandardA4	 =  3;
13332 enum PictStandardA1	 =  4;
13333 enum PictStandardNUM	 =  5;
13334 
13335 interface XRender {
13336 extern(C) @nogc:
13337 
13338 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
13339 
13340 	Status XRenderQueryVersion (Display *dpy,
13341 			int     *major_versionp,
13342 			int     *minor_versionp);
13343 
13344 	Status XRenderQueryFormats (Display *dpy);
13345 
13346 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
13347 
13348 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
13349 
13350 	XRenderPictFormat *
13351 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
13352 
13353 	XRenderPictFormat *
13354 		XRenderFindFormat (Display			*dpy,
13355 				c_ulong		mask,
13356 				const XRenderPictFormat	*templ,
13357 				int				count);
13358 	XRenderPictFormat *
13359 		XRenderFindStandardFormat (Display		*dpy,
13360 				int			format);
13361 
13362 	XIndexValue *
13363 		XRenderQueryPictIndexValues(Display			*dpy,
13364 				const XRenderPictFormat	*format,
13365 				int				*num);
13366 
13367 	Picture XRenderCreatePicture(
13368 		Display *dpy,
13369 		Drawable drawable,
13370 		const XRenderPictFormat *format,
13371 		c_ulong valuemask,
13372 		const XRenderPictureAttributes *attributes);
13373 
13374 	void XRenderChangePicture (Display				*dpy,
13375 				Picture				picture,
13376 				c_ulong			valuemask,
13377 				const XRenderPictureAttributes  *attributes);
13378 
13379 	void
13380 		XRenderSetPictureClipRectangles (Display	    *dpy,
13381 				Picture	    picture,
13382 				int		    xOrigin,
13383 				int		    yOrigin,
13384 				const XRectangle *rects,
13385 				int		    n);
13386 
13387 	void
13388 		XRenderSetPictureClipRegion (Display	    *dpy,
13389 				Picture	    picture,
13390 				Region	    r);
13391 
13392 	void
13393 		XRenderSetPictureTransform (Display	    *dpy,
13394 				Picture	    picture,
13395 				XTransform	    *transform);
13396 
13397 	void
13398 		XRenderFreePicture (Display                   *dpy,
13399 				Picture                   picture);
13400 
13401 	void
13402 		XRenderComposite (Display   *dpy,
13403 				int	    op,
13404 				Picture   src,
13405 				Picture   mask,
13406 				Picture   dst,
13407 				int	    src_x,
13408 				int	    src_y,
13409 				int	    mask_x,
13410 				int	    mask_y,
13411 				int	    dst_x,
13412 				int	    dst_y,
13413 				uint	width,
13414 				uint	height);
13415 
13416 
13417 	Picture XRenderCreateSolidFill (Display *dpy,
13418 			const XRenderColor *color);
13419 
13420 	Picture XRenderCreateLinearGradient (Display *dpy,
13421 			const XLinearGradient *gradient,
13422 			const XFixed *stops,
13423 			const XRenderColor *colors,
13424 			int nstops);
13425 
13426 	Picture XRenderCreateRadialGradient (Display *dpy,
13427 			const XRadialGradient *gradient,
13428 			const XFixed *stops,
13429 			const XRenderColor *colors,
13430 			int nstops);
13431 
13432 	Picture XRenderCreateConicalGradient (Display *dpy,
13433 			const XConicalGradient *gradient,
13434 			const XFixed *stops,
13435 			const XRenderColor *colors,
13436 			int nstops);
13437 
13438 
13439 
13440 	Cursor
13441 		XRenderCreateCursor (Display	    *dpy,
13442 				Picture	    source,
13443 				uint   x,
13444 				uint   y);
13445 
13446 	XFilters *
13447 		XRenderQueryFilters (Display *dpy, Drawable drawable);
13448 
13449 	void
13450 		XRenderSetPictureFilter (Display    *dpy,
13451 				Picture    picture,
13452 				const char *filter,
13453 				XFixed	    *params,
13454 				int	    nparams);
13455 
13456 	Cursor
13457 		XRenderCreateAnimCursor (Display	*dpy,
13458 				int		ncursor,
13459 				XAnimCursor	*cursors);
13460 }
13461 
13462 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
13463 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
13464 
13465 	/* XRender } */
13466 
13467 	/* Xrandr { */
13468 
13469 struct XRRMonitorInfo {
13470     Atom name;
13471     Bool primary;
13472     Bool automatic;
13473     int noutput;
13474     int x;
13475     int y;
13476     int width;
13477     int height;
13478     int mwidth;
13479     int mheight;
13480     /*RROutput*/ void *outputs;
13481 }
13482 
13483 struct XRRScreenChangeNotifyEvent {
13484     int type;                   /* event base */
13485     c_ulong serial;       /* # of last request processed by server */
13486     Bool send_event;            /* true if this came from a SendEvent request */
13487     Display *display;           /* Display the event was read from */
13488     Window window;              /* window which selected for this event */
13489     Window root;                /* Root window for changed screen */
13490     Time timestamp;             /* when the screen change occurred */
13491     Time config_timestamp;      /* when the last configuration change */
13492     ushort/*SizeID*/ size_index;
13493     ushort/*SubpixelOrder*/ subpixel_order;
13494     ushort/*Rotation*/ rotation;
13495     int width;
13496     int height;
13497     int mwidth;
13498     int mheight;
13499 }
13500 
13501 enum RRScreenChangeNotify = 0;
13502 
13503 enum RRScreenChangeNotifyMask = 1;
13504 
13505 __gshared int xrrEventBase = -1;
13506 
13507 
13508 interface XRandr {
13509 extern(C) @nogc:
13510 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
13511 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
13512 
13513 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
13514 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
13515 
13516 	void XRRSelectInput(Display *dpy, Window window, int mask);
13517 }
13518 
13519 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
13520 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
13521 	/* Xrandr } */
13522 
13523 	/* Xft { */
13524 
13525 	// actually freetype
13526 	alias void FT_Face;
13527 
13528 	// actually fontconfig
13529 	private alias FcBool = int;
13530 	alias void FcCharSet;
13531 	alias void FcPattern;
13532 	alias void FcResult;
13533 	enum FcEndian { FcEndianBig, FcEndianLittle }
13534 	struct FcFontSet {
13535 		int nfont;
13536 		int sfont;
13537 		FcPattern** fonts;
13538 	}
13539 
13540 	// actually XRegion
13541 	struct BOX {
13542 		short x1, x2, y1, y2;
13543 	}
13544 	struct _XRegion {
13545 		c_long size;
13546 		c_long numRects;
13547 		BOX* rects;
13548 		BOX extents;
13549 	}
13550 
13551 	alias Region = _XRegion*;
13552 
13553 	// ok actually Xft
13554 
13555 	struct XftFontInfo;
13556 
13557 	struct XftFont {
13558 		int         ascent;
13559 		int         descent;
13560 		int         height;
13561 		int         max_advance_width;
13562 		FcCharSet*  charset;
13563 		FcPattern*  pattern;
13564 	}
13565 
13566 	struct XftDraw;
13567 
13568 	struct XftColor {
13569 		c_ulong pixel;
13570 		XRenderColor color;
13571 	}
13572 
13573 	struct XftCharSpec {
13574 		dchar           ucs4;
13575 		short           x;
13576 		short           y;
13577 	}
13578 
13579 	struct XftCharFontSpec {
13580 		XftFont         *font;
13581 		dchar           ucs4;
13582 		short           x;
13583 		short           y;
13584 	}
13585 
13586 	struct XftGlyphSpec {
13587 		uint            glyph;
13588 		short           x;
13589 		short           y;
13590 	}
13591 
13592 	struct XftGlyphFontSpec {
13593 		XftFont         *font;
13594 		uint            glyph;
13595 		short           x;
13596 		short           y;
13597 	}
13598 
13599 	interface Xft {
13600 	extern(C) @nogc pure:
13601 
13602 	Bool XftColorAllocName (Display  *dpy,
13603 				const Visual   *visual,
13604 				Colormap cmap,
13605 				const char     *name,
13606 				XftColor *result);
13607 
13608 	Bool XftColorAllocValue (Display         *dpy,
13609 				Visual          *visual,
13610 				Colormap        cmap,
13611 				const XRenderColor    *color,
13612 				XftColor        *result);
13613 
13614 	void XftColorFree (Display   *dpy,
13615 				Visual    *visual,
13616 				Colormap  cmap,
13617 				XftColor  *color);
13618 
13619 	Bool XftDefaultHasRender (Display *dpy);
13620 
13621 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
13622 
13623 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
13624 
13625 	XftDraw * XftDrawCreate (Display   *dpy,
13626 		       Drawable  drawable,
13627 		       Visual    *visual,
13628 		       Colormap  colormap);
13629 
13630 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
13631 			     Pixmap   bitmap);
13632 
13633 	XftDraw * XftDrawCreateAlpha (Display *dpy,
13634 			    Pixmap  pixmap,
13635 			    int     depth);
13636 
13637 	void XftDrawChange (XftDraw  *draw,
13638 		       Drawable drawable);
13639 
13640 	Display * XftDrawDisplay (XftDraw *draw);
13641 
13642 	Drawable XftDrawDrawable (XftDraw *draw);
13643 
13644 	Colormap XftDrawColormap (XftDraw *draw);
13645 
13646 	Visual * XftDrawVisual (XftDraw *draw);
13647 
13648 	void XftDrawDestroy (XftDraw *draw);
13649 
13650 	Picture XftDrawPicture (XftDraw *draw);
13651 
13652 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
13653 
13654 	void XftDrawGlyphs (XftDraw          *draw,
13655 				const XftColor *color,
13656 				XftFont          *pub,
13657 				int              x,
13658 				int              y,
13659 				const uint  *glyphs,
13660 				int              nglyphs);
13661 
13662 	void XftDrawString8 (XftDraw             *draw,
13663 				const XftColor    *color,
13664 				XftFont             *pub,
13665 				int                 x,
13666 				int                 y,
13667 				const char     *string,
13668 				int                 len);
13669 
13670 	void XftDrawString16 (XftDraw            *draw,
13671 				const XftColor   *color,
13672 				XftFont            *pub,
13673 				int                x,
13674 				int                y,
13675 				const wchar   *string,
13676 				int                len);
13677 
13678 	void XftDrawString32 (XftDraw            *draw,
13679 				const XftColor   *color,
13680 				XftFont            *pub,
13681 				int                x,
13682 				int                y,
13683 				const dchar   *string,
13684 				int                len);
13685 
13686 	void XftDrawStringUtf8 (XftDraw          *draw,
13687 				const XftColor *color,
13688 				XftFont          *pub,
13689 				int              x,
13690 				int              y,
13691 				const char  *string,
13692 				int              len);
13693 	void XftDrawStringUtf16 (XftDraw             *draw,
13694 				const XftColor    *color,
13695 				XftFont             *pub,
13696 				int                 x,
13697 				int                 y,
13698 				const char     *string,
13699 				FcEndian            endian,
13700 				int                 len);
13701 
13702 	void XftDrawCharSpec (XftDraw                *draw,
13703 				const XftColor       *color,
13704 				XftFont                *pub,
13705 				const XftCharSpec    *chars,
13706 				int                    len);
13707 
13708 	void XftDrawCharFontSpec (XftDraw                    *draw,
13709 				const XftColor           *color,
13710 				const XftCharFontSpec    *chars,
13711 				int                        len);
13712 
13713 	void XftDrawGlyphSpec (XftDraw               *draw,
13714 				const XftColor      *color,
13715 				XftFont               *pub,
13716 				const XftGlyphSpec  *glyphs,
13717 				int                   len);
13718 
13719 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
13720 				const XftColor          *color,
13721 				const XftGlyphFontSpec  *glyphs,
13722 				int                       len);
13723 
13724 	void XftDrawRect (XftDraw            *draw,
13725 				const XftColor   *color,
13726 				int                x,
13727 				int                y,
13728 				uint       width,
13729 				uint       height);
13730 
13731 	Bool XftDrawSetClip (XftDraw     *draw,
13732 				Region      r);
13733 
13734 
13735 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
13736 				int                   xOrigin,
13737 				int                   yOrigin,
13738 				const XRectangle    *rects,
13739 				int                   n);
13740 
13741 	void XftDrawSetSubwindowMode (XftDraw    *draw,
13742 				int        mode);
13743 
13744 	void XftGlyphExtents (Display            *dpy,
13745 				XftFont            *pub,
13746 				const uint    *glyphs,
13747 				int                nglyphs,
13748 				XGlyphInfo         *extents);
13749 
13750 	void XftTextExtents8 (Display            *dpy,
13751 				XftFont            *pub,
13752 				const char    *string,
13753 				int                len,
13754 				XGlyphInfo         *extents);
13755 
13756 	void XftTextExtents16 (Display           *dpy,
13757 				XftFont           *pub,
13758 				const wchar  *string,
13759 				int               len,
13760 				XGlyphInfo        *extents);
13761 
13762 	void XftTextExtents32 (Display           *dpy,
13763 				XftFont           *pub,
13764 				const dchar  *string,
13765 				int               len,
13766 				XGlyphInfo        *extents);
13767 
13768 	void XftTextExtentsUtf8 (Display         *dpy,
13769 				XftFont         *pub,
13770 				const char *string,
13771 				int             len,
13772 				XGlyphInfo      *extents);
13773 
13774 	void XftTextExtentsUtf16 (Display            *dpy,
13775 				XftFont            *pub,
13776 				const char    *string,
13777 				FcEndian           endian,
13778 				int                len,
13779 				XGlyphInfo         *extents);
13780 
13781 	FcPattern * XftFontMatch (Display           *dpy,
13782 				int               screen,
13783 				const FcPattern *pattern,
13784 				FcResult          *result);
13785 
13786 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
13787 
13788 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
13789 
13790 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
13791 
13792 	FT_Face XftLockFace (XftFont *pub);
13793 
13794 	void XftUnlockFace (XftFont *pub);
13795 
13796 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
13797 
13798 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
13799 
13800 	dchar XftFontInfoHash (const XftFontInfo *fi);
13801 
13802 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
13803 
13804 	XftFont * XftFontOpenInfo (Display        *dpy,
13805 				FcPattern      *pattern,
13806 				XftFontInfo    *fi);
13807 
13808 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
13809 
13810 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
13811 
13812 	void XftFontClose (Display *dpy, XftFont *pub);
13813 
13814 	FcBool XftInitFtLibrary();
13815 	void XftFontLoadGlyphs (Display          *dpy,
13816 				XftFont          *pub,
13817 				FcBool           need_bitmaps,
13818 				const uint  *glyphs,
13819 				int              nglyph);
13820 
13821 	void XftFontUnloadGlyphs (Display            *dpy,
13822 				XftFont            *pub,
13823 				const uint    *glyphs,
13824 				int                nglyph);
13825 
13826 	FcBool XftFontCheckGlyph (Display  *dpy,
13827 				XftFont  *pub,
13828 				FcBool   need_bitmaps,
13829 				uint  glyph,
13830 				uint  *missing,
13831 				int      *nmissing);
13832 
13833 	FcBool XftCharExists (Display      *dpy,
13834 				XftFont      *pub,
13835 				dchar    ucs4);
13836 
13837 	uint XftCharIndex (Display       *dpy,
13838 				XftFont       *pub,
13839 				dchar      ucs4);
13840 	FcBool XftInit (const char *config);
13841 
13842 	int XftGetVersion ();
13843 
13844 	FcFontSet * XftListFonts (Display   *dpy,
13845 				int       screen,
13846 				...);
13847 
13848 	FcPattern *XftNameParse (const char *name);
13849 
13850 	void XftGlyphRender (Display         *dpy,
13851 				int             op,
13852 				Picture         src,
13853 				XftFont         *pub,
13854 				Picture         dst,
13855 				int             srcx,
13856 				int             srcy,
13857 				int             x,
13858 				int             y,
13859 				const uint *glyphs,
13860 				int             nglyphs);
13861 
13862 	void XftGlyphSpecRender (Display                 *dpy,
13863 				int                     op,
13864 				Picture                 src,
13865 				XftFont                 *pub,
13866 				Picture                 dst,
13867 				int                     srcx,
13868 				int                     srcy,
13869 				const XftGlyphSpec    *glyphs,
13870 				int                     nglyphs);
13871 
13872 	void XftCharSpecRender (Display              *dpy,
13873 				int                  op,
13874 				Picture              src,
13875 				XftFont              *pub,
13876 				Picture              dst,
13877 				int                  srcx,
13878 				int                  srcy,
13879 				const XftCharSpec  *chars,
13880 				int                  len);
13881 	void XftGlyphFontSpecRender (Display                     *dpy,
13882 				int                         op,
13883 				Picture                     src,
13884 				Picture                     dst,
13885 				int                         srcx,
13886 				int                         srcy,
13887 				const XftGlyphFontSpec    *glyphs,
13888 				int                         nglyphs);
13889 
13890 	void XftCharFontSpecRender (Display                  *dpy,
13891 				int                      op,
13892 				Picture                  src,
13893 				Picture                  dst,
13894 				int                      srcx,
13895 				int                      srcy,
13896 				const XftCharFontSpec  *chars,
13897 				int                      len);
13898 
13899 	void XftTextRender8 (Display         *dpy,
13900 				int             op,
13901 				Picture         src,
13902 				XftFont         *pub,
13903 				Picture         dst,
13904 				int             srcx,
13905 				int             srcy,
13906 				int             x,
13907 				int             y,
13908 				const char *string,
13909 				int             len);
13910 	void XftTextRender16 (Display            *dpy,
13911 				int                op,
13912 				Picture            src,
13913 				XftFont            *pub,
13914 				Picture            dst,
13915 				int                srcx,
13916 				int                srcy,
13917 				int                x,
13918 				int                y,
13919 				const wchar   *string,
13920 				int                len);
13921 
13922 	void XftTextRender16BE (Display          *dpy,
13923 				int              op,
13924 				Picture          src,
13925 				XftFont          *pub,
13926 				Picture          dst,
13927 				int              srcx,
13928 				int              srcy,
13929 				int              x,
13930 				int              y,
13931 				const char  *string,
13932 				int              len);
13933 
13934 	void XftTextRender16LE (Display          *dpy,
13935 				int              op,
13936 				Picture          src,
13937 				XftFont          *pub,
13938 				Picture          dst,
13939 				int              srcx,
13940 				int              srcy,
13941 				int              x,
13942 				int              y,
13943 				const char  *string,
13944 				int              len);
13945 
13946 	void XftTextRender32 (Display            *dpy,
13947 				int                op,
13948 				Picture            src,
13949 				XftFont            *pub,
13950 				Picture            dst,
13951 				int                srcx,
13952 				int                srcy,
13953 				int                x,
13954 				int                y,
13955 				const dchar   *string,
13956 				int                len);
13957 
13958 	void XftTextRender32BE (Display          *dpy,
13959 				int              op,
13960 				Picture          src,
13961 				XftFont          *pub,
13962 				Picture          dst,
13963 				int              srcx,
13964 				int              srcy,
13965 				int              x,
13966 				int              y,
13967 				const char  *string,
13968 				int              len);
13969 
13970 	void XftTextRender32LE (Display          *dpy,
13971 				int              op,
13972 				Picture          src,
13973 				XftFont          *pub,
13974 				Picture          dst,
13975 				int              srcx,
13976 				int              srcy,
13977 				int              x,
13978 				int              y,
13979 				const char  *string,
13980 				int              len);
13981 
13982 	void XftTextRenderUtf8 (Display          *dpy,
13983 				int              op,
13984 				Picture          src,
13985 				XftFont          *pub,
13986 				Picture          dst,
13987 				int              srcx,
13988 				int              srcy,
13989 				int              x,
13990 				int              y,
13991 				const char  *string,
13992 				int              len);
13993 
13994 	void XftTextRenderUtf16 (Display         *dpy,
13995 				int             op,
13996 				Picture         src,
13997 				XftFont         *pub,
13998 				Picture         dst,
13999 				int             srcx,
14000 				int             srcy,
14001 				int             x,
14002 				int             y,
14003 				const char *string,
14004 				FcEndian        endian,
14005 				int             len);
14006 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
14007 
14008 	}
14009 
14010 	interface FontConfig {
14011 	extern(C) @nogc pure:
14012 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
14013 		void FcFontSetDestroy(FcFontSet*);
14014 		char* FcNameUnparse(const FcPattern *);
14015 	}
14016 
14017 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
14018 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
14019 
14020 
14021 	/* Xft } */
14022 
14023 	class XDisconnectException : Exception {
14024 		bool userRequested;
14025 		this(bool userRequested = true) {
14026 			this.userRequested = userRequested;
14027 			super("X disconnected");
14028 		}
14029 	}
14030 
14031 	/++
14032 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
14033 
14034 		Please note that it returns
14035 	+/
14036 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
14037 
14038 		static XErrorEvent[] errorBuffer;
14039 
14040 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
14041 			errorBuffer ~= *evt;
14042 			return 0;
14043 		}
14044 
14045 		auto savedErrorHandler = XSetErrorHandler(&handler);
14046 
14047 		try {
14048 			dg();
14049 		} finally {
14050 			XSync(XDisplayConnection.get, 0/*False*/);
14051 			XSetErrorHandler(savedErrorHandler);
14052 		}
14053 
14054 		auto bfr = errorBuffer;
14055 		errorBuffer = null;
14056 
14057 		return bfr;
14058 	}
14059 
14060 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
14061 	class XDisplayConnection {
14062 		private __gshared Display* display;
14063 		private __gshared XIM xim;
14064 		private __gshared char* displayName;
14065 
14066 		private __gshared int connectionSequence_;
14067 		private __gshared bool isLocal_;
14068 
14069 		/// use this for lazy caching when reconnection
14070 		static int connectionSequenceNumber() { return connectionSequence_; }
14071 
14072 		/++
14073 			Guesses if the connection appears to be local.
14074 
14075 			History:
14076 				Added June 3, 2021
14077 		+/
14078 		static @property bool isLocal() nothrow @trusted @nogc {
14079 			return isLocal_;
14080 		}
14081 
14082 		/// Attempts recreation of state, may require application assistance
14083 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
14084 		/// then call this, and if successful, reenter the loop.
14085 		static void discardAndRecreate(string newDisplayString = null) {
14086 			if(insideXEventLoop)
14087 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
14088 
14089 			// 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
14090 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
14091 
14092 			foreach(handle; chnenhm) {
14093 				handle.discardConnectionState();
14094 			}
14095 
14096 			discardState();
14097 
14098 			if(newDisplayString !is null)
14099 				setDisplayName(newDisplayString);
14100 
14101 			auto display = get();
14102 
14103 			foreach(handle; chnenhm) {
14104 				handle.recreateAfterDisconnect();
14105 			}
14106 		}
14107 
14108 		private __gshared EventMask rootEventMask;
14109 
14110 		/++
14111 			Requests the specified input from the root window on the connection, in addition to any other request.
14112 
14113 
14114 			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.
14115 
14116 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
14117 		+/
14118 		static void addRootInput(EventMask mask) {
14119 			auto old = rootEventMask;
14120 			rootEventMask |= mask;
14121 			get(); // to ensure display connected
14122 			if(display !is null && rootEventMask != old)
14123 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
14124 		}
14125 
14126 		static void discardState() {
14127 			freeImages();
14128 
14129 			foreach(atomPtr; interredAtoms)
14130 				*atomPtr = 0;
14131 			interredAtoms = null;
14132 			interredAtoms.assumeSafeAppend();
14133 
14134 			ScreenPainterImplementation.fontAttempted = false;
14135 			ScreenPainterImplementation.defaultfont = null;
14136 			ScreenPainterImplementation.defaultfontset = null;
14137 
14138 			Image.impl.xshmQueryCompleted = false;
14139 			Image.impl._xshmAvailable = false;
14140 
14141 			SimpleWindow.nativeMapping = null;
14142 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
14143 			// GlobalHotkeyManager
14144 
14145 			display = null;
14146 			xim = null;
14147 		}
14148 
14149 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
14150 		private static void createXIM () {
14151 			import core.stdc.locale : setlocale, LC_ALL;
14152 			import core.stdc.stdio : stderr, fprintf;
14153 			import core.stdc.stdlib : free;
14154 			import core.stdc.string : strdup;
14155 
14156 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
14157 
14158 			auto olocale = strdup(setlocale(LC_ALL, null));
14159 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
14160 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
14161 
14162 			//fprintf(stderr, "opening IM...\n");
14163 			foreach (string s; mtry) {
14164 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
14165 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
14166 			}
14167 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
14168 		}
14169 
14170 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
14171 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
14172 		static struct ImgList {
14173 			size_t img; // class; hide it from GC
14174 			ImgList* next;
14175 		}
14176 
14177 		static __gshared ImgList* imglist = null;
14178 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
14179 
14180 		static void registerImage (Image img) {
14181 			if (!imglistLocked && img !is null) {
14182 				import core.stdc.stdlib : malloc;
14183 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
14184 				assert(it !is null); // do proper checks
14185 				it.img = cast(size_t)cast(void*)img;
14186 				it.next = imglist;
14187 				imglist = it;
14188 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
14189 			}
14190 		}
14191 
14192 		static void unregisterImage (Image img) {
14193 			if (!imglistLocked && img !is null) {
14194 				import core.stdc.stdlib : free;
14195 				ImgList* prev = null;
14196 				ImgList* cur = imglist;
14197 				while (cur !is null) {
14198 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
14199 					prev = cur;
14200 					cur = cur.next;
14201 				}
14202 				if (cur !is null) {
14203 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
14204 					free(cur);
14205 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
14206 				} else {
14207 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
14208 				}
14209 			}
14210 		}
14211 
14212 		static void freeImages () { // needed for discardAndRecreate
14213 			imglistLocked = true;
14214 			scope(exit) imglistLocked = false;
14215 			ImgList* cur = imglist;
14216 			ImgList* next = null;
14217 			while (cur !is null) {
14218 				import core.stdc.stdlib : free;
14219 				next = cur.next;
14220 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
14221 				(cast(Image)cast(void*)cur.img).dispose();
14222 				free(cur);
14223 				cur = next;
14224 			}
14225 			imglist = null;
14226 		}
14227 
14228 		/// can be used to override normal handling of display name
14229 		/// from environment and/or command line
14230 		static setDisplayName(string newDisplayName) {
14231 			displayName = cast(char*) (newDisplayName ~ '\0');
14232 		}
14233 
14234 		/// resets to the default display string
14235 		static resetDisplayName() {
14236 			displayName = null;
14237 		}
14238 
14239 		///
14240 		static Display* get() {
14241 			if(display is null) {
14242 				if(!librariesSuccessfullyLoaded)
14243 					throw new Exception("Unable to load X11 client libraries");
14244 				display = XOpenDisplay(displayName);
14245 
14246 				isLocal_ = false;
14247 
14248 				connectionSequence_++;
14249 				if(display is null)
14250 					throw new Exception("Unable to open X display");
14251 
14252 				auto str = display.display_name;
14253 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
14254 				// and otherwise it probably isn't
14255 				if(str is null || (str[0] != ':' && str[0] != '/'))
14256 					isLocal_ = false;
14257 				else
14258 					isLocal_ = true;
14259 
14260 				debug(sdpy_x_errors) {
14261 					XSetErrorHandler(&adrlogger);
14262 					XSynchronize(display, true);
14263 
14264 					extern(C) int wtf() {
14265 						if(errorHappened) {
14266 							asm { int 3; }
14267 							errorHappened = false;
14268 						}
14269 						return 0;
14270 					}
14271 					XSetAfterFunction(display, &wtf);
14272 				}
14273 
14274 
14275 				XSetIOErrorHandler(&x11ioerrCB);
14276 				Bool sup;
14277 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
14278 				createXIM();
14279 				version(with_eventloop) {
14280 					import arsd.eventloop;
14281 					addFileEventListeners(display.fd, &eventListener, null, null);
14282 				}
14283 			}
14284 
14285 			return display;
14286 		}
14287 
14288 		extern(C)
14289 		static int x11ioerrCB(Display* dpy) {
14290 			throw new XDisconnectException(false);
14291 		}
14292 
14293 		version(with_eventloop) {
14294 			import arsd.eventloop;
14295 			static void eventListener(OsFileHandle fd) {
14296 				//this.mtLock();
14297 				//scope(exit) this.mtUnlock();
14298 				while(XPending(display))
14299 					doXNextEvent(display);
14300 			}
14301 		}
14302 
14303 		// close connection on program exit -- we need this to properly free all images
14304 		static ~this () {
14305 			// the gui thread must clean up after itself or else Xlib might deadlock
14306 			// using this flag on any thread destruction is the easiest way i know of
14307 			// (shared static this is run by the LAST thread to exit, which may not be
14308 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
14309 			if(thisIsGuiThread)
14310 				close();
14311 		}
14312 
14313 		///
14314 		static void close() {
14315 			if(display is null)
14316 				return;
14317 
14318 			version(with_eventloop) {
14319 				import arsd.eventloop;
14320 				removeFileEventListeners(display.fd);
14321 			}
14322 
14323 			// now remove all registered images to prevent shared memory leaks
14324 			freeImages();
14325 
14326 			// tbh I don't know why it is doing this but like if this happens to run
14327 			// from the other thread there's frequent hanging inside here.
14328 			if(thisIsGuiThread)
14329 				XCloseDisplay(display);
14330 			display = null;
14331 		}
14332 	}
14333 
14334 	mixin template NativeImageImplementation() {
14335 		XImage* handle;
14336 		ubyte* rawData;
14337 
14338 		XShmSegmentInfo shminfo;
14339 		bool premultiply = true;
14340 
14341 		__gshared bool xshmQueryCompleted;
14342 		__gshared bool _xshmAvailable;
14343 		public static @property bool xshmAvailable() {
14344 			if(!xshmQueryCompleted) {
14345 				int i1, i2, i3;
14346 				xshmQueryCompleted = true;
14347 
14348 				if(!XDisplayConnection.isLocal)
14349 					_xshmAvailable = false;
14350 				else
14351 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
14352 			}
14353 			return _xshmAvailable;
14354 		}
14355 
14356 		bool usingXshm;
14357 	final:
14358 
14359 		private __gshared bool xshmfailed;
14360 
14361 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
14362 			auto display = XDisplayConnection.get();
14363 			assert(display !is null);
14364 			auto screen = DefaultScreen(display);
14365 
14366 			// it will only use shared memory for somewhat largish images,
14367 			// since otherwise we risk wasting shared memory handles on a lot of little ones
14368 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
14369 
14370 
14371 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
14372 				// the actual use still fails. For example, if the program is in a container and permission denied
14373 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
14374 				//
14375 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
14376 
14377 
14378 				// synchronize so preexisting buffers are clear
14379 				XSync(display, false);
14380 				xshmfailed = false;
14381 
14382 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
14383 
14384 
14385 				usingXshm = true;
14386 				handle = XShmCreateImage(
14387 					display,
14388 					DefaultVisual(display, screen),
14389 					enableAlpha ? 32: 24,
14390 					ImageFormat.ZPixmap,
14391 					null,
14392 					&shminfo,
14393 					width, height);
14394 				if(handle is null)
14395 					goto abortXshm1;
14396 
14397 				if(handle.bytes_per_line != 4 * width)
14398 					goto abortXshm2;
14399 
14400 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
14401 				if(shminfo.shmid < 0)
14402 					goto abortXshm3;
14403 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
14404 				if(rawData == cast(ubyte*) -1)
14405 					goto abortXshm4;
14406 				shminfo.readOnly = 0;
14407 				XShmAttach(display, &shminfo);
14408 
14409 				// and now to the final error check to ensure it actually worked.
14410 				XSync(display, false);
14411 				if(xshmfailed)
14412 					goto abortXshm5;
14413 
14414 				XSetErrorHandler(oldErrorHandler);
14415 
14416 				XDisplayConnection.registerImage(this);
14417 				// if I don't flush here there's a chance the dtor will run before the
14418 				// ctor and lead to a bad value X error. While this hurts the efficiency
14419 				// it is local anyway so prolly better to keep it simple
14420 				XFlush(display);
14421 
14422 				return;
14423 
14424 				abortXshm5:
14425 					shmdt(shminfo.shmaddr);
14426 					rawData = null;
14427 
14428 				abortXshm4:
14429 					shmctl(shminfo.shmid, IPC_RMID, null);
14430 
14431 				abortXshm3:
14432 					// nothing needed, the shmget failed so there's nothing to free
14433 
14434 				abortXshm2:
14435 					XDestroyImage(handle);
14436 					handle = null;
14437 
14438 				abortXshm1:
14439 					XSetErrorHandler(oldErrorHandler);
14440 					usingXshm = false;
14441 					handle = null;
14442 
14443 					shminfo = typeof(shminfo).init;
14444 
14445 					_xshmAvailable = false; // don't try again in the future
14446 
14447 					// writeln("fallingback");
14448 
14449 					goto fallback;
14450 
14451 			} else {
14452 				fallback:
14453 
14454 				if (forcexshm) throw new Exception("can't create XShm Image");
14455 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
14456 				import core.stdc.stdlib : malloc;
14457 				rawData = cast(ubyte*) malloc(width * height * 4);
14458 
14459 				handle = XCreateImage(
14460 					display,
14461 					DefaultVisual(display, screen),
14462 					enableAlpha ? 32 : 24, // bpp
14463 					ImageFormat.ZPixmap,
14464 					0, // offset
14465 					rawData,
14466 					width, height,
14467 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
14468 			}
14469 		}
14470 
14471 		void dispose() {
14472 			// note: this calls free(rawData) for us
14473 			if(handle) {
14474 				if (usingXshm) {
14475 					XDisplayConnection.unregisterImage(this);
14476 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
14477 				}
14478 				XDestroyImage(handle);
14479 				if(usingXshm) {
14480 					shmdt(shminfo.shmaddr);
14481 					shmctl(shminfo.shmid, IPC_RMID, null);
14482 				}
14483 				handle = null;
14484 			}
14485 		}
14486 
14487 		Color getPixel(int x, int y) {
14488 			auto offset = (y * width + x) * 4;
14489 			Color c;
14490 			c.a = enableAlpha ? rawData[offset + 3] : 255;
14491 			c.b = rawData[offset + 0];
14492 			c.g = rawData[offset + 1];
14493 			c.r = rawData[offset + 2];
14494 			if(enableAlpha && premultiply)
14495 				c.unPremultiply;
14496 			return c;
14497 		}
14498 
14499 		void setPixel(int x, int y, Color c) {
14500 			if(enableAlpha && premultiply)
14501 				c.premultiply();
14502 			auto offset = (y * width + x) * 4;
14503 			rawData[offset + 0] = c.b;
14504 			rawData[offset + 1] = c.g;
14505 			rawData[offset + 2] = c.r;
14506 			if(enableAlpha)
14507 				rawData[offset + 3] = c.a;
14508 		}
14509 
14510 		void convertToRgbaBytes(ubyte[] where) {
14511 			assert(where.length == this.width * this.height * 4);
14512 
14513 			// if rawData had a length....
14514 			//assert(rawData.length == where.length);
14515 			for(int idx = 0; idx < where.length; idx += 4) {
14516 				where[idx + 0] = rawData[idx + 2]; // r
14517 				where[idx + 1] = rawData[idx + 1]; // g
14518 				where[idx + 2] = rawData[idx + 0]; // b
14519 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
14520 
14521 				if(enableAlpha && premultiply)
14522 					unPremultiplyRgba(where[idx .. idx + 4]);
14523 			}
14524 		}
14525 
14526 		void setFromRgbaBytes(in ubyte[] where) {
14527 			assert(where.length == this.width * this.height * 4);
14528 
14529 			// if rawData had a length....
14530 			//assert(rawData.length == where.length);
14531 			for(int idx = 0; idx < where.length; idx += 4) {
14532 				rawData[idx + 2] = where[idx + 0]; // r
14533 				rawData[idx + 1] = where[idx + 1]; // g
14534 				rawData[idx + 0] = where[idx + 2]; // b
14535 				if(enableAlpha) {
14536 					rawData[idx + 3] = where[idx + 3]; // a
14537 					if(premultiply)
14538 						premultiplyBgra(rawData[idx .. idx + 4]);
14539 				}
14540 			}
14541 		}
14542 
14543 	}
14544 
14545 	mixin template NativeSimpleWindowImplementation() {
14546 		GC gc;
14547 		Window window;
14548 		Display* display;
14549 
14550 		Pixmap buffer;
14551 		int bufferw, bufferh; // size of the buffer; can be bigger than window
14552 		XIC xic; // input context
14553 		int curHidden = 0; // counter
14554 		Cursor blankCurPtr = 0;
14555 		int cursorSequenceNumber = 0;
14556 		int warpEventCount = 0; // number of mouse movement events to eat
14557 
14558 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
14559 		X11GetSelectionHandler[Atom] getSelectionHandlers;
14560 
14561 		version(without_opengl) {} else
14562 		GLXContext glc;
14563 
14564 		private void fixFixedSize(bool forced=false) (int width, int height) {
14565 			if (forced || this.resizability == Resizability.fixedSize) {
14566 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
14567 				XSizeHints sh;
14568 				static if (!forced) {
14569 					c_long spr;
14570 					XGetWMNormalHints(display, window, &sh, &spr);
14571 					sh.flags |= PMaxSize | PMinSize;
14572 				} else {
14573 					sh.flags = PMaxSize | PMinSize;
14574 				}
14575 				sh.min_width = width;
14576 				sh.min_height = height;
14577 				sh.max_width = width;
14578 				sh.max_height = height;
14579 				XSetWMNormalHints(display, window, &sh);
14580 				//XFlush(display);
14581 			}
14582 		}
14583 
14584 		ScreenPainter getPainter(bool manualInvalidations) {
14585 			return ScreenPainter(this, window, manualInvalidations);
14586 		}
14587 
14588 		void move(int x, int y) {
14589 			XMoveWindow(display, window, x, y);
14590 		}
14591 
14592 		void resize(int w, int h) {
14593 			if (w < 1) w = 1;
14594 			if (h < 1) h = 1;
14595 			XResizeWindow(display, window, w, h);
14596 
14597 			// calling this now to avoid waiting for the server to
14598 			// acknowledge the resize; draws without returning to the
14599 			// event loop will thus actually work. the server's event
14600 			// btw might overrule this and resize it again
14601 			recordX11Resize(display, this, w, h);
14602 
14603 			updateOpenglViewportIfNeeded(w, h);
14604 		}
14605 
14606 		void moveResize (int x, int y, int w, int h) {
14607 			if (w < 1) w = 1;
14608 			if (h < 1) h = 1;
14609 			XMoveResizeWindow(display, window, x, y, w, h);
14610 			updateOpenglViewportIfNeeded(w, h);
14611 		}
14612 
14613 		void hideCursor () {
14614 			if (curHidden++ == 0) {
14615 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
14616 					static const(char)[1] cmbmp = 0;
14617 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
14618 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
14619 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
14620 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
14621 					XFreePixmap(display, pm);
14622 				}
14623 				XDefineCursor(display, window, blankCurPtr);
14624 			}
14625 		}
14626 
14627 		void showCursor () {
14628 			if (--curHidden == 0) XUndefineCursor(display, window);
14629 		}
14630 
14631 		void warpMouse (int x, int y) {
14632 			// here i will send dummy "ignore next mouse motion" event,
14633 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
14634 			// and we don't need to report it to the user (as warping is
14635 			// used when the user needs movement deltas).
14636 			//XClientMessageEvent xclient;
14637 			XEvent e;
14638 			e.xclient.type = EventType.ClientMessage;
14639 			e.xclient.window = window;
14640 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14641 			e.xclient.format = 32;
14642 			e.xclient.data.l[0] = 0;
14643 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
14644 			//{ 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]); }
14645 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14646 			// now warp pointer...
14647 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
14648 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
14649 			// ...and flush
14650 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
14651 			XFlush(display);
14652 		}
14653 
14654 		void sendDummyEvent () {
14655 			// here i will send dummy event to ping event queue
14656 			XEvent e;
14657 			e.xclient.type = EventType.ClientMessage;
14658 			e.xclient.window = window;
14659 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
14660 			e.xclient.format = 32;
14661 			e.xclient.data.l[0] = 0;
14662 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
14663 			XFlush(display);
14664 		}
14665 
14666 		void setTitle(string title) {
14667 			if (title.ptr is null) title = "";
14668 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14669 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14670 			XTextProperty windowName;
14671 			windowName.value = title.ptr;
14672 			windowName.encoding = XA_UTF8; //XA_STRING;
14673 			windowName.format = 8;
14674 			windowName.nitems = cast(uint)title.length;
14675 			XSetWMName(display, window, &windowName);
14676 			char[1024] namebuf = 0;
14677 			auto maxlen = namebuf.length-1;
14678 			if (maxlen > title.length) maxlen = title.length;
14679 			namebuf[0..maxlen] = title[0..maxlen];
14680 			XStoreName(display, window, namebuf.ptr);
14681 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
14682 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
14683 		}
14684 
14685 		string[] getTitles() {
14686 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
14687 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
14688 			XTextProperty textProp;
14689 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
14690 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
14691 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
14692 				} else
14693 					return [];
14694 			} else
14695 				return null;
14696 		}
14697 
14698 		string getTitle() {
14699 			auto titles = getTitles();
14700 			return titles.length ? titles[0] : null;
14701 		}
14702 
14703 		void setMinSize (int minwidth, int minheight) {
14704 			import core.stdc.config : c_long;
14705 			if (minwidth < 1) minwidth = 1;
14706 			if (minheight < 1) minheight = 1;
14707 			XSizeHints sh;
14708 			c_long spr;
14709 			XGetWMNormalHints(display, window, &sh, &spr);
14710 			sh.min_width = minwidth;
14711 			sh.min_height = minheight;
14712 			sh.flags |= PMinSize;
14713 			XSetWMNormalHints(display, window, &sh);
14714 			flushGui();
14715 		}
14716 
14717 		void setMaxSize (int maxwidth, int maxheight) {
14718 			import core.stdc.config : c_long;
14719 			if (maxwidth < 1) maxwidth = 1;
14720 			if (maxheight < 1) maxheight = 1;
14721 			XSizeHints sh;
14722 			c_long spr;
14723 			XGetWMNormalHints(display, window, &sh, &spr);
14724 			sh.max_width = maxwidth;
14725 			sh.max_height = maxheight;
14726 			sh.flags |= PMaxSize;
14727 			XSetWMNormalHints(display, window, &sh);
14728 			flushGui();
14729 		}
14730 
14731 		void setResizeGranularity (int granx, int grany) {
14732 			import core.stdc.config : c_long;
14733 			if (granx < 1) granx = 1;
14734 			if (grany < 1) grany = 1;
14735 			XSizeHints sh;
14736 			c_long spr;
14737 			XGetWMNormalHints(display, window, &sh, &spr);
14738 			sh.width_inc = granx;
14739 			sh.height_inc = grany;
14740 			sh.flags |= PResizeInc;
14741 			XSetWMNormalHints(display, window, &sh);
14742 			flushGui();
14743 		}
14744 
14745 		void setOpacity (uint opacity) {
14746 			arch_ulong o = opacity;
14747 			if (opacity == uint.max)
14748 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
14749 			else
14750 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
14751 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
14752 		}
14753 
14754 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
14755 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
14756 			display = XDisplayConnection.get();
14757 			auto screen = DefaultScreen(display);
14758 
14759 			bool overrideRedirect = false;
14760 			if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild)
14761 				overrideRedirect = true;
14762 
14763 			version(without_opengl) {}
14764 			else {
14765 				if(opengl == OpenGlOptions.yes) {
14766 					GLXFBConfig fbconf = null;
14767 					XVisualInfo* vi = null;
14768 					bool useLegacy = false;
14769 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
14770 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
14771 						int[23] visualAttribs = [
14772 							GLX_X_RENDERABLE , 1/*True*/,
14773 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
14774 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
14775 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
14776 							GLX_RED_SIZE     , 8,
14777 							GLX_GREEN_SIZE   , 8,
14778 							GLX_BLUE_SIZE    , 8,
14779 							GLX_ALPHA_SIZE   , 8,
14780 							GLX_DEPTH_SIZE   , 24,
14781 							GLX_STENCIL_SIZE , 8,
14782 							GLX_DOUBLEBUFFER , 1/*True*/,
14783 							0/*None*/,
14784 						];
14785 						int fbcount;
14786 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
14787 						if (fbcount == 0) {
14788 							useLegacy = true; // try to do at least something
14789 						} else {
14790 							// pick the FB config/visual with the most samples per pixel
14791 							int bestidx = -1, bestns = -1;
14792 							foreach (int fbi; 0..fbcount) {
14793 								int sb, samples;
14794 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
14795 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
14796 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
14797 							}
14798 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
14799 							fbconf = fbc[bestidx];
14800 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
14801 							XFree(fbc);
14802 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
14803 						}
14804 					}
14805 					if (vi is null || useLegacy) {
14806 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
14807 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
14808 						useLegacy = true;
14809 					}
14810 					if (vi is null) throw new Exception("no open gl visual found");
14811 
14812 					XSetWindowAttributes swa;
14813 					auto root = RootWindow(display, screen);
14814 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
14815 
14816 					swa.override_redirect = overrideRedirect;
14817 
14818 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14819 						0, 0, width, height,
14820 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
14821 
14822 					// now try to use `glXCreateContextAttribsARB()` if it's here
14823 					if (!useLegacy) {
14824 						// request fairly advanced context, even with stencil buffer!
14825 						int[9] contextAttribs = [
14826 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
14827 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
14828 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
14829 							// for modern context, set "forward compatibility" flag too
14830 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
14831 							0/*None*/,
14832 						];
14833 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
14834 						if (glc is null && sdpyOpenGLContextAllowFallback) {
14835 							sdpyOpenGLContextVersion = 0;
14836 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14837 						}
14838 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
14839 					} else {
14840 						// fallback to old GLX call
14841 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
14842 							sdpyOpenGLContextVersion = 0;
14843 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
14844 						}
14845 					}
14846 					// sync to ensure any errors generated are processed
14847 					XSync(display, 0/*False*/);
14848 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
14849 					if(glc is null)
14850 						throw new Exception("glc");
14851 				}
14852 			}
14853 
14854 			if(opengl == OpenGlOptions.no) {
14855 
14856 				XSetWindowAttributes swa;
14857 				swa.background_pixel = WhitePixel(display, screen);
14858 				swa.border_pixel = BlackPixel(display, screen);
14859 				swa.override_redirect = overrideRedirect;
14860 				auto root = RootWindow(display, screen);
14861 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
14862 
14863 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
14864 					0, 0, width, height,
14865 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
14866 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
14867 
14868 
14869 
14870 				/*
14871 				window = XCreateSimpleWindow(
14872 					display,
14873 					parent is null ? RootWindow(display, screen) : parent.impl.window,
14874 					0, 0, // x, y
14875 					width, height,
14876 					1, // border width
14877 					BlackPixel(display, screen), // border
14878 					WhitePixel(display, screen)); // background
14879 				*/
14880 
14881 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
14882 				bufferw = width;
14883 				bufferh = height;
14884 
14885 				gc = DefaultGC(display, screen);
14886 
14887 				// clear out the buffer to get us started...
14888 				XSetForeground(display, gc, WhitePixel(display, screen));
14889 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
14890 				XSetForeground(display, gc, BlackPixel(display, screen));
14891 			}
14892 
14893 			// input context
14894 			//TODO: create this only for top-level windows, and reuse that?
14895 			populateXic();
14896 
14897 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
14898 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
14899 			// window class
14900 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
14901 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
14902 				XClassHint klass;
14903 				XWMHints wh;
14904 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14905 					wh.input = true;
14906 					wh.flags |= InputHint;
14907 				}
14908 				XSizeHints size;
14909 				klass.res_name = sdpyWindowClassStr;
14910 				klass.res_class = sdpyWindowClassStr;
14911 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
14912 			}
14913 
14914 			setTitle(title);
14915 			SimpleWindow.nativeMapping[window] = this;
14916 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
14917 
14918 			// This gives our window a close button
14919 			if (windowType != WindowTypes.eventOnly) {
14920 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
14921 				int useAtoms;
14922 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
14923 					useAtoms = 2;
14924 				} else {
14925 					useAtoms = 1;
14926 				}
14927 				assert(useAtoms <= atoms.length);
14928 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
14929 			}
14930 
14931 			// FIXME: windowType and customizationFlags
14932 			Atom[8] wsatoms; // here, due to goto
14933 			int wmsacount = 0; // here, due to goto
14934 
14935 			try
14936 			final switch(windowType) {
14937 				case WindowTypes.normal:
14938 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14939 				break;
14940 				case WindowTypes.undecorated:
14941 					motifHideDecorations();
14942 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
14943 				break;
14944 				case WindowTypes.eventOnly:
14945 					_hidden = true;
14946 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
14947 					goto hiddenWindow;
14948 				//break;
14949 				case WindowTypes.nestedChild:
14950 					// handled in XCreateWindow calls
14951 				break;
14952 
14953 				case WindowTypes.dropdownMenu:
14954 					motifHideDecorations();
14955 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
14956 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14957 				break;
14958 				case WindowTypes.popupMenu:
14959 					motifHideDecorations();
14960 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
14961 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14962 				break;
14963 				case WindowTypes.notification:
14964 					motifHideDecorations();
14965 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
14966 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
14967 				break;
14968 				case WindowTypes.minimallyWrapped:
14969 					assert(0, "don't create a minimallyWrapped thing explicitly!");
14970 				/+
14971 				case WindowTypes.menu:
14972 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14973 					motifHideDecorations();
14974 				break;
14975 				case WindowTypes.desktop:
14976 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
14977 				break;
14978 				case WindowTypes.dock:
14979 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
14980 				break;
14981 				case WindowTypes.toolbar:
14982 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
14983 				break;
14984 				case WindowTypes.menu:
14985 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
14986 				break;
14987 				case WindowTypes.utility:
14988 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
14989 				break;
14990 				case WindowTypes.splash:
14991 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
14992 				break;
14993 				case WindowTypes.dialog:
14994 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
14995 				break;
14996 				case WindowTypes.tooltip:
14997 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
14998 				break;
14999 				case WindowTypes.notification:
15000 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
15001 				break;
15002 				case WindowTypes.combo:
15003 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
15004 				break;
15005 				case WindowTypes.dnd:
15006 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
15007 				break;
15008 				+/
15009 			}
15010 			catch(Exception e) {
15011 				// XInternAtom failed, prolly a WM
15012 				// that doesn't support these things
15013 			}
15014 
15015 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
15016 			// the two following flags may be ignored by WM
15017 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
15018 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
15019 
15020 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
15021 
15022 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
15023 
15024 			// What would be ideal here is if they only were
15025 			// selected if there was actually an event handler
15026 			// for them...
15027 
15028 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
15029 
15030 			hiddenWindow:
15031 
15032 			// set the pid property for lookup later by window managers
15033 			// a standard convenience
15034 			import core.sys.posix.unistd;
15035 			arch_ulong pid = getpid();
15036 
15037 			XChangeProperty(
15038 				display,
15039 				impl.window,
15040 				GetAtom!("_NET_WM_PID", true)(display),
15041 				XA_CARDINAL,
15042 				32 /* bits */,
15043 				0 /*PropModeReplace*/,
15044 				&pid,
15045 				1);
15046 
15047 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
15048 				if(parent is null) assert(0);
15049 				XChangeProperty(
15050 					display,
15051 					impl.window,
15052 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
15053 					XA_WINDOW,
15054 					32 /* bits */,
15055 					0 /*PropModeReplace*/,
15056 					&parent.impl.window,
15057 					1);
15058 
15059 			}
15060 
15061 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
15062 				XMapWindow(display, window);
15063 			} else {
15064 				_hidden = true;
15065 			}
15066 		}
15067 
15068 		void populateXic() {
15069 			if (XDisplayConnection.xim !is null) {
15070 				xic = XCreateIC(XDisplayConnection.xim,
15071 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
15072 						/*XNClientWindow*/"clientWindow".ptr, window,
15073 						/*XNFocusWindow*/"focusWindow".ptr, window,
15074 						null);
15075 				if (xic is null) {
15076 					import core.stdc.stdio : stderr, fprintf;
15077 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
15078 				}
15079 			}
15080 		}
15081 
15082 		void selectDefaultInput(bool forceIncludeMouseMotion) {
15083 			auto mask = EventMask.ExposureMask |
15084 				EventMask.KeyPressMask |
15085 				EventMask.KeyReleaseMask |
15086 				EventMask.PropertyChangeMask |
15087 				EventMask.FocusChangeMask |
15088 				EventMask.StructureNotifyMask |
15089 				EventMask.SubstructureNotifyMask |
15090 				EventMask.VisibilityChangeMask
15091 				| EventMask.ButtonPressMask
15092 				| EventMask.ButtonReleaseMask
15093 			;
15094 
15095 			// xshm is our shortcut for local connections
15096 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
15097 				mask |= EventMask.PointerMotionMask;
15098 			else
15099 				mask |= EventMask.ButtonMotionMask;
15100 
15101 			XSelectInput(display, window, mask);
15102 		}
15103 
15104 
15105 		void setNetWMWindowType(Atom type) {
15106 			Atom[2] atoms;
15107 
15108 			atoms[0] = type;
15109 			// generic fallback
15110 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
15111 
15112 			XChangeProperty(
15113 				display,
15114 				impl.window,
15115 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
15116 				XA_ATOM,
15117 				32 /* bits */,
15118 				0 /*PropModeReplace*/,
15119 				atoms.ptr,
15120 				cast(int) atoms.length);
15121 		}
15122 
15123 		void motifHideDecorations(bool hide = true) {
15124 			MwmHints hints;
15125 			hints.flags = MWM_HINTS_DECORATIONS;
15126 			hints.decorations = hide ? 0 : 1;
15127 
15128 			XChangeProperty(
15129 				display,
15130 				impl.window,
15131 				GetAtom!"_MOTIF_WM_HINTS"(display),
15132 				GetAtom!"_MOTIF_WM_HINTS"(display),
15133 				32 /* bits */,
15134 				0 /*PropModeReplace*/,
15135 				&hints,
15136 				hints.sizeof / 4);
15137 		}
15138 
15139 		/*k8: unused
15140 		void createOpenGlContext() {
15141 
15142 		}
15143 		*/
15144 
15145 		void closeWindow() {
15146 			// I can't close this or a child window closing will
15147 			// break events for everyone. So I'm just leaking it right
15148 			// now and that is probably perfectly fine...
15149 			version(none)
15150 			if (customEventFDRead != -1) {
15151 				import core.sys.posix.unistd : close;
15152 				auto same = customEventFDRead == customEventFDWrite;
15153 
15154 				close(customEventFDRead);
15155 				if(!same)
15156 					close(customEventFDWrite);
15157 				customEventFDRead = -1;
15158 				customEventFDWrite = -1;
15159 			}
15160 
15161 			version(without_opengl) {} else
15162 			if(glc !is null) {
15163 				glXDestroyContext(display, glc);
15164 				glc = null;
15165 			}
15166 
15167 			if(buffer)
15168 				XFreePixmap(display, buffer);
15169 			bufferw = bufferh = 0;
15170 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
15171 			XDestroyWindow(display, window);
15172 			XFlush(display);
15173 		}
15174 
15175 		void dispose() {
15176 		}
15177 
15178 		bool destroyed = false;
15179 	}
15180 
15181 	bool insideXEventLoop;
15182 }
15183 
15184 version(X11) {
15185 
15186 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
15187 
15188 	private class ResizeEvent {
15189 		int width, height;
15190 	}
15191 
15192 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
15193 		if(win.windowType == WindowTypes.minimallyWrapped)
15194 			return;
15195 
15196 		if(win.pendingResizeEvent is null) {
15197 			win.pendingResizeEvent = new ResizeEvent();
15198 			win.addEventListener((ResizeEvent re) {
15199 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
15200 			});
15201 		}
15202 		win.pendingResizeEvent.width = width;
15203 		win.pendingResizeEvent.height = height;
15204 		if(!win.eventQueued!ResizeEvent) {
15205 			win.postEvent(win.pendingResizeEvent);
15206 		}
15207 	}
15208 
15209 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
15210 		if(win.windowType == WindowTypes.minimallyWrapped)
15211 			return;
15212 		if(win.closed)
15213 			return;
15214 
15215 		if(width != win.width || height != win.height) {
15216 
15217 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
15218 			win._width = width;
15219 			win._height = height;
15220 
15221 			if(win.openglMode == OpenGlOptions.no) {
15222 				// FIXME: could this be more efficient?
15223 
15224 				if (win.bufferw < width || win.bufferh < height) {
15225 					//{ 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); }
15226 					// grow the internal buffer to match the window...
15227 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
15228 					{
15229 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15230 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15231 						scope(exit) XFreeGC(win.display, xgc);
15232 						XSetClipMask(win.display, xgc, None);
15233 						XSetForeground(win.display, xgc, 0);
15234 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
15235 					}
15236 					XCopyArea(display,
15237 						cast(Drawable) win.buffer,
15238 						cast(Drawable) newPixmap,
15239 						win.gc, 0, 0,
15240 						win.bufferw < width ? win.bufferw : win.width,
15241 						win.bufferh < height ? win.bufferh : win.height,
15242 						0, 0);
15243 
15244 					XFreePixmap(display, win.buffer);
15245 					win.buffer = newPixmap;
15246 					win.bufferw = width;
15247 					win.bufferh = height;
15248 				}
15249 
15250 				// clear unused parts of the buffer
15251 				if (win.bufferw > width || win.bufferh > height) {
15252 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
15253 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
15254 					scope(exit) XFreeGC(win.display, xgc);
15255 					XSetClipMask(win.display, xgc, None);
15256 					XSetForeground(win.display, xgc, 0);
15257 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
15258 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
15259 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
15260 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
15261 				}
15262 
15263 			}
15264 
15265 			win.updateOpenglViewportIfNeeded(width, height);
15266 
15267 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
15268 
15269 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
15270 			if(win.windowResized !is null) {
15271 				XUnlockDisplay(display);
15272 				scope(exit) XLockDisplay(display);
15273 				win.windowResized(width, height);
15274 			}
15275 		}
15276 	}
15277 
15278 
15279 	/// Platform-specific, you might use it when doing a custom event loop.
15280 	bool doXNextEvent(Display* display) {
15281 		bool done;
15282 		XEvent e;
15283 		XNextEvent(display, &e);
15284 		version(sddddd) {
15285 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15286 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
15287 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
15288 			}
15289 		}
15290 
15291 		// filter out compose events
15292 		if (XFilterEvent(&e, None)) {
15293 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
15294 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
15295 			return false;
15296 		}
15297 		// process keyboard mapping changes
15298 		if (e.type == EventType.KeymapNotify) {
15299 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
15300 			XRefreshKeyboardMapping(&e.xmapping);
15301 			return false;
15302 		}
15303 
15304 		version(with_eventloop)
15305 			import arsd.eventloop;
15306 
15307 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
15308 			// see windows impl's comments
15309 			XUnlockDisplay(display);
15310 			scope(exit) XLockDisplay(display);
15311 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
15312 			if(ret == 0)
15313 				return done;
15314 		}
15315 
15316 
15317 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
15318 			if(win.getNativeEventHandler !is null) {
15319 				XUnlockDisplay(display);
15320 				scope(exit) XLockDisplay(display);
15321 				auto ret = win.getNativeEventHandler()(e);
15322 				if(ret == 0)
15323 					return done;
15324 			}
15325 		}
15326 
15327 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
15328 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
15329 				// we get this because of the RRScreenChangeNotifyMask
15330 
15331 				// this isn't actually an ideal way to do it since it wastes time
15332 				// but meh it is simple and it works.
15333 				win.actualDpiLoadAttempted = false;
15334 				SimpleWindow.xRandrInfoLoadAttemped = false;
15335 				win.updateActualDpi(); // trigger a reload
15336 			}
15337 		}
15338 
15339 		switch(e.type) {
15340 		  case EventType.SelectionClear:
15341 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
15342 				// FIXME so it is supposed to finish any in progress transfers... but idk...
15343 				// writeln("SelectionClear");
15344 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
15345 			}
15346 		  break;
15347 		  case EventType.SelectionRequest:
15348 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
15349 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
15350 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
15351 				XUnlockDisplay(display);
15352 				scope(exit) XLockDisplay(display);
15353 				(*ssh).handleRequest(e);
15354 			}
15355 		  break;
15356 		  case EventType.PropertyNotify:
15357 			// printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
15358 
15359 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
15360 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
15361 					ssh.sendMoreIncr(&e.xproperty);
15362 			}
15363 
15364 
15365 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
15366 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15367 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
15368 					Atom target;
15369 					int format;
15370 					arch_ulong bytesafter, length;
15371 					void* value;
15372 
15373 					ubyte[] s;
15374 					Atom targetToKeep;
15375 
15376 					XGetWindowProperty(
15377 						e.xproperty.display,
15378 						e.xproperty.window,
15379 						e.xproperty.atom,
15380 						0,
15381 						100000 /* length */,
15382 						true, /* erase it to signal we got it and want more */
15383 						0 /*AnyPropertyType*/,
15384 						&target, &format, &length, &bytesafter, &value);
15385 
15386 					if(!targetToKeep)
15387 						targetToKeep = target;
15388 
15389 					auto id = (cast(ubyte*) value)[0 .. length];
15390 
15391 					handler.handleIncrData(targetToKeep, id);
15392 
15393 					XFree(value);
15394 				}
15395 			}
15396 		  break;
15397 		  case EventType.SelectionNotify:
15398 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
15399 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
15400 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
15401 					XUnlockDisplay(display);
15402 					scope(exit) XLockDisplay(display);
15403 					handler.handleData(None, null);
15404 				} else {
15405 					Atom target;
15406 					int format;
15407 					arch_ulong bytesafter, length;
15408 					void* value;
15409 					XGetWindowProperty(
15410 						e.xselection.display,
15411 						e.xselection.requestor,
15412 						e.xselection.property,
15413 						0,
15414 						100000 /* length */,
15415 						//false, /* don't erase it */
15416 						true, /* do erase it lol */
15417 						0 /*AnyPropertyType*/,
15418 						&target, &format, &length, &bytesafter, &value);
15419 
15420 					// FIXME: I don't have to copy it now since it is in char[] instead of string
15421 
15422 					{
15423 						XUnlockDisplay(display);
15424 						scope(exit) XLockDisplay(display);
15425 
15426 						if(target == XA_ATOM) {
15427 							// initial request, see what they are able to work with and request the best one
15428 							// we can handle, if available
15429 
15430 							Atom[] answer = (cast(Atom*) value)[0 .. length];
15431 							Atom best = handler.findBestFormat(answer);
15432 
15433 							/+
15434 							writeln("got ", answer);
15435 							foreach(a; answer)
15436 								printf("%s\n", XGetAtomName(display, a));
15437 							writeln("best ", best);
15438 							+/
15439 
15440 							if(best != None) {
15441 								// actually request the best format
15442 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
15443 							}
15444 						} else if(target == GetAtom!"INCR"(display)) {
15445 							// incremental
15446 
15447 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
15448 
15449 							// signal the sending program that we see
15450 							// the incr and are ready to receive more.
15451 							XDeleteProperty(
15452 								e.xselection.display,
15453 								e.xselection.requestor,
15454 								e.xselection.property);
15455 						} else {
15456 							// unsupported type... maybe, forward
15457 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
15458 						}
15459 					}
15460 					XFree(value);
15461 					/*
15462 					XDeleteProperty(
15463 						e.xselection.display,
15464 						e.xselection.requestor,
15465 						e.xselection.property);
15466 					*/
15467 				}
15468 			}
15469 		  break;
15470 		  case EventType.ConfigureNotify:
15471 			auto event = e.xconfigure;
15472 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
15473 				if(win.windowType == WindowTypes.minimallyWrapped)
15474 					break;
15475 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
15476 
15477 				/+
15478 					The ICCCM says window managers must send a synthetic event when the window
15479 					is moved but NOT when it is resized. In the resize case, an event is sent
15480 					with position (0, 0) which can be wrong and break the dpi calculations.
15481 
15482 					So we only consider the synthetic events from the WM and otherwise
15483 					need to wait for some other event to get the position which... sucks.
15484 
15485 					I'd rather not have windows changing their layout on mouse motion after
15486 					switching monitors... might be forced to but for now just ignoring it.
15487 
15488 					Easiest way to switch monitors without sending a size position is by
15489 					maximize or fullscreen in a setup like mine, but on most setups those
15490 					work on the monitor it is already living on, so it should be ok most the
15491 					time.
15492 				+/
15493 				if(event.send_event) {
15494 					win.screenPositionKnown = true;
15495 					win.screenPositionX = event.x;
15496 					win.screenPositionY = event.y;
15497 					win.updateActualDpi();
15498 				}
15499 
15500 				win.updateIMEPopupLocation();
15501 				recordX11ResizeAsync(display, *win, event.width, event.height);
15502 			}
15503 		  break;
15504 		  case EventType.Expose:
15505 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
15506 				if(win.windowType == WindowTypes.minimallyWrapped)
15507 					break;
15508 				// if it is closing from a popup menu, it can get
15509 				// an Expose event right by the end and trigger a
15510 				// BadDrawable error ... we'll just check
15511 				// closed to handle that.
15512 				if((*win).closed) break;
15513 				if((*win).openglMode == OpenGlOptions.no) {
15514 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
15515 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
15516 					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);
15517 				} else {
15518 					// need to redraw the scene somehow
15519 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
15520 						XUnlockDisplay(display);
15521 						scope(exit) XLockDisplay(display);
15522 						version(without_opengl) {} else
15523 						win.redrawOpenGlSceneSoon();
15524 					}
15525 				}
15526 			}
15527 		  break;
15528 		  case EventType.FocusIn:
15529 		  case EventType.FocusOut:
15530 
15531 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15532 				/+
15533 
15534 				void info(string detail) {
15535 					string s;
15536 					// import std.conv;
15537 					// import std.datetime;
15538 					s ~= to!string(Clock.currTime);
15539 					s ~= " ";
15540 					s ~= e.type == EventType.FocusIn ? "in " : "out";
15541 					s ~= " ";
15542 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
15543 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
15544 					s ~= detail;
15545 					s ~= " ";
15546 
15547 					sdpyPrintDebugString(s);
15548 
15549 				}
15550 
15551 				switch(e.xfocus.detail) {
15552 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
15553 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
15554 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
15555 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
15556 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
15557 					case NotifyDetail.NotifyPointer: info("pointer"); break;
15558 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
15559 					case NotifyDetail.NotifyDetailNone: info("none"); break;
15560 					default:
15561 
15562 				}
15563 				+/
15564 
15565 
15566 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
15567 					break; // just ignore these they seem irrelevant
15568 
15569 				auto old = win._focused;
15570 				win._focused = e.type == EventType.FocusIn;
15571 
15572 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
15573 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
15574 					win._focused = true;
15575 
15576 				if(win.demandingAttention)
15577 					demandAttention(*win, false);
15578 
15579 				win.updateIMEFocused();
15580 
15581 				if(old != win._focused && win.onFocusChange) {
15582 					XUnlockDisplay(display);
15583 					scope(exit) XLockDisplay(display);
15584 					win.onFocusChange(win._focused);
15585 				}
15586 			}
15587 		  break;
15588 		  case EventType.VisibilityNotify:
15589 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
15590 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
15591 						if (win.visibilityChanged !is null) {
15592 								XUnlockDisplay(display);
15593 								scope(exit) XLockDisplay(display);
15594 								win.visibilityChanged(false);
15595 							}
15596 					} else {
15597 						if (win.visibilityChanged !is null) {
15598 							XUnlockDisplay(display);
15599 							scope(exit) XLockDisplay(display);
15600 							win.visibilityChanged(true);
15601 						}
15602 					}
15603 				}
15604 				break;
15605 		  case EventType.ClientMessage:
15606 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
15607 					// "ignore next mouse motion" event, increment ignore counter for teh window
15608 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15609 						++(*win).warpEventCount;
15610 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
15611 					} else {
15612 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
15613 					}
15614 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
15615 					// user clicked the close button on the window manager
15616 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15617 						XUnlockDisplay(display);
15618 						scope(exit) XLockDisplay(display);
15619 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
15620 					}
15621 
15622 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
15623 					// writeln("HAPPENED");
15624 					// user clicked the close button on the window manager
15625 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15626 						XUnlockDisplay(display);
15627 						scope(exit) XLockDisplay(display);
15628 
15629 						auto setTo = *win;
15630 
15631 						if(win.setRequestedInputFocus !is null) {
15632 							auto s = win.setRequestedInputFocus();
15633 							if(s !is null) {
15634 								setTo = s;
15635 							}
15636 						}
15637 
15638 						assert(setTo !is null);
15639 
15640 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
15641 
15642 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
15643 					}
15644 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
15645 					foreach(nai; NotificationAreaIcon.activeIcons)
15646 						nai.newManager();
15647 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
15648 
15649 					bool xDragWindow = true;
15650 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
15651 						//XDefineCursor(display, xDragWindow.impl.window,
15652 							//writeln("XdndStatus ", e.xclient.data.l);
15653 					}
15654 					if(auto dh = win.dropHandler) {
15655 
15656 						static Atom[3] xFormatsBuffer;
15657 						static Atom[] xFormats;
15658 
15659 						void resetXFormats() {
15660 							xFormatsBuffer[] = 0;
15661 							xFormats = xFormatsBuffer[];
15662 						}
15663 
15664 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
15665 							// on Windows it is supposed to return the effect you actually do FIXME
15666 
15667 							auto sourceWindow =  e.xclient.data.l[0];
15668 
15669 							xFormatsBuffer[0] = e.xclient.data.l[2];
15670 							xFormatsBuffer[1] = e.xclient.data.l[3];
15671 							xFormatsBuffer[2] = e.xclient.data.l[4];
15672 
15673 							if(e.xclient.data.l[1] & 1) {
15674 								// can just grab it all but like we don't necessarily need them...
15675 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
15676 							} else {
15677 								int len;
15678 								foreach(fmt; xFormatsBuffer)
15679 									if(fmt) len++;
15680 								xFormats = xFormatsBuffer[0 .. len];
15681 							}
15682 
15683 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
15684 
15685 							dh.dragEnter(&pkg);
15686 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
15687 
15688 							auto pack = e.xclient.data.l[2];
15689 
15690 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
15691 
15692 
15693 							XClientMessageEvent xclient;
15694 
15695 							xclient.type = EventType.ClientMessage;
15696 							xclient.window = e.xclient.data.l[0];
15697 							xclient.message_type = GetAtom!"XdndStatus"(display);
15698 							xclient.format = 32;
15699 							xclient.data.l[0] = win.impl.window;
15700 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
15701 							auto r = result.consistentWithin;
15702 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
15703 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
15704 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
15705 
15706 							XSendEvent(
15707 								display,
15708 								e.xclient.data.l[0],
15709 								false,
15710 								EventMask.NoEventMask,
15711 								cast(XEvent*) &xclient
15712 							);
15713 
15714 
15715 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
15716 							//writeln("XdndLeave");
15717 							// drop cancelled.
15718 							// data.l[0] is the source window
15719 							dh.dragLeave();
15720 
15721 							resetXFormats();
15722 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
15723 							// drop happening, should fetch data, then send finished
15724 							// writeln("XdndDrop");
15725 
15726 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
15727 
15728 							dh.drop(&pkg);
15729 
15730 							resetXFormats();
15731 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
15732 							// writeln("XdndFinished");
15733 
15734 							dh.finish();
15735 						}
15736 
15737 					}
15738 				}
15739 		  break;
15740 		  case EventType.MapNotify:
15741 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
15742 					(*win)._visible = true;
15743 					if (!(*win)._visibleForTheFirstTimeCalled) {
15744 						(*win)._visibleForTheFirstTimeCalled = true;
15745 						if ((*win).visibleForTheFirstTime !is null) {
15746 							XUnlockDisplay(display);
15747 							scope(exit) XLockDisplay(display);
15748 							(*win).visibleForTheFirstTime();
15749 						}
15750 					}
15751 					if ((*win).visibilityChanged !is null) {
15752 						XUnlockDisplay(display);
15753 						scope(exit) XLockDisplay(display);
15754 						(*win).visibilityChanged(true);
15755 					}
15756 				}
15757 		  break;
15758 		  case EventType.UnmapNotify:
15759 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
15760 					win._visible = false;
15761 					if (win.visibilityChanged !is null) {
15762 						XUnlockDisplay(display);
15763 						scope(exit) XLockDisplay(display);
15764 						win.visibilityChanged(false);
15765 					}
15766 			}
15767 		  break;
15768 		  case EventType.DestroyNotify:
15769 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
15770 				if(win.destroyed)
15771 					break; // might get a notification both for itself and from its parent
15772 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
15773 				win._closed = true; // just in case
15774 				win.destroyed = true;
15775 				if (win.xic !is null) {
15776 					XDestroyIC(win.xic);
15777 					win.xic = null; // just in case
15778 				}
15779 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
15780 				bool anyImportant = false;
15781 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
15782 					if(w.beingOpenKeepsAppOpen) {
15783 						anyImportant = true;
15784 						break;
15785 					}
15786 				if(!anyImportant) {
15787 					EventLoop.quitApplication();
15788 					done = true;
15789 				}
15790 			}
15791 			auto window = e.xdestroywindow.window;
15792 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
15793 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
15794 
15795 			version(with_eventloop) {
15796 				if(done) exit();
15797 			}
15798 		  break;
15799 
15800 		  case EventType.MotionNotify:
15801 			MouseEvent mouse;
15802 			auto event = e.xmotion;
15803 
15804 			mouse.type = MouseEventType.motion;
15805 			mouse.x = event.x;
15806 			mouse.y = event.y;
15807 			mouse.modifierState = event.state;
15808 
15809 			mouse.timestamp = event.time;
15810 
15811 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
15812 				mouse.window = *win;
15813 				if (win.warpEventCount > 0) {
15814 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
15815 					--(*win).warpEventCount;
15816 					(*win).mdx(mouse); // so deltas will be correctly updated
15817 				} else {
15818 					win.warpEventCount = 0; // just in case
15819 					(*win).mdx(mouse);
15820 					if((*win).handleMouseEvent) {
15821 						XUnlockDisplay(display);
15822 						scope(exit) XLockDisplay(display);
15823 						(*win).handleMouseEvent(mouse);
15824 					}
15825 				}
15826 			}
15827 
15828 		  	version(with_eventloop)
15829 				send(mouse);
15830 		  break;
15831 		  case EventType.ButtonPress:
15832 		  case EventType.ButtonRelease:
15833 			MouseEvent mouse;
15834 			auto event = e.xbutton;
15835 
15836 			mouse.timestamp = event.time;
15837 
15838 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
15839 			mouse.x = event.x;
15840 			mouse.y = event.y;
15841 
15842 			static Time lastMouseDownTime = 0;
15843 			static int lastMouseDownButton = -1;
15844 
15845 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
15846 			if(e.type == EventType.ButtonPress) {
15847 				lastMouseDownTime = event.time;
15848 				lastMouseDownButton = event.button;
15849 			}
15850 
15851 			switch(event.button) {
15852 				case 1: mouse.button = MouseButton.left; break; // left
15853 				case 2: mouse.button = MouseButton.middle; break; // middle
15854 				case 3: mouse.button = MouseButton.right; break; // right
15855 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
15856 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
15857 				case 6: break; // idk
15858 				case 7: break; // idk
15859 				case 8: mouse.button = MouseButton.backButton; break;
15860 				case 9: mouse.button = MouseButton.forwardButton; break;
15861 				default:
15862 			}
15863 
15864 			// FIXME: double check this
15865 			mouse.modifierState = event.state;
15866 
15867 			//mouse.modifierState = event.detail;
15868 
15869 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
15870 				mouse.window = *win;
15871 				(*win).mdx(mouse);
15872 				if((*win).handleMouseEvent) {
15873 					XUnlockDisplay(display);
15874 					scope(exit) XLockDisplay(display);
15875 					(*win).handleMouseEvent(mouse);
15876 				}
15877 			}
15878 			version(with_eventloop)
15879 				send(mouse);
15880 		  break;
15881 
15882 		  case EventType.KeyPress:
15883 		  case EventType.KeyRelease:
15884 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
15885 			KeyEvent ke;
15886 			ke.pressed = e.type == EventType.KeyPress;
15887 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
15888 
15889 			auto sym = XKeycodeToKeysym(
15890 				XDisplayConnection.get(),
15891 				e.xkey.keycode,
15892 				0);
15893 
15894 			ke.key = cast(Key) sym;//e.xkey.keycode;
15895 
15896 			ke.modifierState = e.xkey.state;
15897 
15898 			// writefln("%x", sym);
15899 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
15900 			int charbuflen = 0; // return value of XwcLookupString
15901 			if (ke.pressed) {
15902 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
15903 				if (win !is null && win.xic !is null) {
15904 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
15905 					Status status;
15906 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
15907 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
15908 				} else {
15909 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
15910 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
15911 					char[16] buffer;
15912 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
15913 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
15914 				}
15915 			}
15916 
15917 			// if there's no char, subst one
15918 			if (charbuflen == 0) {
15919 				switch (sym) {
15920 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
15921 					case 0xff8d: // keypad enter
15922 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
15923 					default : // ignore
15924 				}
15925 			}
15926 
15927 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
15928 				ke.window = *win;
15929 
15930 
15931 				if(win.inputProxy)
15932 					win = &win.inputProxy;
15933 
15934 				// char events are separate since they are on Windows too
15935 				// also, xcompose can generate long char sequences
15936 				// don't send char events if Meta and/or Hyper is pressed
15937 				// TODO: ctrl+char should only send control chars; not yet
15938 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
15939 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
15940 				}
15941 
15942 				dchar[32] charsComingBuffer;
15943 				int charsComingPosition;
15944 				dchar[] charsComing = charsComingBuffer[];
15945 
15946 				if (ke.pressed && charbuflen > 0) {
15947 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
15948 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
15949 						if(charsComingPosition >= charsComing.length)
15950 							charsComing.length = charsComingPosition + 8;
15951 
15952 						charsComing[charsComingPosition++] = ch;
15953 					}
15954 
15955 					charsComing = charsComing[0 .. charsComingPosition];
15956 				} else {
15957 					charsComing = null;
15958 				}
15959 
15960 				ke.charsPossible = charsComing;
15961 
15962 				if (win.handleKeyEvent) {
15963 					XUnlockDisplay(display);
15964 					scope(exit) XLockDisplay(display);
15965 					win.handleKeyEvent(ke);
15966 				}
15967 
15968 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
15969 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
15970 					XUnlockDisplay(display);
15971 					scope(exit) XLockDisplay(display);
15972 					foreach(ch; charsComing)
15973 						win.handleCharEvent(ch);
15974 				}
15975 			}
15976 
15977 			version(with_eventloop)
15978 				send(ke);
15979 		  break;
15980 		  default:
15981 		}
15982 
15983 		return done;
15984 	}
15985 }
15986 
15987 /* *************************************** */
15988 /*      Done with simpledisplay stuff      */
15989 /* *************************************** */
15990 
15991 // Necessary C library bindings follow
15992 version(Windows) {} else
15993 version(X11) {
15994 
15995 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
15996 
15997 // X11 bindings needed here
15998 /*
15999 	A little of this is from the bindings project on
16000 	D Source and some of it is copy/paste from the C
16001 	header.
16002 
16003 	The DSource listing consistently used D's long
16004 	where C used long. That's wrong - C long is 32 bit, so
16005 	it should be int in D. I changed that here.
16006 
16007 	Note:
16008 	This isn't complete, just took what I needed for myself.
16009 */
16010 
16011 import core.stdc.stddef : wchar_t;
16012 
16013 interface XLib {
16014 extern(C) nothrow @nogc {
16015 	char* XResourceManagerString(Display*);
16016 	void XrmInitialize();
16017 	XrmDatabase XrmGetStringDatabase(char* data);
16018 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
16019 
16020 	Cursor XCreateFontCursor(Display*, uint shape);
16021 	int XDefineCursor(Display* display, Window w, Cursor cursor);
16022 	int XUndefineCursor(Display* display, Window w);
16023 
16024 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
16025 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
16026 	int XFreeCursor(Display* display, Cursor cursor);
16027 
16028 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
16029 
16030 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
16031 
16032 	XVaNestedList XVaCreateNestedList(int unused, ...);
16033 
16034 	char *XKeysymToString(KeySym keysym);
16035 	KeySym XKeycodeToKeysym(
16036 		Display*		/* display */,
16037 		KeyCode		/* keycode */,
16038 		int			/* index */
16039 	);
16040 
16041 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
16042 
16043 	int XFree(void*);
16044 	int XDeleteProperty(Display *display, Window w, Atom property);
16045 
16046 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
16047 
16048 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
16049 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
16050 		*actual_type_return, int *actual_format_return, arch_ulong
16051 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
16052 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
16053 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
16054 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
16055 
16056 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
16057 
16058 	Window XGetSelectionOwner(Display *display, Atom selection);
16059 
16060 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
16061 
16062 	char** XListFonts(Display*, const char*, int, int*);
16063 	void XFreeFontNames(char**);
16064 
16065 	Display* XOpenDisplay(const char*);
16066 	int XCloseDisplay(Display*);
16067 
16068 	int function() XSynchronize(Display*, bool);
16069 	int function() XSetAfterFunction(Display*, int function() proc);
16070 
16071 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
16072 
16073 	Bool XSupportsLocale();
16074 	char* XSetLocaleModifiers(const(char)* modifier_list);
16075 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16076 	Status XCloseOM(XOM om);
16077 
16078 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
16079 	Status XCloseIM(XIM im);
16080 
16081 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16082 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
16083 	Display* XDisplayOfIM(XIM im);
16084 	char* XLocaleOfIM(XIM im);
16085 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
16086 	void XDestroyIC(XIC ic);
16087 	void XSetICFocus(XIC ic);
16088 	void XUnsetICFocus(XIC ic);
16089 	//wchar_t* XwcResetIC(XIC ic);
16090 	char* XmbResetIC(XIC ic);
16091 	char* Xutf8ResetIC(XIC ic);
16092 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16093 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
16094 	XIM XIMOfIC(XIC ic);
16095 
16096 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
16097 
16098 
16099 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
16100 	int XFreeFont(Display *display, XFontStruct *font_struct);
16101 	int XSetFont(Display* display, GC gc, Font font);
16102 	int XTextWidth(XFontStruct*, scope const char*, int);
16103 
16104 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
16105 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
16106 
16107 	Window XCreateSimpleWindow(
16108 		Display*	/* display */,
16109 		Window		/* parent */,
16110 		int			/* x */,
16111 		int			/* y */,
16112 		uint		/* width */,
16113 		uint		/* height */,
16114 		uint		/* border_width */,
16115 		uint		/* border */,
16116 		uint		/* background */
16117 	);
16118 	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);
16119 
16120 	int XReparentWindow(Display*, Window, Window, int, int);
16121 	int XClearWindow(Display*, Window);
16122 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
16123 	int XMoveWindow(Display*, Window, int, int);
16124 	int XResizeWindow(Display *display, Window w, uint width, uint height);
16125 
16126 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
16127 
16128 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
16129 
16130 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
16131 
16132 	XImage *XCreateImage(
16133 		Display*		/* display */,
16134 		Visual*		/* visual */,
16135 		uint	/* depth */,
16136 		int			/* format */,
16137 		int			/* offset */,
16138 		ubyte*		/* data */,
16139 		uint	/* width */,
16140 		uint	/* height */,
16141 		int			/* bitmap_pad */,
16142 		int			/* bytes_per_line */
16143 	);
16144 
16145 	Status XInitImage (XImage* image);
16146 
16147 	Atom XInternAtom(
16148 		Display*		/* display */,
16149 		const char*	/* atom_name */,
16150 		Bool		/* only_if_exists */
16151 	);
16152 
16153 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
16154 	char* XGetAtomName(Display*, Atom);
16155 	Status XGetAtomNames(Display*, Atom*, int count, char**);
16156 
16157 	int XPutImage(
16158 		Display*	/* display */,
16159 		Drawable	/* d */,
16160 		GC			/* gc */,
16161 		XImage*	/* image */,
16162 		int			/* src_x */,
16163 		int			/* src_y */,
16164 		int			/* dest_x */,
16165 		int			/* dest_y */,
16166 		uint		/* width */,
16167 		uint		/* height */
16168 	);
16169 
16170 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
16171 
16172 
16173 	int XDestroyWindow(
16174 		Display*	/* display */,
16175 		Window		/* w */
16176 	);
16177 
16178 	int XDestroyImage(XImage*);
16179 
16180 	int XSelectInput(
16181 		Display*	/* display */,
16182 		Window		/* w */,
16183 		EventMask	/* event_mask */
16184 	);
16185 
16186 	int XMapWindow(
16187 		Display*	/* display */,
16188 		Window		/* w */
16189 	);
16190 
16191 	Status XIconifyWindow(Display*, Window, int);
16192 	int XMapRaised(Display*, Window);
16193 	int XMapSubwindows(Display*, Window);
16194 
16195 	int XNextEvent(
16196 		Display*	/* display */,
16197 		XEvent*		/* event_return */
16198 	);
16199 
16200 	int XMaskEvent(Display*, arch_long, XEvent*);
16201 
16202 	Bool XFilterEvent(XEvent *event, Window window);
16203 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
16204 
16205 	Status XSetWMProtocols(
16206 		Display*	/* display */,
16207 		Window		/* w */,
16208 		Atom*		/* protocols */,
16209 		int			/* count */
16210 	);
16211 
16212 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
16213 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
16214 
16215 
16216 	Status XInitThreads();
16217 	void XLockDisplay (Display* display);
16218 	void XUnlockDisplay (Display* display);
16219 
16220 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
16221 
16222 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
16223 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
16224 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
16225 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
16226 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
16227 
16228 
16229 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
16230 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
16231 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
16232 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
16233 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16234 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
16235 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
16236 	int XDrawPoint(Display*, Drawable, GC, int, int);
16237 	int XSetForeground(Display*, GC, uint);
16238 	int XSetBackground(Display*, GC, uint);
16239 
16240 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
16241 	void XFreeFontSet(Display*, XFontSet);
16242 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
16243 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
16244 
16245 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
16246 
16247 
16248 //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);
16249 
16250 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
16251 	int XSetFunction(Display*, GC, int);
16252 
16253 	GC XCreateGC(Display*, Drawable, uint, void*);
16254 	int XCopyGC(Display*, GC, uint, GC);
16255 	int XFreeGC(Display*, GC);
16256 
16257 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
16258 	bool XCheckMaskEvent(Display*, int, XEvent*);
16259 
16260 	int XPending(Display*);
16261 	int XEventsQueued(Display* display, int mode);
16262 
16263 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
16264 	int XFreePixmap(Display*, Pixmap);
16265 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
16266 	int XFlush(Display*);
16267 	int XBell(Display*, int);
16268 	int XSync(Display*, bool);
16269 
16270 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
16271 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
16272 
16273 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
16274 	int XUngrabKeyboard(Display*, Time);
16275 
16276 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
16277 
16278 	KeySym XStringToKeysym(const char *string);
16279 
16280 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
16281 
16282 	Window XDefaultRootWindow(Display*);
16283 
16284 	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);
16285 
16286 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
16287 
16288 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
16289 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
16290 
16291 	Status XAllocColor(Display*, Colormap, XColor*);
16292 
16293 	int XWithdrawWindow(Display*, Window, int);
16294 	int XUnmapWindow(Display*, Window);
16295 	int XLowerWindow(Display*, Window);
16296 	int XRaiseWindow(Display*, Window);
16297 
16298 	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);
16299 	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);
16300 
16301 	int XGetInputFocus(Display*, Window*, int*);
16302 	int XSetInputFocus(Display*, Window, int, Time);
16303 
16304 	XErrorHandler XSetErrorHandler(XErrorHandler);
16305 
16306 	int XGetErrorText(Display*, int, char*, int);
16307 
16308 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
16309 
16310 
16311 	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);
16312 	int XUngrabPointer(Display *display, Time time);
16313 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
16314 
16315 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
16316 
16317 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
16318 	int XSetClipMask(Display*, GC, Pixmap);
16319 	int XSetClipOrigin(Display*, GC, int, int);
16320 
16321 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
16322 
16323 	void XSetWMName(Display*, Window, XTextProperty*);
16324 	Status XGetWMName(Display*, Window, XTextProperty*);
16325 	int XStoreName(Display* display, Window w, const(char)* window_name);
16326 
16327 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
16328 
16329 }
16330 }
16331 
16332 interface Xext {
16333 extern(C) nothrow @nogc {
16334 	Status XShmAttach(Display*, XShmSegmentInfo*);
16335 	Status XShmDetach(Display*, XShmSegmentInfo*);
16336 	Status XShmPutImage(
16337 		Display*            /* dpy */,
16338 		Drawable            /* d */,
16339 		GC                  /* gc */,
16340 		XImage*             /* image */,
16341 		int                 /* src_x */,
16342 		int                 /* src_y */,
16343 		int                 /* dst_x */,
16344 		int                 /* dst_y */,
16345 		uint        /* src_width */,
16346 		uint        /* src_height */,
16347 		Bool                /* send_event */
16348 	);
16349 
16350 	Status XShmQueryExtension(Display*);
16351 
16352 	XImage *XShmCreateImage(
16353 		Display*            /* dpy */,
16354 		Visual*             /* visual */,
16355 		uint        /* depth */,
16356 		int                 /* format */,
16357 		char*               /* data */,
16358 		XShmSegmentInfo*    /* shminfo */,
16359 		uint        /* width */,
16360 		uint        /* height */
16361 	);
16362 
16363 	Pixmap XShmCreatePixmap(
16364 		Display*            /* dpy */,
16365 		Drawable            /* d */,
16366 		char*               /* data */,
16367 		XShmSegmentInfo*    /* shminfo */,
16368 		uint        /* width */,
16369 		uint        /* height */,
16370 		uint        /* depth */
16371 	);
16372 
16373 }
16374 }
16375 
16376 	// this requires -lXpm
16377 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
16378 
16379 
16380 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
16381 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
16382 shared static this() {
16383 	xlib.loadDynamicLibrary();
16384 	xext.loadDynamicLibrary();
16385 }
16386 
16387 
16388 extern(C) nothrow @nogc {
16389 
16390 alias XrmDatabase = void*;
16391 struct XrmValue {
16392 	uint size;
16393 	void* addr;
16394 }
16395 
16396 struct XVisualInfo {
16397 	Visual* visual;
16398 	VisualID visualid;
16399 	int screen;
16400 	uint depth;
16401 	int c_class;
16402 	c_ulong red_mask;
16403 	c_ulong green_mask;
16404 	c_ulong blue_mask;
16405 	int colormap_size;
16406 	int bits_per_rgb;
16407 }
16408 
16409 enum VisualNoMask=	0x0;
16410 enum VisualIDMask=	0x1;
16411 enum VisualScreenMask=0x2;
16412 enum VisualDepthMask=	0x4;
16413 enum VisualClassMask=	0x8;
16414 enum VisualRedMaskMask=0x10;
16415 enum VisualGreenMaskMask=0x20;
16416 enum VisualBlueMaskMask=0x40;
16417 enum VisualColormapSizeMask=0x80;
16418 enum VisualBitsPerRGBMask=0x100;
16419 enum VisualAllMask=	0x1FF;
16420 
16421 enum AnyKey = 0;
16422 enum AnyModifier = 1 << 15;
16423 
16424 // XIM and other crap
16425 struct _XOM {}
16426 struct _XIM {}
16427 struct _XIC {}
16428 alias XOM = _XOM*;
16429 alias XIM = _XIM*;
16430 alias XIC = _XIC*;
16431 
16432 alias XVaNestedList = void*;
16433 
16434 alias XIMStyle = arch_ulong;
16435 enum : arch_ulong {
16436 	XIMPreeditArea      = 0x0001,
16437 	XIMPreeditCallbacks = 0x0002,
16438 	XIMPreeditPosition  = 0x0004,
16439 	XIMPreeditNothing   = 0x0008,
16440 	XIMPreeditNone      = 0x0010,
16441 	XIMStatusArea       = 0x0100,
16442 	XIMStatusCallbacks  = 0x0200,
16443 	XIMStatusNothing    = 0x0400,
16444 	XIMStatusNone       = 0x0800,
16445 }
16446 
16447 
16448 /* X Shared Memory Extension functions */
16449 	//pragma(lib, "Xshm");
16450 	alias arch_ulong ShmSeg;
16451 	struct XShmSegmentInfo {
16452 		ShmSeg shmseg;
16453 		int shmid;
16454 		ubyte* shmaddr;
16455 		Bool readOnly;
16456 	}
16457 
16458 	// and the necessary OS functions
16459 	int shmget(int, size_t, int);
16460 	void* shmat(int, scope const void*, int);
16461 	int shmdt(scope const void*);
16462 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
16463 
16464 	enum IPC_PRIVATE = 0;
16465 	enum IPC_CREAT = 512;
16466 	enum IPC_RMID = 0;
16467 
16468 /* MIT-SHM end */
16469 
16470 
16471 enum MappingType:int {
16472 	MappingModifier		=0,
16473 	MappingKeyboard		=1,
16474 	MappingPointer		=2
16475 }
16476 
16477 /* ImageFormat -- PutImage, GetImage */
16478 enum ImageFormat:int {
16479 	XYBitmap	=0,	/* depth 1, XYFormat */
16480 	XYPixmap	=1,	/* depth == drawable depth */
16481 	ZPixmap	=2	/* depth == drawable depth */
16482 }
16483 
16484 enum ModifierName:int {
16485 	ShiftMapIndex	=0,
16486 	LockMapIndex	=1,
16487 	ControlMapIndex	=2,
16488 	Mod1MapIndex	=3,
16489 	Mod2MapIndex	=4,
16490 	Mod3MapIndex	=5,
16491 	Mod4MapIndex	=6,
16492 	Mod5MapIndex	=7
16493 }
16494 
16495 enum ButtonMask:int {
16496 	Button1Mask	=1<<8,
16497 	Button2Mask	=1<<9,
16498 	Button3Mask	=1<<10,
16499 	Button4Mask	=1<<11,
16500 	Button5Mask	=1<<12,
16501 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16502 }
16503 
16504 enum KeyOrButtonMask:uint {
16505 	ShiftMask	=1<<0,
16506 	LockMask	=1<<1,
16507 	ControlMask	=1<<2,
16508 	Mod1Mask	=1<<3,
16509 	Mod2Mask	=1<<4,
16510 	Mod3Mask	=1<<5,
16511 	Mod4Mask	=1<<6,
16512 	Mod5Mask	=1<<7,
16513 	Button1Mask	=1<<8,
16514 	Button2Mask	=1<<9,
16515 	Button3Mask	=1<<10,
16516 	Button4Mask	=1<<11,
16517 	Button5Mask	=1<<12,
16518 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
16519 }
16520 
16521 enum ButtonName:int {
16522 	Button1	=1,
16523 	Button2	=2,
16524 	Button3	=3,
16525 	Button4	=4,
16526 	Button5	=5
16527 }
16528 
16529 /* Notify modes */
16530 enum NotifyModes:int
16531 {
16532 	NotifyNormal		=0,
16533 	NotifyGrab			=1,
16534 	NotifyUngrab		=2,
16535 	NotifyWhileGrabbed	=3
16536 }
16537 enum NotifyHint = 1;	/* for MotionNotify events */
16538 
16539 /* Notify detail */
16540 enum NotifyDetail:int
16541 {
16542 	NotifyAncestor			=0,
16543 	NotifyVirtual			=1,
16544 	NotifyInferior			=2,
16545 	NotifyNonlinear			=3,
16546 	NotifyNonlinearVirtual	=4,
16547 	NotifyPointer			=5,
16548 	NotifyPointerRoot		=6,
16549 	NotifyDetailNone		=7
16550 }
16551 
16552 /* Visibility notify */
16553 
16554 enum VisibilityNotify:int
16555 {
16556 VisibilityUnobscured		=0,
16557 VisibilityPartiallyObscured	=1,
16558 VisibilityFullyObscured		=2
16559 }
16560 
16561 
16562 enum WindowStackingMethod:int
16563 {
16564 	Above		=0,
16565 	Below		=1,
16566 	TopIf		=2,
16567 	BottomIf	=3,
16568 	Opposite	=4
16569 }
16570 
16571 /* Circulation request */
16572 enum CirculationRequest:int
16573 {
16574 	PlaceOnTop		=0,
16575 	PlaceOnBottom	=1
16576 }
16577 
16578 enum PropertyNotification:int
16579 {
16580 	PropertyNewValue	=0,
16581 	PropertyDelete		=1
16582 }
16583 
16584 enum ColorMapNotification:int
16585 {
16586 	ColormapUninstalled	=0,
16587 	ColormapInstalled		=1
16588 }
16589 
16590 
16591 	struct _XPrivate {}
16592 	struct _XrmHashBucketRec {}
16593 
16594 	alias void* XPointer;
16595 	alias void* XExtData;
16596 
16597 	version( X86_64 ) {
16598 		alias ulong XID;
16599 		alias ulong arch_ulong;
16600 		alias long arch_long;
16601 	} else version (AArch64) {
16602 		alias ulong XID;
16603 		alias ulong arch_ulong;
16604 		alias long arch_long;
16605 	} else {
16606 		alias uint XID;
16607 		alias uint arch_ulong;
16608 		alias int arch_long;
16609 	}
16610 
16611 	alias XID Window;
16612 	alias XID Drawable;
16613 	alias XID Pixmap;
16614 
16615 	alias arch_ulong Atom;
16616 	alias int Bool;
16617 	alias Display XDisplay;
16618 
16619 	alias int ByteOrder;
16620 	alias arch_ulong Time;
16621 	alias void ScreenFormat;
16622 
16623 	struct XImage {
16624 		int width, height;			/* size of image */
16625 		int xoffset;				/* number of pixels offset in X direction */
16626 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
16627 		void *data;					/* pointer to image data */
16628 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
16629 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
16630 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
16631 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
16632 		int depth;					/* depth of image */
16633 		int bytes_per_line;			/* accelarator to next line */
16634 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
16635 		arch_ulong red_mask;	/* bits in z arrangment */
16636 		arch_ulong green_mask;
16637 		arch_ulong blue_mask;
16638 		XPointer obdata;			/* hook for the object routines to hang on */
16639 		static struct F {				/* image manipulation routines */
16640 			XImage* function(
16641 				XDisplay* 			/* display */,
16642 				Visual*				/* visual */,
16643 				uint				/* depth */,
16644 				int					/* format */,
16645 				int					/* offset */,
16646 				ubyte*				/* data */,
16647 				uint				/* width */,
16648 				uint				/* height */,
16649 				int					/* bitmap_pad */,
16650 				int					/* bytes_per_line */) create_image;
16651 			int function(XImage *) destroy_image;
16652 			arch_ulong function(XImage *, int, int) get_pixel;
16653 			int function(XImage *, int, int, arch_ulong) put_pixel;
16654 			XImage* function(XImage *, int, int, uint, uint) sub_image;
16655 			int function(XImage *, arch_long) add_pixel;
16656 		}
16657 		F f;
16658 	}
16659 	version(X86_64) static assert(XImage.sizeof == 136);
16660 	else version(X86) static assert(XImage.sizeof == 88);
16661 
16662 struct XCharStruct {
16663 	short       lbearing;       /* origin to left edge of raster */
16664 	short       rbearing;       /* origin to right edge of raster */
16665 	short       width;          /* advance to next char's origin */
16666 	short       ascent;         /* baseline to top edge of raster */
16667 	short       descent;        /* baseline to bottom edge of raster */
16668 	ushort attributes;  /* per char flags (not predefined) */
16669 }
16670 
16671 /*
16672  * To allow arbitrary information with fonts, there are additional properties
16673  * returned.
16674  */
16675 struct XFontProp {
16676 	Atom name;
16677 	arch_ulong card32;
16678 }
16679 
16680 alias Atom Font;
16681 
16682 struct XFontStruct {
16683 	XExtData *ext_data;           /* Hook for extension to hang data */
16684 	Font fid;                     /* Font ID for this font */
16685 	uint direction;           /* Direction the font is painted */
16686 	uint min_char_or_byte2;   /* First character */
16687 	uint max_char_or_byte2;   /* Last character */
16688 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
16689 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
16690 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
16691 	uint default_char;        /* Char to print for undefined character */
16692 	int n_properties;             /* How many properties there are */
16693 	XFontProp *properties;        /* Pointer to array of additional properties*/
16694 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
16695 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
16696 	XCharStruct *per_char;        /* first_char to last_char information */
16697 	int ascent;                   /* Max extent above baseline for spacing */
16698 	int descent;                  /* Max descent below baseline for spacing */
16699 }
16700 
16701 
16702 /*
16703  * Definitions of specific events.
16704  */
16705 struct XKeyEvent
16706 {
16707 	int type;			/* of event */
16708 	arch_ulong serial;		/* # of last request processed by server */
16709 	Bool send_event;	/* true if this came from a SendEvent request */
16710 	Display *display;	/* Display the event was read from */
16711 	Window window;	        /* "event" window it is reported relative to */
16712 	Window root;	        /* root window that the event occurred on */
16713 	Window subwindow;	/* child window */
16714 	Time time;		/* milliseconds */
16715 	int x, y;		/* pointer x, y coordinates in event window */
16716 	int x_root, y_root;	/* coordinates relative to root */
16717 	KeyOrButtonMask state;	/* key or button mask */
16718 	uint keycode;	/* detail */
16719 	Bool same_screen;	/* same screen flag */
16720 }
16721 version(X86_64) static assert(XKeyEvent.sizeof == 96);
16722 alias XKeyEvent XKeyPressedEvent;
16723 alias XKeyEvent XKeyReleasedEvent;
16724 
16725 struct XButtonEvent
16726 {
16727 	int type;		/* of event */
16728 	arch_ulong serial;	/* # of last request processed by server */
16729 	Bool send_event;	/* true if this came from a SendEvent request */
16730 	Display *display;	/* Display the event was read from */
16731 	Window window;	        /* "event" window it is reported relative to */
16732 	Window root;	        /* root window that the event occurred on */
16733 	Window subwindow;	/* child window */
16734 	Time time;		/* milliseconds */
16735 	int x, y;		/* pointer x, y coordinates in event window */
16736 	int x_root, y_root;	/* coordinates relative to root */
16737 	KeyOrButtonMask state;	/* key or button mask */
16738 	uint button;	/* detail */
16739 	Bool same_screen;	/* same screen flag */
16740 }
16741 alias XButtonEvent XButtonPressedEvent;
16742 alias XButtonEvent XButtonReleasedEvent;
16743 
16744 struct XMotionEvent{
16745 	int type;		/* of event */
16746 	arch_ulong serial;	/* # of last request processed by server */
16747 	Bool send_event;	/* true if this came from a SendEvent request */
16748 	Display *display;	/* Display the event was read from */
16749 	Window window;	        /* "event" window reported relative to */
16750 	Window root;	        /* root window that the event occurred on */
16751 	Window subwindow;	/* child window */
16752 	Time time;		/* milliseconds */
16753 	int x, y;		/* pointer x, y coordinates in event window */
16754 	int x_root, y_root;	/* coordinates relative to root */
16755 	KeyOrButtonMask state;	/* key or button mask */
16756 	byte is_hint;		/* detail */
16757 	Bool same_screen;	/* same screen flag */
16758 }
16759 alias XMotionEvent XPointerMovedEvent;
16760 
16761 struct XCrossingEvent{
16762 	int type;		/* of event */
16763 	arch_ulong serial;	/* # of last request processed by server */
16764 	Bool send_event;	/* true if this came from a SendEvent request */
16765 	Display *display;	/* Display the event was read from */
16766 	Window window;	        /* "event" window reported relative to */
16767 	Window root;	        /* root window that the event occurred on */
16768 	Window subwindow;	/* child window */
16769 	Time time;		/* milliseconds */
16770 	int x, y;		/* pointer x, y coordinates in event window */
16771 	int x_root, y_root;	/* coordinates relative to root */
16772 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
16773 	NotifyDetail detail;
16774 	/*
16775 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16776 	 * NotifyNonlinear,NotifyNonlinearVirtual
16777 	 */
16778 	Bool same_screen;	/* same screen flag */
16779 	Bool focus;		/* Boolean focus */
16780 	KeyOrButtonMask state;	/* key or button mask */
16781 }
16782 alias XCrossingEvent XEnterWindowEvent;
16783 alias XCrossingEvent XLeaveWindowEvent;
16784 
16785 struct XFocusChangeEvent{
16786 	int type;		/* FocusIn or FocusOut */
16787 	arch_ulong serial;	/* # of last request processed by server */
16788 	Bool send_event;	/* true if this came from a SendEvent request */
16789 	Display *display;	/* Display the event was read from */
16790 	Window window;		/* window of event */
16791 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
16792 				   NotifyGrab, NotifyUngrab */
16793 	NotifyDetail detail;
16794 	/*
16795 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
16796 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
16797 	 * NotifyPointerRoot, NotifyDetailNone
16798 	 */
16799 }
16800 alias XFocusChangeEvent XFocusInEvent;
16801 alias XFocusChangeEvent XFocusOutEvent;
16802 
16803 enum CWBackPixmap              = (1L<<0);
16804 enum CWBackPixel               = (1L<<1);
16805 enum CWBorderPixmap            = (1L<<2);
16806 enum CWBorderPixel             = (1L<<3);
16807 enum CWBitGravity              = (1L<<4);
16808 enum CWWinGravity              = (1L<<5);
16809 enum CWBackingStore            = (1L<<6);
16810 enum CWBackingPlanes           = (1L<<7);
16811 enum CWBackingPixel            = (1L<<8);
16812 enum CWOverrideRedirect        = (1L<<9);
16813 enum CWSaveUnder               = (1L<<10);
16814 enum CWEventMask               = (1L<<11);
16815 enum CWDontPropagate           = (1L<<12);
16816 enum CWColormap                = (1L<<13);
16817 enum CWCursor                  = (1L<<14);
16818 
16819 struct XWindowAttributes {
16820 	int x, y;			/* location of window */
16821 	int width, height;		/* width and height of window */
16822 	int border_width;		/* border width of window */
16823 	int depth;			/* depth of window */
16824 	Visual *visual;			/* the associated visual structure */
16825 	Window root;			/* root of screen containing window */
16826 	int class_;			/* InputOutput, InputOnly*/
16827 	int bit_gravity;		/* one of the bit gravity values */
16828 	int win_gravity;		/* one of the window gravity values */
16829 	int backing_store;		/* NotUseful, WhenMapped, Always */
16830 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
16831 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
16832 	Bool save_under;		/* boolean, should bits under be saved? */
16833 	Colormap colormap;		/* color map to be associated with window */
16834 	Bool map_installed;		/* boolean, is color map currently installed*/
16835 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
16836 	arch_long all_event_masks;		/* set of events all people have interest in*/
16837 	arch_long your_event_mask;		/* my event mask */
16838 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
16839 	Bool override_redirect;		/* boolean value for override-redirect */
16840 	Screen *screen;			/* back pointer to correct screen */
16841 }
16842 
16843 enum IsUnmapped = 0;
16844 enum IsUnviewable = 1;
16845 enum IsViewable = 2;
16846 
16847 struct XSetWindowAttributes {
16848 	Pixmap background_pixmap;/* background, None, or ParentRelative */
16849 	arch_ulong background_pixel;/* background pixel */
16850 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
16851 	arch_ulong border_pixel;/* border pixel value */
16852 	int bit_gravity;         /* one of bit gravity values */
16853 	int win_gravity;         /* one of the window gravity values */
16854 	int backing_store;       /* NotUseful, WhenMapped, Always */
16855 	arch_ulong backing_planes;/* planes to be preserved if possible */
16856 	arch_ulong backing_pixel;/* value to use in restoring planes */
16857 	Bool save_under;         /* should bits under be saved? (popups) */
16858 	arch_long event_mask;         /* set of events that should be saved */
16859 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
16860 	Bool override_redirect;  /* boolean value for override_redirect */
16861 	Colormap colormap;       /* color map to be associated with window */
16862 	Cursor cursor;           /* cursor to be displayed (or None) */
16863 }
16864 
16865 
16866 alias int Status;
16867 
16868 
16869 enum EventMask:int
16870 {
16871 	NoEventMask				=0,
16872 	KeyPressMask			=1<<0,
16873 	KeyReleaseMask			=1<<1,
16874 	ButtonPressMask			=1<<2,
16875 	ButtonReleaseMask		=1<<3,
16876 	EnterWindowMask			=1<<4,
16877 	LeaveWindowMask			=1<<5,
16878 	PointerMotionMask		=1<<6,
16879 	PointerMotionHintMask	=1<<7,
16880 	Button1MotionMask		=1<<8,
16881 	Button2MotionMask		=1<<9,
16882 	Button3MotionMask		=1<<10,
16883 	Button4MotionMask		=1<<11,
16884 	Button5MotionMask		=1<<12,
16885 	ButtonMotionMask		=1<<13,
16886 	KeymapStateMask		=1<<14,
16887 	ExposureMask			=1<<15,
16888 	VisibilityChangeMask	=1<<16,
16889 	StructureNotifyMask		=1<<17,
16890 	ResizeRedirectMask		=1<<18,
16891 	SubstructureNotifyMask	=1<<19,
16892 	SubstructureRedirectMask=1<<20,
16893 	FocusChangeMask			=1<<21,
16894 	PropertyChangeMask		=1<<22,
16895 	ColormapChangeMask		=1<<23,
16896 	OwnerGrabButtonMask		=1<<24
16897 }
16898 
16899 struct MwmHints {
16900 	c_ulong flags;
16901 	c_ulong functions;
16902 	c_ulong decorations;
16903 	c_long input_mode;
16904 	c_ulong status;
16905 }
16906 
16907 enum {
16908 	MWM_HINTS_FUNCTIONS = (1L << 0),
16909 	MWM_HINTS_DECORATIONS =  (1L << 1),
16910 
16911 	MWM_FUNC_ALL = (1L << 0),
16912 	MWM_FUNC_RESIZE = (1L << 1),
16913 	MWM_FUNC_MOVE = (1L << 2),
16914 	MWM_FUNC_MINIMIZE = (1L << 3),
16915 	MWM_FUNC_MAXIMIZE = (1L << 4),
16916 	MWM_FUNC_CLOSE = (1L << 5),
16917 
16918 	MWM_DECOR_ALL = (1L << 0),
16919 	MWM_DECOR_BORDER = (1L << 1),
16920 	MWM_DECOR_RESIZEH = (1L << 2),
16921 	MWM_DECOR_TITLE = (1L << 3),
16922 	MWM_DECOR_MENU = (1L << 4),
16923 	MWM_DECOR_MINIMIZE = (1L << 5),
16924 	MWM_DECOR_MAXIMIZE = (1L << 6),
16925 }
16926 
16927 import core.stdc.config : c_long, c_ulong;
16928 
16929 	/* Size hints mask bits */
16930 
16931 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
16932 	enum   USSize      = (1L << 1)          /* user specified width, height */;
16933 	enum   PPosition   = (1L << 2)          /* program specified position */;
16934 	enum   PSize       = (1L << 3)          /* program specified size */;
16935 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
16936 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
16937 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
16938 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
16939 	enum   PBaseSize   = (1L << 8);
16940 	enum   PWinGravity = (1L << 9);
16941 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
16942 	struct XSizeHints {
16943 		arch_long flags;         /* marks which fields in this structure are defined */
16944 		int x, y;           /* Obsolete */
16945 		int width, height;  /* Obsolete */
16946 		int min_width, min_height;
16947 		int max_width, max_height;
16948 		int width_inc, height_inc;
16949 		struct Aspect {
16950 			int x;       /* numerator */
16951 			int y;       /* denominator */
16952 		}
16953 
16954 		Aspect min_aspect;
16955 		Aspect max_aspect;
16956 		int base_width, base_height;
16957 		int win_gravity;
16958 		/* this structure may be extended in the future */
16959 	}
16960 
16961 
16962 
16963 enum EventType:int
16964 {
16965 	KeyPress			=2,
16966 	KeyRelease			=3,
16967 	ButtonPress			=4,
16968 	ButtonRelease		=5,
16969 	MotionNotify		=6,
16970 	EnterNotify			=7,
16971 	LeaveNotify			=8,
16972 	FocusIn				=9,
16973 	FocusOut			=10,
16974 	KeymapNotify		=11,
16975 	Expose				=12,
16976 	GraphicsExpose		=13,
16977 	NoExpose			=14,
16978 	VisibilityNotify	=15,
16979 	CreateNotify		=16,
16980 	DestroyNotify		=17,
16981 	UnmapNotify		=18,
16982 	MapNotify			=19,
16983 	MapRequest			=20,
16984 	ReparentNotify		=21,
16985 	ConfigureNotify		=22,
16986 	ConfigureRequest	=23,
16987 	GravityNotify		=24,
16988 	ResizeRequest		=25,
16989 	CirculateNotify		=26,
16990 	CirculateRequest	=27,
16991 	PropertyNotify		=28,
16992 	SelectionClear		=29,
16993 	SelectionRequest	=30,
16994 	SelectionNotify		=31,
16995 	ColormapNotify		=32,
16996 	ClientMessage		=33,
16997 	MappingNotify		=34,
16998 	LASTEvent			=35	/* must be bigger than any event # */
16999 }
17000 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
17001 struct XKeymapEvent
17002 {
17003 	int type;
17004 	arch_ulong serial;	/* # of last request processed by server */
17005 	Bool send_event;	/* true if this came from a SendEvent request */
17006 	Display *display;	/* Display the event was read from */
17007 	Window window;
17008 	byte[32] key_vector;
17009 }
17010 
17011 struct XExposeEvent
17012 {
17013 	int type;
17014 	arch_ulong serial;	/* # of last request processed by server */
17015 	Bool send_event;	/* true if this came from a SendEvent request */
17016 	Display *display;	/* Display the event was read from */
17017 	Window window;
17018 	int x, y;
17019 	int width, height;
17020 	int count;		/* if non-zero, at least this many more */
17021 }
17022 
17023 struct XGraphicsExposeEvent{
17024 	int type;
17025 	arch_ulong serial;	/* # of last request processed by server */
17026 	Bool send_event;	/* true if this came from a SendEvent request */
17027 	Display *display;	/* Display the event was read from */
17028 	Drawable drawable;
17029 	int x, y;
17030 	int width, height;
17031 	int count;		/* if non-zero, at least this many more */
17032 	int major_code;		/* core is CopyArea or CopyPlane */
17033 	int minor_code;		/* not defined in the core */
17034 }
17035 
17036 struct XNoExposeEvent{
17037 	int type;
17038 	arch_ulong serial;	/* # of last request processed by server */
17039 	Bool send_event;	/* true if this came from a SendEvent request */
17040 	Display *display;	/* Display the event was read from */
17041 	Drawable drawable;
17042 	int major_code;		/* core is CopyArea or CopyPlane */
17043 	int minor_code;		/* not defined in the core */
17044 }
17045 
17046 struct XVisibilityEvent{
17047 	int type;
17048 	arch_ulong serial;	/* # of last request processed by server */
17049 	Bool send_event;	/* true if this came from a SendEvent request */
17050 	Display *display;	/* Display the event was read from */
17051 	Window window;
17052 	VisibilityNotify state;		/* Visibility state */
17053 }
17054 
17055 struct XCreateWindowEvent{
17056 	int type;
17057 	arch_ulong serial;	/* # of last request processed by server */
17058 	Bool send_event;	/* true if this came from a SendEvent request */
17059 	Display *display;	/* Display the event was read from */
17060 	Window parent;		/* parent of the window */
17061 	Window window;		/* window id of window created */
17062 	int x, y;		/* window location */
17063 	int width, height;	/* size of window */
17064 	int border_width;	/* border width */
17065 	Bool override_redirect;	/* creation should be overridden */
17066 }
17067 
17068 struct XDestroyWindowEvent
17069 {
17070 	int type;
17071 	arch_ulong serial;		/* # of last request processed by server */
17072 	Bool send_event;	/* true if this came from a SendEvent request */
17073 	Display *display;	/* Display the event was read from */
17074 	Window event;
17075 	Window window;
17076 }
17077 
17078 struct XUnmapEvent
17079 {
17080 	int type;
17081 	arch_ulong serial;		/* # of last request processed by server */
17082 	Bool send_event;	/* true if this came from a SendEvent request */
17083 	Display *display;	/* Display the event was read from */
17084 	Window event;
17085 	Window window;
17086 	Bool from_configure;
17087 }
17088 
17089 struct XMapEvent
17090 {
17091 	int type;
17092 	arch_ulong serial;		/* # of last request processed by server */
17093 	Bool send_event;	/* true if this came from a SendEvent request */
17094 	Display *display;	/* Display the event was read from */
17095 	Window event;
17096 	Window window;
17097 	Bool override_redirect;	/* Boolean, is override set... */
17098 }
17099 
17100 struct XMapRequestEvent
17101 {
17102 	int type;
17103 	arch_ulong serial;	/* # of last request processed by server */
17104 	Bool send_event;	/* true if this came from a SendEvent request */
17105 	Display *display;	/* Display the event was read from */
17106 	Window parent;
17107 	Window window;
17108 }
17109 
17110 struct XReparentEvent
17111 {
17112 	int type;
17113 	arch_ulong serial;	/* # of last request processed by server */
17114 	Bool send_event;	/* true if this came from a SendEvent request */
17115 	Display *display;	/* Display the event was read from */
17116 	Window event;
17117 	Window window;
17118 	Window parent;
17119 	int x, y;
17120 	Bool override_redirect;
17121 }
17122 
17123 struct XConfigureEvent
17124 {
17125 	int type;
17126 	arch_ulong serial;	/* # of last request processed by server */
17127 	Bool send_event;	/* true if this came from a SendEvent request */
17128 	Display *display;	/* Display the event was read from */
17129 	Window event;
17130 	Window window;
17131 	int x, y;
17132 	int width, height;
17133 	int border_width;
17134 	Window above;
17135 	Bool override_redirect;
17136 }
17137 
17138 struct XGravityEvent
17139 {
17140 	int type;
17141 	arch_ulong serial;	/* # of last request processed by server */
17142 	Bool send_event;	/* true if this came from a SendEvent request */
17143 	Display *display;	/* Display the event was read from */
17144 	Window event;
17145 	Window window;
17146 	int x, y;
17147 }
17148 
17149 struct XResizeRequestEvent
17150 {
17151 	int type;
17152 	arch_ulong serial;	/* # of last request processed by server */
17153 	Bool send_event;	/* true if this came from a SendEvent request */
17154 	Display *display;	/* Display the event was read from */
17155 	Window window;
17156 	int width, height;
17157 }
17158 
17159 struct  XConfigureRequestEvent
17160 {
17161 	int type;
17162 	arch_ulong serial;	/* # of last request processed by server */
17163 	Bool send_event;	/* true if this came from a SendEvent request */
17164 	Display *display;	/* Display the event was read from */
17165 	Window parent;
17166 	Window window;
17167 	int x, y;
17168 	int width, height;
17169 	int border_width;
17170 	Window above;
17171 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
17172 	arch_ulong value_mask;
17173 }
17174 
17175 struct XCirculateEvent
17176 {
17177 	int type;
17178 	arch_ulong serial;	/* # of last request processed by server */
17179 	Bool send_event;	/* true if this came from a SendEvent request */
17180 	Display *display;	/* Display the event was read from */
17181 	Window event;
17182 	Window window;
17183 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17184 }
17185 
17186 struct XCirculateRequestEvent
17187 {
17188 	int type;
17189 	arch_ulong serial;	/* # of last request processed by server */
17190 	Bool send_event;	/* true if this came from a SendEvent request */
17191 	Display *display;	/* Display the event was read from */
17192 	Window parent;
17193 	Window window;
17194 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
17195 }
17196 
17197 struct XPropertyEvent
17198 {
17199 	int type;
17200 	arch_ulong serial;	/* # of last request processed by server */
17201 	Bool send_event;	/* true if this came from a SendEvent request */
17202 	Display *display;	/* Display the event was read from */
17203 	Window window;
17204 	Atom atom;
17205 	Time time;
17206 	PropertyNotification state;		/* NewValue, Deleted */
17207 }
17208 
17209 struct XSelectionClearEvent
17210 {
17211 	int type;
17212 	arch_ulong serial;	/* # of last request processed by server */
17213 	Bool send_event;	/* true if this came from a SendEvent request */
17214 	Display *display;	/* Display the event was read from */
17215 	Window window;
17216 	Atom selection;
17217 	Time time;
17218 }
17219 
17220 struct XSelectionRequestEvent
17221 {
17222 	int type;
17223 	arch_ulong serial;	/* # of last request processed by server */
17224 	Bool send_event;	/* true if this came from a SendEvent request */
17225 	Display *display;	/* Display the event was read from */
17226 	Window owner;
17227 	Window requestor;
17228 	Atom selection;
17229 	Atom target;
17230 	Atom property;
17231 	Time time;
17232 }
17233 
17234 struct XSelectionEvent
17235 {
17236 	int type;
17237 	arch_ulong serial;	/* # of last request processed by server */
17238 	Bool send_event;	/* true if this came from a SendEvent request */
17239 	Display *display;	/* Display the event was read from */
17240 	Window requestor;
17241 	Atom selection;
17242 	Atom target;
17243 	Atom property;		/* ATOM or None */
17244 	Time time;
17245 }
17246 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
17247 
17248 struct XColormapEvent
17249 {
17250 	int type;
17251 	arch_ulong serial;	/* # of last request processed by server */
17252 	Bool send_event;	/* true if this came from a SendEvent request */
17253 	Display *display;	/* Display the event was read from */
17254 	Window window;
17255 	Colormap colormap;	/* COLORMAP or None */
17256 	Bool new_;		/* C++ */
17257 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
17258 }
17259 version(X86_64) static assert(XColormapEvent.sizeof == 56);
17260 
17261 struct XClientMessageEvent
17262 {
17263 	int type;
17264 	arch_ulong serial;	/* # of last request processed by server */
17265 	Bool send_event;	/* true if this came from a SendEvent request */
17266 	Display *display;	/* Display the event was read from */
17267 	Window window;
17268 	Atom message_type;
17269 	int format;
17270 	union Data{
17271 		byte[20] b;
17272 		short[10] s;
17273 		arch_ulong[5] l;
17274 	}
17275 	Data data;
17276 
17277 }
17278 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
17279 
17280 struct XMappingEvent
17281 {
17282 	int type;
17283 	arch_ulong serial;	/* # of last request processed by server */
17284 	Bool send_event;	/* true if this came from a SendEvent request */
17285 	Display *display;	/* Display the event was read from */
17286 	Window window;		/* unused */
17287 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
17288 				   MappingPointer */
17289 	int first_keycode;	/* first keycode */
17290 	int count;		/* defines range of change w. first_keycode*/
17291 }
17292 
17293 struct XErrorEvent
17294 {
17295 	int type;
17296 	Display *display;	/* Display the event was read from */
17297 	XID resourceid;		/* resource id */
17298 	arch_ulong serial;	/* serial number of failed request */
17299 	ubyte error_code;	/* error code of failed request */
17300 	ubyte request_code;	/* Major op-code of failed request */
17301 	ubyte minor_code;	/* Minor op-code of failed request */
17302 }
17303 
17304 struct XAnyEvent
17305 {
17306 	int type;
17307 	arch_ulong serial;	/* # of last request processed by server */
17308 	Bool send_event;	/* true if this came from a SendEvent request */
17309 	Display *display;/* Display the event was read from */
17310 	Window window;	/* window on which event was requested in event mask */
17311 }
17312 
17313 union XEvent{
17314 	int type;		/* must not be changed; first element */
17315 	XAnyEvent xany;
17316 	XKeyEvent xkey;
17317 	XButtonEvent xbutton;
17318 	XMotionEvent xmotion;
17319 	XCrossingEvent xcrossing;
17320 	XFocusChangeEvent xfocus;
17321 	XExposeEvent xexpose;
17322 	XGraphicsExposeEvent xgraphicsexpose;
17323 	XNoExposeEvent xnoexpose;
17324 	XVisibilityEvent xvisibility;
17325 	XCreateWindowEvent xcreatewindow;
17326 	XDestroyWindowEvent xdestroywindow;
17327 	XUnmapEvent xunmap;
17328 	XMapEvent xmap;
17329 	XMapRequestEvent xmaprequest;
17330 	XReparentEvent xreparent;
17331 	XConfigureEvent xconfigure;
17332 	XGravityEvent xgravity;
17333 	XResizeRequestEvent xresizerequest;
17334 	XConfigureRequestEvent xconfigurerequest;
17335 	XCirculateEvent xcirculate;
17336 	XCirculateRequestEvent xcirculaterequest;
17337 	XPropertyEvent xproperty;
17338 	XSelectionClearEvent xselectionclear;
17339 	XSelectionRequestEvent xselectionrequest;
17340 	XSelectionEvent xselection;
17341 	XColormapEvent xcolormap;
17342 	XClientMessageEvent xclient;
17343 	XMappingEvent xmapping;
17344 	XErrorEvent xerror;
17345 	XKeymapEvent xkeymap;
17346 	arch_ulong[24] pad;
17347 }
17348 
17349 
17350 	struct Display {
17351 		XExtData *ext_data;	/* hook for extension to hang data */
17352 		_XPrivate *private1;
17353 		int fd;			/* Network socket. */
17354 		int private2;
17355 		int proto_major_version;/* major version of server's X protocol */
17356 		int proto_minor_version;/* minor version of servers X protocol */
17357 		char *vendor;		/* vendor of the server hardware */
17358 	    	XID private3;
17359 		XID private4;
17360 		XID private5;
17361 		int private6;
17362 		XID function(Display*)resource_alloc;/* allocator function */
17363 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
17364 		int bitmap_unit;	/* padding and data requirements */
17365 		int bitmap_pad;		/* padding requirements on bitmaps */
17366 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
17367 		int nformats;		/* number of pixmap formats in list */
17368 		ScreenFormat *pixmap_format;	/* pixmap format list */
17369 		int private8;
17370 		int release;		/* release of the server */
17371 		_XPrivate *private9;
17372 		_XPrivate *private10;
17373 		int qlen;		/* Length of input event queue */
17374 		arch_ulong last_request_read; /* seq number of last event read */
17375 		arch_ulong request;	/* sequence number of last request. */
17376 		XPointer private11;
17377 		XPointer private12;
17378 		XPointer private13;
17379 		XPointer private14;
17380 		uint max_request_size; /* maximum number 32 bit words in request*/
17381 		_XrmHashBucketRec *db;
17382 		int function  (Display*)private15;
17383 		char *display_name;	/* "host:display" string used on this connect*/
17384 		int default_screen;	/* default screen for operations */
17385 		int nscreens;		/* number of screens on this server*/
17386 		Screen *screens;	/* pointer to list of screens */
17387 		arch_ulong motion_buffer;	/* size of motion buffer */
17388 		arch_ulong private16;
17389 		int min_keycode;	/* minimum defined keycode */
17390 		int max_keycode;	/* maximum defined keycode */
17391 		XPointer private17;
17392 		XPointer private18;
17393 		int private19;
17394 		byte *xdefaults;	/* contents of defaults from server */
17395 		/* there is more to this structure, but it is private to Xlib */
17396 	}
17397 
17398 	// I got these numbers from a C program as a sanity test
17399 	version(X86_64) {
17400 		static assert(Display.sizeof == 296);
17401 		static assert(XPointer.sizeof == 8);
17402 		static assert(XErrorEvent.sizeof == 40);
17403 		static assert(XAnyEvent.sizeof == 40);
17404 		static assert(XMappingEvent.sizeof == 56);
17405 		static assert(XEvent.sizeof == 192);
17406     	} else version (AArch64) {
17407         	// omit check for aarch64
17408 	} else {
17409 		static assert(Display.sizeof == 176);
17410 		static assert(XPointer.sizeof == 4);
17411 		static assert(XEvent.sizeof == 96);
17412 	}
17413 
17414 struct Depth
17415 {
17416 	int depth;		/* this depth (Z) of the depth */
17417 	int nvisuals;		/* number of Visual types at this depth */
17418 	Visual *visuals;	/* list of visuals possible at this depth */
17419 }
17420 
17421 alias void* GC;
17422 alias c_ulong VisualID;
17423 alias XID Colormap;
17424 alias XID Cursor;
17425 alias XID KeySym;
17426 alias uint KeyCode;
17427 enum None = 0;
17428 }
17429 
17430 version(without_opengl) {}
17431 else {
17432 extern(C) nothrow @nogc {
17433 
17434 
17435 static if(!SdpyIsUsingIVGLBinds) {
17436 enum GLX_USE_GL=            1;       /* support GLX rendering */
17437 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
17438 enum GLX_LEVEL=             3;       /* level in plane stacking */
17439 enum GLX_RGBA=              4;       /* true if RGBA mode */
17440 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
17441 enum GLX_STEREO=            6;       /* stereo buffering supported */
17442 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
17443 enum GLX_RED_SIZE=          8;       /* number of red component bits */
17444 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
17445 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
17446 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
17447 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
17448 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
17449 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
17450 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
17451 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
17452 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
17453 
17454 
17455 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
17456 
17457 
17458 
17459 enum GL_TRUE = 1;
17460 enum GL_FALSE = 0;
17461 }
17462 
17463 alias XID GLXContextID;
17464 alias XID GLXPixmap;
17465 alias XID GLXDrawable;
17466 alias XID GLXPbuffer;
17467 alias XID GLXWindow;
17468 alias XID GLXFBConfigID;
17469 alias void* GLXContext;
17470 
17471 }
17472 }
17473 
17474 enum AllocNone = 0;
17475 
17476 extern(C) {
17477 	/* WARNING, this type not in Xlib spec */
17478 	extern(C) alias XIOErrorHandler = int function (Display* display);
17479 }
17480 
17481 extern(C) nothrow
17482 alias XErrorHandler = int function(Display*, XErrorEvent*);
17483 
17484 extern(C) nothrow @nogc {
17485 struct Screen{
17486 	XExtData *ext_data;		/* hook for extension to hang data */
17487 	Display *display;		/* back pointer to display structure */
17488 	Window root;			/* Root window id. */
17489 	int width, height;		/* width and height of screen */
17490 	int mwidth, mheight;	/* width and height of  in millimeters */
17491 	int ndepths;			/* number of depths possible */
17492 	Depth *depths;			/* list of allowable depths on the screen */
17493 	int root_depth;			/* bits per pixel */
17494 	Visual *root_visual;	/* root visual */
17495 	GC default_gc;			/* GC for the root root visual */
17496 	Colormap cmap;			/* default color map */
17497 	uint white_pixel;
17498 	uint black_pixel;		/* White and Black pixel values */
17499 	int max_maps, min_maps;	/* max and min color maps */
17500 	int backing_store;		/* Never, WhenMapped, Always */
17501 	bool save_unders;
17502 	int root_input_mask;	/* initial root input mask */
17503 }
17504 
17505 struct Visual
17506 {
17507 	XExtData *ext_data;	/* hook for extension to hang data */
17508 	VisualID visualid;	/* visual id of this visual */
17509 	int class_;			/* class of screen (monochrome, etc.) */
17510 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
17511 	int bits_per_rgb;	/* log base 2 of distinct color values */
17512 	int map_entries;	/* color map entries */
17513 }
17514 
17515 	alias Display* _XPrivDisplay;
17516 
17517 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
17518 		assert(dpy !is null);
17519 		return &dpy.screens[scr];
17520 	}
17521 
17522 	extern(D) Window RootWindow(Display *dpy,int scr) {
17523 		return ScreenOfDisplay(dpy,scr).root;
17524 	}
17525 
17526 	struct XWMHints {
17527 		arch_long flags;
17528 		Bool input;
17529 		int initial_state;
17530 		Pixmap icon_pixmap;
17531 		Window icon_window;
17532 		int icon_x, icon_y;
17533 		Pixmap icon_mask;
17534 		XID window_group;
17535 	}
17536 
17537 	struct XClassHint {
17538 		char* res_name;
17539 		char* res_class;
17540 	}
17541 
17542 	extern(D) int DefaultScreen(Display *dpy) {
17543 		return dpy.default_screen;
17544 	}
17545 
17546 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
17547 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
17548 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
17549 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
17550 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
17551 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
17552 
17553 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
17554 
17555 	enum int AnyPropertyType = 0;
17556 	enum int Success = 0;
17557 
17558 	enum int RevertToNone = None;
17559 	enum int PointerRoot = 1;
17560 	enum Time CurrentTime = 0;
17561 	enum int RevertToPointerRoot = PointerRoot;
17562 	enum int RevertToParent = 2;
17563 
17564 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
17565 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
17566 	}
17567 
17568 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
17569 		return ScreenOfDisplay(dpy,scr).root_visual;
17570 	}
17571 
17572 	extern(D) GC DefaultGC(Display *dpy,int scr) {
17573 		return ScreenOfDisplay(dpy,scr).default_gc;
17574 	}
17575 
17576 	extern(D) uint BlackPixel(Display *dpy,int scr) {
17577 		return ScreenOfDisplay(dpy,scr).black_pixel;
17578 	}
17579 
17580 	extern(D) uint WhitePixel(Display *dpy,int scr) {
17581 		return ScreenOfDisplay(dpy,scr).white_pixel;
17582 	}
17583 
17584 	alias void* XFontSet; // i think
17585 	struct XmbTextItem {
17586 		char* chars;
17587 		int nchars;
17588 		int delta;
17589 		XFontSet font_set;
17590 	}
17591 
17592 	struct XTextItem {
17593 		char* chars;
17594 		int nchars;
17595 		int delta;
17596 		Font font;
17597 	}
17598 
17599 	enum {
17600 		GXclear        = 0x0, /* 0 */
17601 		GXand          = 0x1, /* src AND dst */
17602 		GXandReverse   = 0x2, /* src AND NOT dst */
17603 		GXcopy         = 0x3, /* src */
17604 		GXandInverted  = 0x4, /* NOT src AND dst */
17605 		GXnoop         = 0x5, /* dst */
17606 		GXxor          = 0x6, /* src XOR dst */
17607 		GXor           = 0x7, /* src OR dst */
17608 		GXnor          = 0x8, /* NOT src AND NOT dst */
17609 		GXequiv        = 0x9, /* NOT src XOR dst */
17610 		GXinvert       = 0xa, /* NOT dst */
17611 		GXorReverse    = 0xb, /* src OR NOT dst */
17612 		GXcopyInverted = 0xc, /* NOT src */
17613 		GXorInverted   = 0xd, /* NOT src OR dst */
17614 		GXnand         = 0xe, /* NOT src OR NOT dst */
17615 		GXset          = 0xf, /* 1 */
17616 	}
17617 	enum QueueMode : int {
17618 		QueuedAlready,
17619 		QueuedAfterReading,
17620 		QueuedAfterFlush
17621 	}
17622 
17623 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
17624 
17625 	struct XPoint {
17626 		short x;
17627 		short y;
17628 	}
17629 
17630 	enum CoordMode:int {
17631 		CoordModeOrigin = 0,
17632 		CoordModePrevious = 1
17633 	}
17634 
17635 	enum PolygonShape:int {
17636 		Complex = 0,
17637 		Nonconvex = 1,
17638 		Convex = 2
17639 	}
17640 
17641 	struct XTextProperty {
17642 		const(char)* value;		/* same as Property routines */
17643 		Atom encoding;			/* prop type */
17644 		int format;				/* prop data format: 8, 16, or 32 */
17645 		arch_ulong nitems;		/* number of data items in value */
17646 	}
17647 
17648 	version( X86_64 ) {
17649 		static assert(XTextProperty.sizeof == 32);
17650 	}
17651 
17652 
17653 	struct XGCValues {
17654 		int function_;           /* logical operation */
17655 		arch_ulong plane_mask;/* plane mask */
17656 		arch_ulong foreground;/* foreground pixel */
17657 		arch_ulong background;/* background pixel */
17658 		int line_width;         /* line width */
17659 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
17660 		int cap_style;          /* CapNotLast, CapButt,
17661 					   CapRound, CapProjecting */
17662 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
17663 		int fill_style;         /* FillSolid, FillTiled,
17664 					   FillStippled, FillOpaeueStippled */
17665 		int fill_rule;          /* EvenOddRule, WindingRule */
17666 		int arc_mode;           /* ArcChord, ArcPieSlice */
17667 		Pixmap tile;            /* tile pixmap for tiling operations */
17668 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
17669 		int ts_x_origin;        /* offset for tile or stipple operations */
17670 		int ts_y_origin;
17671 		Font font;              /* default text font for text operations */
17672 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
17673 		Bool graphics_exposures;/* boolean, should exposures be generated */
17674 		int clip_x_origin;      /* origin for clipping */
17675 		int clip_y_origin;
17676 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
17677 		int dash_offset;        /* patterned/dashed line information */
17678 		char dashes;
17679 	}
17680 
17681 	struct XColor {
17682 		arch_ulong pixel;
17683 		ushort red, green, blue;
17684 		byte flags;
17685 		byte pad;
17686 	}
17687 
17688 	struct XRectangle {
17689 		short x;
17690 		short y;
17691 		ushort width;
17692 		ushort height;
17693 	}
17694 
17695 	enum ClipByChildren = 0;
17696 	enum IncludeInferiors = 1;
17697 
17698 	enum Atom XA_PRIMARY = 1;
17699 	enum Atom XA_SECONDARY = 2;
17700 	enum Atom XA_STRING = 31;
17701 	enum Atom XA_CARDINAL = 6;
17702 	enum Atom XA_WM_NAME = 39;
17703 	enum Atom XA_ATOM = 4;
17704 	enum Atom XA_WINDOW = 33;
17705 	enum Atom XA_WM_HINTS = 35;
17706 	enum int PropModeAppend = 2;
17707 	enum int PropModeReplace = 0;
17708 	enum int PropModePrepend = 1;
17709 
17710 	enum int CopyFromParent = 0;
17711 	enum int InputOutput = 1;
17712 
17713 	// XWMHints
17714 	enum InputHint = 1 << 0;
17715 	enum StateHint = 1 << 1;
17716 	enum IconPixmapHint = (1L << 2);
17717 	enum IconWindowHint = (1L << 3);
17718 	enum IconPositionHint = (1L << 4);
17719 	enum IconMaskHint = (1L << 5);
17720 	enum WindowGroupHint = (1L << 6);
17721 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
17722 	enum XUrgencyHint = (1L << 8);
17723 
17724 	// GC Components
17725 	enum GCFunction           =   (1L<<0);
17726 	enum GCPlaneMask         =    (1L<<1);
17727 	enum GCForeground       =     (1L<<2);
17728 	enum GCBackground      =      (1L<<3);
17729 	enum GCLineWidth      =       (1L<<4);
17730 	enum GCLineStyle     =        (1L<<5);
17731 	enum GCCapStyle     =         (1L<<6);
17732 	enum GCJoinStyle   =          (1L<<7);
17733 	enum GCFillStyle  =           (1L<<8);
17734 	enum GCFillRule  =            (1L<<9);
17735 	enum GCTile     =             (1L<<10);
17736 	enum GCStipple           =    (1L<<11);
17737 	enum GCTileStipXOrigin  =     (1L<<12);
17738 	enum GCTileStipYOrigin =      (1L<<13);
17739 	enum GCFont               =   (1L<<14);
17740 	enum GCSubwindowMode     =    (1L<<15);
17741 	enum GCGraphicsExposures=     (1L<<16);
17742 	enum GCClipXOrigin     =      (1L<<17);
17743 	enum GCClipYOrigin    =       (1L<<18);
17744 	enum GCClipMask      =        (1L<<19);
17745 	enum GCDashOffset   =         (1L<<20);
17746 	enum GCDashList    =          (1L<<21);
17747 	enum GCArcMode    =           (1L<<22);
17748 	enum GCLastBit   =            22;
17749 
17750 
17751 	enum int WithdrawnState = 0;
17752 	enum int NormalState = 1;
17753 	enum int IconicState = 3;
17754 
17755 }
17756 } else version (OSXCocoa) {
17757 
17758 /+
17759 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
17760 +/
17761 
17762 	private __gshared AppDelegate globalAppDelegate;
17763 
17764 	extern(Objective-C)
17765 	class AppDelegate : NSObject, NSApplicationDelegate {
17766 		override static AppDelegate alloc() @selector("alloc");
17767 
17768 
17769 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
17770 			SimpleWindow.processAllCustomEvents();
17771 		}
17772 
17773 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
17774 			immutable style = NSWindowStyleMask.resizable |
17775 				NSWindowStyleMask.closable |
17776 				NSWindowStyleMask.miniaturizable |
17777 				NSWindowStyleMask.titled;
17778 
17779 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
17780 
17781 			{
17782 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
17783 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
17784 				mainMenu.setSubmenu(menu, item);
17785 
17786 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
17787 				newItem.target = NSApp;
17788 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
17789 				newItem2.target = NSApp;
17790 			}
17791 
17792 			{
17793 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
17794 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
17795 				mainMenu.setSubmenu(menu, item);
17796 
17797 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
17798 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
17799 			}
17800 
17801 
17802 			NSApp.menu = mainMenu;
17803 
17804 
17805 			// auto controller = ViewController.alloc.init;
17806 
17807 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
17808 
17809 			/+
17810 			this.window = window;
17811 			this.controller = controller;
17812 			+/
17813 		}
17814 
17815 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
17816 			NSApplication.shared_.activateIgnoringOtherApps(true);
17817 		}
17818 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
17819 			return true;
17820 		}
17821 	}
17822 
17823 	extern(Objective-C)
17824 	class SDWindowDelegate : NSObject, NSWindowDelegate {
17825 		override static SDWindowDelegate alloc() @selector("alloc");
17826 		override SDWindowDelegate init() @selector("init");
17827 
17828 		SimpleWindow simpleWindow;
17829 
17830 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
17831 			auto window = cast(void*) notification.object;
17832 
17833 			// FIXME: do i need to release it?
17834 			SimpleWindow.nativeMapping.remove(window);
17835 		}
17836 
17837 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
17838 			if(simpleWindow.windowResized) {
17839 				// FIXME: automaticallyScaleIfPossible behaviors
17840 
17841 				simpleWindow._width = cast(int) frameSize.width;
17842 				simpleWindow._height = cast(int) frameSize.height;
17843 
17844 				simpleWindow.view.setFrameSize(frameSize);
17845 
17846 				/+
17847 				auto size = simpleWindow.view.frame.size;
17848 				writeln(cast(int) size.width, "x", cast(int) size.height);
17849 				+/
17850 
17851 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
17852 
17853 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
17854 
17855 				// simpleWindow.view.setNeedsDisplay(true);
17856 			}
17857 
17858 			return frameSize;
17859 		}
17860 
17861 		/+
17862 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
17863 			if(simpleWindow.windowResized) {
17864 				auto window = simpleWindow.window;
17865 				auto rect = window.contentRectForFrameRect(window.frame);
17866 				import std.stdio; writeln(window.frame.size);
17867 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
17868 			}
17869 		}
17870 		+/
17871 	}
17872 
17873 	extern(Objective-C)
17874 	class SDGraphicsView : NSView {
17875 		SimpleWindow simpleWindow;
17876 
17877 		override static SDGraphicsView alloc() @selector("alloc");
17878 		override SDGraphicsView init() @selector("init") {
17879 			super.init();
17880 			return this;
17881 		}
17882 
17883 		override void drawRect(NSRect rect) @selector("drawRect:") {
17884 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
17885 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
17886 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
17887 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
17888 			CGImageRelease(cgImage);
17889 		}
17890 
17891 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
17892 			MouseEvent me;
17893 			me.type = type;
17894 
17895 			auto pos = event.locationInWindow;
17896 
17897 			me.x = cast(int) pos.x;
17898 			me.y = cast(int) (simpleWindow.height - pos.y);
17899 
17900 			me.dx = 0; // FIXME
17901 			me.dy = 0; // FIXME
17902 
17903 			me.button = button;
17904 			me.modifierState = cast(uint) event.modifierFlags;
17905 			me.window = simpleWindow;
17906 
17907 			me.doubleClick = false;
17908 
17909 			if(simpleWindow && simpleWindow.handleMouseEvent)
17910 				simpleWindow.handleMouseEvent(me);
17911 		}
17912 
17913 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
17914 			// writeln(event.pressedMouseButtons);
17915 
17916 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17917 		}
17918 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
17919 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
17920 		}
17921 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
17922 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
17923 		}
17924 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
17925 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
17926 		}
17927 		/+
17928 			// FIXME
17929 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
17930 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17931 		}
17932 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
17933 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
17934 		}
17935 		+/
17936 
17937 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
17938 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
17939 		}
17940 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
17941 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
17942 		}
17943 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
17944 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
17945 		}
17946 
17947 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
17948 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
17949 		}
17950 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
17951 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
17952 		}
17953 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
17954 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
17955 		}
17956 
17957 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
17958 			import std.stdio;
17959 			writeln(event.deltaY);
17960 		}
17961 
17962 		override void keyDown(NSEvent event) @selector("keyDown:") {
17963 			// the event may have multiple characters, and we send them all at once.
17964 			if (simpleWindow.handleCharEvent) {
17965 				auto chars = DeifiedNSString(event.characters);
17966 				foreach (dchar dc; chars.str)
17967 					simpleWindow.handleCharEvent(dc);
17968 			}
17969 
17970 			keyHelper(event, true);
17971 		}
17972 
17973 		override void keyUp(NSEvent event) @selector("keyUp:") {
17974 			keyHelper(event, false);
17975 		}
17976 
17977 		private void keyHelper(NSEvent event, bool pressed) {
17978 			if(simpleWindow.handleKeyEvent) {
17979 				KeyEvent ev;
17980 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
17981 				ev.pressed = pressed;
17982 				ev.hardwareCode = cast(ubyte) event.keyCode;
17983 				ev.modifierState = cast(uint) event.modifierFlags;
17984 				ev.window = simpleWindow;
17985 
17986 				simpleWindow.handleKeyEvent(ev);
17987 			}
17988 		}
17989 
17990 		override bool isFlipped() @selector("isFlipped") {
17991 			return true;
17992 		}
17993 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
17994 			return true;
17995 		}
17996 
17997 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
17998 			if(simpleWindow && simpleWindow.handlePulse)
17999 				simpleWindow.handlePulse();
18000 			/+
18001 			setNeedsDisplay = true;
18002 			+/
18003 		}
18004 	}
18005 
18006 private:
18007 	alias const(void)* CFStringRef;
18008 	alias const(void)* CFAllocatorRef;
18009 	alias const(void)* CFTypeRef;
18010 	alias const(void)* CGColorSpaceRef;
18011 	alias const(void)* CGImageRef;
18012 	alias ulong CGBitmapInfo;
18013 	alias NSGraphicsContext CGContextRef;
18014 
18015 	alias NSPoint CGPoint;
18016 	alias NSSize CGSize;
18017 	alias NSRect CGRect;
18018 
18019 	struct CGAffineTransform {
18020 		double a, b, c, d, tx, ty;
18021 	}
18022 
18023 	enum NSApplicationActivationPolicyRegular = 0;
18024 	enum NSBackingStoreBuffered = 2;
18025 	enum kCFStringEncodingUTF8 = 0x08000100;
18026 
18027 	enum : size_t {
18028 		NSBorderlessWindowMask = 0,
18029 		NSTitledWindowMask = 1 << 0,
18030 		NSClosableWindowMask = 1 << 1,
18031 		NSMiniaturizableWindowMask = 1 << 2,
18032 		NSResizableWindowMask = 1 << 3,
18033 		NSTexturedBackgroundWindowMask = 1 << 8
18034 	}
18035 
18036 	enum : ulong {
18037 		kCGImageAlphaNone,
18038 		kCGImageAlphaPremultipliedLast,
18039 		kCGImageAlphaPremultipliedFirst,
18040 		kCGImageAlphaLast,
18041 		kCGImageAlphaFirst,
18042 		kCGImageAlphaNoneSkipLast,
18043 		kCGImageAlphaNoneSkipFirst
18044 	}
18045 	enum : ulong {
18046 		kCGBitmapAlphaInfoMask = 0x1F,
18047 		kCGBitmapFloatComponents = (1 << 8),
18048 		kCGBitmapByteOrderMask = 0x7000,
18049 		kCGBitmapByteOrderDefault = (0 << 12),
18050 		kCGBitmapByteOrder16Little = (1 << 12),
18051 		kCGBitmapByteOrder32Little = (2 << 12),
18052 		kCGBitmapByteOrder16Big = (3 << 12),
18053 		kCGBitmapByteOrder32Big = (4 << 12)
18054 	}
18055 	enum CGPathDrawingMode {
18056 		kCGPathFill,
18057 		kCGPathEOFill,
18058 		kCGPathStroke,
18059 		kCGPathFillStroke,
18060 		kCGPathEOFillStroke
18061 	}
18062 	enum objc_AssociationPolicy : size_t {
18063 		OBJC_ASSOCIATION_ASSIGN = 0,
18064 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
18065 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
18066 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
18067 		OBJC_ASSOCIATION_COPY = 0x303 //01403
18068 	}
18069 
18070 	extern(C) {
18071 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
18072 		void CGContextRelease(CGContextRef c);
18073 		ubyte* CGBitmapContextGetData(CGContextRef c);
18074 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
18075 		size_t CGBitmapContextGetWidth(CGContextRef c);
18076 		size_t CGBitmapContextGetHeight(CGContextRef c);
18077 
18078 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
18079 		void CGColorSpaceRelease(CGColorSpaceRef cs);
18080 
18081 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
18082 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
18083 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
18084 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
18085 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
18086 		void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
18087 
18088 		void CGContextBeginPath(CGContextRef c);
18089 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
18090 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
18091 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
18092 		void CGContextAddRect(CGContextRef c, CGRect rect);
18093 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
18094 		void CGContextSaveGState(CGContextRef c);
18095 		void CGContextRestoreGState(CGContextRef c);
18096 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
18097 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
18098 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
18099 
18100 		void CGImageRelease(CGImageRef image);
18101 	}
18102 } else static assert(0, "Unsupported operating system");
18103 
18104 
18105 version(OSXCocoa) {
18106 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
18107 	//
18108 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
18109 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
18110 	//
18111 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
18112 	// Probably won't even fully compile right now
18113 
18114 	private enum double PI = 3.14159265358979323;
18115 
18116 	alias NSWindow NativeWindowHandle;
18117 	alias void delegate(NSid) NativeEventHandler;
18118 
18119 	enum KEY_ESCAPE = 27;
18120 
18121 	mixin template NativeImageImplementation() {
18122 		CGContextRef context;
18123 		ubyte* rawData;
18124 
18125 		final:
18126 
18127 		void convertToRgbaBytes(ubyte[] where) {
18128 			assert(where.length == this.width * this.height * 4);
18129 
18130 			// if rawData had a length....
18131 			//assert(rawData.length == where.length);
18132 			for(long idx = 0; idx < where.length; idx += 4) {
18133 				auto alpha = rawData[idx + 3];
18134 				if(alpha == 255) {
18135 					where[idx + 0] = rawData[idx + 0]; // r
18136 					where[idx + 1] = rawData[idx + 1]; // g
18137 					where[idx + 2] = rawData[idx + 2]; // b
18138 					where[idx + 3] = rawData[idx + 3]; // a
18139 				} else {
18140 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
18141 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
18142 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
18143 					where[idx + 3] = rawData[idx + 3]; // a
18144 
18145 				}
18146 			}
18147 		}
18148 
18149 		void setFromRgbaBytes(in ubyte[] where) {
18150 			// FIXME: this is probably wrong
18151 			assert(where.length == this.width * this.height * 4);
18152 
18153 			// if rawData had a length....
18154 			//assert(rawData.length == where.length);
18155 			for(long idx = 0; idx < where.length; idx += 4) {
18156 				auto alpha = where[idx + 3];
18157 				if(alpha == 255) {
18158 					rawData[idx + 0] = where[idx + 0]; // r
18159 					rawData[idx + 1] = where[idx + 1]; // g
18160 					rawData[idx + 2] = where[idx + 2]; // b
18161 					rawData[idx + 3] = where[idx + 3]; // a
18162 				} else if(alpha == 0) {
18163 					rawData[idx + 0] = 0;
18164 					rawData[idx + 1] = 0;
18165 					rawData[idx + 2] = 0;
18166 					rawData[idx + 3] = 0;
18167 				} else {
18168 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
18169 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
18170 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
18171 					rawData[idx + 3] = where[idx + 3]; // a
18172 				}
18173 			}
18174 		}
18175 
18176 
18177 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
18178 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18179 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18180 			CGColorSpaceRelease(colorSpace);
18181 			rawData = CGBitmapContextGetData(context);
18182 		}
18183 		void dispose() {
18184 			CGContextRelease(context);
18185 		}
18186 
18187 		void setPixel(int x, int y, Color c) {
18188 			auto offset = (y * width + x) * 4;
18189 			if (c.a == 255) {
18190 				rawData[offset + 0] = c.r;
18191 				rawData[offset + 1] = c.g;
18192 				rawData[offset + 2] = c.b;
18193 				rawData[offset + 3] = c.a;
18194 			} else {
18195 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
18196 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
18197 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
18198 				rawData[offset + 3] = c.a;
18199 			}
18200 		}
18201 	}
18202 
18203 	mixin template NativeScreenPainterImplementation() {
18204 		CGContextRef context;
18205 		ubyte[4] _outlineComponents;
18206 		NSView view;
18207 
18208 		Pen _activePen;
18209 		Color _fillColor;
18210 		Rectangle _clipRectangle;
18211 		OperatingSystemFont _font;
18212 
18213 		OperatingSystemFont getFont() {
18214 			if(_font is null) {
18215 				static OperatingSystemFont _defaultFont;
18216 				if(_defaultFont is null) {
18217 					_defaultFont = new OperatingSystemFont();
18218 					_defaultFont.loadDefault();
18219 				}
18220 				_font = _defaultFont;
18221 			}
18222 
18223 			return _font;
18224 		}
18225 
18226 		void create(PaintingHandle window) {
18227 			// this.destiny = window;
18228 			if(auto sw = cast(SimpleWindow) this.window) {
18229 				context = sw.drawingContext;
18230 				view = sw.view;
18231 			} else {
18232 				throw new NotYetImplementedException();
18233 			}
18234 		}
18235 
18236 		void dispose() {
18237 			view.setNeedsDisplay(true);
18238 		}
18239 
18240 		bool manualInvalidations;
18241 		void invalidateRect(Rectangle invalidRect) { }
18242 
18243 		// NotYetImplementedException
18244 		void rasterOp(RasterOp op) {
18245 		}
18246 		void setClipRectangle(int, int, int, int) {
18247 		}
18248 		Size textSize(in char[] txt) {
18249 			auto font = getFont();
18250 			return Size(font.stringWidth(txt), font.height());
18251 		}
18252 
18253 		void setFont(OperatingSystemFont font) {
18254 			_font = font;
18255 			//font.font.setInContext(context);
18256 		}
18257 		int fontHeight() {
18258 			auto font = getFont();
18259 			return font.height;
18260 		}
18261 
18262 		// end
18263 
18264 		void pen(Pen pen) {
18265 			_activePen = pen;
18266 			auto color = pen.color; // FIXME
18267 			double alphaComponent = color.a/255.0f;
18268 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
18269 
18270 			double[2] patternBuffer;
18271 			double[] pattern;
18272 			final switch(pen.style) {
18273 				case Pen.Style.Solid:
18274 					pattern = null;
18275 				break;
18276 				case Pen.Style.Dashed:
18277 					patternBuffer[0] = 4;
18278 					patternBuffer[1] = 1;
18279 					pattern = patternBuffer[];
18280 				break;
18281 				case Pen.Style.Dotted:
18282 					patternBuffer[0] = 1;
18283 					patternBuffer[1] = 1;
18284 					pattern = patternBuffer[];
18285 				break;
18286 			}
18287 
18288 			CGContextSetLineDash(context, 0, pattern.ptr, pattern.length);
18289 
18290 			if (color.a != 255) {
18291 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
18292 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
18293 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
18294 				_outlineComponents[3] = color.a;
18295 			} else {
18296 				_outlineComponents[0] = color.r;
18297 				_outlineComponents[1] = color.g;
18298 				_outlineComponents[2] = color.b;
18299 				_outlineComponents[3] = color.a;
18300 			}
18301 		}
18302 
18303 		@property void fillColor(Color color) {
18304 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
18305 		}
18306 
18307 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
18308 		// NotYetImplementedException for upper left/width/height
18309 			auto cgImage = CGBitmapContextCreateImage(image.context);
18310 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
18311 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18312 			CGImageRelease(cgImage);
18313 		}
18314 
18315 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
18316 		// FIXME: is this efficient?
18317 			auto cgImage = CGBitmapContextCreateImage(s.handle);
18318 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
18319 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
18320 			CGImageRelease(cgImage);
18321 		}
18322 
18323 
18324 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
18325 		// FIXME: alignment
18326 			if (_outlineComponents[3] != 0) {
18327 				CGContextSaveGState(context);
18328 				auto invAlpha = 1.0f/_outlineComponents[3];
18329 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
18330 												  _outlineComponents[1]*invAlpha,
18331 												  _outlineComponents[2]*invAlpha,
18332 												  _outlineComponents[3]/255.0f);
18333 
18334 
18335 
18336 				// FIXME: should we clip it to the bounding box?
18337 				int textHeight = fontHeight;
18338 
18339 				auto lines = text.split('\n');
18340 
18341 				const lineHeight = textHeight;
18342 				textHeight *= lines.length;
18343 
18344 				int cy = y;
18345 
18346 				if(alignment & TextAlignment.VerticalBottom) {
18347 					if(y2 <= 0)
18348 						return;
18349 					auto h = y2 - y;
18350 					if(h > textHeight) {
18351 						cy += h - textHeight;
18352 						cy -= lineHeight / 2;
18353 					}
18354 				} else if(alignment & TextAlignment.VerticalCenter) {
18355 					if(y2 <= 0)
18356 						return;
18357 					auto h = y2 - y;
18358 					if(textHeight < h) {
18359 						cy += (h - textHeight) / 2;
18360 						//cy -= lineHeight / 4;
18361 					}
18362 				}
18363 
18364 				foreach(line; text.split('\n')) {
18365 					int textWidth = this.textSize(line).width;
18366 
18367 					int px = x, py = cy;
18368 
18369 					if(alignment & TextAlignment.Center) {
18370 						if(x2 <= 0)
18371 							return;
18372 						auto w = x2 - x;
18373 						if(w > textWidth)
18374 							px += (w - textWidth) / 2;
18375 					} else if(alignment & TextAlignment.Right) {
18376 						if(x2 <= 0)
18377 							return;
18378 						auto pos = x2 - textWidth;
18379 						if(pos > x)
18380 							px = pos;
18381 					}
18382 
18383 					CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length);
18384 
18385 					carry_on:
18386 					cy += lineHeight + 4;
18387 				}
18388 
18389 // auto cfstr = cast(NSid)createCFString(text);
18390 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
18391 // NSPoint(x, y), null);
18392 // CFRelease(cfstr);
18393 				CGContextRestoreGState(context);
18394 			}
18395 		}
18396 
18397 		void drawPixel(int x, int y) {
18398 			auto rawData = CGBitmapContextGetData(context);
18399 			auto width = CGBitmapContextGetWidth(context);
18400 			auto height = CGBitmapContextGetHeight(context);
18401 			auto offset = ((height - y - 1) * width + x) * 4;
18402 			rawData[offset .. offset+4] = _outlineComponents;
18403 		}
18404 
18405 		void drawLine(int x1, int y1, int x2, int y2) {
18406 			CGPoint[2] linePoints;
18407 			linePoints[0] = CGPoint(x1, y1);
18408 			linePoints[1] = CGPoint(x2, y2);
18409 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
18410 		}
18411 
18412 		void drawRectangle(int x, int y, int width, int height) {
18413 			CGContextBeginPath(context);
18414 			auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
18415 			CGContextAddRect(context, rect);
18416 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18417 		}
18418 
18419 		void drawEllipse(int x1, int y1, int x2, int y2) {
18420 			CGContextBeginPath(context);
18421 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
18422 			CGContextAddEllipseInRect(context, rect);
18423 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18424 		}
18425 
18426 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
18427 			// @@@BUG@@@ Does not support elliptic arc (width != height).
18428 			CGContextBeginPath(context);
18429 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
18430 							start*PI/(180*64), finish*PI/(180*64), 0);
18431 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18432 		}
18433 
18434 		void drawPolygon(Point[] intPoints) {
18435 			CGContextBeginPath(context);
18436 			CGPoint[16] pointsBuffer;
18437 			CGPoint[] points;
18438 			if(intPoints.length <= pointsBuffer.length)
18439 				points = pointsBuffer[0 .. intPoints.length];
18440 			else
18441 				points = new CGPoint[](intPoints.length);
18442 
18443 			foreach(idx, pt; intPoints)
18444 				points[idx] = CGPoint(pt.x, pt.y);
18445 
18446 			CGContextAddLines(context, points.ptr, points.length);
18447 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
18448 		}
18449 	}
18450 
18451 	private bool appInitialized = false;
18452 	void initializeApp() {
18453 		if(appInitialized)
18454 			return;
18455 		synchronized {
18456 			if(appInitialized)
18457 				return;
18458 
18459 			auto app = NSApp(); // ensure the is initialized
18460 
18461 			auto dg = AppDelegate.alloc;
18462 			globalAppDelegate = dg;
18463 			NSApp.delegate_ = dg;
18464 
18465 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
18466 
18467 			appInitialized = true;
18468 		}
18469 	}
18470 
18471 	mixin template NativeSimpleWindowImplementation() {
18472 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
18473 			initializeApp();
18474 
18475 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
18476 
18477 			auto window = NSWindow.alloc.initWithContentRect(
18478 				contentRect,
18479 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
18480 				NSBackingStoreType.buffered,
18481 				true
18482 			);
18483 
18484 			SimpleWindow.nativeMapping[cast(void*) window] = this;
18485 
18486 			window.title = MacString(title).borrow;
18487 
18488 			auto dg = SDWindowDelegate.alloc.init;
18489 			dg.simpleWindow = this;
18490 			window.delegate_ = dg;
18491 
18492 			auto view = SDGraphicsView.alloc.init;
18493 			assert(view !is null);
18494 			window.contentView = view;
18495 			this.view = view;
18496 			view.simpleWindow = this;
18497 
18498 			window.center();
18499 
18500 			window.makeKeyAndOrderFront(null);
18501 
18502 			// no need to make a bitmap on mac since everything is double buffered already
18503 
18504 			// create area to draw on.
18505 			createNewDrawingContext(width, height);
18506 
18507 			window.setBackgroundColor(NSColor.whiteColor);
18508 		}
18509 
18510 		void createNewDrawingContext(int width, int height) {
18511 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
18512 			if(this.drawingContext)
18513 				CGContextRelease(this.drawingContext);
18514 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
18515 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
18516 			CGColorSpaceRelease(colorSpace);
18517 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
18518 			auto matrix = CGContextGetTextMatrix(drawingContext);
18519 			matrix.c = -matrix.c;
18520 			matrix.d = -matrix.d;
18521 			CGContextSetTextMatrix(drawingContext, matrix);
18522 
18523 		}
18524 
18525 		void dispose() {
18526 			closeWindow();
18527 			// window.release(); // closing the window does this automatically i think
18528 		}
18529 		void closeWindow() {
18530 			if(timer)
18531 				timer.invalidate();
18532 			window.close();
18533 		}
18534 
18535 		ScreenPainter getPainter(bool manualInvalidations) {
18536 			return ScreenPainter(this, this.window, manualInvalidations);
18537 		}
18538 
18539 		NSWindow window;
18540 		NSTimer timer;
18541 		NSView view;
18542 		CGContextRef drawingContext;
18543 	}
18544 }
18545 
18546 version(without_opengl) {} else
18547 extern(System) nothrow @nogc {
18548 	//enum uint GL_VERSION = 0x1F02;
18549 	//const(char)* glGetString (/*GLenum*/uint);
18550 	version(X11) {
18551 	static if (!SdpyIsUsingIVGLBinds) {
18552 
18553 		enum GLX_X_RENDERABLE = 0x8012;
18554 		enum GLX_DRAWABLE_TYPE = 0x8010;
18555 		enum GLX_RENDER_TYPE = 0x8011;
18556 		enum GLX_X_VISUAL_TYPE = 0x22;
18557 		enum GLX_TRUE_COLOR = 0x8002;
18558 		enum GLX_WINDOW_BIT = 0x00000001;
18559 		enum GLX_RGBA_BIT = 0x00000001;
18560 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
18561 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
18562 		enum GLX_SAMPLES = 0x186a1;
18563 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18564 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18565 	}
18566 
18567 		// GLX_EXT_swap_control
18568 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
18569 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
18570 
18571 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
18572 		extern(System) {
18573 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
18574 		}
18575 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
18576 
18577 		// this made public so we don't have to get it again and again
18578 		public bool glXCreateContextAttribsARB_present () {
18579 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
18580 				// get it
18581 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
18582 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
18583 			}
18584 			return (glXCreateContextAttribsARBFn !is null);
18585 		}
18586 
18587 		// this made public so we don't have to get it again and again
18588 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
18589 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
18590 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
18591 		}
18592 
18593 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
18594 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
18595 
18596 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
18597 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
18598 			if (_glx_swapInterval_fn is null) {
18599 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
18600 				if (_glx_swapInterval_fn is null) {
18601 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
18602 					return;
18603 				}
18604 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
18605 			}
18606 
18607 			if(glXSwapIntervalMESA is null) {
18608 				// it seems to require both to actually take effect on many computers
18609 				// idk why
18610 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
18611 				if(glXSwapIntervalMESA is null)
18612 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
18613 			}
18614 
18615 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
18616 				glXSwapIntervalMESA(wait ? 1 : 0);
18617 
18618 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
18619 		}
18620 	} else version(Windows) {
18621 	static if (!SdpyIsUsingIVGLBinds) {
18622 	enum GL_TRUE = 1;
18623 	enum GL_FALSE = 0;
18624 
18625 	public void* glbindGetProcAddress (const(char)* name) {
18626 		void* res = wglGetProcAddress(name);
18627 		if (res is null) {
18628 			/+
18629 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
18630 			import core.sys.windows.windef, core.sys.windows.winbase;
18631 			__gshared HINSTANCE dll = null;
18632 			if (dll is null) {
18633 				dll = LoadLibraryA("opengl32.dll");
18634 				if (dll is null) return null; // <32, but idc
18635 			}
18636 			res = GetProcAddress(dll, name);
18637 			+/
18638 			res = GetProcAddress(gl.libHandle, name);
18639 		}
18640 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
18641 		return res;
18642 	}
18643 	}
18644 
18645 
18646  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
18647         void wglSetVSync(bool wait) {
18648 		if(wglSwapIntervalEXT is null) {
18649 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
18650 			if(wglSwapIntervalEXT is null)
18651 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
18652 		}
18653 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
18654 			return;
18655 
18656 		wglSwapIntervalEXT(wait ? 1 : 0);
18657 	}
18658 
18659 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
18660 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
18661 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
18662 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
18663 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
18664 
18665 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
18666 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
18667 
18668 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
18669 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
18670 
18671 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
18672 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
18673 
18674 		void wglInitOtherFunctions () {
18675 			if (wglCreateContextAttribsARB is null) {
18676 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
18677 			}
18678 		}
18679 	}
18680 
18681 	static if (!SdpyIsUsingIVGLBinds) {
18682 
18683 	interface GL {
18684 		extern(System) @nogc nothrow:
18685 
18686 		void glGetIntegerv(int, void*);
18687 		void glMatrixMode(int);
18688 		void glPushMatrix();
18689 		void glLoadIdentity();
18690 		void glOrtho(double, double, double, double, double, double);
18691 		void glFrustum(double, double, double, double, double, double);
18692 
18693 		void glPopMatrix();
18694 		void glEnable(int);
18695 		void glDisable(int);
18696 		void glClear(int);
18697 		void glBegin(int);
18698 		void glVertex2f(float, float);
18699 		void glVertex3f(float, float, float);
18700 		void glEnd();
18701 		void glColor3b(byte, byte, byte);
18702 		void glColor3ub(ubyte, ubyte, ubyte);
18703 		void glColor4b(byte, byte, byte, byte);
18704 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
18705 		void glColor3i(int, int, int);
18706 		void glColor3ui(uint, uint, uint);
18707 		void glColor4i(int, int, int, int);
18708 		void glColor4ui(uint, uint, uint, uint);
18709 		void glColor3f(float, float, float);
18710 		void glColor4f(float, float, float, float);
18711 		void glTranslatef(float, float, float);
18712 		void glScalef(float, float, float);
18713 		version(X11) {
18714 			void glSecondaryColor3b(byte, byte, byte);
18715 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
18716 			void glSecondaryColor3i(int, int, int);
18717 			void glSecondaryColor3ui(uint, uint, uint);
18718 			void glSecondaryColor3f(float, float, float);
18719 		}
18720 
18721 		void glDrawElements(int, int, int, void*);
18722 
18723 		void glRotatef(float, float, float, float);
18724 
18725 		uint glGetError();
18726 
18727 		void glDeleteTextures(int, uint*);
18728 
18729 
18730 		void glRasterPos2i(int, int);
18731 		void glDrawPixels(int, int, uint, uint, void*);
18732 		void glClearColor(float, float, float, float);
18733 
18734 
18735 		void glPixelStorei(uint, int);
18736 
18737 		void glGenTextures(uint, uint*);
18738 		void glBindTexture(int, int);
18739 		void glTexParameteri(uint, uint, int);
18740 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18741 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
18742 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
18743 			/*GLsizei*/int width, /*GLsizei*/int height,
18744 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18745 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
18746 
18747 		void glLineWidth(int);
18748 
18749 
18750 		void glTexCoord2f(float, float);
18751 		void glVertex2i(int, int);
18752 		void glBlendFunc (int, int);
18753 		void glDepthFunc (int);
18754 		void glViewport(int, int, int, int);
18755 
18756 		void glClearDepth(double);
18757 
18758 		void glReadBuffer(uint);
18759 		void glReadPixels(int, int, int, int, int, int, void*);
18760 
18761 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
18762 
18763 		void glFlush();
18764 		void glFinish();
18765 
18766 		version(Windows) {
18767 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
18768 			HGLRC wglCreateContext(HDC);
18769 			HGLRC wglCreateLayerContext(HDC, int);
18770 			BOOL wglDeleteContext(HGLRC);
18771 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
18772 			HGLRC wglGetCurrentContext();
18773 			HDC wglGetCurrentDC();
18774 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
18775 			PROC wglGetProcAddress(LPCSTR);
18776 			BOOL wglMakeCurrent(HDC, HGLRC);
18777 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
18778 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
18779 			BOOL wglShareLists(HGLRC, HGLRC);
18780 			BOOL wglSwapLayerBuffers(HDC, UINT);
18781 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
18782 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
18783 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18784 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
18785 		}
18786 
18787 	}
18788 
18789 	interface GL3 {
18790 		extern(System) @nogc nothrow:
18791 
18792 		void glGenVertexArrays(GLsizei, GLuint*);
18793 		void glBindVertexArray(GLuint);
18794 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
18795 		void glGenerateMipmap(GLenum);
18796 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
18797 		void glStencilMask(GLuint);
18798 		void glStencilFunc(GLenum, GLint, GLuint);
18799 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18800 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
18801 		GLuint glCreateProgram();
18802 		GLuint glCreateShader(GLenum);
18803 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
18804 		void glCompileShader(GLuint);
18805 		void glGetShaderiv(GLuint, GLenum, GLint*);
18806 		void glAttachShader(GLuint, GLuint);
18807 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
18808 		void glLinkProgram(GLuint);
18809 		void glGetProgramiv(GLuint, GLenum, GLint*);
18810 		void glDeleteProgram(GLuint);
18811 		void glDeleteShader(GLuint);
18812 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
18813 		void glGenBuffers(GLsizei, GLuint*);
18814 
18815 		void glUniform1f(GLint location, GLfloat v0);
18816 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
18817 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
18818 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
18819 		void glUniform1i(GLint location, GLint v0);
18820 		void glUniform2i(GLint location, GLint v0, GLint v1);
18821 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
18822 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
18823 		void glUniform1ui(GLint location, GLuint v0);
18824 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
18825 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
18826 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
18827 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
18828 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
18829 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
18830 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
18831 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
18832 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
18833 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
18834 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
18835 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
18836 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
18837 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
18838 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
18839 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18840 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18841 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18842 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18843 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18844 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18845 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18846 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18847 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
18848 
18849 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
18850 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
18851 		void glDrawArrays(GLenum, GLint, GLsizei);
18852 		void glStencilOp(GLenum, GLenum, GLenum);
18853 		void glUseProgram(GLuint);
18854 		void glCullFace(GLenum);
18855 		void glFrontFace(GLenum);
18856 		void glActiveTexture(GLenum);
18857 		void glBindBuffer(GLenum, GLuint);
18858 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
18859 		void glEnableVertexAttribArray(GLuint);
18860 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
18861 		void glUniform1i(GLint, GLint);
18862 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
18863 		void glDisableVertexAttribArray(GLuint);
18864 		void glDeleteBuffers(GLsizei, const(GLuint)*);
18865 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
18866 		void glLogicOp (GLenum opcode);
18867 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
18868 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
18869 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
18870 		GLenum glCheckFramebufferStatus (GLenum target);
18871 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
18872 	}
18873 
18874 	interface GL4 {
18875 		extern(System) @nogc nothrow:
18876 
18877 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
18878 			/*GLsizei*/int width, /*GLsizei*/int height,
18879 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
18880 	}
18881 
18882 	interface GLU {
18883 		extern(System) @nogc nothrow:
18884 
18885 		void gluLookAt(double, double, double, double, double, double, double, double, double);
18886 		void gluPerspective(double, double, double, double);
18887 
18888 		char* gluErrorString(uint);
18889 	}
18890 
18891 
18892 	enum GL_RED = 0x1903;
18893 	enum GL_ALPHA = 0x1906;
18894 
18895 	enum uint GL_FRONT = 0x0404;
18896 
18897 	enum uint GL_BLEND = 0x0be2;
18898 	enum uint GL_LEQUAL = 0x0203;
18899 
18900 
18901 	enum uint GL_RGB = 0x1907;
18902 	enum uint GL_BGRA = 0x80e1;
18903 	enum uint GL_RGBA = 0x1908;
18904 	enum uint GL_RGBA8 = 0x8058;
18905 	enum uint GL_TEXTURE_2D =   0x0DE1;
18906 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
18907 	enum uint GL_NEAREST = 0x2600;
18908 	enum uint GL_LINEAR = 0x2601;
18909 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
18910 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
18911 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
18912 	enum uint GL_REPEAT = 0x2901;
18913 	enum uint GL_CLAMP = 0x2900;
18914 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
18915 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
18916 	enum uint GL_DECAL = 0x2101;
18917 	enum uint GL_MODULATE = 0x2100;
18918 	enum uint GL_TEXTURE_ENV = 0x2300;
18919 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
18920 	enum uint GL_REPLACE = 0x1E01;
18921 	enum uint GL_LIGHTING = 0x0B50;
18922 	enum uint GL_DITHER = 0x0BD0;
18923 
18924 	enum uint GL_NO_ERROR = 0;
18925 
18926 
18927 
18928 	enum int GL_VIEWPORT = 0x0BA2;
18929 	enum int GL_MODELVIEW = 0x1700;
18930 	enum int GL_TEXTURE = 0x1702;
18931 	enum int GL_PROJECTION = 0x1701;
18932 	enum int GL_DEPTH_TEST = 0x0B71;
18933 
18934 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
18935 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
18936 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
18937 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
18938 
18939 	enum int GL_POINTS = 0x0000;
18940 	enum int GL_LINES =  0x0001;
18941 	enum int GL_LINE_LOOP = 0x0002;
18942 	enum int GL_LINE_STRIP = 0x0003;
18943 	enum int GL_TRIANGLES = 0x0004;
18944 	enum int GL_TRIANGLE_STRIP = 5;
18945 	enum int GL_TRIANGLE_FAN = 6;
18946 	enum int GL_QUADS = 7;
18947 	enum int GL_QUAD_STRIP = 8;
18948 	enum int GL_POLYGON = 9;
18949 
18950 	alias GLvoid = void;
18951 	alias GLboolean = ubyte;
18952 	alias GLint = int;
18953 	alias GLuint = uint;
18954 	alias GLenum = uint;
18955 	alias GLchar = char;
18956 	alias GLsizei = int;
18957 	alias GLfloat = float;
18958 	alias GLintptr = size_t;
18959 	alias GLsizeiptr = ptrdiff_t;
18960 
18961 
18962 	enum uint GL_INVALID_ENUM = 0x0500;
18963 
18964 	enum uint GL_ZERO = 0;
18965 	enum uint GL_ONE = 1;
18966 
18967 	enum uint GL_BYTE = 0x1400;
18968 	enum uint GL_UNSIGNED_BYTE = 0x1401;
18969 	enum uint GL_SHORT = 0x1402;
18970 	enum uint GL_UNSIGNED_SHORT = 0x1403;
18971 	enum uint GL_INT = 0x1404;
18972 	enum uint GL_UNSIGNED_INT = 0x1405;
18973 	enum uint GL_FLOAT = 0x1406;
18974 	enum uint GL_2_BYTES = 0x1407;
18975 	enum uint GL_3_BYTES = 0x1408;
18976 	enum uint GL_4_BYTES = 0x1409;
18977 	enum uint GL_DOUBLE = 0x140A;
18978 
18979 	enum uint GL_STREAM_DRAW = 0x88E0;
18980 
18981 	enum uint GL_CCW = 0x0901;
18982 
18983 	enum uint GL_STENCIL_TEST = 0x0B90;
18984 	enum uint GL_SCISSOR_TEST = 0x0C11;
18985 
18986 	enum uint GL_EQUAL = 0x0202;
18987 	enum uint GL_NOTEQUAL = 0x0205;
18988 
18989 	enum uint GL_ALWAYS = 0x0207;
18990 	enum uint GL_KEEP = 0x1E00;
18991 
18992 	enum uint GL_INCR = 0x1E02;
18993 
18994 	enum uint GL_INCR_WRAP = 0x8507;
18995 	enum uint GL_DECR_WRAP = 0x8508;
18996 
18997 	enum uint GL_CULL_FACE = 0x0B44;
18998 	enum uint GL_BACK = 0x0405;
18999 
19000 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
19001 	enum uint GL_VERTEX_SHADER = 0x8B31;
19002 
19003 	enum uint GL_COMPILE_STATUS = 0x8B81;
19004 	enum uint GL_LINK_STATUS = 0x8B82;
19005 
19006 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
19007 
19008 	enum uint GL_STATIC_DRAW = 0x88E4;
19009 
19010 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
19011 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
19012 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
19013 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
19014 
19015 	enum uint GL_GENERATE_MIPMAP = 0x8191;
19016 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
19017 
19018 	enum uint GL_TEXTURE0 = 0x84C0U;
19019 	enum uint GL_TEXTURE1 = 0x84C1U;
19020 
19021 	enum uint GL_ARRAY_BUFFER = 0x8892;
19022 
19023 	enum uint GL_SRC_COLOR = 0x0300;
19024 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
19025 	enum uint GL_SRC_ALPHA = 0x0302;
19026 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
19027 	enum uint GL_DST_ALPHA = 0x0304;
19028 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
19029 	enum uint GL_DST_COLOR = 0x0306;
19030 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
19031 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
19032 
19033 	enum uint GL_INVERT = 0x150AU;
19034 
19035 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
19036 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
19037 
19038 	enum uint GL_FRAMEBUFFER = 0x8D40U;
19039 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
19040 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
19041 
19042 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
19043 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
19044 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
19045 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
19046 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
19047 
19048 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
19049 	enum uint GL_CLEAR = 0x1500U;
19050 	enum uint GL_COPY = 0x1503U;
19051 	enum uint GL_XOR = 0x1506U;
19052 
19053 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
19054 
19055 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
19056 
19057 	}
19058 }
19059 
19060 /++
19061 	History:
19062 		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.
19063 +/
19064 __gshared bool gluSuccessfullyLoaded = true;
19065 
19066 version(without_opengl) {} else {
19067 static if(!SdpyIsUsingIVGLBinds) {
19068 	version(Windows) {
19069 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
19070 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
19071 	} else {
19072 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
19073 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
19074 	}
19075 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
19076 
19077 
19078 	shared static this() {
19079 		gl.loadDynamicLibrary();
19080 
19081 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
19082 		// unless those functions are actually used
19083 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
19084 		glu.loadDynamicLibrary();
19085 	}
19086 }
19087 }
19088 
19089 /++
19090 	Convenience method for converting D arrays to opengl buffer data
19091 
19092 	I would LOVE to overload it with the original glBufferData, but D won't
19093 	let me since glBufferData is a function pointer :(
19094 
19095 	Added: August 25, 2020 (version 8.5)
19096 +/
19097 version(without_opengl) {} else
19098 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
19099 	glBufferData(target, data.length, data.ptr, usage);
19100 }
19101 
19102 /+
19103 /++
19104 	A matrix for simple uses that easily integrates with [OpenGlShader].
19105 
19106 	Might not be useful to you since it only as some simple functions and
19107 	probably isn't that fast.
19108 
19109 	Note it uses an inline static array for its storage, so copying it
19110 	may be expensive.
19111 +/
19112 struct BasicMatrix(int columns, int rows, T = float) {
19113 	import core.stdc.math;
19114 
19115 	T[columns * rows] data = 0.0;
19116 
19117 	/++
19118 		Basic operations that operate *in place*.
19119 	+/
19120 	void translate() {
19121 
19122 	}
19123 
19124 	/// ditto
19125 	void scale() {
19126 
19127 	}
19128 
19129 	/// ditto
19130 	void rotate() {
19131 
19132 	}
19133 
19134 	/++
19135 
19136 	+/
19137 	static if(columns == rows)
19138 	static BasicMatrix identity() {
19139 		BasicMatrix m;
19140 		foreach(i; 0 .. columns)
19141 			data[0 + i + i * columns] = 1.0;
19142 		return m;
19143 	}
19144 
19145 	static BasicMatrix ortho() {
19146 		return BasicMatrix.init;
19147 	}
19148 }
19149 +/
19150 
19151 /++
19152 	Convenience class for using opengl shaders.
19153 
19154 	Ensure that you've loaded opengl 3+ and set your active
19155 	context before trying to use this.
19156 
19157 	Added: August 25, 2020 (version 8.5)
19158 +/
19159 version(without_opengl) {} else
19160 final class OpenGlShader {
19161 	private int shaderProgram_;
19162 	private @property void shaderProgram(int a) {
19163 		shaderProgram_ = a;
19164 	}
19165 	/// Get the program ID for use in OpenGL functions.
19166 	public @property int shaderProgram() {
19167 		return shaderProgram_;
19168 	}
19169 
19170 	/++
19171 
19172 	+/
19173 	static struct Source {
19174 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
19175 		string code; ///
19176 	}
19177 
19178 	/++
19179 		Helper method to just compile some shader code and check for errors
19180 		while you do glCreateShader, etc. on the outside yourself.
19181 
19182 		This just does `glShaderSource` and `glCompileShader` for the given code.
19183 
19184 		If you the OpenGlShader class constructor, you never need to call this yourself.
19185 	+/
19186 	static void compile(int sid, Source code) {
19187 		const(char)*[1] buffer;
19188 		int[1] lengthBuffer;
19189 
19190 		buffer[0] = code.code.ptr;
19191 		lengthBuffer[0] = cast(int) code.code.length;
19192 
19193 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
19194 		glCompileShader(sid);
19195 
19196 		int success;
19197 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
19198 		if(!success) {
19199 			char[512] info;
19200 			int len;
19201 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
19202 
19203 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
19204 		}
19205 	}
19206 
19207 	/++
19208 		Calls `glLinkProgram` and throws if error a occurs.
19209 
19210 		If you the OpenGlShader class constructor, you never need to call this yourself.
19211 	+/
19212 	static void link(int shaderProgram) {
19213 		glLinkProgram(shaderProgram);
19214 		int success;
19215 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
19216 		if(!success) {
19217 			char[512] info;
19218 			int len;
19219 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
19220 
19221 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
19222 		}
19223 	}
19224 
19225 	/++
19226 		Constructs the shader object by calling `glCreateProgram`, then
19227 		compiling each given [Source], and finally, linking them together.
19228 
19229 		Throws: on compile or link failure.
19230 	+/
19231 	this(Source[] codes...) {
19232 		shaderProgram = glCreateProgram();
19233 
19234 		int[16] shadersBufferStack;
19235 
19236 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
19237 			shadersBufferStack[0 .. codes.length] :
19238 			new int[](codes.length);
19239 
19240 		foreach(idx, code; codes) {
19241 			shadersBuffer[idx] = glCreateShader(code.type);
19242 
19243 			compile(shadersBuffer[idx], code);
19244 
19245 			glAttachShader(shaderProgram, shadersBuffer[idx]);
19246 		}
19247 
19248 		link(shaderProgram);
19249 
19250 		foreach(s; shadersBuffer)
19251 			glDeleteShader(s);
19252 	}
19253 
19254 	/// Calls `glUseProgram(this.shaderProgram)`
19255 	void use() {
19256 		glUseProgram(this.shaderProgram);
19257 	}
19258 
19259 	/// Deletes the program.
19260 	void delete_() {
19261 		glDeleteProgram(shaderProgram);
19262 		shaderProgram = 0;
19263 	}
19264 
19265 	/++
19266 		[OpenGlShader.uniforms].name gives you one of these.
19267 
19268 		You can get the id out of it or just assign
19269 	+/
19270 	static struct Uniform {
19271 		/// the id passed to glUniform*
19272 		int id;
19273 
19274 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
19275 		void opAssign(float x, float y, float z, float w) {
19276 			if(id != -1)
19277 			glUniform4f(id, x, y, z, w);
19278 		}
19279 
19280 		void opAssign(float x) {
19281 			if(id != -1)
19282 			glUniform1f(id, x);
19283 		}
19284 
19285 		void opAssign(float x, float y) {
19286 			if(id != -1)
19287 			glUniform2f(id, x, y);
19288 		}
19289 
19290 		void opAssign(T)(T t) {
19291 			t.glUniform(id);
19292 		}
19293 	}
19294 
19295 	static struct UniformsHelper {
19296 		OpenGlShader _shader;
19297 
19298 		@property Uniform opDispatch(string name)() {
19299 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
19300 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
19301 			//if(i == -1)
19302 				//throw new Exception("Could not find uniform " ~ name);
19303 			return Uniform(i);
19304 		}
19305 
19306 		@property void opDispatch(string name, T)(T t) {
19307 			Uniform f = this.opDispatch!name;
19308 			t.glUniform(f);
19309 		}
19310 	}
19311 
19312 	/++
19313 		Gives access to the uniforms through dot access.
19314 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
19315 	+/
19316 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
19317 }
19318 
19319 version(without_opengl) {} else {
19320 /++
19321 	A static container of experimental types and value constructors for opengl 3+ shaders.
19322 
19323 
19324 	You can declare variables like:
19325 
19326 	```
19327 	OGL.vec3f something;
19328 	```
19329 
19330 	But generally it would be used with [OpenGlShader]'s uniform helpers like
19331 
19332 	```
19333 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
19334 	```
19335 
19336 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
19337 
19338 
19339 	History:
19340 		Added December 7, 2021. Not yet stable.
19341 +/
19342 final class OGL {
19343 	static:
19344 
19345 	private template typeFromSpecifier(string specifier) {
19346 		static if(specifier == "f")
19347 			alias typeFromSpecifier = GLfloat;
19348 		else static if(specifier == "i")
19349 			alias typeFromSpecifier = GLint;
19350 		else static if(specifier == "ui")
19351 			alias typeFromSpecifier = GLuint;
19352 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
19353 	}
19354 
19355 	private template CommonType(T...) {
19356 		static if(T.length == 1)
19357 			alias CommonType = T[0];
19358 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
19359 			alias CommonType = CommonType!(C, T[2 .. $]);
19360 	}
19361 
19362 	private template typesToSpecifier(T...) {
19363 		static if(is(CommonType!T == float))
19364 			enum typesToSpecifier = "f";
19365 		else static if(is(CommonType!T == int))
19366 			enum typesToSpecifier = "i";
19367 		else static if(is(CommonType!T == uint))
19368 			enum typesToSpecifier = "ui";
19369 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
19370 	}
19371 
19372 	private template genNames(size_t dim, size_t dim2 = 0) {
19373 		string helper() {
19374 			string s;
19375 			if(dim2) {
19376 				s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;";
19377 			} else {
19378 				if(dim > 0) s ~= "type x = 0;";
19379 				if(dim > 1) s ~= "type y = 0;";
19380 				if(dim > 2) s ~= "type z = 0;";
19381 				if(dim > 3) s ~= "type w = 0;";
19382 			}
19383 			return s;
19384 		}
19385 
19386 		enum genNames = helper();
19387 	}
19388 
19389 	// there's vec, arrays of vec, mat, and arrays of mat
19390 	template opDispatch(string name)
19391 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
19392 	{
19393 		static if(name[4] == 'x') {
19394 			enum dimX = cast(int) (name[3] - '0');
19395 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
19396 
19397 			enum dimY = cast(int) (name[5] - '0');
19398 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
19399 
19400 			enum isArray = name[$ - 1] == 'v';
19401 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
19402 			alias type = typeFromSpecifier!typeSpecifier;
19403 		} else {
19404 			enum dim = cast(int) (name[3] - '0');
19405 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
19406 			enum isArray = name[$ - 1] == 'v';
19407 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
19408 			alias type = typeFromSpecifier!typeSpecifier;
19409 		}
19410 
19411 		align(1)
19412 		struct opDispatch {
19413 			align(1):
19414 			static if(name[4] == 'x')
19415 				mixin(genNames!(dimX, dimY));
19416 			else
19417 				mixin(genNames!dim);
19418 
19419 			private void glUniform(OpenGlShader.Uniform assignTo) {
19420 				glUniform(assignTo.id);
19421 			}
19422 			private void glUniform(int assignTo) {
19423 				static if(name[4] == 'x') {
19424 					// FIXME
19425 					pragma(msg, "This matrix uniform helper has never been tested!!!!");
19426 					mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr);
19427 				} else
19428 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
19429 			}
19430 		}
19431 	}
19432 
19433 	auto vec(T...)(T members) {
19434 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
19435 	}
19436 }
19437 }
19438 
19439 version(linux) {
19440 	version(with_eventloop) {} else {
19441 		private int epollFd = -1;
19442 		void prepareEventLoop() {
19443 			if(epollFd != -1)
19444 				return; // already initialized, no need to do it again
19445 			import ep = core.sys.linux.epoll;
19446 
19447 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
19448 			if(epollFd == -1)
19449 				throw new Exception("epoll create failure");
19450 		}
19451 	}
19452 } else version(Posix) {
19453 	void prepareEventLoop() {}
19454 }
19455 
19456 version(X11) {
19457 	import core.stdc.locale : LC_ALL; // rdmd fix
19458 	__gshared bool sdx_isUTF8Locale;
19459 
19460 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
19461 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
19462 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
19463 	// anal magic is here. I (Ketmar) hope you like it.
19464 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
19465 	// always return correct unicode symbols. The detection is here 'cause user can change locale
19466 	// later.
19467 
19468 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
19469 	shared static this () {
19470 		if(!librariesSuccessfullyLoaded)
19471 			return;
19472 
19473 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
19474 
19475 		// this doesn't hurt; it may add some locking, but the speed is still
19476 		// allows doing 60 FPS videogames; also, ignore the result, as most
19477 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
19478 		// never seen this failing).
19479 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
19480 
19481 		setlocale(LC_ALL, "");
19482 		// check if out locale is UTF-8
19483 		auto lct = setlocale(LC_CTYPE, null);
19484 		if (lct is null) {
19485 			sdx_isUTF8Locale = false;
19486 		} else {
19487 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
19488 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
19489 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
19490 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
19491 				{
19492 					sdx_isUTF8Locale = true;
19493 					break;
19494 				}
19495 			}
19496 		}
19497 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
19498 	}
19499 }
19500 
19501 class ExperimentalTextComponent2 {
19502 	/+
19503 		Stage 1: get it working monospace
19504 		Stage 2: use proportional font
19505 		Stage 3: allow changes in inline style
19506 		Stage 4: allow new fonts and sizes in the middle
19507 		Stage 5: optimize gap buffer
19508 		Stage 6: optimize layout
19509 		Stage 7: word wrap
19510 		Stage 8: justification
19511 		Stage 9: editing, selection, etc.
19512 
19513 			Operations:
19514 				insert text
19515 				overstrike text
19516 				select
19517 				cut
19518 				modify
19519 	+/
19520 
19521 	/++
19522 		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.
19523 	+/
19524 	this(SimpleWindow window) {
19525 		this.window = window;
19526 	}
19527 
19528 	private SimpleWindow window;
19529 
19530 
19531 	/++
19532 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
19533 		representing the internal parts. The first pass is focused on the x parameter, then the
19534 		renderer is responsible for going back to the parts in the current line and calling
19535 		adjustDownForAscent to change the y params.
19536 	+/
19537 	static interface ComponentRenderHelper {
19538 
19539 		/+
19540 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
19541 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
19542 			to move (adjust y to make room for new line) until you get back to the same position,
19543 			then you can stop - if one thing is unchanged, nothing after it is changed too.
19544 
19545 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
19546 			once you reach something that is unchanged, you can stop.
19547 		+/
19548 
19549 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
19550 
19551 		int ascent() const;
19552 		int descent() const;
19553 
19554 		int advance() const;
19555 
19556 		bool endsWithExplititLineBreak() const;
19557 	}
19558 
19559 	static interface RenderResult {
19560 		/++
19561 			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.
19562 		+/
19563 		void popFront();
19564 		@property bool empty() const;
19565 		@property ComponentRenderHelper front() const;
19566 
19567 		void repositionForNextLine(Point baseline, int availableWidth);
19568 	}
19569 
19570 	static interface ComponentInFlow {
19571 		void draw(ScreenPainter painter);
19572 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
19573 
19574 		bool startsWithExplicitLineBreak() const;
19575 	}
19576 
19577 	static class TextFlowComponent : ComponentInFlow {
19578 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
19579 
19580 		Color foreground;
19581 		Color background;
19582 
19583 		OperatingSystemFont font; // should NEVER be null
19584 
19585 		ubyte attributes; // underline, strike through, display on new block
19586 
19587 		version(Windows)
19588 			const(wchar)[] content;
19589 		else
19590 			const(char)[] content; // this should NEVER have a newline, except at the end
19591 
19592 		RenderedComponent[] rendered; // entirely controlled by [rerender]
19593 
19594 		// could prolly put some spacing around it too like margin / padding
19595 
19596 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
19597 			in { assert(font !is null);
19598 			     assert(!font.isNull); }
19599 			do
19600 		{
19601 			this.foreground = f;
19602 			this.background = b;
19603 			this.font = font;
19604 
19605 			this.attributes = attr;
19606 			version(Windows) {
19607 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
19608 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
19609 				auto buffer = new wchar[](sz);
19610 				this.content = makeWindowsString(c, buffer, conversionFlags);
19611 			} else {
19612 				this.content = c.dup;
19613 			}
19614 		}
19615 
19616 		void draw(ScreenPainter painter) {
19617 			painter.setFont(this.font);
19618 			painter.outlineColor = this.foreground;
19619 			painter.fillColor = Color.transparent;
19620 			foreach(rendered; this.rendered) {
19621 				// the component works in term of baseline,
19622 				// but the painter works in term of upper left bounding box
19623 				// so need to translate that
19624 
19625 				if(this.background.a) {
19626 					painter.fillColor = this.background;
19627 					painter.outlineColor = this.background;
19628 
19629 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
19630 
19631 					painter.outlineColor = this.foreground;
19632 					painter.fillColor = Color.transparent;
19633 				}
19634 
19635 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
19636 
19637 				// FIXME: strike through, underline, highlight selection, etc.
19638 			}
19639 		}
19640 	}
19641 
19642 	// I could split the parts into words on render
19643 	// for easier word-wrap, each one being an unbreakable "inline-block"
19644 	private TextFlowComponent[] parts;
19645 	private int needsRerenderFrom;
19646 
19647 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
19648 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
19649 		parts ~= new TextFlowComponent(f, b, font, attr, c);
19650 	}
19651 
19652 	static struct RenderedComponent {
19653 		int startX;
19654 		int startY;
19655 		short width;
19656 		// 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!
19657 		// for individual chars in here you've gotta process on demand
19658 		version(Windows)
19659 			const(wchar)[] slice;
19660 		else
19661 			const(char)[] slice;
19662 	}
19663 
19664 
19665 	void rerender(Rectangle boundingBox) {
19666 		Point baseline = boundingBox.upperLeft;
19667 
19668 		this.boundingBox.left = boundingBox.left;
19669 		this.boundingBox.top = boundingBox.top;
19670 
19671 		auto remainingParts = parts;
19672 
19673 		int largestX;
19674 
19675 
19676 		foreach(part; parts)
19677 			part.font.prepareContext(window);
19678 		scope(exit)
19679 		foreach(part; parts)
19680 			part.font.releaseContext();
19681 
19682 		calculateNextLine:
19683 
19684 		int nextLineHeight = 0;
19685 		int nextBiggestDescent = 0;
19686 
19687 		foreach(part; remainingParts) {
19688 			auto height = part.font.ascent;
19689 			if(height > nextLineHeight)
19690 				nextLineHeight = height;
19691 			if(part.font.descent > nextBiggestDescent)
19692 				nextBiggestDescent = part.font.descent;
19693 			if(part.content.length && part.content[$-1] == '\n')
19694 				break;
19695 		}
19696 
19697 		baseline.y += nextLineHeight;
19698 		auto lineStart = baseline;
19699 
19700 		while(remainingParts.length) {
19701 			remainingParts[0].rendered = null;
19702 
19703 			bool eol;
19704 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
19705 				eol = true;
19706 
19707 			// FIXME: word wrap
19708 			auto font = remainingParts[0].font;
19709 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
19710 			auto width = font.stringWidth(slice, window);
19711 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
19712 
19713 			remainingParts = remainingParts[1 .. $];
19714 			baseline.x += width;
19715 
19716 			if(eol) {
19717 				baseline.y += nextBiggestDescent;
19718 				if(baseline.x > largestX)
19719 					largestX = baseline.x;
19720 				baseline.x = lineStart.x;
19721 				goto calculateNextLine;
19722 			}
19723 		}
19724 
19725 		if(baseline.x > largestX)
19726 			largestX = baseline.x;
19727 
19728 		this.boundingBox.right = largestX;
19729 		this.boundingBox.bottom = baseline.y;
19730 	}
19731 
19732 	// you must call rerender first!
19733 	void draw(ScreenPainter painter) {
19734 		foreach(part; parts) {
19735 			part.draw(painter);
19736 		}
19737 	}
19738 
19739 	struct IdentifyResult {
19740 		TextFlowComponent part;
19741 		int charIndexInPart;
19742 		int totalCharIndex = -1; // if this is -1, it just means the end
19743 
19744 		Rectangle boundingBox;
19745 	}
19746 
19747 	IdentifyResult identify(Point pt, bool exact = false) {
19748 		if(parts.length == 0)
19749 			return IdentifyResult(null, 0);
19750 
19751 		if(pt.y < boundingBox.top) {
19752 			if(exact)
19753 				return IdentifyResult(null, 1);
19754 			return IdentifyResult(parts[0], 0);
19755 		}
19756 		if(pt.y > boundingBox.bottom) {
19757 			if(exact)
19758 				return IdentifyResult(null, 2);
19759 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
19760 		}
19761 
19762 		int tci = 0;
19763 
19764 		// I should probably like binary search this or something...
19765 		foreach(ref part; parts) {
19766 			foreach(rendered; part.rendered) {
19767 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
19768 				if(rect.contains(pt)) {
19769 					auto x = pt.x - rendered.startX;
19770 					auto estimatedIdx = x / part.font.averageWidth;
19771 
19772 					if(estimatedIdx < 0)
19773 						estimatedIdx = 0;
19774 
19775 					if(estimatedIdx > rendered.slice.length)
19776 						estimatedIdx = cast(int) rendered.slice.length;
19777 
19778 					int idx;
19779 					int x1, x2;
19780 					if(part.font.isMonospace) {
19781 						auto w = part.font.averageWidth;
19782 						if(!exact && x > (estimatedIdx + 1) * w)
19783 							return IdentifyResult(null, 4);
19784 						idx = estimatedIdx;
19785 						x1 = idx * w;
19786 						x2 = (idx + 1) * w;
19787 					} else {
19788 						idx = estimatedIdx;
19789 
19790 						part.font.prepareContext(window);
19791 						scope(exit) part.font.releaseContext();
19792 
19793 						// int iterations;
19794 
19795 						while(true) {
19796 							// iterations++;
19797 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
19798 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
19799 
19800 							x1 += rendered.startX;
19801 							x2 += rendered.startX;
19802 
19803 							if(pt.x < x1) {
19804 								if(idx == 0) {
19805 									if(exact)
19806 										return IdentifyResult(null, 6);
19807 									else
19808 										break;
19809 								}
19810 								idx--;
19811 							} else if(pt.x > x2) {
19812 								idx++;
19813 								if(idx > rendered.slice.length) {
19814 									if(exact)
19815 										return IdentifyResult(null, 5);
19816 									else
19817 										break;
19818 								}
19819 							} else if(pt.x >= x1 && pt.x <= x2) {
19820 								if(idx)
19821 									idx--; // point it at the original index
19822 								break; // we fit
19823 							}
19824 						}
19825 
19826 						// writeln(iterations)
19827 					}
19828 
19829 
19830 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
19831 				}
19832 			}
19833 			tci += cast(int) part.content.length; // FIXME: utf-8?
19834 		}
19835 		return IdentifyResult(null, 3);
19836 	}
19837 
19838 	Rectangle boundingBox; // only set after [rerender]
19839 
19840 	// text will be positioned around the exclusion zone
19841 	static struct ExclusionZone {
19842 
19843 	}
19844 
19845 	ExclusionZone[] exclusionZones;
19846 }
19847 
19848 
19849 // Don't use this yet. When I'm happy with it, I will move it to the
19850 // regular module namespace.
19851 mixin template ExperimentalTextComponent() {
19852 
19853 static:
19854 
19855 	alias Rectangle = arsd.color.Rectangle;
19856 
19857 	struct ForegroundColor {
19858 		Color color;
19859 		alias color this;
19860 
19861 		this(Color c) {
19862 			color = c;
19863 		}
19864 
19865 		this(int r, int g, int b, int a = 255) {
19866 			color = Color(r, g, b, a);
19867 		}
19868 
19869 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
19870 			return ForegroundColor(mixin("Color." ~ s));
19871 		}
19872 	}
19873 
19874 	struct BackgroundColor {
19875 		Color color;
19876 		alias color this;
19877 
19878 		this(Color c) {
19879 			color = c;
19880 		}
19881 
19882 		this(int r, int g, int b, int a = 255) {
19883 			color = Color(r, g, b, a);
19884 		}
19885 
19886 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
19887 			return BackgroundColor(mixin("Color." ~ s));
19888 		}
19889 	}
19890 
19891 	static class InlineElement {
19892 		string text;
19893 
19894 		BlockElement containingBlock;
19895 
19896 		Color color = Color.black;
19897 		Color backgroundColor = Color.transparent;
19898 		ushort styles;
19899 
19900 		string font;
19901 		int fontSize;
19902 
19903 		int lineHeight;
19904 
19905 		void* identifier;
19906 
19907 		Rectangle boundingBox;
19908 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
19909 
19910 		bool isMergeCompatible(InlineElement other) {
19911 			return
19912 				containingBlock is other.containingBlock &&
19913 				color == other.color &&
19914 				backgroundColor == other.backgroundColor &&
19915 				styles == other.styles &&
19916 				font == other.font &&
19917 				fontSize == other.fontSize &&
19918 				lineHeight == other.lineHeight &&
19919 				true;
19920 		}
19921 
19922 		int xOfIndex(size_t index) {
19923 			if(index < letterXs.length)
19924 				return letterXs[index];
19925 			else
19926 				return boundingBox.right;
19927 		}
19928 
19929 		InlineElement clone() {
19930 			auto ie = new InlineElement();
19931 			ie.tupleof = this.tupleof;
19932 			return ie;
19933 		}
19934 
19935 		InlineElement getPreviousInlineElement() {
19936 			InlineElement prev = null;
19937 			foreach(ie; this.containingBlock.parts) {
19938 				if(ie is this)
19939 					break;
19940 				prev = ie;
19941 			}
19942 			if(prev is null) {
19943 				BlockElement pb;
19944 				BlockElement cb = this.containingBlock;
19945 				moar:
19946 				foreach(ie; this.containingBlock.containingLayout.blocks) {
19947 					if(ie is cb)
19948 						break;
19949 					pb = ie;
19950 				}
19951 				if(pb is null)
19952 					return null;
19953 				if(pb.parts.length == 0) {
19954 					cb = pb;
19955 					goto moar;
19956 				}
19957 
19958 				prev = pb.parts[$-1];
19959 
19960 			}
19961 			return prev;
19962 		}
19963 
19964 		InlineElement getNextInlineElement() {
19965 			InlineElement next = null;
19966 			foreach(idx, ie; this.containingBlock.parts) {
19967 				if(ie is this) {
19968 					if(idx + 1 < this.containingBlock.parts.length)
19969 						next = this.containingBlock.parts[idx + 1];
19970 					break;
19971 				}
19972 			}
19973 			if(next is null) {
19974 				BlockElement n;
19975 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
19976 					if(ie is this.containingBlock) {
19977 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
19978 							n = this.containingBlock.containingLayout.blocks[idx + 1];
19979 						break;
19980 					}
19981 				}
19982 				if(n is null)
19983 					return null;
19984 
19985 				if(n.parts.length)
19986 					next = n.parts[0];
19987 				else {} // FIXME
19988 
19989 			}
19990 			return next;
19991 		}
19992 
19993 	}
19994 
19995 	// Block elements are used entirely for positioning inline elements,
19996 	// which are the things that are actually drawn.
19997 	class BlockElement {
19998 		InlineElement[] parts;
19999 		uint alignment;
20000 
20001 		int whiteSpace; // pre, pre-wrap, wrap
20002 
20003 		TextLayout containingLayout;
20004 
20005 		// inputs
20006 		Point where;
20007 		Size minimumSize;
20008 		Size maximumSize;
20009 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
20010 		void* identifier;
20011 
20012 		Rectangle margin;
20013 		Rectangle padding;
20014 
20015 		// outputs
20016 		Rectangle[] boundingBoxes;
20017 	}
20018 
20019 	struct TextIdentifyResult {
20020 		InlineElement element;
20021 		int offset;
20022 
20023 		private TextIdentifyResult fixupNewline() {
20024 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
20025 				offset--;
20026 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
20027 				offset--;
20028 			}
20029 			return this;
20030 		}
20031 	}
20032 
20033 	class TextLayout {
20034 		BlockElement[] blocks;
20035 		Rectangle boundingBox_;
20036 		Rectangle boundingBox() { return boundingBox_; }
20037 		void boundingBox(Rectangle r) {
20038 			if(r != boundingBox_) {
20039 				boundingBox_ = r;
20040 				layoutInvalidated = true;
20041 			}
20042 		}
20043 
20044 		Rectangle contentBoundingBox() {
20045 			Rectangle r;
20046 			foreach(block; blocks)
20047 			foreach(ie; block.parts) {
20048 				if(ie.boundingBox.right > r.right)
20049 					r.right = ie.boundingBox.right;
20050 				if(ie.boundingBox.bottom > r.bottom)
20051 					r.bottom = ie.boundingBox.bottom;
20052 			}
20053 			return r;
20054 		}
20055 
20056 		BlockElement[] getBlocks() {
20057 			return blocks;
20058 		}
20059 
20060 		InlineElement[] getTexts() {
20061 			InlineElement[] elements;
20062 			foreach(block; blocks)
20063 				elements ~= block.parts;
20064 			return elements;
20065 		}
20066 
20067 		string getPlainText() {
20068 			string text;
20069 			foreach(block; blocks)
20070 				foreach(part; block.parts)
20071 					text ~= part.text;
20072 			return text;
20073 		}
20074 
20075 		string getHtml() {
20076 			return null; // FIXME
20077 		}
20078 
20079 		this(Rectangle boundingBox) {
20080 			this.boundingBox = boundingBox;
20081 		}
20082 
20083 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
20084 			auto be = new BlockElement();
20085 			be.containingLayout = this;
20086 			if(after is null)
20087 				blocks ~= be;
20088 			else {
20089 				foreach(idx, b; blocks) {
20090 					if(b is after.containingBlock) {
20091 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
20092 						break;
20093 					}
20094 				}
20095 			}
20096 			return be;
20097 		}
20098 
20099 		void clear() {
20100 			blocks = null;
20101 			selectionStart = selectionEnd = caret = Caret.init;
20102 		}
20103 
20104 		void addText(Args...)(Args args) {
20105 			if(blocks.length == 0)
20106 				addBlock();
20107 
20108 			InlineElement ie = new InlineElement();
20109 			foreach(idx, arg; args) {
20110 				static if(is(typeof(arg) == ForegroundColor))
20111 					ie.color = arg;
20112 				else static if(is(typeof(arg) == TextFormat)) {
20113 					if(arg & 0x8000) // ~TextFormat.something turns it off
20114 						ie.styles &= arg;
20115 					else
20116 						ie.styles |= arg;
20117 				} else static if(is(typeof(arg) == string)) {
20118 					static if(idx == 0 && args.length > 1)
20119 						static assert(0, "Put styles before the string.");
20120 					size_t lastLineIndex;
20121 					foreach(cidx, char a; arg) {
20122 						if(a == '\n') {
20123 							ie.text = arg[lastLineIndex .. cidx + 1];
20124 							lastLineIndex = cidx + 1;
20125 							ie.containingBlock = blocks[$-1];
20126 							blocks[$-1].parts ~= ie.clone;
20127 							ie.text = null;
20128 						} else {
20129 
20130 						}
20131 					}
20132 
20133 					ie.text = arg[lastLineIndex .. $];
20134 					ie.containingBlock = blocks[$-1];
20135 					blocks[$-1].parts ~= ie.clone;
20136 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
20137 				}
20138 			}
20139 
20140 			invalidateLayout();
20141 		}
20142 
20143 		void tryMerge(InlineElement into, InlineElement what) {
20144 			if(!into.isMergeCompatible(what)) {
20145 				return; // cannot merge, different configs
20146 			}
20147 
20148 			// cool, can merge, bring text together...
20149 			into.text ~= what.text;
20150 
20151 			// and remove what
20152 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
20153 				if(what.containingBlock.parts[a] is what) {
20154 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
20155 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
20156 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
20157 
20158 				}
20159 			}
20160 
20161 			// FIXME: ensure no other carets have a reference to it
20162 		}
20163 
20164 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
20165 		TextIdentifyResult identify(int x, int y, bool exact = false) {
20166 			TextIdentifyResult inexactMatch;
20167 			foreach(block; blocks) {
20168 				foreach(part; block.parts) {
20169 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
20170 
20171 						// FIXME binary search
20172 						int tidx;
20173 						int lastX;
20174 						foreach_reverse(idxo, lx; part.letterXs) {
20175 							int idx = cast(int) idxo;
20176 							if(lx <= x) {
20177 								if(lastX && lastX - x < x - lx)
20178 									tidx = idx + 1;
20179 								else
20180 									tidx = idx;
20181 								break;
20182 							}
20183 							lastX = lx;
20184 						}
20185 
20186 						return TextIdentifyResult(part, tidx).fixupNewline;
20187 					} else if(!exact) {
20188 						// we're not in the box, but are we on the same line?
20189 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
20190 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
20191 					}
20192 				}
20193 			}
20194 
20195 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
20196 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
20197 
20198 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
20199 		}
20200 
20201 		void moveCaretToPixelCoordinates(int x, int y) {
20202 			auto result = identify(x, y);
20203 			caret.inlineElement = result.element;
20204 			caret.offset = result.offset;
20205 		}
20206 
20207 		void selectToPixelCoordinates(int x, int y) {
20208 			auto result = identify(x, y);
20209 
20210 			if(y < caretLastDrawnY1) {
20211 				// on a previous line, carat is selectionEnd
20212 				selectionEnd = caret;
20213 
20214 				selectionStart = Caret(this, result.element, result.offset);
20215 			} else if(y > caretLastDrawnY2) {
20216 				// on a later line
20217 				selectionStart = caret;
20218 
20219 				selectionEnd = Caret(this, result.element, result.offset);
20220 			} else {
20221 				// on the same line...
20222 				if(x <= caretLastDrawnX) {
20223 					selectionEnd = caret;
20224 					selectionStart = Caret(this, result.element, result.offset);
20225 				} else {
20226 					selectionStart = caret;
20227 					selectionEnd = Caret(this, result.element, result.offset);
20228 				}
20229 
20230 			}
20231 		}
20232 
20233 
20234 		/// Call this if the inputs change. It will reflow everything
20235 		void redoLayout(ScreenPainter painter) {
20236 			//painter.setClipRectangle(boundingBox);
20237 			auto pos = Point(boundingBox.left, boundingBox.top);
20238 
20239 			int lastHeight;
20240 			void nl() {
20241 				pos.x = boundingBox.left;
20242 				pos.y += lastHeight;
20243 			}
20244 			foreach(block; blocks) {
20245 				nl();
20246 				foreach(part; block.parts) {
20247 					part.letterXs = null;
20248 
20249 					auto size = painter.textSize(part.text);
20250 					version(Windows)
20251 						if(part.text.length && part.text[$-1] == '\n')
20252 							size.height /= 2; // windows counts the new line at the end, but we don't want that
20253 
20254 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
20255 
20256 					foreach(idx, char c; part.text) {
20257 							// FIXME: unicode
20258 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
20259 					}
20260 
20261 					pos.x += size.width;
20262 					if(pos.x >= boundingBox.right) {
20263 						pos.y += size.height;
20264 						pos.x = boundingBox.left;
20265 						lastHeight = 0;
20266 					} else {
20267 						lastHeight = size.height;
20268 					}
20269 
20270 					if(part.text.length && part.text[$-1] == '\n')
20271 						nl();
20272 				}
20273 			}
20274 
20275 			layoutInvalidated = false;
20276 		}
20277 
20278 		bool layoutInvalidated = true;
20279 		void invalidateLayout() {
20280 			layoutInvalidated = true;
20281 		}
20282 
20283 // FIXME: caret can remain sometimes when inserting
20284 // FIXME: inserting at the beginning once you already have something can eff it up.
20285 		void drawInto(ScreenPainter painter, bool focused = false) {
20286 			if(layoutInvalidated)
20287 				redoLayout(painter);
20288 			foreach(block; blocks) {
20289 				foreach(part; block.parts) {
20290 					painter.outlineColor = part.color;
20291 					painter.fillColor = part.backgroundColor;
20292 
20293 					auto pos = part.boundingBox.upperLeft;
20294 					auto size = part.boundingBox.size;
20295 
20296 					painter.drawText(pos, part.text);
20297 					if(part.styles & TextFormat.underline)
20298 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
20299 					if(part.styles & TextFormat.strikethrough)
20300 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
20301 				}
20302 			}
20303 
20304 			// on every redraw, I will force the caret to be
20305 			// redrawn too, in order to eliminate perceived lag
20306 			// when moving around with the mouse.
20307 			eraseCaret(painter);
20308 
20309 			if(focused) {
20310 				highlightSelection(painter);
20311 				drawCaret(painter);
20312 			}
20313 		}
20314 
20315 		Color selectionXorColor = Color(255, 255, 127);
20316 
20317 		void highlightSelection(ScreenPainter painter) {
20318 			if(selectionStart is selectionEnd)
20319 				return; // no selection
20320 
20321 			if(selectionStart.inlineElement is null) return;
20322 			if(selectionEnd.inlineElement is null) return;
20323 
20324 			assert(selectionStart.inlineElement !is null);
20325 			assert(selectionEnd.inlineElement !is null);
20326 
20327 			painter.rasterOp = RasterOp.xor;
20328 			painter.outlineColor = Color.transparent;
20329 			painter.fillColor = selectionXorColor;
20330 
20331 			auto at = selectionStart.inlineElement;
20332 			auto atOffset = selectionStart.offset;
20333 			bool done;
20334 			while(at) {
20335 				auto box = at.boundingBox;
20336 				if(atOffset < at.letterXs.length)
20337 					box.left = at.letterXs[atOffset];
20338 
20339 				if(at is selectionEnd.inlineElement) {
20340 					if(selectionEnd.offset < at.letterXs.length)
20341 						box.right = at.letterXs[selectionEnd.offset];
20342 					done = true;
20343 				}
20344 
20345 				painter.drawRectangle(box.upperLeft, box.width, box.height);
20346 
20347 				if(done)
20348 					break;
20349 
20350 				at = at.getNextInlineElement();
20351 				atOffset = 0;
20352 			}
20353 		}
20354 
20355 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
20356 		bool caretShowingOnScreen = false;
20357 		void drawCaret(ScreenPainter painter) {
20358 			//painter.setClipRectangle(boundingBox);
20359 			int x, y1, y2;
20360 			if(caret.inlineElement is null) {
20361 				x = boundingBox.left;
20362 				y1 = boundingBox.top + 2;
20363 				y2 = boundingBox.top + painter.fontHeight;
20364 			} else {
20365 				x = caret.inlineElement.xOfIndex(caret.offset);
20366 				y1 = caret.inlineElement.boundingBox.top + 2;
20367 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20368 			}
20369 
20370 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
20371 				eraseCaret(painter);
20372 
20373 			painter.pen = Pen(Color.white, 1);
20374 			painter.rasterOp = RasterOp.xor;
20375 			painter.drawLine(
20376 				Point(x, y1),
20377 				Point(x, y2)
20378 			);
20379 			painter.rasterOp = RasterOp.normal;
20380 			caretShowingOnScreen = !caretShowingOnScreen;
20381 
20382 			if(caretShowingOnScreen) {
20383 				caretLastDrawnX = x;
20384 				caretLastDrawnY1 = y1;
20385 				caretLastDrawnY2 = y2;
20386 			}
20387 		}
20388 
20389 		Rectangle caretBoundingBox() {
20390 			int x, y1, y2;
20391 			if(caret.inlineElement is null) {
20392 				x = boundingBox.left;
20393 				y1 = boundingBox.top + 2;
20394 				y2 = boundingBox.top + 16;
20395 			} else {
20396 				x = caret.inlineElement.xOfIndex(caret.offset);
20397 				y1 = caret.inlineElement.boundingBox.top + 2;
20398 				y2 = caret.inlineElement.boundingBox.bottom - 2;
20399 			}
20400 
20401 			return Rectangle(x, y1, x + 1, y2);
20402 		}
20403 
20404 		void eraseCaret(ScreenPainter painter) {
20405 			//painter.setClipRectangle(boundingBox);
20406 			if(!caretShowingOnScreen) return;
20407 			painter.pen = Pen(Color.white, 1);
20408 			painter.rasterOp = RasterOp.xor;
20409 			painter.drawLine(
20410 				Point(caretLastDrawnX, caretLastDrawnY1),
20411 				Point(caretLastDrawnX, caretLastDrawnY2)
20412 			);
20413 
20414 			caretShowingOnScreen = false;
20415 			painter.rasterOp = RasterOp.normal;
20416 		}
20417 
20418 		/// Caret movement api
20419 		/// These should give the user a logical result based on what they see on screen...
20420 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
20421 		void moveUp() {
20422 			if(caret.inlineElement is null) return;
20423 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20424 			auto y = caret.inlineElement.boundingBox.top + 2;
20425 
20426 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20427 			if(y < 0)
20428 				return;
20429 
20430 			auto i = identify(x, y);
20431 
20432 			if(i.element) {
20433 				caret.inlineElement = i.element;
20434 				caret.offset = i.offset;
20435 			}
20436 		}
20437 		void moveDown() {
20438 			if(caret.inlineElement is null) return;
20439 			auto x = caret.inlineElement.xOfIndex(caret.offset);
20440 			auto y = caret.inlineElement.boundingBox.bottom - 2;
20441 
20442 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
20443 
20444 			auto i = identify(x, y);
20445 			if(i.element) {
20446 				caret.inlineElement = i.element;
20447 				caret.offset = i.offset;
20448 			}
20449 		}
20450 		void moveLeft() {
20451 			if(caret.inlineElement is null) return;
20452 			if(caret.offset)
20453 				caret.offset--;
20454 			else {
20455 				auto p = caret.inlineElement.getPreviousInlineElement();
20456 				if(p) {
20457 					caret.inlineElement = p;
20458 					if(p.text.length && p.text[$-1] == '\n')
20459 						caret.offset = cast(int) p.text.length - 1;
20460 					else
20461 						caret.offset = cast(int) p.text.length;
20462 				}
20463 			}
20464 		}
20465 		void moveRight() {
20466 			if(caret.inlineElement is null) return;
20467 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
20468 				caret.offset++;
20469 			} else {
20470 				auto p = caret.inlineElement.getNextInlineElement();
20471 				if(p) {
20472 					caret.inlineElement = p;
20473 					caret.offset = 0;
20474 				}
20475 			}
20476 		}
20477 		void moveHome() {
20478 			if(caret.inlineElement is null) return;
20479 			auto x = 0;
20480 			auto y = caret.inlineElement.boundingBox.top + 2;
20481 
20482 			auto i = identify(x, y);
20483 
20484 			if(i.element) {
20485 				caret.inlineElement = i.element;
20486 				caret.offset = i.offset;
20487 			}
20488 		}
20489 		void moveEnd() {
20490 			if(caret.inlineElement is null) return;
20491 			auto x = int.max;
20492 			auto y = caret.inlineElement.boundingBox.top + 2;
20493 
20494 			auto i = identify(x, y);
20495 
20496 			if(i.element) {
20497 				caret.inlineElement = i.element;
20498 				caret.offset = i.offset;
20499 			}
20500 
20501 		}
20502 		void movePageUp(ref Caret caret) {}
20503 		void movePageDown(ref Caret caret) {}
20504 
20505 		void moveDocumentStart(ref Caret caret) {
20506 			if(blocks.length && blocks[0].parts.length)
20507 				caret = Caret(this, blocks[0].parts[0], 0);
20508 			else
20509 				caret = Caret.init;
20510 		}
20511 
20512 		void moveDocumentEnd(ref Caret caret) {
20513 			if(blocks.length) {
20514 				auto parts = blocks[$-1].parts;
20515 				if(parts.length) {
20516 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
20517 				} else {
20518 					caret = Caret.init;
20519 				}
20520 			} else
20521 				caret = Caret.init;
20522 		}
20523 
20524 		void deleteSelection() {
20525 			if(selectionStart is selectionEnd)
20526 				return;
20527 
20528 			if(selectionStart.inlineElement is null) return;
20529 			if(selectionEnd.inlineElement is null) return;
20530 
20531 			assert(selectionStart.inlineElement !is null);
20532 			assert(selectionEnd.inlineElement !is null);
20533 
20534 			auto at = selectionStart.inlineElement;
20535 
20536 			if(selectionEnd.inlineElement is at) {
20537 				// same element, need to chop out
20538 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
20539 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
20540 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
20541 			} else {
20542 				// different elements, we can do it with slicing
20543 				at.text = at.text[0 .. selectionStart.offset];
20544 				if(selectionStart.offset < at.letterXs.length)
20545 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
20546 
20547 				at = at.getNextInlineElement();
20548 
20549 				while(at) {
20550 					if(at is selectionEnd.inlineElement) {
20551 						at.text = at.text[selectionEnd.offset .. $];
20552 						if(selectionEnd.offset < at.letterXs.length)
20553 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
20554 						selectionEnd.offset = 0;
20555 						break;
20556 					} else {
20557 						auto cfd = at;
20558 						cfd.text = null; // delete the whole thing
20559 
20560 						at = at.getNextInlineElement();
20561 
20562 						if(cfd.text.length == 0) {
20563 							// and remove cfd
20564 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
20565 								if(cfd.containingBlock.parts[a] is cfd) {
20566 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
20567 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
20568 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
20569 
20570 								}
20571 							}
20572 						}
20573 					}
20574 				}
20575 			}
20576 
20577 			caret = selectionEnd;
20578 			selectNone();
20579 
20580 			invalidateLayout();
20581 
20582 		}
20583 
20584 		/// Plain text editing api. These work at the current caret inside the selected inline element.
20585 		void insert(in char[] text) {
20586 			foreach(dchar ch; text)
20587 				insert(ch);
20588 		}
20589 		/// ditto
20590 		void insert(dchar ch) {
20591 
20592 			bool selectionDeleted = false;
20593 			if(selectionStart !is selectionEnd) {
20594 				deleteSelection();
20595 				selectionDeleted = true;
20596 			}
20597 
20598 			if(ch == 127) {
20599 				delete_();
20600 				return;
20601 			}
20602 			if(ch == 8) {
20603 				if(!selectionDeleted)
20604 					backspace();
20605 				return;
20606 			}
20607 
20608 			invalidateLayout();
20609 
20610 			if(ch == 13) ch = 10;
20611 			auto e = caret.inlineElement;
20612 			if(e is null) {
20613 				addText("" ~ cast(char) ch) ; // FIXME
20614 				return;
20615 			}
20616 
20617 			if(caret.offset == e.text.length) {
20618 				e.text ~= cast(char) ch; // FIXME
20619 				caret.offset++;
20620 				if(ch == 10) {
20621 					auto c = caret.inlineElement.clone;
20622 					c.text = null;
20623 					c.letterXs = null;
20624 					insertPartAfter(c,e);
20625 					caret = Caret(this, c, 0);
20626 				}
20627 			} else {
20628 				// FIXME cast char sucks
20629 				if(ch == 10) {
20630 					auto c = caret.inlineElement.clone;
20631 					c.text = e.text[caret.offset .. $];
20632 					if(caret.offset < c.letterXs.length)
20633 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
20634 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
20635 					if(caret.offset <= e.letterXs.length) {
20636 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
20637 					}
20638 					insertPartAfter(c,e);
20639 					caret = Caret(this, c, 0);
20640 				} else {
20641 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
20642 					caret.offset++;
20643 				}
20644 			}
20645 		}
20646 
20647 		void insertPartAfter(InlineElement what, InlineElement where) {
20648 			foreach(idx, p; where.containingBlock.parts) {
20649 				if(p is where) {
20650 					if(idx + 1 == where.containingBlock.parts.length)
20651 						where.containingBlock.parts ~= what;
20652 					else
20653 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
20654 					return;
20655 				}
20656 			}
20657 		}
20658 
20659 		void cleanupStructures() {
20660 			for(size_t i = 0; i < blocks.length; i++) {
20661 				auto block = blocks[i];
20662 				for(size_t a = 0; a < block.parts.length; a++) {
20663 					auto part = block.parts[a];
20664 					if(part.text.length == 0) {
20665 						for(size_t b = a; b < block.parts.length - 1; b++)
20666 							block.parts[b] = block.parts[b+1];
20667 						block.parts = block.parts[0 .. $-1];
20668 					}
20669 				}
20670 				if(block.parts.length == 0) {
20671 					for(size_t a = i; a < blocks.length - 1; a++)
20672 						blocks[a] = blocks[a+1];
20673 					blocks = blocks[0 .. $-1];
20674 				}
20675 			}
20676 		}
20677 
20678 		void backspace() {
20679 			try_again:
20680 			auto e = caret.inlineElement;
20681 			if(e is null)
20682 				return;
20683 			if(caret.offset == 0) {
20684 				auto prev = e.getPreviousInlineElement();
20685 				if(prev is null)
20686 					return;
20687 				auto newOffset = cast(int) prev.text.length;
20688 				tryMerge(prev, e);
20689 				caret.inlineElement = prev;
20690 				caret.offset = prev is null ? 0 : newOffset;
20691 
20692 				goto try_again;
20693 			} else if(caret.offset == e.text.length) {
20694 				e.text = e.text[0 .. $-1];
20695 				caret.offset--;
20696 			} else {
20697 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
20698 				caret.offset--;
20699 			}
20700 			//cleanupStructures();
20701 
20702 			invalidateLayout();
20703 		}
20704 		void delete_() {
20705 			if(selectionStart !is selectionEnd)
20706 				deleteSelection();
20707 			else {
20708 				auto before = caret;
20709 				moveRight();
20710 				if(caret != before) {
20711 					backspace();
20712 				}
20713 			}
20714 
20715 			invalidateLayout();
20716 		}
20717 		void overstrike() {}
20718 
20719 		/// Selection API. See also: caret movement.
20720 		void selectAll() {
20721 			moveDocumentStart(selectionStart);
20722 			moveDocumentEnd(selectionEnd);
20723 		}
20724 		bool selectNone() {
20725 			if(selectionStart != selectionEnd) {
20726 				selectionStart = selectionEnd = Caret.init;
20727 				return true;
20728 			}
20729 			return false;
20730 		}
20731 
20732 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
20733 		/// They will modify the current selection if there is one and will splice one in if needed.
20734 		void changeAttributes() {}
20735 
20736 
20737 		/// Text search api. They manipulate the selection and/or caret.
20738 		void findText(string text) {}
20739 		void findIndex(size_t textIndex) {}
20740 
20741 		// sample event handlers
20742 
20743 		void handleEvent(KeyEvent event) {
20744 			//if(event.type == KeyEvent.Type.KeyPressed) {
20745 
20746 			//}
20747 		}
20748 
20749 		void handleEvent(dchar ch) {
20750 
20751 		}
20752 
20753 		void handleEvent(MouseEvent event) {
20754 
20755 		}
20756 
20757 		bool contentEditable; // can it be edited?
20758 		bool contentCaretable; // is there a caret/cursor that moves around in there?
20759 		bool contentSelectable; // selectable?
20760 
20761 		Caret caret;
20762 		Caret selectionStart;
20763 		Caret selectionEnd;
20764 
20765 		bool insertMode;
20766 	}
20767 
20768 	struct Caret {
20769 		TextLayout layout;
20770 		InlineElement inlineElement;
20771 		int offset;
20772 	}
20773 
20774 	enum TextFormat : ushort {
20775 		// decorations
20776 		underline = 1,
20777 		strikethrough = 2,
20778 
20779 		// font selectors
20780 
20781 		bold = 0x4000 | 1, // weight 700
20782 		light = 0x4000 | 2, // weight 300
20783 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
20784 		// bold | light is really invalid but should give weight 500
20785 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
20786 
20787 		italic = 0x4000 | 8,
20788 		smallcaps = 0x4000 | 16,
20789 	}
20790 
20791 	void* findFont(string family, int weight, TextFormat formats) {
20792 		return null;
20793 	}
20794 
20795 }
20796 
20797 /++
20798 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20799 
20800 	History:
20801 		Added February 19, 2021
20802 +/
20803 /// Group: drag_and_drop
20804 interface DropHandler {
20805 	/++
20806 		Called when the drag enters the handler's area.
20807 	+/
20808 	DragAndDropAction dragEnter(DropPackage*);
20809 	/++
20810 		Called when the drag leaves the handler's area or is
20811 		cancelled. You should free your resources when this is called.
20812 	+/
20813 	void dragLeave();
20814 	/++
20815 		Called continually as the drag moves over the handler's area.
20816 
20817 		Returns: feedback to the dragger
20818 	+/
20819 	DropParameters dragOver(Point pt);
20820 	/++
20821 		The user dropped the data and you should process it now. You can
20822 		access the data through the given [DropPackage].
20823 	+/
20824 	void drop(scope DropPackage*);
20825 	/++
20826 		Called when the drop is complete. You should free whatever temporary
20827 		resources you were using. It is often reasonable to simply forward
20828 		this call to [dragLeave].
20829 	+/
20830 	void finish();
20831 
20832 	/++
20833 		Parameters returned by [DropHandler.drop].
20834 	+/
20835 	static struct DropParameters {
20836 		/++
20837 			Acceptable action over this area.
20838 		+/
20839 		DragAndDropAction action;
20840 		/++
20841 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
20842 
20843 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
20844 		+/
20845 		Rectangle consistentWithin;
20846 	}
20847 }
20848 
20849 /++
20850 	History:
20851 		Added February 19, 2021
20852 +/
20853 /// Group: drag_and_drop
20854 enum DragAndDropAction {
20855 	none = 0,
20856 	copy,
20857 	move,
20858 	link,
20859 	ask,
20860 	custom
20861 }
20862 
20863 /++
20864 	An opaque structure representing dropped data. It contains
20865 	private, platform-specific data that your `drop` function
20866 	should simply forward.
20867 
20868 	$(PITFALL This is not yet stable and may break in future versions without notice.)
20869 
20870 	History:
20871 		Added February 19, 2021
20872 +/
20873 /// Group: drag_and_drop
20874 struct DropPackage {
20875 	/++
20876 		Lists the available formats as magic numbers. You should compare these
20877 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
20878 		understand the passed data.
20879 	+/
20880 	DraggableData.FormatId[] availableFormats() {
20881 		version(X11) {
20882 			return xFormats;
20883 		} else version(Windows) {
20884 			if(pDataObj is null)
20885 				return null;
20886 
20887 			typeof(return) ret;
20888 
20889 			IEnumFORMATETC ef;
20890 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
20891 				FORMATETC fmt;
20892 				ULONG fetched;
20893 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
20894 					if(fetched == 0)
20895 						break;
20896 
20897 					if(fmt.lindex != -1)
20898 						continue;
20899 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
20900 						continue;
20901 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
20902 						continue;
20903 
20904 					ret ~= fmt.cfFormat;
20905 				}
20906 			}
20907 
20908 			return ret;
20909 		} else throw new NotYetImplementedException();
20910 	}
20911 
20912 	/++
20913 		Gets data from the drop and optionally accepts it.
20914 
20915 		Returns:
20916 			void because the data is fed asynchronously through the `dg` parameter.
20917 
20918 		Params:
20919 			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.
20920 
20921 			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.
20922 
20923 			Calling `getData` again after accepting a drop is not permitted.
20924 
20925 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
20926 
20927 			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.
20928 
20929 		Throws:
20930 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
20931 
20932 		History:
20933 			Included in first release of [DropPackage].
20934 	+/
20935 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
20936 		version(X11) {
20937 
20938 			auto display = XDisplayConnection.get();
20939 			auto selectionAtom = GetAtom!"XdndSelection"(display);
20940 			auto best = format;
20941 
20942 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
20943 
20944 				XDisplay* display;
20945 				Atom selectionAtom;
20946 				DraggableData.FormatId best;
20947 				DraggableData.FormatId format;
20948 				void delegate(scope ubyte[] data) dg;
20949 				DragAndDropAction acceptedAction;
20950 				Window sourceWindow;
20951 				SimpleWindow win;
20952 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
20953 					this.display = display;
20954 					this.win = win;
20955 					this.sourceWindow = sourceWindow;
20956 					this.format = format;
20957 					this.selectionAtom = selectionAtom;
20958 					this.best = best;
20959 					this.dg = dg;
20960 					this.acceptedAction = acceptedAction;
20961 				}
20962 
20963 
20964 				mixin X11GetSelectionHandler_Basics;
20965 
20966 				void handleData(Atom target, in ubyte[] data) {
20967 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
20968 
20969 					dg(cast(ubyte[]) data);
20970 
20971 					if(acceptedAction != DragAndDropAction.none) {
20972 						auto display = XDisplayConnection.get;
20973 
20974 						XClientMessageEvent xclient;
20975 
20976 						xclient.type = EventType.ClientMessage;
20977 						xclient.window = sourceWindow;
20978 						xclient.message_type = GetAtom!"XdndFinished"(display);
20979 						xclient.format = 32;
20980 						xclient.data.l[0] = win.impl.window;
20981 						xclient.data.l[1] = 1; // drop successful
20982 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
20983 
20984 						XSendEvent(
20985 							display,
20986 							sourceWindow,
20987 							false,
20988 							EventMask.NoEventMask,
20989 							cast(XEvent*) &xclient
20990 						);
20991 
20992 						XFlush(display);
20993 					}
20994 				}
20995 
20996 				Atom findBestFormat(Atom[] answer) {
20997 					Atom best = None;
20998 					foreach(option; answer) {
20999 						if(option == format) {
21000 							best = option;
21001 							break;
21002 						}
21003 						/*
21004 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
21005 							best = option;
21006 							break;
21007 						} else if(option == XA_STRING) {
21008 							best = option;
21009 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
21010 							best = option;
21011 						}
21012 						*/
21013 					}
21014 					return best;
21015 				}
21016 			}
21017 
21018 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
21019 
21020 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
21021 
21022 		} else version(Windows) {
21023 
21024 			// clean up like DragLeave
21025 			// pass effect back up
21026 
21027 			FORMATETC t;
21028 			assert(format >= 0 && format <= ushort.max);
21029 			t.cfFormat = cast(ushort) format;
21030 			t.lindex = -1;
21031 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21032 			t.tymed = TYMED.TYMED_HGLOBAL;
21033 
21034 			STGMEDIUM m;
21035 
21036 			if(pDataObj.GetData(&t, &m) != S_OK) {
21037 				// fail
21038 			} else {
21039 				// succeed, take the data and clean up
21040 
21041 				// FIXME: ensure it is legit HGLOBAL
21042 				auto handle = m.hGlobal;
21043 
21044 				if(handle) {
21045 					auto sz = GlobalSize(handle);
21046 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
21047 						scope(exit) GlobalUnlock(handle);
21048 						scope(exit) GlobalFree(handle);
21049 
21050 						auto data = ptr[0 .. sz];
21051 
21052 						dg(data);
21053 					}
21054 				}
21055 			}
21056 		}
21057 	}
21058 
21059 	private:
21060 
21061 	version(X11) {
21062 		SimpleWindow win;
21063 		Window sourceWindow;
21064 		Time dataTimestamp;
21065 
21066 		Atom[] xFormats;
21067 	}
21068 	version(Windows) {
21069 		IDataObject pDataObj;
21070 	}
21071 }
21072 
21073 /++
21074 	A generic helper base class for making a drop handler with a preference list of custom types.
21075 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
21076 	droppers too.
21077 
21078 	It assumes the whole window it used, but you can subclass to change that.
21079 
21080 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21081 
21082 	History:
21083 		Added February 19, 2021
21084 +/
21085 /// Group: drag_and_drop
21086 class GenericDropHandlerBase : DropHandler {
21087 	// no fancy state here so no need to do anything here
21088 	void finish() { }
21089 	void dragLeave() { }
21090 
21091 	private DragAndDropAction acceptedAction;
21092 	private DraggableData.FormatId acceptedFormat;
21093 	private void delegate(scope ubyte[]) acceptedHandler;
21094 
21095 	struct FormatHandler {
21096 		DraggableData.FormatId format;
21097 		void delegate(scope ubyte[]) handler;
21098 	}
21099 
21100 	protected abstract FormatHandler[] formatHandlers();
21101 
21102 	DragAndDropAction dragEnter(DropPackage* pkg) {
21103 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
21104 		foreach(fmt; formatHandlers())
21105 		foreach(f; pkg.availableFormats())
21106 			if(f == fmt.format) {
21107 				acceptedFormat = f;
21108 				acceptedHandler = fmt.handler;
21109 				return acceptedAction = DragAndDropAction.copy;
21110 			}
21111 		return acceptedAction = DragAndDropAction.none;
21112 	}
21113 	DropParameters dragOver(Point pt) {
21114 		return DropParameters(acceptedAction);
21115 	}
21116 
21117 	void drop(scope DropPackage* dropPackage) {
21118 		if(!acceptedFormat || acceptedHandler is null) {
21119 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
21120 			return; // prolly shouldn't happen anyway...
21121 		}
21122 
21123 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
21124 	}
21125 }
21126 
21127 /++
21128 	A simple handler for making your window accept drops of plain text.
21129 
21130 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21131 
21132 	History:
21133 		Added February 22, 2021
21134 +/
21135 /// Group: drag_and_drop
21136 class TextDropHandler : GenericDropHandlerBase {
21137 	private void delegate(in char[] text) dg;
21138 
21139 	/++
21140 
21141 	+/
21142 	this(void delegate(in char[] text) dg) {
21143 		this.dg = dg;
21144 	}
21145 
21146 	protected override FormatHandler[] formatHandlers() {
21147 		version(X11)
21148 			return [
21149 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21150 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21151 			];
21152 		else version(Windows)
21153 			return [
21154 				FormatHandler(CF_UNICODETEXT, &translator),
21155 			];
21156 		else throw new NotYetImplementedException();
21157 	}
21158 
21159 	private void translator(scope ubyte[] data) {
21160 		version(X11)
21161 			dg(cast(char[]) data);
21162 		else version(Windows)
21163 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21164 	}
21165 }
21166 
21167 /++
21168 	A simple handler for making your window accept drops of files, issued to you as file names.
21169 
21170 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21171 
21172 	History:
21173 		Added February 22, 2021
21174 +/
21175 /// Group: drag_and_drop
21176 
21177 class FilesDropHandler : GenericDropHandlerBase {
21178 	private void delegate(in char[][]) dg;
21179 
21180 	/++
21181 
21182 	+/
21183 	this(void delegate(in char[][] fileNames) dg) {
21184 		this.dg = dg;
21185 	}
21186 
21187 	protected override FormatHandler[] formatHandlers() {
21188 		version(X11)
21189 			return [
21190 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
21191 			];
21192 		else version(Windows)
21193 			return [
21194 				FormatHandler(CF_HDROP, &translator),
21195 			];
21196 		else throw new NotYetImplementedException();
21197 	}
21198 
21199 	private void translator(scope ubyte[] data) {
21200 		version(X11) {
21201 			char[] listString = cast(char[]) data;
21202 			char[][16] buffer;
21203 			int count;
21204 			char[][] result = buffer[];
21205 
21206 			void commit(char[] s) {
21207 				if(count == result.length)
21208 					result.length += 16;
21209 				if(s.length > 7 && s[0 ..7] == "file://")
21210 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
21211 				result[count++] = s;
21212 			}
21213 
21214 			size_t last;
21215 			foreach(idx, char c; listString) {
21216 				if(c == '\n') {
21217 					commit(listString[last .. idx - 1]); // a \r
21218 					last = idx + 1; // a \n
21219 				}
21220 			}
21221 
21222 			if(last < listString.length) {
21223 				commit(listString[last .. $]);
21224 			}
21225 
21226 			// FIXME: they are uris now, should I translate it to local file names?
21227 			// of course the host name is supposed to be there cuz of X rokking...
21228 
21229 			dg(result[0 .. count]);
21230 		} else version(Windows) {
21231 
21232 			static struct DROPFILES {
21233 				DWORD pFiles;
21234 				POINT pt;
21235 				BOOL  fNC;
21236 				BOOL  fWide;
21237 			}
21238 
21239 
21240 			const(char)[][16] buffer;
21241 			int count;
21242 			const(char)[][] result = buffer[];
21243 			size_t last;
21244 
21245 			void commitA(in char[] stuff) {
21246 				if(count == result.length)
21247 					result.length += 16;
21248 				result[count++] = stuff;
21249 			}
21250 
21251 			void commitW(in wchar[] stuff) {
21252 				commitA(makeUtf8StringFromWindowsString(stuff));
21253 			}
21254 
21255 			void magic(T)(T chars) {
21256 				size_t idx;
21257 				while(chars[idx]) {
21258 					last = idx;
21259 					while(chars[idx]) {
21260 						idx++;
21261 					}
21262 					static if(is(T == char*))
21263 						commitA(chars[last .. idx]);
21264 					else
21265 						commitW(chars[last .. idx]);
21266 					idx++;
21267 				}
21268 			}
21269 
21270 			auto df = cast(DROPFILES*) data.ptr;
21271 			if(df.fWide) {
21272 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
21273 				magic(chars);
21274 			} else {
21275 				char* chars = cast(char*) (data.ptr + df.pFiles);
21276 				magic(chars);
21277 			}
21278 			dg(result[0 .. count]);
21279 		}
21280 		else throw new NotYetImplementedException();
21281 	}
21282 }
21283 
21284 /++
21285 	Interface to describe data being dragged. See also [draggable] helper function.
21286 
21287 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21288 
21289 	History:
21290 		Added February 19, 2021
21291 +/
21292 interface DraggableData {
21293 	version(X11)
21294 		alias FormatId = Atom;
21295 	else
21296 		alias FormatId = uint;
21297 	/++
21298 		Gets the platform-specific FormatId associated with the given named format.
21299 
21300 		This may be a MIME type, but may also be other various strings defined by the
21301 		programs you want to interoperate with.
21302 
21303 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
21304 		and convert it to some particular type for you.
21305 	+/
21306 	static FormatId getFormatId(string name)() {
21307 		version(X11)
21308 			return GetAtom!name(XDisplayConnection.get);
21309 		else version(Windows) {
21310 			static UINT cache;
21311 			if(!cache)
21312 				cache = RegisterClipboardFormatA(name);
21313 			return cache;
21314 		} else
21315 			throw new NotYetImplementedException();
21316 	}
21317 
21318 	/++
21319 		Looks up a string to represent the name for the given format, if there is one.
21320 
21321 		You should avoid using this function because it is slow. It is provided more for
21322 		debugging than for primary use.
21323 	+/
21324 	static string getFormatName(FormatId format) {
21325 		version(X11) {
21326 			if(format == 0)
21327 				return "None";
21328 			else
21329 				return getAtomName(format, XDisplayConnection.get);
21330 		} else version(Windows) {
21331 			switch(format) {
21332 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
21333 				case CF_DIBV5: return "CF_DIBV5";
21334 				case CF_RIFF: return "CF_RIFF";
21335 				case CF_WAVE: return "CF_WAVE";
21336 				case CF_HDROP: return "CF_HDROP";
21337 				default:
21338 					char[1024] name;
21339 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
21340 					return name[0 .. count].idup;
21341 			}
21342 		} else throw new NotYetImplementedException();
21343 	}
21344 
21345 	FormatId[] availableFormats();
21346 	// Return the slice of data you filled, empty slice if done.
21347 	// this is to support the incremental thing
21348 	ubyte[] getData(FormatId format, return scope ubyte[] data);
21349 
21350 	size_t dataLength(FormatId format);
21351 }
21352 
21353 /++
21354 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21355 
21356 	History:
21357 		Added February 19, 2021
21358 +/
21359 DraggableData draggable(string s) {
21360 	version(X11)
21361 	return new class X11SetSelectionHandler_Text, DraggableData {
21362 		this() {
21363 			super(s);
21364 		}
21365 
21366 		override FormatId[] availableFormats() {
21367 			return X11SetSelectionHandler_Text.availableFormats();
21368 		}
21369 
21370 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
21371 			return X11SetSelectionHandler_Text.getData(format, data);
21372 		}
21373 
21374 		size_t dataLength(FormatId format) {
21375 			return s.length;
21376 		}
21377 	};
21378 	else version(Windows)
21379 	return new class DraggableData {
21380 		FormatId[] availableFormats() {
21381 			return [CF_UNICODETEXT];
21382 		}
21383 
21384 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
21385 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
21386 		}
21387 
21388 		size_t dataLength(FormatId format) {
21389 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
21390 		}
21391 	};
21392 	else
21393 	throw new NotYetImplementedException();
21394 }
21395 
21396 /++
21397 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21398 
21399 	History:
21400 		Added February 19, 2021
21401 +/
21402 /// Group: drag_and_drop
21403 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
21404 in {
21405 	assert(window !is null);
21406 	assert(handler !is null);
21407 }
21408 do
21409 {
21410 	version(X11) {
21411 		auto sh = cast(X11SetSelectionHandler) handler;
21412 		if(sh is null) {
21413 			// gotta make my own adapter.
21414 			sh = new class X11SetSelectionHandler {
21415 				mixin X11SetSelectionHandler_Basics;
21416 
21417 				Atom[] availableFormats() { return handler.availableFormats(); }
21418 				ubyte[] getData(Atom format, return scope ubyte[] data) {
21419 					return handler.getData(format, data);
21420 				}
21421 
21422 				// since the drop selection is only ever used once it isn't important
21423 				// to reset it.
21424 				void done() {}
21425 			};
21426 		}
21427 		return doDragDropX11(window, sh, action);
21428 	} else version(Windows) {
21429 		return doDragDropWindows(window, handler, action);
21430 	} else throw new NotYetImplementedException();
21431 }
21432 
21433 version(Windows)
21434 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
21435 	IDataObject obj = new class IDataObject {
21436 		ULONG refCount;
21437 		ULONG AddRef() {
21438 			return ++refCount;
21439 		}
21440 		ULONG Release() {
21441 			return --refCount;
21442 		}
21443 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21444 			if (IID_IUnknown == *riid) {
21445 				*ppv = cast(void*) cast(IUnknown) this;
21446 			}
21447 			else if (IID_IDataObject == *riid) {
21448 				*ppv = cast(void*) cast(IDataObject) this;
21449 			}
21450 			else {
21451 				*ppv = null;
21452 				return E_NOINTERFACE;
21453 			}
21454 
21455 			AddRef();
21456 			return NOERROR;
21457 		}
21458 
21459 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
21460 			//  writeln("Advise");
21461 			return E_NOTIMPL;
21462 		}
21463 		HRESULT DUnadvise(DWORD dwConnection) {
21464 			return E_NOTIMPL;
21465 		}
21466 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
21467 			//  writeln("EnumDAdvise");
21468 			return OLE_E_ADVISENOTSUPPORTED;
21469 		}
21470 		// tell what formats it supports
21471 
21472 		FORMATETC[] types;
21473 		this() {
21474 			FORMATETC t;
21475 			foreach(ty; handler.availableFormats()) {
21476 				assert(ty <= ushort.max && ty >= 0);
21477 				t.cfFormat = cast(ushort) ty;
21478 				t.lindex = -1;
21479 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21480 				t.tymed = TYMED.TYMED_HGLOBAL;
21481 			}
21482 			types ~= t;
21483 		}
21484 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
21485 			if(dwDirection == DATADIR.DATADIR_GET) {
21486 				*ppenumFormatEtc = new class IEnumFORMATETC {
21487 					ULONG refCount;
21488 					ULONG AddRef() {
21489 						return ++refCount;
21490 					}
21491 					ULONG Release() {
21492 						return --refCount;
21493 					}
21494 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21495 						if (IID_IUnknown == *riid) {
21496 							*ppv = cast(void*) cast(IUnknown) this;
21497 						}
21498 						else if (IID_IEnumFORMATETC == *riid) {
21499 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
21500 						}
21501 						else {
21502 							*ppv = null;
21503 							return E_NOINTERFACE;
21504 						}
21505 
21506 						AddRef();
21507 						return NOERROR;
21508 					}
21509 
21510 
21511 					int pos;
21512 					this() {
21513 						pos = 0;
21514 					}
21515 
21516 					HRESULT Clone(IEnumFORMATETC* ppenum) {
21517 						// writeln("clone");
21518 						return E_NOTIMPL; // FIXME
21519 					}
21520 
21521 					// Caller is responsible for freeing memory
21522 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
21523 						// fetched may be null if celt is one
21524 						if(celt != 1)
21525 							return E_NOTIMPL; // FIXME
21526 
21527 						if(celt + pos > types.length)
21528 							return S_FALSE;
21529 
21530 						*rgelt = types[pos++];
21531 
21532 						if(pceltFetched !is null)
21533 							*pceltFetched = 1;
21534 
21535 						// writeln("ok celt ", celt);
21536 						return S_OK;
21537 					}
21538 
21539 					HRESULT Reset() {
21540 						pos = 0;
21541 						return S_OK;
21542 					}
21543 
21544 					HRESULT Skip(ULONG celt) {
21545 						if(celt + pos <= types.length) {
21546 							pos += celt;
21547 							return S_OK;
21548 						}
21549 						return S_FALSE;
21550 					}
21551 				};
21552 
21553 				return S_OK;
21554 			} else
21555 				return E_NOTIMPL;
21556 		}
21557 		// given a format, return the format you'd prefer to use cuz it is identical
21558 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
21559 			// FIXME: prolly could be better but meh
21560 			// writeln("gcf: ", *pformatectIn);
21561 			*pformatetcOut = *pformatectIn;
21562 			return S_OK;
21563 		}
21564 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21565 			foreach(ty; types) {
21566 				if(ty == *pformatetcIn) {
21567 					auto format = ty.cfFormat;
21568 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
21569 					STGMEDIUM medium;
21570 					medium.tymed = TYMED.TYMED_HGLOBAL;
21571 
21572 					auto sz = handler.dataLength(format);
21573 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
21574 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
21575 					if(auto data = cast(wchar*) GlobalLock(handle)) {
21576 						auto slice = data[0 .. sz];
21577 						scope(exit)
21578 							GlobalUnlock(handle);
21579 
21580 						handler.getData(format, cast(ubyte[]) slice[]);
21581 					}
21582 
21583 
21584 					medium.hGlobal = handle; // FIXME
21585 					*pmedium = medium;
21586 					return S_OK;
21587 				}
21588 			}
21589 			return DV_E_FORMATETC;
21590 		}
21591 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
21592 			// writeln("GDH: ", *pformatetcIn);
21593 			return E_NOTIMPL; // FIXME
21594 		}
21595 		HRESULT QueryGetData(FORMATETC* pformatetc) {
21596 			auto search = *pformatetc;
21597 			search.tymed &= TYMED.TYMED_HGLOBAL;
21598 			foreach(ty; types)
21599 				if(ty == search) {
21600 					// writeln("QueryGetData ", search, " ", types[0]);
21601 					return S_OK;
21602 				}
21603 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
21604 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
21605 			}
21606 			return S_FALSE;
21607 		}
21608 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
21609 			//  writeln("SetData: ");
21610 			return E_NOTIMPL;
21611 		}
21612 	};
21613 
21614 
21615 	IDropSource src = new class IDropSource {
21616 		ULONG refCount;
21617 		ULONG AddRef() {
21618 			return ++refCount;
21619 		}
21620 		ULONG Release() {
21621 			return --refCount;
21622 		}
21623 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21624 			if (IID_IUnknown == *riid) {
21625 				*ppv = cast(void*) cast(IUnknown) this;
21626 			}
21627 			else if (IID_IDropSource == *riid) {
21628 				*ppv = cast(void*) cast(IDropSource) this;
21629 			}
21630 			else {
21631 				*ppv = null;
21632 				return E_NOINTERFACE;
21633 			}
21634 
21635 			AddRef();
21636 			return NOERROR;
21637 		}
21638 
21639 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
21640 			if(fEscapePressed)
21641 				return DRAGDROP_S_CANCEL;
21642 			if(!(grfKeyState & MK_LBUTTON))
21643 				return DRAGDROP_S_DROP;
21644 			return S_OK;
21645 		}
21646 
21647 		int GiveFeedback(uint dwEffect) {
21648 			return DRAGDROP_S_USEDEFAULTCURSORS;
21649 		}
21650 	};
21651 
21652 	DWORD effect;
21653 
21654 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
21655 
21656 	DROPEFFECT de = win32DragAndDropAction(action);
21657 
21658 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
21659 	// but still prolly a FIXME
21660 
21661 	auto ret = DoDragDrop(obj, src, de, &effect);
21662 	/+
21663 	if(ret == DRAGDROP_S_DROP)
21664 		writeln("drop ", effect);
21665 	else if(ret == DRAGDROP_S_CANCEL)
21666 		writeln("cancel");
21667 	else if(ret == S_OK)
21668 		writeln("ok");
21669 	else writeln(ret);
21670 	+/
21671 
21672 	return ret;
21673 }
21674 
21675 version(Windows)
21676 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
21677 	DROPEFFECT de;
21678 
21679 	with(DragAndDropAction)
21680 	with(DROPEFFECT)
21681 	final switch(action) {
21682 		case none: de = DROPEFFECT_NONE; break;
21683 		case copy: de = DROPEFFECT_COPY; break;
21684 		case move: de = DROPEFFECT_MOVE; break;
21685 		case link: de = DROPEFFECT_LINK; break;
21686 		case ask: throw new Exception("ask not implemented yet");
21687 		case custom: throw new Exception("custom not implemented yet");
21688 	}
21689 
21690 	return de;
21691 }
21692 
21693 
21694 /++
21695 	History:
21696 		Added February 19, 2021
21697 +/
21698 /// Group: drag_and_drop
21699 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
21700 	version(X11) {
21701 		auto display = XDisplayConnection.get;
21702 
21703 		Atom atom = 5; // right???
21704 
21705 		XChangeProperty(
21706 			display,
21707 			window.impl.window,
21708 			GetAtom!"XdndAware"(display),
21709 			XA_ATOM,
21710 			32 /* bits */,
21711 			PropModeReplace,
21712 			&atom,
21713 			1);
21714 
21715 		window.dropHandler = handler;
21716 	} else version(Windows) {
21717 
21718 		initDnd();
21719 
21720 		auto dropTarget = new class (handler) IDropTarget {
21721 			DropHandler handler;
21722 			this(DropHandler handler) {
21723 				this.handler = handler;
21724 			}
21725 			ULONG refCount;
21726 			ULONG AddRef() {
21727 				return ++refCount;
21728 			}
21729 			ULONG Release() {
21730 				return --refCount;
21731 			}
21732 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
21733 				if (IID_IUnknown == *riid) {
21734 					*ppv = cast(void*) cast(IUnknown) this;
21735 				}
21736 				else if (IID_IDropTarget == *riid) {
21737 					*ppv = cast(void*) cast(IDropTarget) this;
21738 				}
21739 				else {
21740 					*ppv = null;
21741 					return E_NOINTERFACE;
21742 				}
21743 
21744 				AddRef();
21745 				return NOERROR;
21746 			}
21747 
21748 
21749 			// ///////////////////
21750 
21751 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21752 				DropPackage dropPackage = DropPackage(pDataObj);
21753 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
21754 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
21755 			}
21756 
21757 			HRESULT DragLeave() {
21758 				handler.dragLeave();
21759 				// release the IDataObject if needed
21760 				return S_OK;
21761 			}
21762 
21763 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21764 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
21765 
21766 				*pdwEffect = win32DragAndDropAction(res.action);
21767 				// same as DragEnter basically
21768 				return S_OK;
21769 			}
21770 
21771 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
21772 				DropPackage pkg = DropPackage(pDataObj);
21773 				handler.drop(&pkg);
21774 
21775 				return S_OK;
21776 			}
21777 		};
21778 		// Windows can hold on to the handler and try to call it
21779 		// during which time the GC can't see it. so important to
21780 		// manually manage this. At some point i'll FIXME and make
21781 		// all my com instances manually managed since they supposed
21782 		// to respect the refcount.
21783 		import core.memory;
21784 		GC.addRoot(cast(void*) dropTarget);
21785 
21786 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
21787 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
21788 
21789 		window.dropHandler = handler;
21790 	} else throw new NotYetImplementedException();
21791 }
21792 
21793 
21794 
21795 static if(UsingSimpledisplayX11) {
21796 
21797 enum _NET_WM_STATE_ADD = 1;
21798 enum _NET_WM_STATE_REMOVE = 0;
21799 enum _NET_WM_STATE_TOGGLE = 2;
21800 
21801 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
21802 void demandAttention(SimpleWindow window, bool needs = true) {
21803 	demandAttention(window.impl.window, needs);
21804 }
21805 
21806 /// ditto
21807 void demandAttention(Window window, bool needs = true) {
21808 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
21809 }
21810 
21811 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
21812 	auto display = XDisplayConnection.get();
21813 	if(atom == None)
21814 		return; // non-failure error
21815 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
21816 
21817 	XClientMessageEvent xclient;
21818 
21819 	xclient.type = EventType.ClientMessage;
21820 	xclient.window = window;
21821 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
21822 	xclient.format = 32;
21823 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
21824 	xclient.data.l[1] = atom;
21825 	xclient.data.l[2] = atom2;
21826 	xclient.data.l[3] = 1;
21827 	// [3] == source. 0 == unknown, 1 == app, 2 == else
21828 
21829 	XSendEvent(
21830 		display,
21831 		RootWindow(display, DefaultScreen(display)),
21832 		false,
21833 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
21834 		cast(XEvent*) &xclient
21835 	);
21836 
21837 	/+
21838 	XChangeProperty(
21839 		display,
21840 		window.impl.window,
21841 		GetAtom!"_NET_WM_STATE"(display),
21842 		XA_ATOM,
21843 		32 /* bits */,
21844 		PropModeAppend,
21845 		&atom,
21846 		1);
21847 	+/
21848 }
21849 
21850 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
21851 	Atom actionAtom;
21852 	with(DragAndDropAction)
21853 	final switch(action) {
21854 		case none: actionAtom = None; break;
21855 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
21856 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
21857 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
21858 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
21859 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
21860 	}
21861 
21862 	return actionAtom;
21863 }
21864 
21865 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
21866 	// FIXME: I need to show user feedback somehow.
21867 	auto display = XDisplayConnection.get;
21868 
21869 	auto actionAtom = dndActionAtom(display, action);
21870 	assert(actionAtom, "Don't use action none to accept a drop");
21871 
21872 	setX11Selection!"XdndSelection"(window, handler, null);
21873 
21874 	auto oldKeyHandler = window.handleKeyEvent;
21875 	scope(exit) window.handleKeyEvent = oldKeyHandler;
21876 
21877 	auto oldCharHandler = window.handleCharEvent;
21878 	scope(exit) window.handleCharEvent = oldCharHandler;
21879 
21880 	auto oldMouseHandler = window.handleMouseEvent;
21881 	scope(exit) window.handleMouseEvent = oldMouseHandler;
21882 
21883 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
21884 
21885 	import core.sys.posix.sys.time;
21886 	timeval tv;
21887 	gettimeofday(&tv, null);
21888 
21889 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
21890 
21891 	Time lastMouseTimestamp;
21892 
21893 	bool dnding = true;
21894 	Window lastIn = None;
21895 
21896 	void leave() {
21897 		if(lastIn == None)
21898 			return;
21899 
21900 		XEvent ev;
21901 		ev.xclient.type = EventType.ClientMessage;
21902 		ev.xclient.window = lastIn;
21903 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
21904 		ev.xclient.format = 32;
21905 		ev.xclient.data.l[0] = window.impl.window;
21906 
21907 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21908 		XFlush(display);
21909 
21910 		lastIn = None;
21911 	}
21912 
21913 	void enter(Window w) {
21914 		assert(lastIn == None);
21915 
21916 		lastIn = w;
21917 
21918 		XEvent ev;
21919 		ev.xclient.type = EventType.ClientMessage;
21920 		ev.xclient.window = lastIn;
21921 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
21922 		ev.xclient.format = 32;
21923 		ev.xclient.data.l[0] = window.impl.window;
21924 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
21925 
21926 		auto types = handler.availableFormats();
21927 		assert(types.length > 0);
21928 
21929 		ev.xclient.data.l[2] = types[0];
21930 		if(types.length > 1)
21931 			ev.xclient.data.l[3] = types[1];
21932 		if(types.length > 2)
21933 			ev.xclient.data.l[4] = types[2];
21934 
21935 		// FIXME: other types?!?!? and make sure we skip TARGETS
21936 
21937 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21938 		XFlush(display);
21939 	}
21940 
21941 	void position(int rootX, int rootY) {
21942 		assert(lastIn != None);
21943 
21944 		XEvent ev;
21945 		ev.xclient.type = EventType.ClientMessage;
21946 		ev.xclient.window = lastIn;
21947 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
21948 		ev.xclient.format = 32;
21949 		ev.xclient.data.l[0] = window.impl.window;
21950 		ev.xclient.data.l[1] = 0; // reserved
21951 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
21952 		ev.xclient.data.l[3] = dataTimestamp;
21953 		ev.xclient.data.l[4] = actionAtom;
21954 
21955 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21956 		XFlush(display);
21957 
21958 	}
21959 
21960 	void drop() {
21961 		XEvent ev;
21962 		ev.xclient.type = EventType.ClientMessage;
21963 		ev.xclient.window = lastIn;
21964 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
21965 		ev.xclient.format = 32;
21966 		ev.xclient.data.l[0] = window.impl.window;
21967 		ev.xclient.data.l[1] = 0; // reserved
21968 		ev.xclient.data.l[2] = dataTimestamp;
21969 
21970 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
21971 		XFlush(display);
21972 
21973 		lastIn = None;
21974 		dnding = false;
21975 	}
21976 
21977 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
21978 	// but idk if i should...
21979 
21980 	window.setEventHandlers(
21981 		delegate(KeyEvent ev) {
21982 			if(ev.pressed == true && ev.key == Key.Escape) {
21983 				// cancel
21984 				dnding = false;
21985 			}
21986 		},
21987 		delegate(MouseEvent ev) {
21988 			if(ev.timestamp < lastMouseTimestamp)
21989 				return;
21990 
21991 			lastMouseTimestamp = ev.timestamp;
21992 
21993 			if(ev.type == MouseEventType.motion) {
21994 				auto display = XDisplayConnection.get;
21995 				auto root = RootWindow(display, DefaultScreen(display));
21996 
21997 				Window topWindow;
21998 				int rootX, rootY;
21999 
22000 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
22001 
22002 				if(topWindow == None)
22003 					return;
22004 
22005 				top:
22006 				if(auto result = topWindow in eligibility) {
22007 					auto dropWindow = *result;
22008 					if(dropWindow == None) {
22009 						leave();
22010 						return;
22011 					}
22012 
22013 					if(dropWindow != lastIn) {
22014 						leave();
22015 						enter(dropWindow);
22016 						position(rootX, rootY);
22017 					} else {
22018 						position(rootX, rootY);
22019 					}
22020 				} else {
22021 					// determine eligibility
22022 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
22023 					if(data.length == 1) {
22024 						// in case there is no WM or it isn't reparenting
22025 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
22026 					} else {
22027 
22028 						Window tryScanChildren(Window search, int maxRecurse) {
22029 							// could be reparenting window manager, so gotta check the next few children too
22030 							Window child;
22031 							int x;
22032 							int y;
22033 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
22034 
22035 							if(child == None)
22036 								return None;
22037 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
22038 							if(data.length == 1) {
22039 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
22040 							} else {
22041 								if(maxRecurse)
22042 									return tryScanChildren(child, maxRecurse - 1);
22043 								else
22044 									return None;
22045 							}
22046 
22047 						}
22048 
22049 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
22050 						auto topResult = tryScanChildren(topWindow, 3);
22051 						// it is easy to have a false negative due to the mouse going over a WM
22052 						// child window like the close button if separate from the frame... so I
22053 						// can't really cache negatives, :(
22054 						if(topResult != None) {
22055 							eligibility[topWindow] = topResult;
22056 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
22057 						}
22058 					}
22059 
22060 				}
22061 
22062 			} else if(ev.type == MouseEventType.buttonReleased) {
22063 				drop();
22064 				dnding = false;
22065 			}
22066 		}
22067 	);
22068 
22069 	window.grabInput();
22070 	scope(exit)
22071 		window.releaseInputGrab();
22072 
22073 
22074 	EventLoop.get.run(() => dnding);
22075 
22076 	return 0;
22077 }
22078 
22079 /// X-specific
22080 TrueColorImage getWindowNetWmIcon(Window window) {
22081 	try {
22082 		auto display = XDisplayConnection.get;
22083 
22084 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
22085 
22086 		if (data.length > arch_ulong.sizeof * 2) {
22087 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
22088 			// these are an array of rgba images that we have to convert into pixmaps ourself
22089 
22090 			int width = cast(int) meta[0];
22091 			int height = cast(int) meta[1];
22092 
22093 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
22094 
22095 			static if(arch_ulong.sizeof == 4) {
22096 				bytes = bytes[0 .. width * height * 4];
22097 				alias imageData = bytes;
22098 			} else static if(arch_ulong.sizeof == 8) {
22099 				bytes = bytes[0 .. width * height * 8];
22100 				auto imageData = new ubyte[](4 * width * height);
22101 			} else static assert(0);
22102 
22103 
22104 
22105 			// this returns ARGB. Remember it is little-endian so
22106 			//                                         we have BGRA
22107 			// our thing uses RGBA, which in little endian, is ABGR
22108 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
22109 				auto r = bytes[idx + 2];
22110 				auto g = bytes[idx + 1];
22111 				auto b = bytes[idx + 0];
22112 				auto a = bytes[idx + 3];
22113 
22114 				imageData[idx2 + 0] = r;
22115 				imageData[idx2 + 1] = g;
22116 				imageData[idx2 + 2] = b;
22117 				imageData[idx2 + 3] = a;
22118 			}
22119 
22120 			return new TrueColorImage(width, height, imageData);
22121 		}
22122 
22123 		return null;
22124 	} catch(Exception e) {
22125 		return null;
22126 	}
22127 }
22128 
22129 } /* UsingSimpledisplayX11 */
22130 
22131 
22132 void loadBinNameToWindowClassName () {
22133 	import core.stdc.stdlib : realloc;
22134 	version(linux) {
22135 		// args[0] MAY be empty, so we'll just use this
22136 		import core.sys.posix.unistd : readlink;
22137 		char[1024] ebuf = void; // 1KB should be enough for everyone!
22138 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
22139 		if (len < 1) return;
22140 	} else /*version(Windows)*/ {
22141 		import core.runtime : Runtime;
22142 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
22143 		auto ebuf = Runtime.args[0];
22144 		auto len = ebuf.length;
22145 	}
22146 	auto pos = len;
22147 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22148 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22149 	if (sdpyWindowClassStr is null) return; // oops
22150 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22151 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22152 }
22153 
22154 /++
22155 	An interface representing a font that is drawn with custom facilities.
22156 
22157 	You might want [OperatingSystemFont] instead, which represents
22158 	a font loaded and drawn by functions native to the operating system.
22159 
22160 	WARNING: I might still change this.
22161 +/
22162 interface DrawableFont : MeasurableFont {
22163 	/++
22164 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22165 
22166 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22167 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22168 		fill color, but that's up to the implementation.
22169 	+/
22170 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22171 
22172 	/++
22173 		Requests that the given string is added to the image cache. You should only do this rarely, but
22174 		if you have a string that you know will be used over and over again, adding it to a cache can
22175 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22176 		to implement this as a do-nothing method).
22177 	+/
22178 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22179 }
22180 
22181 /++
22182 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
22183 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
22184 
22185 	You should also consider [OperatingSystemFont], which loads and draws a font with
22186 	facilities native to the user's operating system. You might also consider
22187 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
22188 	of game, as they have their own ways to draw text too.
22189 
22190 	Be warned: this can be slow, especially on remote connections to the X server, since
22191 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
22192 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
22193 	experiment in your specific case.
22194 
22195 	Please note that the return type of [DrawableFont] also includes an implementation of
22196 	[MeasurableFont].
22197 +/
22198 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
22199 	import arsd.ttf;
22200 	static class ArsdTtfFont : DrawableFont {
22201 		TtfFont font;
22202 		int size;
22203 		this(in ubyte[] data, int size) {
22204 			font = TtfFont(data);
22205 			this.size = size;
22206 
22207 
22208 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
22209 			int ascent_, descent_, line_gap;
22210 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
22211 
22212 			int advance, lsb;
22213 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
22214 			xWidth = cast(int) (advance * scale);
22215 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
22216 			MWidth = cast(int) (advance * scale);
22217 		}
22218 
22219 		private int ascent_;
22220 		private int descent_;
22221 		private int xWidth;
22222 		private int MWidth;
22223 
22224 		bool isMonospace() {
22225 			return xWidth == MWidth;
22226 		}
22227 		int averageWidth() {
22228 			return xWidth;
22229 		}
22230 		int height() {
22231 			return size;
22232 		}
22233 		int ascent() {
22234 			return ascent_;
22235 		}
22236 		int descent() {
22237 			return descent_;
22238 		}
22239 
22240 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
22241 			int width, height;
22242 			font.getStringSize(s, size, width, height);
22243 			return width;
22244 		}
22245 
22246 
22247 
22248 		Sprite[string] cache;
22249 
22250 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
22251 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
22252 			cache[text] = sprite;
22253 		}
22254 
22255 		Image stringToImage(Color fg, Color bg, in char[] text) {
22256 			int width, height;
22257 			auto data = font.renderString(text, size, width, height);
22258 			auto image = new TrueColorImage(width, height);
22259 			int pos = 0;
22260 			foreach(y; 0 .. height)
22261 			foreach(x; 0 .. width) {
22262 				fg.a = data[0];
22263 				bg.a = 255;
22264 				auto color = alphaBlend(fg, bg);
22265 				image.imageData.bytes[pos++] = color.r;
22266 				image.imageData.bytes[pos++] = color.g;
22267 				image.imageData.bytes[pos++] = color.b;
22268 				image.imageData.bytes[pos++] = data[0];
22269 				data = data[1 .. $];
22270 			}
22271 			assert(data.length == 0);
22272 
22273 			return Image.fromMemoryImage(image);
22274 		}
22275 
22276 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
22277 			Sprite sprite = (text in cache) ? *(text in cache) : null;
22278 
22279 			auto fg = painter.impl._outlineColor;
22280 			auto bg = painter.impl._fillColor;
22281 
22282 			if(sprite !is null) {
22283 				auto w = cast(SimpleWindow) painter.window;
22284 				assert(w !is null);
22285 
22286 				sprite.drawAt(painter, upperLeft);
22287 			} else {
22288 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
22289 			}
22290 		}
22291 	}
22292 
22293 	return new ArsdTtfFont(data, size);
22294 }
22295 
22296 class NotYetImplementedException : Exception {
22297 	this(string file = __FILE__, size_t line = __LINE__) {
22298 		super("Not yet implemented", file, line);
22299 	}
22300 }
22301 
22302 ///
22303 __gshared bool librariesSuccessfullyLoaded = true;
22304 ///
22305 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
22306 
22307 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
22308 	mixin(staticForeachReplacement!Iface);
22309 
22310 	void loadDynamicLibrary() @nogc {
22311 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22312 	}
22313 
22314         void loadDynamicLibraryForReal() {
22315                 foreach(name; __traits(derivedMembers, Iface)) {
22316                         mixin("alias tmp = " ~ name ~ ";");
22317                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
22318                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
22319                 }
22320         }
22321 }
22322 
22323 private const(char)[] staticForeachReplacement(Iface)() pure {
22324 /*
22325 	// just this for gdc 9....
22326 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
22327 
22328         static foreach(name; __traits(derivedMembers, Iface))
22329                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
22330 */
22331 
22332 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
22333 	size_t pos;
22334 
22335 	void append(in char[] what) {
22336 		if(pos + what.length > code.length)
22337 			code.length = (code.length * 3) / 2;
22338 		code[pos .. pos + what.length] = what[];
22339 		pos += what.length;
22340 	}
22341 
22342         foreach(name; __traits(derivedMembers, Iface)) {
22343                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
22344 		append(name);
22345 		append(`")) `);
22346 		append(name);
22347 		append(";");
22348 	}
22349 
22350 	return code[0 .. pos];
22351 }
22352 
22353 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
22354 	mixin(staticForeachReplacement!Iface);
22355 
22356 	private __gshared void* libHandle;
22357 	private __gshared bool attempted;
22358 
22359         void loadDynamicLibrary() @nogc {
22360 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
22361 	}
22362 
22363 	bool loadAttempted() {
22364 		return attempted;
22365 	}
22366 	bool loadSuccessful() {
22367 		return libHandle !is null;
22368 	}
22369 
22370         void loadDynamicLibraryForReal() {
22371 		attempted = true;
22372                 version(Posix) {
22373                         import core.sys.posix.dlfcn;
22374 			version(OSX) {
22375 				version(X11)
22376                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
22377 				else
22378                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
22379 			} else {
22380                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
22381 				if(libHandle is null)
22382                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
22383 			}
22384 
22385 			static void* loadsym(void* l, const char* name) {
22386 				import core.stdc.stdlib;
22387 				if(l is null)
22388 					return &abort;
22389 				return dlsym(l, name);
22390 			}
22391                 } else version(Windows) {
22392                         import core.sys.windows.winbase;
22393                         libHandle = LoadLibrary(library ~ ".dll");
22394 			static void* loadsym(void* l, const char* name) {
22395 				import core.stdc.stdlib;
22396 				if(l is null)
22397 					return &abort;
22398 				return GetProcAddress(l, name);
22399 			}
22400                 }
22401                 if(libHandle is null) {
22402 			success = false;
22403                         //throw new Exception("load failure of library " ~ library);
22404 		}
22405                 foreach(name; __traits(derivedMembers, Iface)) {
22406                         mixin("alias tmp = " ~ name ~ ";");
22407                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
22408                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
22409                 }
22410         }
22411 
22412         void unloadDynamicLibrary() {
22413                 version(Posix) {
22414                         import core.sys.posix.dlfcn;
22415                         dlclose(libHandle);
22416                 } else version(Windows) {
22417                         import core.sys.windows.winbase;
22418                         FreeLibrary(libHandle);
22419                 }
22420                 foreach(name; __traits(derivedMembers, Iface))
22421                         mixin(name ~ " = null;");
22422         }
22423 }
22424 
22425 /+
22426 	The GC can be called from any thread, and a lot of cleanup must be done
22427 	on the gui thread. Since the GC can interrupt any locks - including being
22428 	triggered inside a critical section - it is vital to avoid deadlocks to get
22429 	these functions called from the right place.
22430 
22431 	If the buffer overflows, things are going to get leaked. I'm kinda ok with that
22432 	right now.
22433 
22434 	The cleanup function is run when the event loop gets around to it, which is just
22435 	whenever there's something there after it has been woken up for other work. It does
22436 	NOT wake up the loop itself - can't risk doing that from inside the GC in another thread.
22437 	(Well actually it might be ok but i don't wanna mess with it right now.)
22438 +/
22439 private struct CleanupQueue {
22440 	import core.stdc.stdlib;
22441 
22442 	void queue(alias func, T...)(T args) {
22443 		static struct Args {
22444 			T args;
22445 		}
22446 		static struct RealJob {
22447 			Job j;
22448 			Args a;
22449 		}
22450 		static void call(Job* data) {
22451 			auto rj = cast(RealJob*) data;
22452 			func(rj.a.args);
22453 		}
22454 
22455 		RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof);
22456 		thing.j.call = &call;
22457 		thing.a.args = args;
22458 
22459 		buffer[tail++] = cast(Job*) thing;
22460 
22461 		// FIXME: set overflowed
22462 	}
22463 
22464 	void process() {
22465 		const tail = this.tail;
22466 
22467 		while(tail != head) {
22468 			Job* job = cast(Job*) buffer[head++];
22469 			job.call(job);
22470 			free(job);
22471 		}
22472 
22473 		if(overflowed)
22474 			throw new Exception("cleanup overflowed");
22475 	}
22476 
22477 	private:
22478 
22479 	ubyte tail; // must ONLY be written by queue
22480 	ubyte head; // must ONLY be written by process
22481 	bool overflowed;
22482 
22483 	static struct Job {
22484 		void function(Job*) call;
22485 	}
22486 
22487 	void*[256] buffer;
22488 }
22489 private __gshared CleanupQueue cleanupQueue;
22490 
22491 // version(X11)
22492 /++
22493 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
22494 
22495 	$(WARNING
22496 		This function is exempted from stability guarantees.
22497 	)
22498 +/
22499 float customScalingFactorForMonitor(int monitorNumber) {
22500 	import core.stdc.stdlib;
22501 	auto val = getenv("ARSD_SCALING_FACTOR");
22502 
22503 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
22504 	if(val is null)
22505 		return 1.0;
22506 
22507 	char[16] buffer = 0;
22508 	int pos;
22509 
22510 	const(char)* at = val;
22511 
22512 	foreach(item; 0 .. monitorNumber + 1) {
22513 		if(*at == 0)
22514 			break; // reuse the last number when we at the end of the string
22515 		pos = 0;
22516 		while(pos + 1 < buffer.length && *at && *at != ';') {
22517 			buffer[pos++] = *at;
22518 			at++;
22519 		}
22520 		if(*at)
22521 			at++; // skip the semicolon
22522 		buffer[pos] = 0;
22523 	}
22524 
22525 	//sdpyPrintDebugString(buffer[0 .. pos]);
22526 
22527 	import core.stdc.math;
22528 	auto f = atof(buffer.ptr);
22529 
22530 	if(f <= 0.0 || isnan(f) || isinf(f))
22531 		return 1.0;
22532 
22533 	return f;
22534 }
22535 
22536 void guiAbortProcess(string msg) {
22537 	import core.stdc.stdlib;
22538 	version(Windows) {
22539 		WCharzBuffer t = WCharzBuffer(msg);
22540 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
22541 	} else {
22542 		import core.stdc.stdio;
22543 		fwrite(msg.ptr, 1, msg.length, stderr);
22544 		msg = "\n";
22545 		fwrite(msg.ptr, 1, msg.length, stderr);
22546 		fflush(stderr);
22547 	}
22548 
22549 	abort();
22550 }
22551 
22552 private int minInternal(int a, int b) {
22553 	return (a < b) ? a : b;
22554 }
22555 
22556 private alias scriptable = arsd_jsvar_compatible;