1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 /+
4 	To share some stuff between two opengl threads:
5 	windows
6 	https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading
7 	linux
8 	https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux
9 +/
10 
11 
12 // Search for: FIXME: leaks if multithreaded gc
13 
14 // https://freedesktop.org/wiki/Specifications/XDND/
15 
16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
17 
18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
20 
21 
22 // on Mac with X11: -L-L/usr/X11/lib
23 
24 /+
25 
26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
27 
28 	Progress bar in taskbar
29 		- i can probably just set a property on the window...
30 		  it sets that prop to an integer 0 .. 100. Taskbar
31 		  deletes it or window deletes it when it is handled.
32 		- prolly display it as a nice little line at the bottom.
33 
34 
35 from gtk:
36 
37 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
38 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
39 
40 >+  if (cardinal > 0)
41 >+  {
42 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
43 >+                     xid,
44 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
45 >+                     XA_CARDINAL, 32,
46 >+                     PropModeReplace,
47 >+                     (guchar *) &cardinal, 1);
48 >+  }
49 >+  else
50 >+  {
51 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
52 >+                     xid,
53 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
54 >+  }
55 
56 from Windows:
57 
58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
59 
60 interface
61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
63 listen for msg, return TRUE
64 interface->SetProgressState(hwnd, TBPF_NORMAL);
65 interface->SetProgressValue(hwnd, 40, 100);
66 
67 
68 	My new notification system.
69 		- use a unix socket? or a x property? or a udp port?
70 		- could of course also get on the dbus train but ugh.
71 		- it could also reply with the info as a string for easy remote examination.
72 
73 +/
74 
75 /*
76 	Event Loop would be nices:
77 
78 	* add on idle - runs when nothing else happens
79 		* which can specify how long to yield for
80 	* send messages without a recipient window
81 	* setTimeout
82 	* setInterval
83 */
84 
85 /*
86 	Classic games I want to add:
87 		* my tetris clone
88 		* pac man
89 */
90 
91 /*
92 	Text layout needs a lot of work. Plain drawText is useful but too
93 	limited. It will need some kind of text context thing which it will
94 	update and you can pass it on and get more details out of it.
95 
96 	It will need a bounding box, a current cursor location that is updated
97 	as drawing continues, and various changable facts (which can also be
98 	changed on the painter i guess) like font, color, size, background,
99 	etc.
100 
101 	We can also fetch the caret location from it somehow.
102 
103 	Should prolly be an overload of drawText
104 
105 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
106 
107 		WS_EX_NOACTIVATE
108 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
109 		full screen windows. Can just set the atom on X. Windows will be harder.
110 
111 		moving windows. resizing windows.
112 
113 		hide cursor, capture cursor, change cursor.
114 
115 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
116 	sure the pieces are there to do its job easily and make other jobs possible.
117 */
118 
119 /++
120 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
121 	including creating windows, drawing on them, working with the clipboard,
122 	timers, OpenGL, and more. However, it does NOT provide high level GUI
123 	widgets. See my minigui.d, an extension to this module, for that
124 	functionality.
125 
126 	simpledisplay provides cross-platform wrapping for Windows and Linux
127 	(and perhaps other OSes that use X11), but also does not prevent you
128 	from using the underlying facilities if you need them. It has a goal
129 	of working efficiently over a remote X link (at least as far as Xlib
130 	reasonably allows.)
131 
132 	simpledisplay depends on [arsd.color|color.d], which should be available from the
133 	same place where you got this file. Other than that, however, it has
134 	very few dependencies and ones that don't come with the OS and/or the
135 	compiler are all opt-in.
136 
137 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
138 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
139 
140 	simpledisplay is basically stable. I plan to refactor the internals,
141 	and may add new features and fix bugs, but It do not expect to
142 	significantly change the API. It has been stable a few years already now.
143 
144 	Installation_instructions:
145 
146 	`simpledisplay.d` does not have any dependencies outside the
147 	operating system and `color.d`, so it should just work most the
148 	time, but there are a few caveats on some systems:
149 
150 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
151 	console to be automatically allocated.
152 
153 	Please note when compiling on Win64, you need to explicitly list
154 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
155 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
156 
157 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
158 	note the "w".
159 
160 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
161 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
162 	See [EnableWindowsSubsystem] for more information.
163 
164 	$(PITFALL
165 		With the Windows subsystem, there is no console, so standard writeln will throw!
166 		You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
167 		create a console as needed.
168 	)
169 
170 	On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command.
171 
172 	On Ubuntu, you might need to install X11 development libraries to
173 	successfully link.
174 
175 	$(CONSOLE
176 		$ sudo apt-get install libglc-dev
177 		$ sudo apt-get install libx11-dev
178 	)
179 
180 
181 	Jump_list:
182 
183 	Don't worry, you don't have to read this whole documentation file!
184 
185 	Check out the [#event-example] and [#Pong-example] to get started quickly.
186 
187 	The main classes you may want to create are [SimpleWindow], [Timer],
188 	[Image], and [Sprite].
189 
190 	The main functions you'll want are [setClipboardText] and [getClipboardText].
191 
192 	There are also platform-specific functions available such as [XDisplayConnection]
193 	and [GetAtom] for X11, among others.
194 
195 	See the examples and topics list below to learn more.
196 
197 	$(WARNING
198 		There should only be one GUI thread per application,
199 		and all windows should be created in it and your
200 		event loop should run there.
201 
202 		To do otherwise is undefined behavior and has no
203 		cross platform guarantees.
204 	)
205 
206 	$(H2 About this documentation)
207 
208 	The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow.
209 
210 	Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples!
211 
212 	All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them.
213 
214 	To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example.
215 
216 	If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file.
217 
218 	At points, I will talk about implementation details in the documentation. These are sometimes
219 	subject to change, but nevertheless useful to understand what is really going on. You can learn
220 	more about some of the referenced things by searching the web for info about using them from C.
221 	You can always look at the source of simpledisplay.d too for the most authoritative source on
222 	its specific implementation. If you disagree with how I did something, please contact me so we
223 	can discuss it!
224 
225 	$(H2 Using with fibers)
226 
227 	simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor).
228 
229 	$(H2 Topics)
230 
231 	$(H3 $(ID topic-windows) Windows)
232 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
233 		window on the user's screen.
234 
235 		You may create multiple windows, if the underlying platform supports it. You may check
236 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
237 		SimpleWindow's constructor at runtime to handle those cases.
238 
239 		A single running event loop will handle as many windows as needed.
240 
241 	$(H3 $(ID topic-event-loops) Event loops)
242 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
243 
244 		The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there:
245 
246 		---
247 		// dmd example.d simpledisplay.d color.d
248 		import arsd.simpledisplay;
249 		void main() {
250 			auto window = new SimpleWindow(200, 200);
251 			window.eventLoop(0,
252 			  delegate (dchar) { /* got a character key press */ }
253 			);
254 		}
255 		---
256 
257 		$(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.)
258 
259 		On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run.
260 
261 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
262 
263 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
264 
265 		You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway.
266 
267 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
268 		Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like.
269 
270 		See the [NotificationAreaIcon] class.
271 
272 	$(H3 $(ID topic-input-handling) Input handling)
273 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
274 
275 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
276 
277 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
278 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
279 
280 		Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example:
281 
282 		---
283 		// dmd example.d simpledisplay.d color.d
284 		import arsd.simpledisplay;
285 		void main() {
286 			auto window = new SimpleWindow(200, 200);
287 			{ // introduce sub-scope
288 				auto painter = window.draw(); // begin drawing
289 				/* draw here */
290 				painter.outlineColor = Color.red;
291 				painter.fillColor = Color.black;
292 				painter.drawRectangle(Point(0, 0), 200, 200);
293 			} // end scope, calling `painter`'s destructor, drawing to the screen.
294 			window.eventLoop(0); // handle events
295 		}
296 		---
297 
298 		Painting is done based on two color properties, a pen and a brush.
299 
300 		At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead.
301 
302 		FIXME Add example of 2d opengl drawing here.
303 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
304 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
305 
306 		Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it.
307 
308 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
309 
310 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
311 
312 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon].
313 
314 		simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program.
315 
316 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
317 
318 		---
319 		import arsd.simpledisplay;
320 
321 		void main() {
322 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
323 
324 			float otherColor = 0.0;
325 			float colorDelta = 0.05;
326 
327 			window.redrawOpenGlScene = delegate() {
328 				glLoadIdentity();
329 				glBegin(GL_QUADS);
330 
331 				glColor3f(1.0, otherColor, 0);
332 				glVertex3f(-0.8, -0.8, 0);
333 
334 				glColor3f(1.0, otherColor, 1.0);
335 				glVertex3f(0.8, -0.8, 0);
336 
337 				glColor3f(0, 1.0, otherColor);
338 				glVertex3f(0.8, 0.8, 0);
339 
340 				glColor3f(otherColor, 0, 1.0);
341 				glVertex3f(-0.8, 0.8, 0);
342 
343 				glEnd();
344 			};
345 
346 			window.eventLoop(50, () {
347 				otherColor += colorDelta;
348 				if(otherColor > 1.0) {
349 					otherColor = 1.0;
350 					colorDelta = -0.05;
351 				}
352 				if(otherColor < 0) {
353 					otherColor = 0;
354 					colorDelta = 0.05;
355 				}
356 				// at the end of the timer, we have to request a redraw
357 				// or we won't see the changes.
358 				window.redrawOpenGlSceneSoon();
359 			});
360 		}
361 		---
362 
363 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
364 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
365 		simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too.
366 
367 		This example program shows how you can set up a shader to draw a rectangle:
368 
369 		---
370 		import arsd.simpledisplay;
371 
372 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
373 
374 		void main() {
375 			// First thing we do, before creating the window, is declare what version we want.
376 			setOpenGLContextVersion(3, 3);
377 			// turning off legacy compat is required to use version 3.3 and newer
378 			openGLContextCompatible = false;
379 
380 			uint VAO;
381 			OpenGlShader shader;
382 
383 			// then we can create the window.
384 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
385 
386 			// additional setup needs to be done when it is visible, simpledisplay offers a property
387 			// for exactly that:
388 			window.visibleForTheFirstTime = delegate() {
389 				// now with the window loaded, we can start loading the modern opengl functions.
390 
391 				// you MUST set the context first.
392 				window.setAsCurrentOpenGlContext;
393 				// then load the remainder of the library
394 				gl3.loadDynamicLibrary();
395 
396 				// now you can create the shaders, etc.
397 				shader = new OpenGlShader(
398 					OpenGlShader.Source(GL_VERTEX_SHADER, `
399 						#version 330 core
400 						layout (location = 0) in vec3 aPos;
401 						void main() {
402 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
403 						}
404 					`),
405 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
406 						#version 330 core
407 						out vec4 FragColor;
408 						uniform vec4 mycolor;
409 						void main() {
410 							FragColor = mycolor;
411 						}
412 					`),
413 				);
414 
415 				// and do whatever other setup you want.
416 
417 				float[] vertices = [
418 					0.5f,  0.5f, 0.0f,  // top right
419 					0.5f, -0.5f, 0.0f,  // bottom right
420 					-0.5f, -0.5f, 0.0f,  // bottom left
421 					-0.5f,  0.5f, 0.0f   // top left
422 				];
423 				uint[] indices = [  // note that we start from 0!
424 					0, 1, 3,  // first Triangle
425 					1, 2, 3   // second Triangle
426 				];
427 				uint VBO, EBO;
428 				glGenVertexArrays(1, &VAO);
429 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
430 				glBindVertexArray(VAO);
431 
432 				glGenBuffers(1, &VBO);
433 				glGenBuffers(1, &EBO);
434 
435 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
436 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
437 
438 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
439 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
440 
441 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
442 				glEnableVertexAttribArray(0);
443 
444 				// the library will set the initial viewport and trigger our first draw,
445 				// so these next two lines are NOT needed. they are just here as comments
446 				// to show what would happen next.
447 
448 				// glViewport(0, 0, window.width, window.height);
449 				// window.redrawOpenGlSceneNow();
450 			};
451 
452 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
453 			// it is our render method.
454 			window.redrawOpenGlScene = delegate() {
455 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
456 				glClear(GL_COLOR_BUFFER_BIT);
457 
458 				glUseProgram(shader.shaderProgram);
459 
460 				// the shader helper class has methods to set uniforms too
461 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
462 
463 				glBindVertexArray(VAO);
464 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
465 			};
466 
467 			window.eventLoop(0);
468 		}
469 		---
470 
471 	This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread.
472 
473 	$(H3 $(ID vulkan) Vulkan)
474 
475 	See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings:
476 
477 	https://github.com/adamdruppe/VulkanizeDSdpy
478 
479 	https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo
480 
481 	$(H3 $(ID topic-images) Displaying images)
482 		You can also load PNG images using [arsd.png].
483 
484 		---
485 		// dmd example.d simpledisplay.d color.d png.d
486 		import arsd.simpledisplay;
487 		import arsd.png;
488 
489 		void main() {
490 			auto image = Image.fromMemoryImage(readPng("image.png"));
491 			displayImage(image);
492 		}
493 		---
494 
495 		Compile with `dmd example.d simpledisplay.d png.d`.
496 
497 		If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel.
498 
499 	$(H3 $(ID topic-sprites) Sprites)
500 		The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link.
501 
502 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
503 
504 	$(H3 $(ID topic-clipboard) Clipboard)
505 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
506 
507 		It also has helpers for handling X-specific events.
508 
509 	$(H3 $(ID topic-dnd) Drag and Drop)
510 		See [enableDragAndDrop] and [draggable].
511 
512 	$(H3 $(ID topic-timers) Timers)
513 		There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer].
514 
515 		The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse.
516 
517 		---
518 			import arsd.simpledisplay;
519 
520 			void main() {
521 				auto window = new SimpleWindow(400, 400);
522 				// every 100 ms, it will draw a random line
523 				// on the window.
524 				window.eventLoop(100, {
525 					auto painter = window.draw();
526 
527 					import std.random;
528 					// random color
529 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
530 					// random line
531 					painter.drawLine(
532 						Point(uniform(0, window.width), uniform(0, window.height)),
533 						Point(uniform(0, window.width), uniform(0, window.height)));
534 
535 				});
536 			}
537 		---
538 
539 		The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish.
540 
541 		The pulse timer and instances of the [Timer] class may be combined at will.
542 
543 		---
544 			import arsd.simpledisplay;
545 
546 			void main() {
547 				auto window = new SimpleWindow(400, 400);
548 				auto timer = new Timer(1000, delegate {
549 					auto painter = window.draw();
550 					painter.clear();
551 				});
552 
553 				window.eventLoop(0);
554 			}
555 		---
556 
557 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
558 
559 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
560 		simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself.
561 
562 		See also: `xwindows.d` from my github.
563 
564 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
565 		`handleNativeEvent` and `handleNativeGlobalEvent`.
566 
567 	$(H3 $(ID topic-integration) Integration with other libraries)
568 		Integration with a third-party event loop is possible.
569 
570 		On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d.
571 
572 	$(H3 $(ID topic-guis) GUI widgets)
573 		simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you!
574 
575 		Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes.
576 
577 		Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.)
578 
579 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
580 
581 	$(H2 Platform-specific tips and tricks)
582 
583 	X_tips:
584 
585 	On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release.
586 
587 	$(H4 apitrace)
588 
589 	Out of the box, simpledisplay might not work as expected in combination with
590 	[apitrace](https://apitrace.github.io).
591 	However it can be instructed to specifically load the GL/GLX wrapper libraries provided by apitrace instead of
592 	the system libraries. This should restore the lost functionality.
593 
594 	$(NUMBERED_LIST
595 		* Compile with `-version=apitrace`.
596 		* When launching such a simpledisplay app, it must be able to locate the apitrace wrapper libraries.
597 		* Running the app will generate an apitrace trace file.
598 		  It should print a log message similar to "apitrace: loaded into /directory" during startup.
599 	)
600 
601 	There are multiple ways to enable a simpledisplay app to locate the wrapper libraries.
602 
603 	One way to achieved this is by pointing the `LD_LIBRARY_PATH` environment variable to the directory containing
604 	those wrappers.
605 
606 	```sh
607 	LD_LIBRARY_PATH=/path/to/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
608 
609 	# e.g.
610 	LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
611 	```
612 
613 	Alternatively, the simpledisplay app can also be launched via $(I apitrace).
614 
615 	```sh
616 	apitrace trace -a gl ./myapp
617 	```
618 
619 	Another way that seems to work is to preload `glxtrace.so` through `LD_PRELOAD`.
620 
621 	```sh
622 	LD_PRELOAD=/path/to/apitrace/wrappers/glxtrace.so ./myapp
623 	```
624 
625 	Windows_tips:
626 
627 	You can add icons or manifest files to your exe using a resource file.
628 
629 	To create a Windows .ico file, use the gimp or something. I'll write a helper
630 	program later.
631 
632 	Create `yourapp.rc`:
633 
634 	```rc
635 		1 ICON filename.ico
636 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
637 	```
638 
639 	And `yourapp.exe.manifest`:
640 
641 	```xml
642 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
643 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
644 		<assemblyIdentity
645 		    version="1.0.0.0"
646 		    processorArchitecture="*"
647 		    name="CompanyName.ProductName.YourApplication"
648 		    type="win32"
649 		/>
650 		<description>Your application description here.</description>
651 		<dependency>
652 		    <dependentAssembly>
653 			<assemblyIdentity
654 			    type="win32"
655 			    name="Microsoft.Windows.Common-Controls"
656 			    version="6.0.0.0"
657 			    processorArchitecture="*"
658 			    publicKeyToken="6595b64144ccf1df"
659 			    language="*"
660 			/>
661 		    </dependentAssembly>
662 		</dependency>
663 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
664 			<windowsSettings>
665 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
666 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
667 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
668 				<!-- to render crisply in DPI-unaware contexts -->
669 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
670 			</windowsSettings>
671 		</application>
672 		</assembly>
673 	```
674 
675 	You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`.
676 
677 	Doing this lets you opt into various new things since Windows XP.
678 
679 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
680 
681 	$(H2 Tips)
682 
683 	$(H3 Name conflicts)
684 
685 	simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import:
686 
687 	---
688 	static import sdpy = arsd.simpledisplay;
689 	import arsd.simpledisplay : SimpleWindow;
690 
691 	void main() {
692 		auto window = new SimpleWindow();
693 		sdpy.EventLoop.get.run();
694 	}
695 	---
696 
697 	$(H2 $(ID developer-notes) Developer notes)
698 
699 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
700 	implementation though.
701 
702 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
703 	suck. If I was rewriting it, I wouldn't do it that way again.
704 
705 	This file must not have any more required dependencies. If you need bindings, add
706 	them right to this file. Once it gets into druntime and is there for a while, remove
707 	bindings from here to avoid conflicts (or put them in an appropriate version block
708 	so it continues to just work on old dmd), but wait a couple releases before making the
709 	transition so this module remains usable with older versions of dmd.
710 
711 	You may have optional dependencies if needed by putting them in version blocks or
712 	template functions. You may also extend the module with other modules with UFCS without
713 	actually editing this - that is nice to do if you can.
714 
715 	Try to make functions work the same way across operating systems. I typically make
716 	it thinly wrap Windows, then emulate that on Linux.
717 
718 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
719 	Phobos! So try to avoid it.
720 
721 	See more comments throughout the source.
722 
723 	I realize this file is fairly large, but over half that is just bindings at the bottom
724 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
725 	to understand. I suggest you jump around the source by looking for a particular
726 	declaration you're interested in, like `class SimpleWindow` using your editor's search
727 	function, then look at one piece at a time.
728 
729 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
730 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
731 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
732 
733 	I live in the eastern United States, so I will most likely not be around at night in
734 	that US east timezone.
735 
736 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
737 
738 	Building documentation: use my adrdox generator, `dub run adrdox`.
739 
740 	Examples:
741 
742 	$(DIV $(ID Event-example))
743 	$(H3 $(ID event-example) Event example)
744 	This program creates a window and draws events inside them as they
745 	happen, scrolling the text in the window as needed. Run this program
746 	and experiment to get a feel for where basic input events take place
747 	in the library.
748 
749 	---
750 	// dmd example.d simpledisplay.d color.d
751 	import arsd.simpledisplay;
752 	import std.conv;
753 
754 	void main() {
755 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
756 
757 		int y = 0;
758 
759 		void addLine(string text) {
760 			auto painter = window.draw();
761 
762 			if(y + painter.fontHeight >= window.height) {
763 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
764 				y -= painter.fontHeight;
765 			}
766 
767 			painter.outlineColor = Color.red;
768 			painter.fillColor = Color.black;
769 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
770 
771 			painter.outlineColor = Color.white;
772 
773 			painter.drawText(Point(10, y), text);
774 
775 			y += painter.fontHeight;
776 		}
777 
778 		window.eventLoop(1000,
779 		  () {
780 			addLine("Timer went off!");
781 		  },
782 		  (KeyEvent event) {
783 			addLine(to!string(event));
784 		  },
785 		  (MouseEvent event) {
786 			addLine(to!string(event));
787 		  },
788 		  (dchar ch) {
789 			addLine(to!string(ch));
790 		  }
791 		);
792 	}
793 	---
794 
795 	If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too.
796 
797 	$(COMMENT
798 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
799 
800 	---
801 
802 	---
803 	)
804 
805 	History:
806 		Initial release in April 2011.
807 
808 		simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
809 
810 		On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
811 
812 		On October 5, 2024, apitrace support was added for Linux targets.
813 
814 		The ExperimentalTextComponent and ExperimentalTextComponent2 were both removed on April 12, 2025. Use [arsd.textlayouter] or the [arsd.minigui] widgets instead.
815 +/
816 module arsd.simpledisplay;
817 
818 // FIXME: tetris demo
819 // FIXME: space invaders demo
820 // FIXME: asteroids demo
821 
822 /++ $(ID Pong-example)
823 	$(H3 Pong)
824 
825 	This program creates a little Pong-like game. Player one is controlled
826 	with the keyboard.  Player two is controlled with the mouse. It demos
827 	the pulse timer, event handling, and some basic drawing.
828 +/
829 version(demos)
830 unittest {
831 	// dmd example.d simpledisplay.d color.d
832 	import arsd.simpledisplay;
833 
834 	enum paddleMovementSpeed = 8;
835 	enum paddleHeight = 48;
836 
837 	void main() {
838 		auto window = new SimpleWindow(600, 400, "Pong game!");
839 
840 		int playerOnePosition, playerTwoPosition;
841 		int playerOneMovement, playerTwoMovement;
842 		int playerOneScore, playerTwoScore;
843 
844 		int ballX, ballY;
845 		int ballDx, ballDy;
846 
847 		void serve() {
848 			import std.random;
849 
850 			ballX = window.width / 2;
851 			ballY = window.height / 2;
852 			ballDx = uniform(-4, 4) * 3;
853 			ballDy = uniform(-4, 4) * 3;
854 			if(ballDx == 0)
855 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
856 		}
857 
858 		serve();
859 
860 		window.eventLoop(50, // set a 50 ms timer pulls
861 			// This runs once per timer pulse
862 			delegate () {
863 				auto painter = window.draw();
864 
865 				painter.clear();
866 
867 				// Update everyone's motion
868 				playerOnePosition += playerOneMovement;
869 				playerTwoPosition += playerTwoMovement;
870 
871 				ballX += ballDx;
872 				ballY += ballDy;
873 
874 				// Bounce off the top and bottom edges of the window
875 				if(ballY + 7 >= window.height)
876 					ballDy = -ballDy;
877 				if(ballY - 8 <= 0)
878 					ballDy = -ballDy;
879 
880 				// Bounce off the paddle, if it is in position
881 				if(ballX - 8 <= 16) {
882 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
883 						ballDx = -ballDx + 1; // add some speed to keep it interesting
884 						ballDy += playerOneMovement; // and y movement based on your controls too
885 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
886 					} else {
887 						// Missed it
888 						playerTwoScore ++;
889 						serve();
890 					}
891 				}
892 
893 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
894 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
895 						ballDx = -ballDx - 1;
896 						ballDy += playerTwoMovement;
897 						ballX = window.width - 24;
898 					} else {
899 						// Missed it
900 						playerOneScore ++;
901 						serve();
902 					}
903 				}
904 
905 				// Draw the paddles
906 				painter.outlineColor = Color.black;
907 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
908 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
909 
910 				// Draw the ball
911 				painter.fillColor = Color.red;
912 				painter.outlineColor = Color.yellow;
913 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
914 
915 				// Draw the score
916 				painter.outlineColor = Color.blue;
917 				import std.conv;
918 				painter.drawText(Point(64, 4), to!string(playerOneScore));
919 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
920 
921 			},
922 			delegate (KeyEvent event) {
923 				// Player 1's controls are the arrow keys on the keyboard
924 				if(event.key == Key.Down)
925 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
926 				if(event.key == Key.Up)
927 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
928 
929 			},
930 			delegate (MouseEvent event) {
931 				// Player 2's controls are mouse movement while the left button is held down
932 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
933 					if(event.dy > 0)
934 						playerTwoMovement = paddleMovementSpeed;
935 					else if(event.dy < 0)
936 						playerTwoMovement = -paddleMovementSpeed;
937 				} else {
938 					playerTwoMovement = 0;
939 				}
940 			}
941 		);
942 	}
943 }
944 
945 /++ $(H3 $(ID example-minesweeper) Minesweeper)
946 
947 	This minesweeper demo shows how we can implement another classic
948 	game with simpledisplay and shows some mouse input and basic output
949 	code.
950 +/
951 version(demos)
952 unittest {
953 	import arsd.simpledisplay;
954 
955 	enum GameSquare {
956 		mine = 0,
957 		clear,
958 		m1, m2, m3, m4, m5, m6, m7, m8
959 	}
960 
961 	enum UserSquare {
962 		unknown,
963 		revealed,
964 		flagged,
965 		questioned
966 	}
967 
968 	enum GameState {
969 		inProgress,
970 		lose,
971 		win
972 	}
973 
974 	GameSquare[] board;
975 	UserSquare[] userState;
976 	GameState gameState;
977 	int boardWidth;
978 	int boardHeight;
979 
980 	bool isMine(int x, int y) {
981 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
982 			return false;
983 		return board[y * boardWidth + x] == GameSquare.mine;
984 	}
985 
986 	GameState reveal(int x, int y) {
987 		if(board[y * boardWidth + x] == GameSquare.clear) {
988 			floodFill(userState, boardWidth, boardHeight,
989 				UserSquare.unknown, UserSquare.revealed,
990 				x, y,
991 				(x, y) {
992 					if(board[y * boardWidth + x] == GameSquare.clear)
993 						return true;
994 					else {
995 						userState[y * boardWidth + x] = UserSquare.revealed;
996 						return false;
997 					}
998 				});
999 		} else {
1000 			userState[y * boardWidth + x] = UserSquare.revealed;
1001 			if(isMine(x, y))
1002 				return GameState.lose;
1003 		}
1004 
1005 		foreach(state; userState) {
1006 			if(state == UserSquare.unknown || state == UserSquare.questioned)
1007 				return GameState.inProgress;
1008 		}
1009 
1010 		return GameState.win;
1011 	}
1012 
1013 	void initializeBoard(int width, int height, int numberOfMines) {
1014 		boardWidth = width;
1015 		boardHeight = height;
1016 		board.length = width * height;
1017 
1018 		userState.length = width * height;
1019 		userState[] = UserSquare.unknown;
1020 
1021 		import std.algorithm, std.random, std.range;
1022 
1023 		board[] = GameSquare.clear;
1024 
1025 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
1026 			board[minePosition] = GameSquare.mine;
1027 
1028 		int x;
1029 		int y;
1030 		foreach(idx, ref square; board) {
1031 			if(square == GameSquare.clear) {
1032 				int danger = 0;
1033 				danger += isMine(x-1, y-1)?1:0;
1034 				danger += isMine(x-1, y)?1:0;
1035 				danger += isMine(x-1, y+1)?1:0;
1036 				danger += isMine(x, y-1)?1:0;
1037 				danger += isMine(x, y+1)?1:0;
1038 				danger += isMine(x+1, y-1)?1:0;
1039 				danger += isMine(x+1, y)?1:0;
1040 				danger += isMine(x+1, y+1)?1:0;
1041 
1042 				square = cast(GameSquare) (danger + 1);
1043 			}
1044 
1045 			x++;
1046 			if(x == width) {
1047 				x = 0;
1048 				y++;
1049 			}
1050 		}
1051 	}
1052 
1053 	void redraw(SimpleWindow window) {
1054 		import std.conv;
1055 
1056 		auto painter = window.draw();
1057 
1058 		painter.clear();
1059 
1060 		final switch(gameState) with(GameState) {
1061 			case inProgress:
1062 				break;
1063 			case win:
1064 				painter.fillColor = Color.green;
1065 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1066 				return;
1067 			case lose:
1068 				painter.fillColor = Color.red;
1069 				painter.drawRectangle(Point(0, 0), window.width, window.height);
1070 				return;
1071 		}
1072 
1073 		int x = 0;
1074 		int y = 0;
1075 
1076 		foreach(idx, square; board) {
1077 			auto state = userState[idx];
1078 
1079 			final switch(state) with(UserSquare) {
1080 				case unknown:
1081 					painter.outlineColor = Color.black;
1082 					painter.fillColor = Color(128,128,128);
1083 
1084 					painter.drawRectangle(
1085 						Point(x * 20, y * 20),
1086 						20, 20
1087 					);
1088 				break;
1089 				case revealed:
1090 					if(square == GameSquare.clear) {
1091 						painter.outlineColor = Color.white;
1092 						painter.fillColor = Color.white;
1093 
1094 						painter.drawRectangle(
1095 							Point(x * 20, y * 20),
1096 							20, 20
1097 						);
1098 					} else {
1099 						painter.outlineColor = Color.black;
1100 						painter.fillColor = Color.white;
1101 
1102 						painter.drawText(
1103 							Point(x * 20, y * 20),
1104 							to!string(square)[1..2],
1105 							Point(x * 20 + 20, y * 20 + 20),
1106 							TextAlignment.Center | TextAlignment.VerticalCenter);
1107 					}
1108 				break;
1109 				case flagged:
1110 					painter.outlineColor = Color.black;
1111 					painter.fillColor = Color.red;
1112 					painter.drawRectangle(
1113 						Point(x * 20, y * 20),
1114 						20, 20
1115 					);
1116 				break;
1117 				case questioned:
1118 					painter.outlineColor = Color.black;
1119 					painter.fillColor = Color.yellow;
1120 					painter.drawRectangle(
1121 						Point(x * 20, y * 20),
1122 						20, 20
1123 					);
1124 				break;
1125 			}
1126 
1127 			x++;
1128 			if(x == boardWidth) {
1129 				x = 0;
1130 				y++;
1131 			}
1132 		}
1133 
1134 	}
1135 
1136 	void main() {
1137 		auto window = new SimpleWindow(200, 200);
1138 
1139 		initializeBoard(10, 10, 10);
1140 
1141 		redraw(window);
1142 		window.eventLoop(0,
1143 			delegate (MouseEvent me) {
1144 				if(me.type != MouseEventType.buttonPressed)
1145 					return;
1146 				auto x = me.x / 20;
1147 				auto y = me.y / 20;
1148 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1149 					if(me.button == MouseButton.left) {
1150 						gameState = reveal(x, y);
1151 					} else {
1152 						userState[y*boardWidth+x] = UserSquare.flagged;
1153 					}
1154 					redraw(window);
1155 				}
1156 			}
1157 		);
1158 	}
1159 }
1160 
1161 // FIXME: tetris demo
1162 // FIXME: space invaders demo
1163 // FIXME: asteroids demo
1164 
1165 import arsd.core;
1166 
1167 version(ArsdNoCocoa) {
1168 } else {
1169 	version(D_OpenD) {
1170 		version(OSX)
1171 			version=OSXCocoa;
1172 		version(iOS)
1173 			version=OSXCocoa;
1174 	} else {
1175 		version(OSX) version(DigitalMars) version=OSXCocoa;
1176 	}
1177 }
1178 
1179 
1180 version(Emscripten) {
1181 	version=allow_unimplemented_features;
1182 	version=without_opengl;
1183 }
1184 
1185 
1186 version(OSXCocoa) {
1187 	version=without_opengl;
1188 	version=allow_unimplemented_features;
1189 	// version=OSXCocoa;
1190 	// pragma(linkerDirective, "-framework Cocoa");
1191 }
1192 
1193 version(without_opengl) {
1194 	enum SdpyIsUsingIVGLBinds = false;
1195 } else /*version(Posix)*/ {
1196 	static if (__traits(compiles, (){import iv.glbinds;})) {
1197 		enum SdpyIsUsingIVGLBinds = true;
1198 		public import iv.glbinds;
1199 		//pragma(msg, "SDPY: using iv.glbinds");
1200 	} else {
1201 		enum SdpyIsUsingIVGLBinds = false;
1202 	}
1203 //} else {
1204 //	enum SdpyIsUsingIVGLBinds = false;
1205 }
1206 
1207 version(Windows) {
1208 	//import core.sys.windows.windows;
1209 	import core.sys.windows.winnls;
1210 	import core.sys.windows.windef;
1211 	import core.sys.windows.basetyps;
1212 	import core.sys.windows.winbase;
1213 	import core.sys.windows.winuser;
1214 	import core.sys.windows.shellapi;
1215 	import core.sys.windows.wingdi;
1216 	static import gdi = core.sys.windows.wingdi; // so i
1217 
1218 	pragma(lib, "gdi32");
1219 	pragma(lib, "user32");
1220 
1221 	// for AlphaBlend... a breaking change....
1222 	version(CRuntime_DigitalMars) { } else
1223 		pragma(lib, "msimg32");
1224 
1225 	// core.sys.windows.dwmapi
1226 	private {
1227 		/++
1228 			See_also:
1229 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute
1230 		 +/
1231 		extern extern(Windows) HRESULT DwmGetWindowAttribute(
1232 			HWND hwnd,
1233 			DWORD dwAttribute,
1234 			PVOID pvAttribute,
1235 			DWORD cbAttribute
1236 		) nothrow @nogc;
1237 
1238 		/++
1239 			See_also:
1240 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
1241 		 +/
1242 		extern extern(Windows) HRESULT DwmSetWindowAttribute(
1243 			HWND hwnd,
1244 			DWORD dwAttribute,
1245 			LPCVOID pvAttribute,
1246 			DWORD cbAttribute
1247 		) nothrow @nogc;
1248 
1249 		/++
1250 			See_also:
1251 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
1252 		 +/
1253 		enum DWMWINDOWATTRIBUTE {
1254 			// incomplete, only declare what we need
1255 
1256 			/++
1257 				Usage:
1258 					pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*`
1259 
1260 				$(NOTE
1261 					Requires Windows 11 or later.
1262 				)
1263 			 +/
1264 			DWMWA_WINDOW_CORNER_PREFERENCE = 33,
1265 		}
1266 
1267 		/++
1268 			See_also:
1269 				https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
1270 		 +/
1271 		enum DWM_WINDOW_CORNER_PREFERENCE {
1272 			/// System decision
1273 			DWMWCP_DEFAULT = 0,
1274 
1275 			/// Never
1276 			DWMWCP_DONOTROUND = 1,
1277 
1278 			// If "appropriate"
1279 			DWMWCP_ROUND = 2,
1280 
1281 			// If "appropriate", but smaller radius
1282 			DWMWCP_ROUNDSMALL = 3
1283 		}
1284 
1285 		bool fromDWM(
1286 			DWM_WINDOW_CORNER_PREFERENCE value,
1287 			out CornerStyle result,
1288 		) @safe pure nothrow @nogc {
1289 			switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1290 				case DWMWCP_DEFAULT:
1291 					result = CornerStyle.automatic;
1292 					return true;
1293 				case DWMWCP_DONOTROUND:
1294 					result = CornerStyle.rectangular;
1295 					return true;
1296 				case DWMWCP_ROUND:
1297 					result = CornerStyle.rounded;
1298 					return true;
1299 				case DWMWCP_ROUNDSMALL:
1300 					result = CornerStyle.roundedSlightly;
1301 					return true;
1302 				default:
1303 					return false;
1304 			}
1305 		}
1306 
1307 		bool toDWM(
1308 			CornerStyle value,
1309 			out DWM_WINDOW_CORNER_PREFERENCE result,
1310 		) @safe pure nothrow @nogc {
1311 			final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) {
1312 				case CornerStyle.automatic:
1313 					result = DWMWCP_DEFAULT;
1314 					return true;
1315 				case CornerStyle.rectangular:
1316 					result = DWMWCP_DONOTROUND;
1317 					return true;
1318 				case CornerStyle.rounded:
1319 					result = DWMWCP_ROUND;
1320 					return true;
1321 				case CornerStyle.roundedSlightly:
1322 					result = DWMWCP_ROUNDSMALL;
1323 					return true;
1324 			}
1325 		}
1326 
1327 		pragma(lib, "dwmapi");
1328 	}
1329 } else version(Emscripten) {
1330 } else version (linux) {
1331 	//k8: this is hack for rdmd. sorry.
1332 	static import core.sys.linux.epoll;
1333 	static import core.sys.linux.timerfd;
1334 }
1335 
1336 
1337 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1338 
1339 // http://wiki.dlang.org/Simpledisplay.d
1340 
1341 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1342 
1343 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1344 // but can i control the scroll lock led
1345 
1346 
1347 // Note: if you are using Image on X, you might want to do:
1348 /*
1349 	static if(UsingSimpledisplayX11) {
1350 		if(!Image.impl.xshmAvailable) {
1351 			// the images will use the slower XPutImage, you might
1352 			// want to consider an alternative method to get better speed
1353 		}
1354 	}
1355 
1356 	If the shared memory extension is available though, simpledisplay uses it
1357 	for a significant speed boost whenever you draw large Images.
1358 */
1359 
1360 // 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.
1361 
1362 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1363 
1364 /*
1365 	Biggest FIXME:
1366 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1367 
1368 		clean up opengl contexts when their windows close
1369 
1370 		fix resizing the bitmaps/pixmaps
1371 */
1372 
1373 // BTW on Windows:
1374 // -L/SUBSYSTEM:WINDOWS:5.0
1375 // to dmd will make a nice windows binary w/o a console if you want that.
1376 
1377 /*
1378 	Stuff to add:
1379 
1380 	use multibyte functions everywhere we can
1381 
1382 	OpenGL windows
1383 	more event stuff
1384 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1385 
1386 
1387 	resizeEvent
1388 		and make the windows non-resizable by default,
1389 		or perhaps stretched (if I can find something in X like StretchBlt)
1390 
1391 	take a screenshot function!
1392 
1393 	Pens and brushes?
1394 	Maybe a global event loop?
1395 
1396 	Mouse deltas
1397 	Key items
1398 */
1399 
1400 /*
1401 From MSDN:
1402 
1403 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1404 
1405 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.
1406 
1407 */
1408 
1409 version(Emscripten) {
1410 
1411 } else version(linux) {
1412 	version = X11;
1413 	version(without_libnotify) {
1414 		// we cool
1415 	}
1416 	else
1417 		version = libnotify;
1418 }
1419 
1420 version(libnotify) {
1421 	//pragma(lib, "dl");
1422 	import core.sys.posix.dlfcn;
1423 
1424 	void delegate()[int] libnotify_action_delegates;
1425 	int libnotify_action_delegates_count;
1426 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1427 		auto idx = cast(int) user_data;
1428 		if(auto dgptr = idx in libnotify_action_delegates) {
1429 			(*dgptr)();
1430 			libnotify_action_delegates.remove(idx);
1431 		}
1432 	}
1433 
1434 	struct C_DynamicLibrary {
1435 		void* handle;
1436 		this(string name) {
1437 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1438 			if(handle is null)
1439 				throw new Exception("dlopen");
1440 		}
1441 
1442 		void close() {
1443 			dlclose(handle);
1444 		}
1445 
1446 		~this() {
1447 			// close
1448 		}
1449 
1450 		// FIXME: this looks up by name every time....
1451 		template call(string func, Ret, Args...) {
1452 			extern(C) Ret function(Args) fptr;
1453 			typeof(fptr) call() {
1454 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1455 				return fptr;
1456 			}
1457 		}
1458 	}
1459 
1460 	C_DynamicLibrary* libnotify;
1461 }
1462 
1463 version(OSX) {
1464 	version(OSXCocoa) {}
1465 	else { version = X11; }
1466 }
1467 	//version = OSXCocoa; // this was written by KennyTM
1468 version(FreeBSD)
1469 	version = X11;
1470 version(Solaris)
1471 	version = X11;
1472 
1473 version(X11) {
1474 	version(without_xft) {}
1475 	else version=with_xft;
1476 }
1477 
1478 void featureNotImplemented()() {
1479 	version(allow_unimplemented_features)
1480 		throw new NotYetImplementedException();
1481 	else
1482 		static assert(0);
1483 }
1484 
1485 // these are so the static asserts don't trigger unless you want to
1486 // add support to it for an OS
1487 version(Windows)
1488 	version = with_timer;
1489 version(linux)
1490 	version = with_timer;
1491 version(OSXCocoa)
1492 	version = with_timer;
1493 
1494 version(with_timer)
1495 	enum bool SimpledisplayTimerAvailable = true;
1496 else
1497 	enum bool SimpledisplayTimerAvailable = false;
1498 
1499 /// 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.
1500 version(Windows)
1501 	enum bool UsingSimpledisplayWindows = true;
1502 else
1503 	enum bool UsingSimpledisplayWindows = false;
1504 
1505 /// 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.
1506 version(X11)
1507 	enum bool UsingSimpledisplayX11 = true;
1508 else
1509 	enum bool UsingSimpledisplayX11 = false;
1510 
1511 /// 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.
1512 version(OSXCocoa)
1513 	enum bool UsingSimpledisplayCocoa = true;
1514 else
1515 	enum bool UsingSimpledisplayCocoa = false;
1516 
1517 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1518 version(Windows)
1519 	enum multipleWindowsSupported = true;
1520 else version(Emscripten)
1521 	enum multipleWindowsSupported = false;
1522 else version(X11)
1523 	enum multipleWindowsSupported = true;
1524 else version(OSXCocoa)
1525 	enum multipleWindowsSupported = true;
1526 else
1527 	static assert(0);
1528 
1529 version(without_opengl)
1530 	enum bool OpenGlEnabled = false;
1531 else
1532 	enum bool OpenGlEnabled = true;
1533 
1534 /++
1535 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1536 	If you mix this in above your `main` function, you no longer need to use the linker
1537 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1538 
1539 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1540 
1541 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1542 	stderr writeln. It will fail and throw an exception.
1543 
1544 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1545 
1546 	History:
1547 		Added November 24, 2021 (dub v10.4)
1548 +/
1549 mixin template EnableWindowsSubsystem() {
1550 	version(Windows)
1551 	version(CRuntime_Microsoft) {
1552 		pragma(linkerDirective, "/subsystem:windows");
1553 		version(D_OpenD) {
1554 			pragma(linkerDirective, "/entry:mainCRTStartup");
1555 		} else {
1556 			version(LDC)
1557 				pragma(linkerDirective, "/entry:wmainCRTStartup");
1558 			else
1559 				pragma(linkerDirective, "/entry:mainCRTStartup");
1560 		}
1561 	}
1562 }
1563 
1564 
1565 /++
1566 	After selecting a type from [WindowTypes], you may further customize
1567 	its behavior by setting one or more of these flags.
1568 
1569 
1570 	The different window types have different meanings of `normal`. If the
1571 	window type already is a good match for what you want to do, you should
1572 	just use [WindowFlags.normal], the default, which will do the right thing
1573 	for your users.
1574 
1575 	The window flags will not always be honored by the operating system
1576 	and window managers; they are hints, not commands.
1577 +/
1578 enum WindowFlags : int {
1579 	normal = 0, ///
1580 	skipTaskbar = 1, ///
1581 	alwaysOnTop = 2, ///
1582 	alwaysOnBottom = 4, ///
1583 	cannotBeActivated = 8, ///
1584 	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.
1585 	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.
1586 	/++
1587 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1588 		it is still a top-level window. This should NOT be set separately for most window types.
1589 
1590 		A transient window will not keep the application open if its main window closes.
1591 
1592 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1593 
1594 
1595 		From the ICCM:
1596 
1597 		$(BLOCKQUOTE
1598 			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.
1599 
1600 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1601 		)
1602 
1603 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1604 
1605 		History:
1606 			Added February 23, 2021 but not yet stabilized.
1607 	+/
1608 	transient = 64,
1609 	/++
1610 		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.
1611 
1612 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
1613 
1614 		History:
1615 			Added April 1, 2022
1616 	+/
1617 	managesChildWindowFocus = 128,
1618 
1619 	/++
1620 	+/
1621 	overrideRedirect = 256,
1622 
1623 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1624 }
1625 
1626 /++
1627 	When creating a window, you can pass a type to SimpleWindow's constructor,
1628 	then further customize the window by changing `WindowFlags`.
1629 
1630 
1631 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1632 	use. The others are there to build a foundation for a higher level GUI toolkit,
1633 	but are themselves not as high level as you might think from their names.
1634 
1635 	This list is based on the EMWH spec for X11.
1636 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1637 +/
1638 enum WindowTypes : int {
1639 	/// An ordinary application window.
1640 	normal,
1641 	/// 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.
1642 	undecorated,
1643 	/// 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.
1644 	eventOnly,
1645 	/// A drop down menu, such as from a menu bar
1646 	dropdownMenu,
1647 	/// A popup menu, such as from a right click
1648 	popupMenu,
1649 	/// A popup bubble notification
1650 	notification,
1651 	/*
1652 	menu, /// a tearable menu bar (not override-redirect - contrast to popups)
1653 	toolbar, /// a tearable menu bar (not override-redirect)
1654 	splashScreen, /// a loading splash screen for your application
1655 	desktop, ///
1656 	dockOrPanel, /// think taskbar
1657 	utility, /// a palette or something
1658 	*/
1659 	/// A tiny window showing temporary help text or something.
1660 	tooltip,
1661 	/// only supported on X; will assert fail elsewhere
1662 	dnd,
1663 	/// can also be used for other auto-complete presentations
1664 	comboBoxDropdown,
1665 	/// a dialog box of some sort
1666 	dialog,
1667 	/// a child nested inside the parent. You must pass a parent window to the ctor
1668 	nestedChild,
1669 
1670 	/++
1671 		The type you get when you pass in an existing browser handle, which means most
1672 		of simpledisplay's fancy things will not be done since they were never set up.
1673 
1674 		Using this to the main SimpleWindow constructor explicitly will trigger an assertion
1675 		failure; you should use the existing handle constructor.
1676 
1677 		History:
1678 			Added November 17, 2022 (previously it would have type `normal`)
1679 	+/
1680 	minimallyWrapped
1681 }
1682 
1683 
1684 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1685 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1686 private __gshared char* sdpyWindowClassStr = null;
1687 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1688 
1689 /**
1690 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1691 	You may want to change context version if you want to use advanced shaders or
1692 	other modern OpenGL techinques. This setting doesn't affect already created
1693 	windows. You may use version 2.1 as your default, which should be supported
1694 	by any box since 2006, so seems to be a reasonable choice.
1695 
1696 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1697 	old context creation code without any version specified. This is the safest
1698 	way to init OpenGL, but it may not give you access to advanced features.
1699 
1700 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1701 */
1702 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1703 
1704 /**
1705 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1706 	pipeline functions, and without "compatible" mode you won't be able to use
1707 	your old non-shader-based code with such contexts. By default SimpleDisplay
1708 	creates compatible context, so you can gradually upgrade your OpenGL code if
1709 	you want to (or leave it as is, as it should "just work").
1710 */
1711 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1712 
1713 /**
1714 	Set to `true` to allow creating OpenGL context with lower version than requested
1715 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1716 	`openGLContextFallbackActivated()` will return `true`.
1717 	*/
1718 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1719 
1720 /**
1721 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1722 	*/
1723 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1724 
1725 /++
1726 	History:
1727 		Added April 24, 2023  (dub v11.0)
1728 +/
1729 version(without_opengl) {} else
1730 auto openGLCurrentContext() {
1731 	version(Windows)
1732 		return wglGetCurrentContext();
1733 	else
1734 		return glXGetCurrentContext();
1735 }
1736 
1737 
1738 /**
1739 	Set window class name for all following `new SimpleWindow()` calls.
1740 
1741 	WARNING! For Windows, you should set your class name before creating any
1742 	window, and NEVER change it after that!
1743 */
1744 void sdpyWindowClass (const(char)[] v) {
1745 	import core.stdc.stdlib : realloc;
1746 	if (v.length == 0) v = "SimpleDisplayWindow";
1747 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1748 	if (sdpyWindowClassStr is null) return; // oops
1749 	sdpyWindowClassStr[0..v.length+1] = 0;
1750 	sdpyWindowClassStr[0..v.length] = v[];
1751 }
1752 
1753 /**
1754 	Get current window class name.
1755 */
1756 string sdpyWindowClass () @trusted {
1757 	if (sdpyWindowClassStr is null) return null;
1758 	foreach (immutable idx; 0..size_t.max-1) {
1759 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1760 	}
1761 	return null;
1762 }
1763 
1764 /++
1765 	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.
1766 
1767 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1768 +/
1769 float[2] getDpi() {
1770 	float[2] dpi;
1771 	version(Windows) {
1772 		HDC screen = GetDC(null);
1773 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1774 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1775 	} else version(X11) {
1776 		auto display = XDisplayConnection.get;
1777 		auto screen = DefaultScreen(display);
1778 
1779 		void fallback() {
1780 			/+
1781 			// 25.4 millimeters in an inch...
1782 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1783 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1784 			+/
1785 
1786 			// the physical size isn't actually as important as the logical size since this is
1787 			// all about scaling really
1788 			dpi[0] = 96;
1789 			dpi[1] = 96;
1790 		}
1791 
1792 		auto xft = getXftDpi();
1793 		if(xft is float.nan)
1794 			fallback();
1795 		else {
1796 			dpi[0] = xft;
1797 			dpi[1] = xft;
1798 		}
1799 	}
1800 
1801 	return dpi;
1802 }
1803 
1804 version(X11)
1805 float getXftDpi() {
1806 	auto display = XDisplayConnection.get;
1807 
1808 	char* resourceString = XResourceManagerString(display);
1809 	XrmInitialize();
1810 
1811 	if (resourceString) {
1812 		auto db = XrmGetStringDatabase(resourceString);
1813 		XrmValue value;
1814 		char* type;
1815 		if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1816 			if (value.addr) {
1817 				import core.stdc.stdlib;
1818 				return atof(cast(char*) value.addr);
1819 			}
1820 		}
1821 	}
1822 
1823 	return float.nan;
1824 }
1825 
1826 /++
1827 	Implementation used by [SimpleWindow.takeScreenshot].
1828 
1829 	Params:
1830 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1831 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1832 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1833 		x = the x-offset of the image to capture, from the left.
1834 		y = the y-offset of the image to capture, from the top.
1835 
1836 	History:
1837 		Added on March 14, 2021
1838 
1839 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1840 
1841 +/
1842 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1843 	TrueColorImage got;
1844 	version(X11) {
1845 		auto display = XDisplayConnection.get;
1846 		if(handle == 0)
1847 			handle = RootWindow(display, DefaultScreen(display));
1848 
1849 		if(width == 0 || height == 0) {
1850 			Window root;
1851 			int xpos, ypos;
1852 			uint widthret, heightret, borderret, depthret;
1853 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1854 
1855 			if(width == 0)
1856 				width = widthret;
1857 			if(height == 0)
1858 				height = heightret;
1859 		}
1860 
1861 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1862 
1863 		// https://github.com/adamdruppe/arsd/issues/98
1864 
1865 		auto i = new Image(image);
1866 		got = i.toTrueColorImage();
1867 
1868 		XDestroyImage(image);
1869 	} else version(Windows) {
1870 		auto hdc = GetDC(handle);
1871 		scope(exit) ReleaseDC(handle, hdc);
1872 
1873 		if(width == 0 || height == 0) {
1874 			BITMAP bmHeader;
1875 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1876 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1877 			if(width == 0)
1878 				width = bmHeader.bmWidth;
1879 			if(height == 0)
1880 				height = bmHeader.bmHeight;
1881 		}
1882 
1883 		auto i = new Image(width, height);
1884 		HDC hdcMem = CreateCompatibleDC(hdc);
1885 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1886 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1887 		SelectObject(hdcMem, hbmOld);
1888 		DeleteDC(hdcMem);
1889 
1890 		got = i.toTrueColorImage();
1891 	} else featureNotImplemented();
1892 
1893 	return got;
1894 }
1895 
1896 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1897 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1898 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1899 
1900 version(Windows)
1901 shared static this() {
1902 	auto lib = LoadLibrary("User32.dll");
1903 	if(lib is null)
1904 		return;
1905 	//scope(exit)
1906 		//FreeLibrary(lib);
1907 
1908 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1909 
1910 	if(SetProcessDpiAwarenessContext is null)
1911 		return;
1912 
1913 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1914 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1915 		//writeln(GetLastError());
1916 	}
1917 
1918 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1919 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1920 }
1921 
1922 /++
1923 	Blocking mode for event loop calls associated with a window instance.
1924 
1925 	History:
1926 		Added December 8, 2021 (dub v10.5). Prior to that, all calls to
1927 		`window.eventLoop` were the same as calls to `EventLoop.get.run`; that
1928 		is, all would block until the application quit.
1929 
1930 		That behavior can still be achieved here with `untilApplicationQuits`,
1931 		or explicitly calling the top-level `EventLoop.get.run` function.
1932 +/
1933 enum BlockingMode {
1934 	/++
1935 		The event loop call will block until the whole application is ready
1936 		to quit if it is the only one running, but if it is nested inside
1937 		another one, it will only block until the window you're calling it on
1938 		closes.
1939 	+/
1940 	automatic             = 0x00,
1941 	/++
1942 		The event loop call will only return when the whole application
1943 		is ready to quit. This usually means all windows have been closed.
1944 
1945 		This is appropriate for your main application event loop.
1946 	+/
1947 	untilApplicationQuits = 0x01,
1948 	/++
1949 		The event loop will return when the window you're calling it on
1950 		closes. If there are other windows still open, they may be destroyed
1951 		unless you have another event loop running later.
1952 
1953 		This might be appropriate for a modal dialog box loop. Remember that
1954 		other windows are still processing input though, so you can end up
1955 		with a lengthy call stack if this happens in a loop, similar to a
1956 		recursive function (well, it literally is a recursive function, just
1957 		not an obvious looking one).
1958 	+/
1959 	untilWindowCloses     = 0x02,
1960 	/++
1961 		If an event loop is already running, this call will immediately
1962 		return, allowing the existing loop to handle it. If not, this call
1963 		will block until the condition you bitwise-or into the flag.
1964 
1965 		The default is to block until the application quits, same as with
1966 		the `automatic` setting (since if it were nested, which triggers until
1967 		window closes in automatic, this flag would instead not block at all),
1968 		but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`,
1969 		it will only nest until the window closes. You might want that if you are
1970 		going to open two windows simultaneously and want closing just one of them
1971 		to trigger the event loop return.
1972 	+/
1973 	onlyIfNotNested       = 0x10,
1974 }
1975 
1976 /++
1977 	Window corner visuals preference
1978  +/
1979 enum CornerStyle {
1980 	/++
1981 		Use the default style automatically applied by the system or its window manager/compositor.
1982 	 +/
1983 	automatic,
1984 
1985 	/++
1986 		Prefer rectangular window corners
1987 	 +/
1988 	rectangular,
1989 
1990 	/++
1991 		Prefer rounded window corners
1992 	 +/
1993 	rounded,
1994 
1995 	/++
1996 		Prefer slightly-rounded window corners
1997 	 +/
1998 	roundedSlightly,
1999 }
2000 
2001 /++
2002 	The flagship window class.
2003 
2004 
2005 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
2006 	out of more advanced or complex features of the underlying windowing system.
2007 
2008 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
2009 	and get a suitable window to work with.
2010 
2011 	From there, you can opt into additional features, like custom resizability and OpenGL support
2012 	with the next two constructor arguments. Or, if you need even more, you can set a window type
2013 	and customization flags with the final two constructor arguments.
2014 
2015 	If none of that works for you, you can also create a window using native function calls, then
2016 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
2017 	though, if you do this, managing the window is still your own responsibility! Notably, you
2018 	will need to destroy it yourself.
2019 +/
2020 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
2021 	version(D_OpenD) mixin EnableSynchronization;
2022 
2023 	/++
2024 		Copies the window's current state into a [TrueColorImage].
2025 
2026 		Be warned: this can be a very slow operation
2027 
2028 		History:
2029 			Actually implemented on March 14, 2021
2030 	+/
2031 	TrueColorImage takeScreenshot() {
2032 		version(Windows)
2033 			return trueColorImageFromNativeHandle(impl.hwnd, _width, _height);
2034 		else version(OSXCocoa)
2035 			throw new NotYetImplementedException();
2036 		else
2037 			return trueColorImageFromNativeHandle(impl.window, _width, _height);
2038 	}
2039 
2040 	/++
2041 		Returns the actual logical DPI for the window on its current display monitor. If the window
2042 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
2043 
2044 		Please note this function may return zero if it doesn't know the answer!
2045 
2046 
2047 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
2048 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
2049 
2050 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
2051 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
2052 		window primarily resides on by checking the center point of the window against the monitor map.
2053 
2054 		Returns:
2055 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
2056 			assumes the X and Y dpi are the same.
2057 
2058 		History:
2059 			Added November 26, 2021 (dub v10.4)
2060 
2061 			It said "physical dpi" in the description prior to July 29, 2022, but the behavior was
2062 			always a logical value on Windows and usually one on Linux too, so now the docs reflect
2063 			that.
2064 
2065 		Bugs:
2066 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically
2067 			just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to
2068 			set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor
2069 			and 1.5 on the secondary monitor.
2070 
2071 			The local dpi is not necessarily related to the physical dpi of the monitor. The name
2072 			is a historical misnomer - the real thing of interest is the scale factor and due to
2073 			compatibility concerns the scale would modify dpi values to trick applications. But since
2074 			that's the terminology common out there, I used it too.
2075 
2076 		See_Also:
2077 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
2078 			as this since the window many be on a different monitor, but it is a reasonable fallback
2079 			to use if `actualDpi` returns 0.
2080 
2081 			[onDpiChanged] is changed when `actualDpi` has changed.
2082 	+/
2083 	int actualDpi() {
2084 		version(X11) bool useFallbackDpi = false;
2085 		if(!actualDpiLoadAttempted) {
2086 			// FIXME: do the actual monitor we are on
2087 			// and on X this is a good chance to load the monitor map.
2088 			version(Windows) {
2089 				if(GetDpiForWindow)
2090 					actualDpi_ = GetDpiForWindow(impl.hwnd);
2091 			} else version(X11) {
2092 				if(!xRandrInfoLoadAttemped) {
2093 					xRandrInfoLoadAttemped = true;
2094 					if(!XRandrLibrary.attempted) {
2095 						XRandrLibrary.loadDynamicLibrary();
2096 					}
2097 
2098 					if(XRandrLibrary.loadSuccessful) {
2099 						auto display = XDisplayConnection.get;
2100 						int scratch;
2101 						int major, minor;
2102 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
2103 							goto fallback;
2104 
2105 						XRRQueryVersion(display, &major, &minor);
2106 						if(major <= 1 && minor < 5)
2107 							goto fallback;
2108 
2109 						int count;
2110 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
2111 						if(monitors is null)
2112 							goto fallback;
2113 						scope(exit) XRRFreeMonitors(monitors);
2114 
2115 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
2116 						MonitorInfo.info.assumeSafeAppend();
2117 						foreach(idx, monitor; monitors[0 .. count]) {
2118 							MonitorInfo.info ~= MonitorInfo(
2119 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2120 								Size(monitor.mwidth, monitor.mheight),
2121 								cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0])
2122 							);
2123 
2124 							/+
2125 							if(monitor.mwidth == 0 || monitor.mheight == 0)
2126 							// unknown physical size, just guess 96 to avoid divide by zero
2127 							MonitorInfo.info ~= MonitorInfo(
2128 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2129 								Size(monitor.mwidth, monitor.mheight),
2130 								96
2131 							);
2132 							else
2133 							// and actual thing
2134 							MonitorInfo.info ~= MonitorInfo(
2135 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
2136 								Size(monitor.mwidth, monitor.mheight),
2137 								minInternal(
2138 									// millimeter to int then rounding up.
2139 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
2140 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
2141 								)
2142 							);
2143 							+/
2144 						}
2145 					// writeln("Here", MonitorInfo.info);
2146 					}
2147 				}
2148 
2149 				if(XRandrLibrary.loadSuccessful) {
2150 					updateActualDpi(true);
2151 					// writeln("updated");
2152 
2153 					if(!requestedInput) {
2154 						// this is what requests live updates should the configuration change
2155 						// each time you select input, it sends an initial event, so very important
2156 						// to not get into a loop of selecting input, getting event, updating data,
2157 						// and reselecting input...
2158 						requestedInput = true;
2159 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
2160 						// writeln("requested input");
2161 					}
2162 				} else {
2163 					fallback:
2164 					// make sure we disable events that aren't coming
2165 					xrrEventBase = -1;
2166 					// best guess... respect the custom scaling user command to some extent at least though
2167 					useFallbackDpi = true;
2168 				}
2169 			} else version(OSXCocoa) {
2170 				actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME
2171 			}
2172 			actualDpiLoadAttempted = true;
2173 		} else version(X11) if(MonitorInfo.info.length == 0) {
2174 			useFallbackDpi = true;
2175 		}
2176 
2177 		version(X11)
2178 		if(useFallbackDpi || actualDpi_ == 0) // FIXME: the actualDpi_ will be populated eventually when we get the first synthetic configure event from the window manager, but that might be a little while so actualDpi_ can be 0 until then...
2179 			actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0));
2180 		return actualDpi_;
2181 	}
2182 
2183 	private int actualDpi_;
2184 	private bool actualDpiLoadAttempted;
2185 
2186 	version(X11) private {
2187 		bool requestedInput;
2188 		static bool xRandrInfoLoadAttemped;
2189 		struct MonitorInfo {
2190 			Rectangle position;
2191 			Size size;
2192 			int dpi;
2193 
2194 			static MonitorInfo[] info;
2195 		}
2196 		bool screenPositionKnown;
2197 		int screenPositionX;
2198 		int screenPositionY;
2199 		void updateActualDpi(bool loadingNow = false) {
2200 			if(!loadingNow && !actualDpiLoadAttempted)
2201 				actualDpi(); // just to make it do the load
2202 			foreach(idx, m; MonitorInfo.info) {
2203 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
2204 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
2205 					actualDpi_ = m.dpi;
2206 					// writeln("monitor ", idx);
2207 					if(changed && onDpiChanged)
2208 						onDpiChanged();
2209 					break;
2210 				}
2211 			}
2212 		}
2213 	}
2214 
2215 	/++
2216 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
2217 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
2218 
2219 		History:
2220 			Added November 26, 2021 (dub v10.4)
2221 
2222 		See_Also:
2223 			[actualDpi]
2224 	+/
2225 	void delegate() onDpiChanged;
2226 
2227 	version(X11) {
2228 		void recreateAfterDisconnect() {
2229 			if(!stateDiscarded) return;
2230 
2231 			if(_parent !is null && _parent.stateDiscarded)
2232 				_parent.recreateAfterDisconnect();
2233 
2234 			bool wasHidden = hidden;
2235 
2236 			activeScreenPainter = null; // should already be done but just to confirm
2237 
2238 			actualDpi_ = 0;
2239 			actualDpiLoadAttempted = false;
2240 			xRandrInfoLoadAttemped = false;
2241 
2242 			impl.createWindow(_width, _height, _title, openglMode, _parent);
2243 
2244 			if(auto dh = dropHandler) {
2245 				dropHandler = null;
2246 				enableDragAndDrop(this, dh);
2247 			}
2248 
2249 			if(recreateAdditionalConnectionState)
2250 				recreateAdditionalConnectionState();
2251 
2252 			hidden = wasHidden;
2253 			stateDiscarded = false;
2254 		}
2255 
2256 		bool stateDiscarded;
2257 		void discardConnectionState() {
2258 			if(XDisplayConnection.display)
2259 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
2260 			if(discardAdditionalConnectionState)
2261 				discardAdditionalConnectionState();
2262 			stateDiscarded = true;
2263 		}
2264 
2265 		void delegate() discardAdditionalConnectionState;
2266 		void delegate() recreateAdditionalConnectionState;
2267 
2268 	}
2269 
2270 	private DropHandler dropHandler;
2271 
2272 	SimpleWindow _parent;
2273 	bool beingOpenKeepsAppOpen = true;
2274 	/++
2275 		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.
2276 
2277 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
2278 
2279 		Params:
2280 
2281 		width = the width of the window's client area, in pixels
2282 		height = the height of the window's client area, in pixels
2283 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
2284 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
2285 		resizable = [Resizability] has three options:
2286 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
2287 			$(P `fixedSize` will not allow the user to resize the window.)
2288 			$(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.)
2289 		windowType = The type of window you want to make.
2290 		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.
2291 		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".
2292 	+/
2293 	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) {
2294 		claimGuiThread();
2295 		version(sdpy_thread_checks) assert(thisIsGuiThread);
2296 		this._width = this._virtualWidth = width;
2297 		this._height = this._virtualHeight = height;
2298 		this.openglMode = opengl;
2299 		version(X11) {
2300 			// auto scale not implemented except with opengl and even there it is kinda weird
2301 			if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no)
2302 				resizable = Resizability.fixedSize;
2303 		}
2304 		this.resizability = resizable;
2305 		this.windowType = windowType;
2306 		this.customizationFlags = customizationFlags;
2307 		this._title = (title is null ? "D Application" : title);
2308 		this._parent = parent;
2309 		impl.createWindow(width, height, this._title, opengl, parent);
2310 
2311 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
2312 			beingOpenKeepsAppOpen = false;
2313 	}
2314 
2315 	/// ditto
2316 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
2317 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
2318 	}
2319 
2320 	/// Same as above, except using the `Size` struct instead of separate width and height.
2321 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
2322 		this(size.width, size.height, title, opengl, resizable);
2323 	}
2324 
2325 	/// ditto
2326 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
2327 		this(size, title, opengl, resizable);
2328 	}
2329 
2330 
2331 	/++
2332 		Creates a window based on the given [Image]. It's client area
2333 		width and height is equal to the image. (A window's client area
2334 		is the drawable space inside; it excludes the title bar, etc.)
2335 
2336 		Windows based on images will not be resizable and do not use OpenGL.
2337 
2338 		It will draw the image in upon creation, but this will be overwritten
2339 		upon any draws, including the initial window visible event.
2340 
2341 		You probably do not want to use this and it may be removed from
2342 		the library eventually, or I might change it to be a "permanent"
2343 		background image; one that is automatically drawn on it before any
2344 		other drawing event. idk.
2345 	+/
2346 	this(Image image, string title = null) {
2347 		this(image.width, image.height, title);
2348 		this.image = image;
2349 	}
2350 
2351 	/++
2352 		Wraps a native window handle with very little additional processing - notably no destruction
2353 		this is incomplete so don't use it for much right now. The purpose of this is to make native
2354 		windows created through the low level API (so you can use platform-specific options and
2355 		other details SimpleWindow does not expose) available to the event loop wrappers.
2356 	+/
2357 	this(NativeWindowHandle nativeWindow) {
2358 		windowType = WindowTypes.minimallyWrapped;
2359 		version(Windows)
2360 			impl.hwnd = nativeWindow;
2361 		else version(X11) {
2362 			impl.window = nativeWindow;
2363 			if(nativeWindow)
2364 				display = XDisplayConnection.get(); // get initial display to not segfault
2365 		} else version(Emscripten) {
2366 			// FIXME
2367 		} else version(OSXCocoa) {
2368 			if(nativeWindow !is NullWindow) throw new NotYetImplementedException();
2369 		} else featureNotImplemented();
2370 		// FIXME: set the size correctly
2371 		_width = 1;
2372 		_height = 1;
2373 		if(nativeWindow)
2374 			nativeMapping[cast(void*) nativeWindow] = this;
2375 
2376 		beingOpenKeepsAppOpen = false;
2377 		useDirectDraw = true;
2378 
2379 		if(nativeWindow) {
2380 			version(OSXCocoa)
2381 				CapableOfHandlingNativeEvent.nativeHandleMapping[cast(void*) nativeWindow] = this;
2382 			else
2383 				CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
2384 		}
2385 		_suppressDestruction = true; // so it doesn't try to close
2386 	}
2387 
2388 	private bool useDirectDraw;
2389 
2390 	/++
2391 		Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created.
2392 		The delegate will be called when the window manager asks you to take focus.
2393 
2394 		This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time.
2395 
2396 		History:
2397 			Added April 1, 2022 (dub v10.8)
2398 	+/
2399 	SimpleWindow delegate() setRequestedInputFocus;
2400 
2401 	/// Experimental, do not use yet
2402 	/++
2403 		Grabs exclusive input from the user until you release it with
2404 		[releaseInputGrab].
2405 
2406 
2407 		Note: it is extremely rude to do this without good reason.
2408 		Reasons may include doing some kind of mouse drag operation
2409 		or popping up a temporary menu that should get events and will
2410 		be dismissed at ease by the user clicking away.
2411 
2412 		Params:
2413 			keyboard = do you want to grab keyboard input?
2414 			mouse = grab mouse input?
2415 			confine = confine the mouse cursor to inside this window?
2416 
2417 		History:
2418 			Prior to March 11, 2021, grabbing the keyboard would always also
2419 			set the X input focus. Now, it only focuses if it is a non-transient
2420 			window and otherwise manages the input direction internally.
2421 
2422 			This means spurious focus/blur events will no longer be sent and the
2423 			application will not steal focus from other applications (which the
2424 			window manager may have rejected anyway).
2425 	+/
2426 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2427 		static if(UsingSimpledisplayX11) {
2428 			XSync(XDisplayConnection.get, 0);
2429 			if(keyboard) {
2430 				if(isTransient && _parent) {
2431 					/*
2432 					FIXME:
2433 						setting the keyboard focus is not actually that helpful, what I more likely want
2434 						is the events from the parent window to be sent over here if we're transient.
2435 					*/
2436 
2437 					_parent.inputProxy = this;
2438 				} else {
2439 
2440 					SimpleWindow setTo;
2441 					if(setRequestedInputFocus !is null)
2442 						setTo = setRequestedInputFocus();
2443 					if(setTo is null)
2444 						setTo = this;
2445 
2446 					// sdpyPrintDebugString("grabInput() ", setTo.impl.window;
2447 					XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2448 				}
2449 			}
2450 			if(mouse) {
2451 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */,
2452 				EventMask.PointerMotionMask // FIXME: not efficient
2453 				| EventMask.ButtonPressMask
2454 				| EventMask.ButtonReleaseMask
2455 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2456 				)
2457 			{
2458 				XSync(XDisplayConnection.get, 0);
2459 				import core.stdc.stdio;
2460 				printf("Grab input failed %d\n", res);
2461 				//throw new Exception("Grab input failed");
2462 			} else {
2463 				// cool
2464 			}
2465 			}
2466 
2467 		} else version(Windows) {
2468 			// FIXME: keyboard?
2469 			SetCapture(impl.hwnd);
2470 			if(confine) {
2471 				RECT rcClip;
2472 				//RECT rcOldClip;
2473 				//GetClipCursor(&rcOldClip);
2474 				GetWindowRect(hwnd, &rcClip);
2475 				ClipCursor(&rcClip);
2476 			}
2477 		} else version(Emscripten) {
2478 			// nothing necessary
2479 		} else version(OSXCocoa) {
2480 			// throw new NotYetImplementedException();
2481 		} else static assert(0);
2482 	}
2483 
2484 	private Point imePopupLocation = Point(0, 0);
2485 
2486 	/++
2487 		Sets the location for the IME (input method editor) to pop up when the user activates it.
2488 
2489 		Bugs:
2490 			Not implemented outside X11.
2491 	+/
2492 	void setIMEPopupLocation(Point location) {
2493 		static if(UsingSimpledisplayX11) {
2494 			imePopupLocation = location;
2495 			updateIMEPopupLocation();
2496 		} else {
2497 			// this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least
2498 			// throw new NotYetImplementedException();
2499 		}
2500 	}
2501 
2502 	/// ditto
2503 	void setIMEPopupLocation(int x, int y) {
2504 		return setIMEPopupLocation(Point(x, y));
2505 	}
2506 
2507 	// we need to remind XIM of where we wanted to place the IME whenever the window moves
2508 	// so this function gets called in setIMEPopupLocation as well as whenever the window
2509 	// receives a ConfigureNotify event
2510 	private void updateIMEPopupLocation() {
2511 		static if(UsingSimpledisplayX11) {
2512 			if (xic is null) {
2513 				return;
2514 			}
2515 
2516 			XPoint nspot;
2517 			nspot.x = cast(short) imePopupLocation.x;
2518 			nspot.y = cast(short) imePopupLocation.y;
2519 			XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null);
2520 			XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null);
2521 			XFree(preeditAttr);
2522 		}
2523 	}
2524 
2525 	private bool imeFocused = true;
2526 
2527 	/++
2528 		Tells the IME whether or not an input field is currently focused in the window.
2529 
2530 		Bugs:
2531 			Not implemented outside X11.
2532 	+/
2533 	void setIMEFocused(bool value) {
2534 		imeFocused = value;
2535 		updateIMEFocused();
2536 	}
2537 
2538 	// used to focus/unfocus the IC if necessary when the window gains/loses focus
2539 	private void updateIMEFocused() {
2540 		static if(UsingSimpledisplayX11) {
2541 			if (xic is null) {
2542 				return;
2543 			}
2544 
2545 			if (focused && imeFocused) {
2546 				XSetICFocus(xic);
2547 			} else {
2548 				XUnsetICFocus(xic);
2549 			}
2550 		}
2551 	}
2552 
2553 	/++
2554 		Returns the native window.
2555 
2556 		History:
2557 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2558 			to access it through the `impl` member (which is semi-supported
2559 			but platform specific and here it is simple enough to offer an accessor).
2560 
2561 		Bugs:
2562 			Not implemented outside Windows or X11.
2563 	+/
2564 	NativeWindowHandle nativeWindowHandle() {
2565 		version(X11)
2566 			return impl.window;
2567 		else version(Windows)
2568 			return impl.hwnd;
2569 		else
2570 			throw new NotYetImplementedException();
2571 	}
2572 
2573 	private bool isTransient() {
2574 		with(WindowTypes)
2575 		final switch(windowType) {
2576 			case normal, undecorated, eventOnly:
2577 			case nestedChild, minimallyWrapped:
2578 				return (customizationFlags & WindowFlags.transient) ? true : false;
2579 			case dropdownMenu, popupMenu, notification, dialog, tooltip, dnd, comboBoxDropdown:
2580 				return true;
2581 		}
2582 	}
2583 
2584 	private SimpleWindow inputProxy;
2585 
2586 	/++
2587 		Releases the grab acquired by [grabInput].
2588 	+/
2589 	void releaseInputGrab() {
2590 		static if(UsingSimpledisplayX11) {
2591 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2592 			if(_parent)
2593 				_parent.inputProxy = null;
2594 		} else version(Windows) {
2595 			ReleaseCapture();
2596 			ClipCursor(null);
2597 		} else version(OSXCocoa) {
2598 			// throw new NotYetImplementedException();
2599 		} else version(Emscripten) {
2600 			// nothing needed
2601 		} else static assert(0);
2602 	}
2603 
2604 	/++
2605 		Sets the input focus to this window.
2606 
2607 		You shouldn't call this very often - please let the user control the input focus.
2608 	+/
2609 	void focus() {
2610 		static if(UsingSimpledisplayX11) {
2611 			SimpleWindow setTo;
2612 			if(setRequestedInputFocus !is null)
2613 				setTo = setRequestedInputFocus();
2614 			if(setTo is null)
2615 				setTo = this;
2616 			// sdpyPrintDebugString("sdpy.focus() ", setTo.impl.window);
2617 			XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime);
2618 		} else version(Windows) {
2619 			SetFocus(this.impl.hwnd);
2620 		} else version(Emscripten) {
2621 			throw new NotYetImplementedException();
2622 		} else version(OSXCocoa) {
2623 			throw new NotYetImplementedException();
2624 		} else static assert(0);
2625 	}
2626 
2627 	/++
2628 		Requests attention from the user for this window.
2629 
2630 
2631 		The typical result of this function is to change the color
2632 		of the taskbar icon, though it may be tweaked on specific
2633 		platforms.
2634 
2635 		It is meant to unobtrusively tell the user that something
2636 		relevant to them happened in the background and they should
2637 		check the window when they get a chance. Upon receiving the
2638 		keyboard focus, the window will automatically return to its
2639 		natural state.
2640 
2641 		If the window already has the keyboard focus, this function
2642 		may do nothing, because the user is presumed to already be
2643 		giving the window attention.
2644 
2645 		Implementation_note:
2646 
2647 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2648 		atom on X11 and the FlashWindow function on Windows.
2649 	+/
2650 	void requestAttention() {
2651 		if(_focused)
2652 			return;
2653 
2654 		version(Windows) {
2655 			FLASHWINFO info;
2656 			info.cbSize = info.sizeof;
2657 			info.hwnd = impl.hwnd;
2658 			info.dwFlags = FLASHW_TRAY;
2659 			info.uCount = 1;
2660 
2661 			FlashWindowEx(&info);
2662 
2663 		} else version(X11) {
2664 			demandingAttention = true;
2665 			demandAttention(this, true);
2666 		} else version(Emscripten) {
2667 			throw new NotYetImplementedException();
2668 		} else version(OSXCocoa) {
2669 			throw new NotYetImplementedException();
2670 		} else static assert(0);
2671 	}
2672 
2673 	private bool _focused;
2674 
2675 	version(X11) private bool demandingAttention;
2676 
2677 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2678 	/// You'll have to call `close()` manually if you set this delegate.
2679 	void delegate () closeQuery;
2680 
2681 	/// This will be called when window visibility was changed.
2682 	void delegate (bool becomesVisible) visibilityChanged;
2683 
2684 	/// This will be called when window becomes visible for the first time.
2685 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2686 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2687 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2688 	/// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization.
2689 	private bool _visibleForTheFirstTimeCalled;
2690 	void delegate () visibleForTheFirstTime;
2691 
2692 	/// Returns true if the window has been closed.
2693 	final @property bool closed() { return _closed; }
2694 
2695 	private final @property bool notClosed() { return !_closed; }
2696 
2697 	/// Returns true if the window is focused.
2698 	final @property bool focused() { return _focused; }
2699 
2700 	private bool _visible;
2701 	/// Returns true if the window is visible (mapped).
2702 	final @property bool visible() { return _visible; }
2703 
2704 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2705 	void close() {
2706 		if (!_closed) {
2707 			runInGuiThread( {
2708 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2709 				if (onClosing !is null) onClosing();
2710 				impl.closeWindow();
2711 				_closed = true;
2712 			} );
2713 		}
2714 	}
2715 
2716 	/++
2717 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2718 
2719 		History:
2720 			Overload added on March 7, 2021.
2721 	+/
2722 	void close() shared {
2723 		(cast() this).close();
2724 	}
2725 
2726 	/++
2727 
2728 	+/
2729 	void maximize() {
2730 		version(Windows)
2731 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2732 		else version(X11) {
2733 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2734 
2735 			// also note _NET_WM_STATE_FULLSCREEN
2736 		}
2737 
2738 	}
2739 
2740 	private bool _fullscreen;
2741 	version(Windows)
2742 	private WINDOWPLACEMENT g_wpPrev;
2743 
2744 	/// not fully implemented but planned for a future release
2745 	void fullscreen(bool yes) {
2746 		version(Windows) {
2747 			g_wpPrev.length = WINDOWPLACEMENT.sizeof;
2748 			DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
2749 			if (dwStyle & WS_OVERLAPPEDWINDOW) {
2750 				MONITORINFO mi;
2751 				mi.cbSize = MONITORINFO.sizeof;
2752 				if (GetWindowPlacement(hwnd, &g_wpPrev) &&
2753 					GetMonitorInfo(MonitorFromWindow(hwnd,
2754 					               MONITOR_DEFAULTTOPRIMARY), &mi)) {
2755 					SetWindowLong(hwnd, GWL_STYLE,
2756 					              dwStyle & ~WS_OVERLAPPEDWINDOW);
2757 					SetWindowPos(hwnd, HWND_TOP,
2758 					             mi.rcMonitor.left, mi.rcMonitor.top,
2759 					             mi.rcMonitor.right - mi.rcMonitor.left,
2760 					             mi.rcMonitor.bottom - mi.rcMonitor.top,
2761 					             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2762 				}
2763 			} else {
2764 				SetWindowLong(hwnd, GWL_STYLE,
2765 				              dwStyle | WS_OVERLAPPEDWINDOW);
2766 				SetWindowPlacement(hwnd, &g_wpPrev);
2767 				SetWindowPos(hwnd, null, 0, 0, 0, 0,
2768 				             SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
2769 				             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
2770 			}
2771 
2772 		} else version(X11) {
2773 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2774 		}
2775 
2776 		_fullscreen = yes;
2777 
2778 	}
2779 
2780 	bool fullscreen() {
2781 		return _fullscreen;
2782 	}
2783 
2784 	/++
2785 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2786 
2787 	+/
2788 	void minimize() {
2789 		version(Windows)
2790 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2791 		//else version(X11)
2792 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2793 	}
2794 
2795 	/// Alias for `hidden = false`
2796 	void show() {
2797 		hidden = false;
2798 	}
2799 
2800 	/// Alias for `hidden = true`
2801 	void hide() {
2802 		hidden = true;
2803 	}
2804 
2805 	/// Hide cursor when it enters the window.
2806 	void hideCursor() {
2807 		version(OSXCocoa) throw new NotYetImplementedException(); else
2808 		if (!_closed) impl.hideCursor();
2809 	}
2810 
2811 	/// Don't hide cursor when it enters the window.
2812 	void showCursor() {
2813 		version(OSXCocoa) throw new NotYetImplementedException(); else
2814 		if (!_closed) impl.showCursor();
2815 	}
2816 
2817 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2818 	 *
2819 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2820 	 * control. Try to think for other approaches before using this function.
2821 	 *
2822 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2823 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2824 	 *       receive "mouse moved here" event.
2825 	 */
2826 	bool warpMouse (int x, int y) {
2827 		version(X11) {
2828 			if (!_closed) { impl.warpMouse(x, y); return true; }
2829 		} else version(Windows) {
2830 			if (!_closed) {
2831 				POINT point;
2832 				point.x = x;
2833 				point.y = y;
2834 				if(ClientToScreen(impl.hwnd, &point)) {
2835 					SetCursorPos(point.x, point.y);
2836 					return true;
2837 				}
2838 			}
2839 		}
2840 		return false;
2841 	}
2842 
2843 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2844 	void sendDummyEvent () {
2845 		version(X11) {
2846 			if (!_closed) { impl.sendDummyEvent(); }
2847 		}
2848 	}
2849 
2850 	/// Set window minimal size.
2851 	void setMinSize (int minwidth, int minheight) {
2852 		version(OSXCocoa) throw new NotYetImplementedException(); else
2853 		if (!_closed) impl.setMinSize(minwidth, minheight);
2854 	}
2855 
2856 	/// Set window maximal size.
2857 	void setMaxSize (int maxwidth, int maxheight) {
2858 		version(OSXCocoa) throw new NotYetImplementedException(); else
2859 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2860 	}
2861 
2862 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2863 	/// Currently only supported on X11.
2864 	void setResizeGranularity (int granx, int grany) {
2865 		version(OSXCocoa) throw new NotYetImplementedException(); else
2866 		if (!_closed) impl.setResizeGranularity(granx, grany);
2867 	}
2868 
2869 	/// Move window.
2870 	void move(int x, int y) {
2871 		version(OSXCocoa) throw new NotYetImplementedException(); else
2872 		if (!_closed) impl.move(x, y);
2873 	}
2874 
2875 	/// ditto
2876 	void move(Point p) {
2877 		version(OSXCocoa) throw new NotYetImplementedException(); else
2878 		if (!_closed) impl.move(p.x, p.y);
2879 	}
2880 
2881 	/++
2882 		Resize window.
2883 
2884 		Note that the width and height of the window are NOT instantly
2885 		updated - it waits for the window manager to approve the resize
2886 		request, which means you must return to the event loop before the
2887 		width and height are actually changed.
2888 	+/
2889 	void resize(int w, int h) {
2890 		if(!_closed && _fullscreen) fullscreen = false;
2891 		if (!_closed) impl.resize(w, h);
2892 	}
2893 
2894 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2895 	void moveResize (int x, int y, int w, int h) {
2896 		if(!_closed && _fullscreen) fullscreen = false;
2897 		if (!_closed) impl.moveResize(x, y, w, h);
2898 	}
2899 
2900 	private bool _hidden;
2901 
2902 	/// Returns true if the window is hidden.
2903 	final @property bool hidden() {
2904 		return _hidden;
2905 	}
2906 
2907 	/// Shows or hides the window based on the bool argument.
2908 	final @property void hidden(bool b) {
2909 		_hidden = b;
2910 		version(Windows) {
2911 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2912 		} else version(X11) {
2913 			if(b)
2914 				//XUnmapWindow(impl.display, impl.window);
2915 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2916 			else
2917 				XMapWindow(impl.display, impl.window);
2918 		} else version(OSXCocoa) {
2919 			if(impl.window)
2920 				impl.window.setIsVisible = !b;
2921 			if(!hidden)
2922 			impl.view.setNeedsDisplay(true);
2923 		} else version(Emscripten) {
2924 		} else static assert(0);
2925 	}
2926 
2927 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2928 	void opacity(double opacity) @property
2929 	in {
2930 		assert(opacity >= 0 && opacity <= 1);
2931 	} do {
2932 		version (Windows) {
2933 			impl.setOpacity(cast(ubyte)(255 * opacity));
2934 		} else version (X11) {
2935 			impl.setOpacity(cast(uint)(uint.max * opacity));
2936 		} else throw new NotYetImplementedException();
2937 	}
2938 
2939 	/++
2940 		Sets your event handlers, without entering the event loop. Useful if you
2941 		have multiple windows - set the handlers on each window, then only do
2942 		[eventLoop] on your main window or call `EventLoop.get.run();`.
2943 
2944 		This assigns the given handlers to [handleKeyEvent], [handleCharEvent],
2945 		[handlePulse], and [handleMouseEvent] automatically based on the provide
2946 		delegate signatures.
2947 	+/
2948 	void setEventHandlers(T...)(T eventHandlers) {
2949 		// FIXME: add more events
2950 		foreach(handler; eventHandlers) {
2951 			static if(__traits(compiles, handleKeyEvent = handler)) {
2952 				handleKeyEvent = handler;
2953 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2954 				handleCharEvent = handler;
2955 			} else static if(__traits(compiles, handlePulse = handler)) {
2956 				handlePulse = handler;
2957 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2958 				handleMouseEvent = handler;
2959 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2960 		}
2961 	}
2962 
2963 	/++
2964 		The event loop automatically returns when the window is closed
2965 		pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2966 		pulse timer is created. The event loop will block until an event
2967 		arrives or the pulse timer goes off.
2968 
2969 		The given `eventHandlers` are passed to [setEventHandlers], which in turn
2970 		assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and
2971 		[handleMouseEvent], based on the signature of delegates you provide.
2972 
2973 		Give one with no parameters to set a timer pulse handler. Give one that
2974 		takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler,
2975 		and one that takes `dchar` for a char event handler. You can use as many
2976 		or as few handlers as you need for your application.
2977 
2978 		Bugs:
2979 
2980 		$(PITFALL
2981 			You should always have one event loop live for your application.
2982 			If you make two windows in sequence, the second call to eventLoop
2983 			might fail:
2984 
2985 			---
2986 			// don't do this!
2987 			auto window = new SimpleWindow();
2988 			window.eventLoop(0);
2989 
2990 			auto window2 = new SimpleWindow();
2991 			window2.eventLoop(0); // problematic! might crash
2992 			---
2993 
2994 			simpledisplay's current implementation assumes that final cleanup is
2995 			done when the event loop refcount reaches zero. So after the first
2996 			eventLoop returns, when there isn't already another one active, it assumes
2997 			the program will exit soon and cleans up.
2998 
2999 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
3000 			it eventually, but in the mean time, there's an easy solution:
3001 
3002 			---
3003 			// do this
3004 			EventLoop mainEventLoop = EventLoop.get; // just add this line
3005 
3006 			auto window = new SimpleWindow();
3007 			window.eventLoop(0);
3008 
3009 			auto window2 = new SimpleWindow();
3010 			window2.eventLoop(0); // perfectly fine since mainEventLoop still alive
3011 			---
3012 
3013 			By adding a top-level reference to the event loop, it ensures the final cleanup
3014 			is not performed until it goes out of scope too, letting the individual window loops
3015 			work without trouble despite the bug.
3016 		)
3017 
3018 		History:
3019 			The overload without `pulseTimeout` was added on December 8, 2021.
3020 
3021 			On December 9, 2021, the default blocking mode (which is now configurable
3022 			because [eventLoopWithBlockingMode] was added) switched from
3023 			[BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This
3024 			should almost never be noticeable to you since the typical simpledisplay
3025 			paradigm has been (and I still recommend) to have one `eventLoop` call.
3026 
3027 		See_Also:
3028 			[eventLoopWithBlockingMode]
3029 	+/
3030 	final int eventLoop(T...)(
3031 		long pulseTimeout,    /// set to zero if you don't want a pulse.
3032 		T eventHandlers) /// delegate list like std.concurrency.receive
3033 	{
3034 		return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers);
3035 	}
3036 
3037 	/// ditto
3038 	final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate))
3039 	{
3040 		return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers);
3041 	}
3042 
3043 	/++
3044 		This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`.
3045 
3046 		History:
3047 			Added December 8, 2021 (dub v10.5)
3048 
3049 			Previously, this implementation was right inside [eventLoop], but when I wanted
3050 			to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I
3051 			just renamed it instead of adding as an overload. Besides, the new name makes it
3052 			easier to remember the order and avoids ambiguity between two int-like params anyway.
3053 
3054 		See_Also:
3055 			[SimpleWindow.eventLoop], [EventLoop]
3056 
3057 		Bugs:
3058 			The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop.
3059 	+/
3060 	final int eventLoopWithBlockingMode(T...)(
3061 		BlockingMode blockingMode, /// when you want this function to block until
3062 		long pulseTimeout,    /// set to zero if you don't want a pulse.
3063 		T eventHandlers) /// delegate list like std.concurrency.receive
3064 	{
3065 		setEventHandlers(eventHandlers);
3066 
3067 		version(with_eventloop) {
3068 			// delegates event loop to my other module
3069 			version(X11)
3070 				XFlush(display);
3071 
3072 			import arsd.eventloop;
3073 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
3074 			scope(exit) clearInterval(handle);
3075 
3076 			loop();
3077 			return 0;
3078 		} else {
3079 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
3080 
3081 			if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1)
3082 				return 0;
3083 
3084 			return el.run(
3085 				((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ?
3086 					null :
3087 					&this.notClosed
3088 			);
3089 		}
3090 	}
3091 
3092 	/++
3093 		This lets you draw on the window (or its backing buffer) using basic
3094 		2D primitives.
3095 
3096 		Be sure to call this in a limited scope because your changes will not
3097 		actually appear on the window until ScreenPainter's destructor runs.
3098 
3099 		Returns: an instance of [ScreenPainter], which has the drawing methods
3100 		on it to draw on this window.
3101 
3102 		Params:
3103 			manualInvalidations = if you set this to true, you will need to
3104 			set the invalid rectangle on the painter yourself. If false, it
3105 			assumes the whole window has been redrawn each time you draw.
3106 
3107 			Only invalidated rectangles are blitted back to the window when
3108 			the destructor runs. Doing this yourself can reduce flickering
3109 			of child windows.
3110 
3111 		History:
3112 			The `manualInvalidations` parameter overload was added on
3113 			December 30, 2021 (dub v10.5)
3114 	+/
3115 	ScreenPainter draw() {
3116 		return draw(false);
3117 	}
3118 	/// ditto
3119 	ScreenPainter draw(bool manualInvalidations) {
3120 		return impl.getPainter(manualInvalidations);
3121 	}
3122 
3123 	// This is here to implement the interface we use for various native handlers.
3124 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
3125 
3126 	// maps native window handles to SimpleWindow instances, if there are any
3127 	// you shouldn't need this, but it is public in case you do in a native event handler or something
3128 	// mac uses void* cuz NSObject opHash won't pick up in typeinfo
3129 	version(OSXCocoa)
3130 	public __gshared SimpleWindow[void*] nativeMapping;
3131 	else
3132 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
3133 
3134 	// the size the user requested in the constructor, in automatic scale modes it always pretends to be this size
3135 	private int _virtualWidth;
3136 	private int _virtualHeight;
3137 
3138 	/// Width of the window's drawable client area, in pixels.
3139 	@scriptable
3140 	final @property int width() const pure nothrow @safe @nogc {
3141 		if(resizability == Resizability.automaticallyScaleIfPossible)
3142 			return _virtualWidth;
3143 		else
3144 			return _width;
3145 	}
3146 
3147 	/// Height of the window's drawable client area, in pixels.
3148 	@scriptable
3149 	final @property int height() const pure nothrow @safe @nogc {
3150 		if(resizability == Resizability.automaticallyScaleIfPossible)
3151 			return _virtualHeight;
3152 		else
3153 			return _height;
3154 	}
3155 
3156 	/++
3157 		Returns the actual size of the window, bypassing the logical
3158 		illusions of [Resizability.automaticallyScaleIfPossible].
3159 
3160 		History:
3161 			Added November 11, 2022 (dub v10.10)
3162 	+/
3163 	final @property Size actualWindowSize() const pure nothrow @safe @nogc {
3164 		return Size(_width, _height);
3165 	}
3166 
3167 
3168 	private int _width;
3169 	private int _height;
3170 
3171 	// HACK: making the best of some copy constructor woes with refcounting
3172 	private ScreenPainterImplementation* activeScreenPainter_;
3173 
3174 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
3175 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
3176 
3177 	private OpenGlOptions openglMode;
3178 	private Resizability resizability;
3179 	private WindowTypes windowType;
3180 	private int customizationFlags;
3181 
3182 	/// `true` if OpenGL was initialized for this window.
3183 	@property bool isOpenGL () const pure nothrow @safe @nogc {
3184 		version(without_opengl)
3185 			return false;
3186 		else
3187 			return (openglMode == OpenGlOptions.yes);
3188 	}
3189 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
3190 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
3191 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
3192 
3193 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3194 	/// to call this, as it's not recommended to share window between threads.
3195 	void mtLock () {
3196 		version(X11) {
3197 			XLockDisplay(this.display);
3198 		}
3199 	}
3200 
3201 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3202 	/// to call this, as it's not recommended to share window between threads.
3203 	void mtUnlock () {
3204 		version(X11) {
3205 			XUnlockDisplay(this.display);
3206 		}
3207 	}
3208 
3209 	/// Emit a beep to get user's attention.
3210 	void beep () {
3211 		version(X11) {
3212 			XBell(this.display, 100);
3213 		} else version(Windows) {
3214 			MessageBeep(0xFFFFFFFF);
3215 		}
3216 	}
3217 
3218 
3219 
3220 	version(without_opengl) {} else {
3221 
3222 		/// 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`.
3223 		void delegate() redrawOpenGlScene;
3224 
3225 		/// This will allow you to change OpenGL vsync state.
3226 		final @property void vsync (bool wait) {
3227 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3228 			version(X11) {
3229 				setAsCurrentOpenGlContext();
3230 				glxSetVSync(display, impl.window, wait);
3231 			} else version(Windows) {
3232 				setAsCurrentOpenGlContext();
3233 				wglSetVSync(wait);
3234 			}
3235 		}
3236 
3237 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
3238 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
3239 		/// enough without waiting 'em to finish their frame business.
3240 		bool useGLFinish = true;
3241 
3242 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
3243 		/// 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.
3244 		void redrawOpenGlSceneNow() {
3245 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
3246 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3247 			if(redrawOpenGlScene is null)
3248 				return;
3249 
3250 			this.mtLock();
3251 			scope(exit) this.mtUnlock();
3252 
3253 			this.setAsCurrentOpenGlContext();
3254 
3255 			redrawOpenGlScene();
3256 
3257 			this.swapOpenGlBuffers();
3258 			// 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.
3259 			if (useGLFinish) glFinish();
3260 		}
3261 
3262 		private bool redrawOpenGlSceneSoonSet = false;
3263 		private static class RedrawOpenGlSceneEvent {
3264 			SimpleWindow w;
3265 			this(SimpleWindow w) { this.w = w; }
3266 		}
3267 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
3268 		/++
3269 			Queues an opengl redraw as soon as the other pending events are cleared.
3270 		+/
3271 		void redrawOpenGlSceneSoon() {
3272 			if(redrawOpenGlScene is null)
3273 				return;
3274 
3275 			if(!redrawOpenGlSceneSoonSet) {
3276 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
3277 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
3278 				redrawOpenGlSceneSoonSet = true;
3279 			}
3280 			this.postEvent(redrawOpenGlSceneEvent, true);
3281 		}
3282 
3283 
3284 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3285 		void setAsCurrentOpenGlContext() {
3286 			assert(openglMode == OpenGlOptions.yes);
3287 			version(X11) {
3288 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
3289 					throw new Exception("glXMakeCurrent");
3290 			} else version(Windows) {
3291 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3292 				if (!wglMakeCurrent(ghDC, ghRC))
3293 					throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too
3294 			}
3295 		}
3296 
3297 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
3298 		/// This doesn't throw, returning success flag instead.
3299 		bool setAsCurrentOpenGlContextNT() nothrow {
3300 			assert(openglMode == OpenGlOptions.yes);
3301 			version(X11) {
3302 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
3303 			} else version(Windows) {
3304 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3305 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
3306 			}
3307 		}
3308 
3309 		/// 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.
3310 		/// This doesn't throw, returning success flag instead.
3311 		bool releaseCurrentOpenGlContext() nothrow {
3312 			assert(openglMode == OpenGlOptions.yes);
3313 			version(X11) {
3314 				return (glXMakeCurrent(display, 0, null) != 0);
3315 			} else version(Windows) {
3316 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
3317 				return wglMakeCurrent(ghDC, null) ? true : false;
3318 			}
3319 		}
3320 
3321 		/++
3322 			simpledisplay always uses double buffering, usually automatically. This
3323 			manually swaps the OpenGL buffers. You should only use this if you are NOT
3324 			using the [redrawOpenGlScene] delegate.
3325 
3326 
3327 			You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it
3328 			for you after calling your `redrawOpenGlScene`. Please note that once you swap
3329 			buffers, the contents become undefined - the implementation, in the OpenGL driver
3330 			or the desktop compositor, may not actually just swap two buffers. The back buffer's
3331 			contents are $(B undefined) after calling this function.
3332 
3333 			See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers
3334 			and https://linux.die.net/man/3/glxswapbuffers
3335 
3336 			Remember that this may throw an exception, which you can catch in a multithreaded
3337 			application to keep your thread from dying from an unhandled exception.
3338 		+/
3339 		void swapOpenGlBuffers() {
3340 			assert(openglMode == OpenGlOptions.yes);
3341 			version(X11) {
3342 				if (!this._visible) return; // no need to do this if window is invisible
3343 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
3344 				glXSwapBuffers(display, impl.window);
3345 			} else version(Windows) {
3346 				SwapBuffers(ghDC);
3347 			}
3348 		}
3349 	}
3350 
3351 	/++
3352 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
3353 
3354 
3355 		---
3356 			auto window = new SimpleWindow(100, 100, "First title");
3357 			window.title = "A new title";
3358 		---
3359 
3360 		You may call this function at any time.
3361 	+/
3362 	@property void title(string title) {
3363 		_title = title;
3364 		impl.setTitle(title);
3365 	}
3366 
3367 	private string _title;
3368 
3369 	/// Gets the title
3370 	@property string title() {
3371 		if(_title is null)
3372 			_title = getRealTitle();
3373 		return _title;
3374 	}
3375 
3376 	/++
3377 		Get the title as set by the window manager.
3378 		May not match what you attempted to set.
3379 	+/
3380 	string getRealTitle() {
3381 		static if(is(typeof(impl.getTitle())))
3382 			return impl.getTitle();
3383 		else
3384 			return null;
3385 	}
3386 
3387 	// don't use this generally it is not yet really released
3388 	version(X11)
3389 	@property Image secret_icon() {
3390 		return secret_icon_inner;
3391 	}
3392 	private Image secret_icon_inner;
3393 
3394 
3395 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing.
3396 	@property void icon(MemoryImage icon) {
3397 		if(icon is null)
3398 			return;
3399 		auto tci = icon.getAsTrueColorImage();
3400 		version(Windows) {
3401 			winIcon = new WindowsIcon(icon);
3402 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
3403 		} else version(X11) {
3404 			secret_icon_inner = Image.fromMemoryImage(icon);
3405 			// FIXME: ensure this is correct
3406 			auto display = XDisplayConnection.get;
3407 			arch_ulong[] buffer;
3408 			buffer ~= icon.width;
3409 			buffer ~= icon.height;
3410 			foreach(c; tci.imageData.colors) {
3411 				arch_ulong b;
3412 				b |= c.a << 24;
3413 				b |= c.r << 16;
3414 				b |= c.g << 8;
3415 				b |= c.b;
3416 				buffer ~= b;
3417 			}
3418 
3419 			XChangeProperty(
3420 				display,
3421 				impl.window,
3422 				GetAtom!("_NET_WM_ICON", true)(display),
3423 				GetAtom!"CARDINAL"(display),
3424 				32 /* bits */,
3425 				0 /*PropModeReplace*/,
3426 				buffer.ptr,
3427 				cast(int) buffer.length);
3428 		} else version(OSXCocoa) {
3429 			throw new NotYetImplementedException();
3430 		} else version(Emscripten) {
3431 			throw new NotYetImplementedException();
3432 		} else static assert(0);
3433 	}
3434 
3435 	version(Windows)
3436 		private WindowsIcon winIcon;
3437 
3438 	bool _suppressDestruction;
3439 
3440 	~this() {
3441 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
3442 		if(_suppressDestruction)
3443 			return;
3444 		impl.dispose();
3445 	}
3446 
3447 	private bool _closed;
3448 
3449 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
3450 	/*
3451 	ScreenPainter drawTransiently() {
3452 		return impl.getPainter();
3453 	}
3454 	*/
3455 
3456 	/// Draws an image on the window. This is meant to provide quick look
3457 	/// of a static image generated elsewhere.
3458 	@property void image(Image i) {
3459 	/+
3460 		version(Windows) {
3461 			BITMAP bm;
3462 			HDC hdc = GetDC(hwnd);
3463 			HDC hdcMem = CreateCompatibleDC(hdc);
3464 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
3465 
3466 			GetObject(i.handle, bm.sizeof, &bm);
3467 
3468 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
3469 
3470 			SelectObject(hdcMem, hbmOld);
3471 			DeleteDC(hdcMem);
3472 			ReleaseDC(hwnd, hdc);
3473 
3474 			/*
3475 			RECT r;
3476 			r.right = i.width;
3477 			r.bottom = i.height;
3478 			InvalidateRect(hwnd, &r, false);
3479 			*/
3480 		} else
3481 		version(X11) {
3482 			if(!destroyed) {
3483 				if(i.usingXshm)
3484 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
3485 				else
3486 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
3487 			}
3488 		} else
3489 		version(OSXCocoa) {
3490 			draw().drawImage(Point(0, 0), i);
3491 			setNeedsDisplay(view, true);
3492 		} else static assert(0);
3493 	+/
3494 		auto painter = this.draw;
3495 		painter.drawImage(Point(0, 0), i);
3496 	}
3497 
3498 	/++
3499 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
3500 
3501 		---
3502 		window.cursor = GenericCursor.Help;
3503 		// now the window mouse cursor is set to a generic help
3504 		---
3505 
3506 	+/
3507 	@property void cursor(MouseCursor cursor) {
3508 		version(OSXCocoa)
3509 			{} // featureNotImplemented();
3510 		else
3511 		if(this.impl.curHidden <= 0) {
3512 			static if(UsingSimpledisplayX11) {
3513 				auto ch = cursor.cursorHandle;
3514 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
3515 			} else version(Windows) {
3516 				auto ch = cursor.cursorHandle;
3517 				impl.currentCursor = ch;
3518 				SetCursor(ch); // redraw without waiting for mouse movement to update
3519 			} else featureNotImplemented();
3520 		}
3521 
3522 	}
3523 
3524 	/// What follows are the event handlers. These are set automatically
3525 	/// by the eventLoop function, but are still public so you can change
3526 	/// them later. wasPressed == true means key down. false == key up.
3527 
3528 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
3529 	void delegate(KeyEvent ke) handleKeyEvent;
3530 
3531 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
3532 	void delegate(dchar c) handleCharEvent;
3533 
3534 	/// Handles a timer pulse. Settable through setEventHandlers.
3535 	void delegate() handlePulse;
3536 
3537 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
3538 	void delegate(bool) onFocusChange;
3539 
3540 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
3541 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
3542 	void delegate() onClosing;
3543 
3544 	/** Called when we received destroy notification. At this stage we cannot do much with our window
3545 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
3546 	 * last minute cleanup. */
3547 	void delegate() onDestroyed;
3548 
3549 	static if (UsingSimpledisplayX11)
3550 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
3551 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
3552 	 * You will probably never need to setup this handler, it is for very low-level stuff.
3553 	 *
3554 	 * WARNING! Xlib is multithread-locked when this handles is called! */
3555 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
3556 
3557 	//version(Windows)
3558 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
3559 
3560 	private {
3561 		int lastMouseX = int.min;
3562 		int lastMouseY = int.min;
3563 		void mdx(ref MouseEvent ev) {
3564 			if(lastMouseX == int.min || lastMouseY == int.min) {
3565 				ev.dx = 0;
3566 				ev.dy = 0;
3567 			} else {
3568 				ev.dx = ev.x - lastMouseX;
3569 				ev.dy = ev.y - lastMouseY;
3570 			}
3571 
3572 			lastMouseX = ev.x;
3573 			lastMouseY = ev.y;
3574 		}
3575 	}
3576 
3577 	/// Mouse event handler. Settable through setEventHandlers.
3578 	void delegate(MouseEvent) handleMouseEvent;
3579 
3580 	/// use to redraw child widgets if you use system apis to add stuff
3581 	void delegate() paintingFinished;
3582 
3583 	void delegate() paintingFinishedDg() {
3584 		return paintingFinished;
3585 	}
3586 
3587 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
3588 	/// for this to ever happen.
3589 	void delegate(int width, int height) windowResized;
3590 
3591 	/++
3592 		Platform specific - handle any native message this window gets.
3593 
3594 		Note: this is called *in addition to* other event handlers, unless you either:
3595 
3596 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
3597 
3598 		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.
3599 
3600 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
3601 
3602 		On X, it takes the form of `int delegate(XEvent)`.
3603 
3604 		History:
3605 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
3606 
3607 			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.
3608 	+/
3609 	NativeEventHandler handleNativeEvent_;
3610 
3611 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
3612 		return handleNativeEvent_;
3613 	}
3614 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
3615 		handleNativeEvent_ = neh;
3616 	}
3617 
3618 	private void dispatchXInputEvent(InputDeviceEvent ide) @system {
3619 		if(auto aih = ide.deviceId in advancedInputHandlers) {
3620 			ide.deviceObject = aih.device;
3621 			aih.handler(ide);
3622 		}
3623 		if(auto aih = (cast(typeof(ide.deviceId)) 0) in advancedInputHandlers) {
3624 			ide.deviceObject = null;
3625 			aih.handler(ide);
3626 		}
3627 	}
3628 
3629 	private struct AdvancedInputHandler {
3630 		InputDevice device;
3631 		void delegate(InputDeviceEvent ide) handler;
3632 	}
3633 
3634 	private AdvancedInputHandler[typeof(InputDevice.deviceId)] advancedInputHandlers;
3635 	private void setAdvancedInputHandler(InputDevice id, void delegate(InputDeviceEvent ide) handler) {
3636 		advancedInputHandlers[id ? id.deviceId : (cast(typeof(InputDeviceEvent.deviceId)) 0)] = AdvancedInputHandler(id, handler);
3637 	}
3638 
3639 	version(Windows)
3640 	// compatibility shim with the old deprecated way
3641 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
3642 	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) {
3643 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
3644 			auto ret = dg(h, m, w, l);
3645 			if(ret == 0)
3646 				r = 1;
3647 			return ret;
3648 		};
3649 	}
3650 
3651 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
3652 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
3653 	/// this instead and it will work the same way.
3654 	__gshared NativeEventHandler handleNativeGlobalEvent;
3655 
3656 //  private:
3657 	/// The native implementation is available, but you shouldn't use it unless you are
3658 	/// familiar with the underlying operating system, don't mind depending on it, and
3659 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
3660 	/// do what you need to do with handleNativeEvent instead.
3661 	///
3662 	/// This is likely to eventually change to be just a struct holding platform-specific
3663 	/// handles instead of a template mixin at some point because I'm not happy with the
3664 	/// code duplication here (ironically).
3665 	mixin NativeSimpleWindowImplementation!() impl;
3666 
3667 	/**
3668 		This is in-process one-way (from anything to window) event sending mechanics.
3669 		It is thread-safe, so it can be used in multi-threaded applications to send,
3670 		for example, "wake up and repaint" events when thread completed some operation.
3671 		This will allow to avoid using timer pulse to check events with synchronization,
3672 		'cause event handler will be called in UI thread. You can stop guessing which
3673 		pulse frequency will be enough for your app.
3674 		Note that events handlers may be called in arbitrary order, i.e. last registered
3675 		handler can be called first, and vice versa.
3676 	*/
3677 public:
3678 	/** Is our custom event queue empty? Can be used in simple cases to prevent
3679 	 * "spamming" window with events it can't cope with.
3680 	 * It is safe to call this from non-UI threads.
3681 	 */
3682 	@property bool eventQueueEmpty() () {
3683 		synchronized(this) {
3684 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
3685 		}
3686 		return true;
3687 	}
3688 
3689 	/** Does our custom event queue contains at least one with the given type?
3690 	 * Can be used in simple cases to prevent "spamming" window with events
3691 	 * it can't cope with.
3692 	 * It is safe to call this from non-UI threads.
3693 	 */
3694 	@property bool eventQueued(ET:Object) () {
3695 		synchronized(this) {
3696 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
3697 				if (!o.doProcess) {
3698 					if (cast(ET)(o.evt)) return true;
3699 				}
3700 			}
3701 		}
3702 		return false;
3703 	}
3704 
3705 	/++
3706 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
3707 
3708 		History:
3709 			Added May 12, 2021
3710 	+/
3711 	void delegate(Exception e) nothrow eventUncaughtException;
3712 
3713 	/** Add listener for custom event. Can be used like this:
3714 	 *
3715 	 * ---------------------
3716 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3717 	 *   ...
3718 	 *   win.removeEventListener(eid);
3719 	 * ---------------------
3720 	 *
3721 	 * Returns: 0 on failure (should never happen, so ignore it)
3722 	 *
3723 	 * $(WARNING Don't use this method in object destructors!)
3724 	 *
3725 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3726 	 *           'cause if event handler id counter will overflow, you won't be able
3727 	 *           to register any more events.)
3728 	 */
3729 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3730 		if (dg is null) return 0; // ignore empty handlers
3731 		synchronized(this) {
3732 			//FIXME: abort on overflow?
3733 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3734 			EventHandlerEntry e;
3735 			e.dg = delegate (Object o) {
3736 				if (auto co = cast(ET)o) {
3737 					try {
3738 						dg(co);
3739 					} catch (Exception e) {
3740 						// sorry!
3741 						if(eventUncaughtException)
3742 							eventUncaughtException(e);
3743 					}
3744 					return true;
3745 				}
3746 				return false;
3747 			};
3748 			e.id = lastUsedHandlerId;
3749 			auto optr = eventHandlers.ptr;
3750 			eventHandlers ~= e;
3751 			if (eventHandlers.ptr !is optr) {
3752 				import core.memory : GC;
3753 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3754 			}
3755 			return lastUsedHandlerId;
3756 		}
3757 	}
3758 
3759 	/// Remove event listener. It is safe to pass invalid event id here.
3760 	/// $(WARNING Don't use this method in object destructors!)
3761 	void removeEventListener() (uint id) {
3762 		if (id == 0 || id > lastUsedHandlerId) return;
3763 		synchronized(this) {
3764 			foreach (immutable idx; 0..eventHandlers.length) {
3765 				if (eventHandlers[idx].id == id) {
3766 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3767 					eventHandlers[$-1].dg = null;
3768 					eventHandlers.length -= 1;
3769 					eventHandlers.assumeSafeAppend;
3770 					return;
3771 				}
3772 			}
3773 		}
3774 	}
3775 
3776 	/// Post event to queue. It is safe to call this from non-UI threads.
3777 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3778 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3779 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3780 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false, bool replaceTime = true) {
3781 		if (this.closed) return false; // closed windows can't handle events
3782 
3783 		MonoTime storedHittime;
3784 
3785 		// remove all events of type `ET`
3786 		void removeAllET () {
3787 			uint eidx = 0, ec = eventQueueUsed;
3788 			auto eptr = eventQueue.ptr;
3789 			while (eidx < ec) {
3790 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3791 				if (cast(ET)eptr.evt !is null) {
3792 					if(!replaceTime) {
3793 						storedHittime = eptr.hittime;
3794 					}
3795 					// i found her!
3796 					if (inCustomEventProcessor) {
3797 						// if we're in custom event processing loop, processor will clear it for us
3798 						eptr.evt = null;
3799 						++eidx;
3800 						++eptr;
3801 					} else {
3802 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3803 						ec = --eventQueueUsed;
3804 						// clear last event (it is already copied)
3805 						eventQueue.ptr[ec].evt = null;
3806 					}
3807 				} else {
3808 					++eidx;
3809 					++eptr;
3810 				}
3811 			}
3812 		}
3813 
3814 		if (evt is null) {
3815 			if (replace) { synchronized(this) removeAllET(); }
3816 			// ignore empty events, they can't be handled anyway
3817 			return false;
3818 		}
3819 
3820 		// add events even if no event FD/event object created yet
3821 		synchronized(this) {
3822 			if (replace) removeAllET();
3823 			if (eventQueueUsed == uint.max) return false; // just in case
3824 
3825 			auto nev = (replaceTime || storedHittime == MonoTime.zero) ? QueuedEvent(evt, timeoutmsecs) : QueuedEvent(evt, storedHittime);
3826 
3827 			if (eventQueueUsed < eventQueue.length) {
3828 				eventQueue[eventQueueUsed++] = nev;
3829 			} else {
3830 				if (eventQueue.capacity == eventQueue.length) {
3831 					// need to reallocate; do a trick to ensure that old array is cleared
3832 					auto oarr = eventQueue;
3833 					if(replaceTime)
3834 						eventQueue ~= nev;
3835 					else {
3836 						eventQueue ~= nev;
3837 					}
3838 					// just in case, do yet another check
3839 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3840 					import core.memory : GC;
3841 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3842 				} else {
3843 					auto optr = eventQueue.ptr;
3844 					eventQueue ~= nev;
3845 					assert(eventQueue.ptr is optr);
3846 				}
3847 				++eventQueueUsed;
3848 				assert(eventQueueUsed == eventQueue.length);
3849 			}
3850 			if (!eventWakeUp()) {
3851 				// can't wake up event processor, so there is no reason to keep the event
3852 				assert(eventQueueUsed > 0);
3853 				eventQueue[--eventQueueUsed].evt = null;
3854 				return false;
3855 			}
3856 			return true;
3857 		}
3858 	}
3859 
3860 	/// Post event to queue. It is safe to call this from non-UI threads.
3861 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3862 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3863 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3864 		return postTimeout!ET(evt, 0, replace);
3865 	}
3866 
3867 private:
3868 	private import core.time : MonoTime;
3869 
3870 	version(Posix) {
3871 		__gshared int customEventFDRead = -1;
3872 		__gshared int customEventFDWrite = -1;
3873 		__gshared int customSignalFD = -1;
3874 	} else version(Windows) {
3875 		__gshared HANDLE customEventH = null;
3876 	}
3877 
3878 	// wake up event processor
3879 	static bool eventWakeUp () {
3880 		version(X11) {
3881 			import core.sys.posix.unistd : write;
3882 			ulong n = 1;
3883 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3884 			return true;
3885 		} else version(Windows) {
3886 			if (customEventH !is null) SetEvent(customEventH);
3887 			return true;
3888 		} else version(OSXCocoa) {
3889 			if(globalAppDelegate)
3890 				globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false);
3891 			return true;
3892 		} else {
3893 			// not implemented for other OSes
3894 			return false;
3895 		}
3896 	}
3897 
3898 	static struct QueuedEvent {
3899 		Object evt;
3900 		bool timed = false;
3901 		MonoTime hittime = MonoTime.zero;
3902 		bool doProcess = false; // process event at the current iteration (internal flag)
3903 
3904 		this (Object aevt, uint toutmsecs) {
3905 			evt = aevt;
3906 			if (toutmsecs > 0) {
3907 				import core.time : msecs;
3908 				timed = true;
3909 				hittime = MonoTime.currTime+toutmsecs.msecs;
3910 			}
3911 		}
3912 
3913 		this (Object aevt, MonoTime hittime) {
3914 			evt = aevt;
3915 			timed = true;
3916 			this.hittime = hittime;
3917 		}
3918 	}
3919 
3920 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3921 	static struct EventHandlerEntry {
3922 		CustomEventHandler dg;
3923 		uint id;
3924 	}
3925 
3926 	uint lastUsedHandlerId;
3927 	EventHandlerEntry[] eventHandlers;
3928 	QueuedEvent[] eventQueue = null;
3929 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3930 	bool inCustomEventProcessor = false; // required to properly remove events
3931 
3932 	// process queued events and call custom event handlers
3933 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3934 	void processCustomEvents () @system {
3935 		bool hasSomethingToDo = false;
3936 		uint ecount;
3937 		bool ocep;
3938 		synchronized(this) {
3939 			ocep = inCustomEventProcessor;
3940 			inCustomEventProcessor = true;
3941 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3942 			auto ctt = MonoTime.currTime;
3943 			bool hasEmpty = false;
3944 			// mark events to process (this is required for `eventQueued()`)
3945 			foreach (ref qe; eventQueue[0..ecount]) {
3946 				if (qe.evt is null) { hasEmpty = true; continue; }
3947 				if (qe.timed) {
3948 					qe.doProcess = (qe.hittime <= ctt);
3949 				} else {
3950 					qe.doProcess = true;
3951 				}
3952 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3953 			}
3954 			if (!hasSomethingToDo) {
3955 				// remove empty events
3956 				if (hasEmpty) {
3957 					uint eidx = 0, ec = eventQueueUsed;
3958 					auto eptr = eventQueue.ptr;
3959 					while (eidx < ec) {
3960 						if (eptr.evt is null) {
3961 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3962 							ec = --eventQueueUsed;
3963 							eventQueue.ptr[ec].evt = null; // make GC life easier
3964 						} else {
3965 							++eidx;
3966 							++eptr;
3967 						}
3968 					}
3969 				}
3970 				inCustomEventProcessor = ocep;
3971 				return;
3972 			}
3973 		}
3974 		// process marked events
3975 		uint efree = 0; // non-processed events will be put at this index
3976 		EventHandlerEntry[] eh;
3977 		Object evt;
3978 		foreach (immutable eidx; 0..ecount) {
3979 			synchronized(this) {
3980 				if (!eventQueue[eidx].doProcess) {
3981 					// skip this event
3982 					assert(efree <= eidx);
3983 					if (efree != eidx) {
3984 						// copy this event to queue start
3985 						eventQueue[efree] = eventQueue[eidx];
3986 						eventQueue[eidx].evt = null; // just in case
3987 					}
3988 					++efree;
3989 					continue;
3990 				}
3991 				evt = eventQueue[eidx].evt;
3992 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3993 				if (evt is null) continue; // just in case
3994 				// try all handlers; this can be slow, but meh...
3995 				eh = eventHandlers;
3996 			}
3997 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3998 			evt = null;
3999 			eh = null;
4000 		}
4001 		synchronized(this) {
4002 			// move all unprocessed events to queue top; efree holds first "free index"
4003 			foreach (immutable eidx; ecount..eventQueueUsed) {
4004 				assert(efree <= eidx);
4005 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
4006 				++efree;
4007 			}
4008 			eventQueueUsed = efree;
4009 			// wake up event processor on next event loop iteration if we have more queued events
4010 			// also, remove empty events
4011 			bool awaken = false;
4012 			uint eidx = 0, ec = eventQueueUsed;
4013 			auto eptr = eventQueue.ptr;
4014 			while (eidx < ec) {
4015 				if (eptr.evt is null) {
4016 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
4017 					ec = --eventQueueUsed;
4018 					eventQueue.ptr[ec].evt = null; // make GC life easier
4019 				} else {
4020 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
4021 					++eidx;
4022 					++eptr;
4023 				}
4024 			}
4025 			inCustomEventProcessor = ocep;
4026 		}
4027 	}
4028 
4029 	// for all windows in nativeMapping
4030 	package static void processAllCustomEvents () @system {
4031 
4032 		cleanupQueue.process();
4033 
4034 		justCommunication.processCustomEvents();
4035 
4036 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
4037 			if (sw is null || sw.closed) continue;
4038 			sw.processCustomEvents();
4039 		}
4040 
4041 		runPendingRunInGuiThreadDelegates();
4042 	}
4043 
4044 	// 0: infinite (i.e. no scheduled events in queue)
4045 	uint eventQueueTimeoutMSecs () {
4046 		synchronized(this) {
4047 			if (eventQueueUsed == 0) return 0;
4048 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
4049 			uint res = int.max;
4050 			auto ctt = MonoTime.currTime;
4051 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
4052 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
4053 				if (qe.doProcess) continue; // just in case
4054 				if (!qe.timed) return 1; // minimal
4055 				if (qe.hittime <= ctt) return 1; // minimal
4056 				auto tms = (qe.hittime-ctt).total!"msecs";
4057 				if (tms < 1) tms = 1; // safety net
4058 				if (tms >= int.max) tms = int.max-1; // and another safety net
4059 				if (res > tms) res = cast(uint)tms;
4060 			}
4061 			return (res >= int.max ? 0 : res);
4062 		}
4063 	}
4064 
4065 	// for all windows in nativeMapping
4066 	static uint eventAllQueueTimeoutMSecs () {
4067 		uint res = uint.max;
4068 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
4069 			if (sw is null || sw.closed) continue;
4070 			uint to = sw.eventQueueTimeoutMSecs();
4071 			if (to && to < res) {
4072 				res = to;
4073 				if (to == 1) break; // can't have less than this
4074 			}
4075 		}
4076 		return (res >= int.max ? 0 : res);
4077 	}
4078 
4079 	version(X11) {
4080 		ResizeEvent pendingResizeEvent;
4081 	}
4082 
4083 	/++
4084 		When in opengl mode and automatically resizing, it will set the opengl viewport to stretch.
4085 
4086 		If you work with multiple opengl contexts and/or threads, this might be more trouble than it is
4087 		worth so you can disable it by setting this to `true`.
4088 
4089 		History:
4090 			Added November 13, 2022.
4091 	+/
4092 	public bool suppressAutoOpenglViewport = false;
4093 	private void updateOpenglViewportIfNeeded(int width, int height) {
4094 		if(suppressAutoOpenglViewport) return;
4095 
4096 		version(without_opengl) {} else
4097 		if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
4098 		// writeln(width, " ", height);
4099 			setAsCurrentOpenGlContextNT();
4100 			glViewport(0, 0, width, height);
4101 		}
4102 	}
4103 
4104 	// TODO: Implement on non-Windows platforms (where available).
4105 	private CornerStyle _fauxCornerStyle = CornerStyle.automatic;
4106 
4107 	/++
4108 		Style of the window's corners
4109 
4110 		$(WARNING
4111 			Currently only implemented on Windows targets.
4112 			Has no visual effect elsewhere.
4113 
4114 			Windows: Requires Windows 11 or later.
4115 		)
4116 
4117 		History:
4118 			Added September 09, 2024.
4119 	 +/
4120 	public CornerStyle cornerStyle() @trusted {
4121 		version(Windows) {
4122 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4123 			const apiResult = DwmGetWindowAttribute(
4124 				this.hwnd,
4125 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4126 				&dwmCorner,
4127 				typeof(dwmCorner).sizeof
4128 			);
4129 
4130 			if (apiResult != S_OK) {
4131 				// Unsupported?
4132 				if (apiResult == E_INVALIDARG) {
4133 					// Feature unsupported; Windows version probably too old.
4134 					// Requires Windows 11 (build 22000) or later.
4135 					return _fauxCornerStyle;
4136 				}
4137 
4138 				throw new WindowsApiException("DwmGetWindowAttribute", apiResult);
4139 			}
4140 
4141 			CornerStyle corner;
4142 			if (!dwmCorner.fromDWM(corner)) {
4143 				throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner);
4144 			}
4145 			return corner;
4146 		} else {
4147 			return _fauxCornerStyle;
4148 		}
4149 	}
4150 
4151 	/// ditto
4152 	public void cornerStyle(const CornerStyle corner) @trusted {
4153 		version(Windows) {
4154 			DWM_WINDOW_CORNER_PREFERENCE dwmCorner;
4155 			if (!corner.toDWM(dwmCorner)) {
4156 				assert(false, "This should have been impossible because of a final switch.");
4157 			}
4158 
4159 			const apiResult = DwmSetWindowAttribute(
4160 				this.hwnd,
4161 				DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
4162 				&dwmCorner,
4163 				typeof(dwmCorner).sizeof
4164 			);
4165 
4166 			if (apiResult != S_OK) {
4167 				// Unsupported?
4168 				if (apiResult == E_INVALIDARG) {
4169 					// Feature unsupported; Windows version probably too old.
4170 					// Requires Windows 11 (build 22000) or later.
4171 					_fauxCornerStyle = corner;
4172 					return;
4173 				}
4174 
4175 				throw new WindowsApiException("DwmSetWindowAttribute", apiResult);
4176 			}
4177 		} else {
4178 			_fauxCornerStyle = corner;
4179 		}
4180 	}
4181 }
4182 
4183 version(OSXCocoa)
4184 	enum NSWindow NullWindow = null;
4185 else
4186 	enum NullWindow = NativeWindowHandle.init;
4187 
4188 /++
4189 	Magic pseudo-window for just posting events to a global queue.
4190 
4191 	Not entirely supported, I might delete it at any time.
4192 
4193 	Added Nov 5, 2021.
4194 +/
4195 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow);
4196 
4197 /* Advanced input support { */
4198 
4199 /++
4200 	Returns input devices currently attached to the computer that can be used to advanced input event subscriptions.
4201 
4202 	On Windows, this generally means touch screens and pen tablets.
4203 
4204 	On X, this can be just about anything.
4205 
4206 	This may change in the future.
4207 
4208 
4209 	History:
4210 		Added December 1, 2025
4211 +/
4212 InputDevice[] getInputDevices() {
4213 	version(Windows) {
4214 		POINTER_DEVICE_INFO[32] buffer;
4215 		uint count = cast(uint) buffer.length;
4216 		if(GetPointerDevices(&count, buffer.ptr)) {
4217 			InputDevice[] ret;
4218 			foreach(dev; buffer[0 .. count]) {
4219 				auto id = new InputDevice;
4220 				id.deviceId = dev.device;
4221 				id.name = makeUtf8StringFromWindowsString(dev.productString.ptr);
4222 				id.enabled = true;
4223 
4224 				// pointerDeviceType is useful
4225 
4226 				// FIXME: use more of it i guess
4227 
4228 				ret ~= id;
4229 			}
4230 			return ret;
4231 		} else {
4232 			throw new WindowsApiException("GetPointerDevices", GetLastError());
4233 		}
4234 	} else
4235 	static if(UsingSimpledisplayX11) {
4236 		xi_opcode(); // load XInput2
4237 
4238 		int cnt;
4239 		auto di = XIQueryDevice(XDisplayConnection.get, XIAllDevices, &cnt);
4240 		if(di is null)
4241 			return null;
4242 		scope(exit)
4243 			XIFreeDeviceInfo(di);
4244 
4245 		InputDevice[] ret;
4246 		foreach(dev; di[0 .. cnt]) {
4247 			auto id = new InputDevice;
4248 			id.deviceId = dev.deviceid;
4249 			id.name = stringz(dev.name).borrow.idup;
4250 			// use, attachment ?
4251 			id.enabled = dev.enabled != 0;
4252 
4253 			foreach(cls; dev.classes[0 .. dev.num_classes]) {
4254 				// FIXME: process these somehow...
4255 				switch(cls.type) {
4256 					case XIDeviceClass.XIKeyClass:
4257 						// has num keycodes but it isn't super accurate...
4258 						// writeln("\t", *cast(XIKeyClassInfo*) cls);
4259 						id.hasKeyClass = true;
4260 						break;
4261 					case XIDeviceClass.XIButtonClass:
4262 						// has num buttons but it isn't super accurate...
4263 						// writeln("\t", *cast(XIButtonClassInfo*) cls);
4264 						id.hasButtonClass = true;
4265 						break;
4266 					case XIDeviceClass.XIValuatorClass:
4267 						// writeln("\t", getAtomName((cast(XIValuatorClassInfo*) cls).label, XDisplayConnection.get), " ", *cast(XIValuatorClassInfo*) cls);
4268 						auto info = cast(XIValuatorClassInfo*) cls;
4269 						id.valuators ~= InputDevice.Valuator(info.label, info.min, info.max);
4270 						break;
4271 					case XIDeviceClass.XIScrollClass:
4272 						// FIXME anything else useful?
4273 						writeln(id.deviceId, ": ", id.name);
4274 						writeln("\t", *cast(XIScrollClassInfo*) cls);
4275 						id.hasScrollClass = true;
4276 						break;
4277 					case XIDeviceClass.XITouchClass:
4278 						// FIXME anything else useful?
4279 						writeln(id.deviceId, ": ", id.name);
4280 						writeln("\t", *cast(XITouchClassInfo*) cls);
4281 						id.hasTouchClass = true;
4282 						break;
4283 					case XIDeviceClass.XIGestureClass:
4284 						// FIXME what to do?
4285 						writeln(id.deviceId, ": ", id.name);
4286 						writeln("\t", *cast(XIGestureClassInfo*) cls);
4287 						break;
4288 					default:
4289 						// writeln("\t", cls.type);
4290 				}
4291 			}
4292 
4293 			ret ~= id;
4294 		}
4295 		return ret;
4296 	} else return null;
4297 }
4298 
4299 /// ditto
4300 class InputDevice {
4301 	private this() {}
4302 
4303 	static if(UsingSimpledisplayX11) {
4304 		int deviceId;
4305 	} else version(Windows) {
4306 		HANDLE deviceId;
4307 	}
4308 	string name;
4309 	bool enabled;
4310 
4311 	bool hasKeyClass;
4312 	bool hasButtonClass;
4313 	bool hasScrollClass;
4314 	bool hasTouchClass;
4315 
4316 	static if(UsingSimpledisplayX11) {
4317 		struct Valuator {
4318 			Atom label;
4319 			double min;
4320 			double max;
4321 		}
4322 
4323 		Valuator[] valuators;
4324 	}
4325 }
4326 
4327 /// ditto
4328 struct InputDeviceEvent {
4329 	SimpleWindow window;
4330 	InputDevice deviceObject;
4331 
4332 	int event;
4333 
4334 	static if(UsingSimpledisplayX11) {
4335 		int deviceId;
4336 	} else version(Windows) {
4337 		HANDLE deviceId;
4338 	}
4339 
4340 	int detail;
4341 	int flags;
4342 
4343 	double rootX;
4344 	double rootY;
4345 	double windowX;
4346 	double windowY;
4347 
4348 	ulong buttons;
4349 
4350 	double[16] valuators;
4351 
4352 	// parsed valuators
4353 	static if(UsingSimpledisplayX11) {
4354 		double relX() { return findValuator(GetAtom!"Rel X"(XDisplayConnection.get)); }
4355 		double relY() { return findValuator(GetAtom!"Rel Y"(XDisplayConnection.get)); }
4356 		double absX() { return findValuator(GetAtom!"Abs X"(XDisplayConnection.get)); }
4357 		double absY() { return findValuator(GetAtom!"Abs Y"(XDisplayConnection.get)); }
4358 		double pressure() { return findValuator(GetAtom!"Abs Pressure"(XDisplayConnection.get)); }
4359 		double tiltX() { return findValuator(GetAtom!"Abs Tilt X"(XDisplayConnection.get)); }
4360 		double tiltY() { return findValuator(GetAtom!"Abs Tilt Y"(XDisplayConnection.get)); }
4361 
4362 /+
4363 
4364                 "Rel Horiz Scroll",
4365                 "Rel Vert Scroll",
4366                 "Abs Wheel"
4367 
4368                 "Abs MT Position X",
4369                 "Abs MT Position Y",
4370 +/
4371 
4372 		private double findValuator(Atom what) {
4373 			assert(deviceObject !is null);
4374 
4375 			foreach(idx, v; deviceObject.valuators) {
4376 				if(idx >= valuators.length)
4377 					break;
4378 				if(v.label == what)
4379 					return valuators[idx];
4380 			}
4381 			return double.nan;
4382 		}
4383 	} else {
4384 		double pressure = double.nan;
4385 		double tiltX = double.nan;
4386 		double tiltY = double.nan;
4387 	}
4388 
4389 	// parsed flags
4390 	// all 1 << 16
4391 	bool wasKeyRepeat; // only on key event
4392 	bool wasEmulatedPointer; // only on pointer event
4393 	bool wasTouchPendingEnd; // only on touch event
4394 
4395 	bool wasTouchEmulatingPointer; // 1 << 16
4396 }
4397 
4398 /// ditto
4399 void subscribeToInputEvents(SimpleWindow window, InputDevice device, void delegate(InputDeviceEvent ide) handler) {
4400 	static if(UsingSimpledisplayX11) {
4401 		XIEventMask mask = XIEventMask(device.deviceId);
4402 		with(XIEventType)
4403 			mask.set(
4404 				XI_ButtonPress, XI_ButtonRelease, XI_Motion,
4405 				XI_FocusIn, XI_FocusOut,
4406 				XI_Enter, XI_Leave,
4407 				XI_KeyPress, XI_KeyRelease,
4408 				// tbh the raw events are useful af too
4409 				XI_TouchBegin, XI_TouchUpdate, XI_TouchEnd
4410 			);
4411 
4412 		XISelectEvents(XDisplayConnection.get, window.window, &mask, 1);
4413 
4414 		window.setAdvancedInputHandler(device, handler);
4415 	} else {
4416 		window.setAdvancedInputHandler(null, handler);
4417 	}
4418 }
4419 
4420 /* } Advanced input support */
4421 
4422 /* Drag and drop support { */
4423 version(X11) {
4424 
4425 } else version(Windows) {
4426 	import core.sys.windows.uuid;
4427 	import core.sys.windows.ole2;
4428 	import core.sys.windows.oleidl;
4429 	import core.sys.windows.objidl;
4430 	import core.sys.windows.wtypes;
4431 
4432 	pragma(lib, "ole32");
4433 	void initDnd() {
4434 		auto err = OleInitialize(null);
4435 		if(err != S_OK && err != S_FALSE)
4436 			throw new Exception("init");//err);
4437 	}
4438 }
4439 /* } End drag and drop support */
4440 
4441 
4442 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
4443 /// See [GenericCursor].
4444 class MouseCursor {
4445 	int osId;
4446 	bool isStockCursor;
4447 	private this(int osId) {
4448 		this.osId = osId;
4449 		this.isStockCursor = true;
4450 	}
4451 
4452 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
4453 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
4454 
4455 	version(Windows) {
4456 		HCURSOR cursor_;
4457 		HCURSOR cursorHandle() {
4458 			if(cursor_ is null)
4459 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
4460 			return cursor_;
4461 		}
4462 
4463 	} else static if(UsingSimpledisplayX11) {
4464 		Cursor cursor_ = None;
4465 		int xDisplaySequence;
4466 
4467 		Cursor cursorHandle() {
4468 			if(this.osId == None)
4469 				return None;
4470 
4471 			// we need to reload if we on a new X connection
4472 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
4473 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
4474 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
4475 			}
4476 			return cursor_;
4477 		}
4478 	}
4479 }
4480 
4481 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
4482 // https://tronche.com/gui/x/xlib/appendix/b/
4483 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
4484 /// 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.
4485 enum GenericCursorType {
4486 	Default, /// The default arrow pointer.
4487 	Wait, /// A cursor indicating something is loading and the user must wait.
4488 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
4489 	Help, /// A cursor indicating the user can get help about the pointer location.
4490 	Cross, /// A crosshair.
4491 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
4492 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
4493 	UpArrow, /// An arrow pointing straight up.
4494 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
4495 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
4496 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
4497 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
4498 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
4499 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
4500 
4501 }
4502 
4503 /*
4504 	X_plus == css cell == Windows ?
4505 */
4506 
4507 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
4508 static struct GenericCursor {
4509 	static:
4510 	///
4511 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
4512 		static MouseCursor mc;
4513 
4514 		auto type = __traits(getMember, GenericCursorType, str);
4515 
4516 		if(mc is null) {
4517 
4518 			version(Windows) {
4519 				int osId;
4520 				final switch(type) {
4521 					case GenericCursorType.Default: osId = IDC_ARROW; break;
4522 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
4523 					case GenericCursorType.Hand: osId = IDC_HAND; break;
4524 					case GenericCursorType.Help: osId = IDC_HELP; break;
4525 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
4526 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
4527 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
4528 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
4529 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
4530 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
4531 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
4532 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
4533 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
4534 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
4535 				}
4536 			} else static if(UsingSimpledisplayX11) {
4537 				int osId;
4538 				final switch(type) {
4539 					case GenericCursorType.Default: osId = None; break;
4540 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
4541 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
4542 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
4543 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
4544 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
4545 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
4546 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
4547 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
4548 
4549 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
4550 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
4551 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
4552 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
4553 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
4554 				}
4555 
4556 			} else {
4557 				int osId;
4558 				// featureNotImplemented();
4559 			}
4560 
4561 			mc = new MouseCursor(osId);
4562 		}
4563 		return mc;
4564 	}
4565 }
4566 
4567 
4568 /++
4569 	If you want to get more control over the event loop, you can use this.
4570 
4571 	Typically though, you can just call [SimpleWindow.eventLoop] which forwards
4572 	to `EventLoop.get.run`.
4573 +/
4574 struct EventLoop {
4575 	@disable this();
4576 
4577 	/// Gets a reference to an existing event loop
4578 	static EventLoop get() {
4579 		return EventLoop(0, null);
4580 	}
4581 
4582 	static void quitApplication() {
4583 		static if(use_arsd_core) {
4584 			import arsd.core;
4585 			ICoreEventLoop.exitApplication();
4586 		}
4587 		EventLoop.get().exit();
4588 	}
4589 
4590 	private __gshared static SynchronizableObject monitor = new SynchronizableObject(); // deliberate CTFE usage here fyi
4591 
4592 	/// Construct an application-global event loop for yourself
4593 	/// See_Also: [SimpleWindow.setEventHandlers]
4594 	this(long pulseTimeout, void delegate() handlePulse) {
4595 		synchronized(monitor) {
4596 			if(impl is null) {
4597 				claimGuiThread();
4598 				version(sdpy_thread_checks) assert(thisIsGuiThread);
4599 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
4600 			} else {
4601 				if(pulseTimeout) {
4602 					impl.pulseTimeout = pulseTimeout;
4603 					impl.handlePulse = handlePulse;
4604 				}
4605 			}
4606 			impl.refcount++;
4607 		}
4608 	}
4609 
4610 	~this() {
4611 		if(impl is null)
4612 			return;
4613 		impl.refcount--;
4614 		if(impl.refcount == 0) {
4615 			impl.dispose();
4616 			if(thisIsGuiThread)
4617 				guiThreadFinalize();
4618 		}
4619 
4620 	}
4621 
4622 	this(this) {
4623 		if(impl is null)
4624 			return;
4625 		impl.refcount++;
4626 	}
4627 
4628 	/// Runs the event loop until the whileCondition, if present, returns false
4629 	int run(bool delegate() whileCondition = null) {
4630 		assert(impl !is null);
4631 		impl.notExited = true;
4632 		return impl.run(whileCondition);
4633 	}
4634 
4635 	/// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program)
4636 	void exit() {
4637 		assert(impl !is null);
4638 		impl.notExited = false;
4639 
4640 		static if(use_arsd_core) {
4641 			import arsd.core;
4642 			ICoreEventLoop.exitApplication();
4643 		}
4644 	}
4645 
4646 	version(linux)
4647 	ref void delegate(int) signalHandler() {
4648 		assert(impl !is null);
4649 		return impl.signalHandler;
4650 	}
4651 
4652 	__gshared static EventLoopImpl* impl;
4653 }
4654 
4655 version(linux)
4656 	void delegate(int, int) globalHupHandler;
4657 
4658 version(Posix)
4659 	void makeNonBlocking(int fd) {
4660 		import fcntl = core.sys.posix.fcntl;
4661 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
4662 		if(flags == -1)
4663 			throw new Exception("fcntl get");
4664 		flags |= fcntl.O_NONBLOCK;
4665 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
4666 		if(s == -1)
4667 			throw new Exception("fcntl set");
4668 	}
4669 
4670 struct EventLoopImpl {
4671 	int refcount;
4672 
4673 	bool notExited = true;
4674 
4675 	version(Emscripten) {
4676 		void delegate(int) signalHandler;
4677 		static import unix = core.sys.posix.unistd;
4678 		static import err = core.stdc.errno;
4679 	} else
4680 	version(linux) {
4681 		static import ep = core.sys.linux.epoll;
4682 		static import unix = core.sys.posix.unistd;
4683 		static import err = core.stdc.errno;
4684 		import core.sys.linux.timerfd;
4685 
4686 		void delegate(int) signalHandler;
4687 	}
4688 	else
4689 	version(Posix) {
4690 		static import unix = core.sys.posix.unistd;
4691 	}
4692 
4693 	version(X11) {
4694 		int pulseFd = -1;
4695 		version(Emscripten) {} else
4696 		version(linux) ep.epoll_event[16] events = void;
4697 	} else version(Windows) {
4698 		Timer pulser;
4699 		HANDLE[] handles;
4700 	}
4701 
4702 
4703 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
4704 	/// to call this, as it's not recommended to share window between threads.
4705 	void mtLock () {
4706 		version(X11) {
4707 			XLockDisplay(this.display);
4708 		}
4709 	}
4710 
4711 	version(X11)
4712 	auto display() { return XDisplayConnection.get; }
4713 
4714 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
4715 	/// to call this, as it's not recommended to share window between threads.
4716 	void mtUnlock () {
4717 		version(X11) {
4718 			XUnlockDisplay(this.display);
4719 		}
4720 	}
4721 
4722 	version(with_eventloop)
4723 	void initialize(long pulseTimeout) {}
4724 	else
4725 	void initialize(long pulseTimeout) @system {
4726 		version(Windows) {
4727 			if(pulseTimeout && handlePulse !is null)
4728 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
4729 
4730 			if (customEventH is null) {
4731 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
4732 				if (customEventH !is null) {
4733 					handles ~= customEventH;
4734 				} else {
4735 					// this is something that should not be; better be safe than sorry
4736 					throw new Exception("can't create eventfd for custom event processing");
4737 				}
4738 			}
4739 
4740 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
4741 		}
4742 
4743 		version(Emscripten) {
4744 
4745 		} else version(linux) {
4746 			prepareEventLoop();
4747 			{
4748 				auto display = XDisplayConnection.get;
4749 				// adding Xlib file
4750 				ep.epoll_event ev = void;
4751 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4752 				ev.events = ep.EPOLLIN;
4753 				ev.data.fd = display.fd;
4754 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
4755 					throw new Exception("add x fd");// ~ to!string(epollFd));
4756 				displayFd = display.fd;
4757 			}
4758 
4759 			if(pulseTimeout && handlePulse !is null) {
4760 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
4761 				if(pulseFd == -1)
4762 					throw new Exception("pulse timer create failed");
4763 
4764 				itimerspec value;
4765 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
4766 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4767 
4768 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
4769 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
4770 
4771 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
4772 					throw new Exception("couldn't make pulse timer");
4773 
4774 				ep.epoll_event ev = void;
4775 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4776 				ev.events = ep.EPOLLIN;
4777 				ev.data.fd = pulseFd;
4778 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
4779 			}
4780 
4781 			// eventfd for custom events
4782 			if (customEventFDWrite == -1) {
4783 				customEventFDWrite = eventfd(0, 0);
4784 				customEventFDRead = customEventFDWrite;
4785 				if (customEventFDRead >= 0) {
4786 					ep.epoll_event ev = void;
4787 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4788 					ev.events = ep.EPOLLIN;
4789 					ev.data.fd = customEventFDRead;
4790 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
4791 				} else {
4792 					// this is something that should not be; better be safe than sorry
4793 					throw new Exception("can't create eventfd for custom event processing");
4794 				}
4795 			}
4796 
4797 			if (customSignalFD == -1) {
4798 				import core.sys.linux.sys.signalfd;
4799 
4800 				sigset_t sigset;
4801 				auto err = sigemptyset(&sigset);
4802 				assert(!err);
4803 				err = sigaddset(&sigset, SIGINT);
4804 				assert(!err);
4805 				err = sigaddset(&sigset, SIGHUP);
4806 				assert(!err);
4807 				err = sigprocmask(SIG_BLOCK, &sigset, null);
4808 				assert(!err);
4809 
4810 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
4811 				assert(customSignalFD != -1);
4812 
4813 				ep.epoll_event ev = void;
4814 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4815 				ev.events = ep.EPOLLIN;
4816 				ev.data.fd = customSignalFD;
4817 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
4818 			}
4819 		} else version(Posix) {
4820 			prepareEventLoop();
4821 			if (customEventFDRead == -1) {
4822 				int[2] bfr;
4823 				import core.sys.posix.unistd;
4824 				auto ret = pipe(bfr);
4825 				if(ret == -1) throw new Exception("pipe");
4826 				customEventFDRead = bfr[0];
4827 				customEventFDWrite = bfr[1];
4828 			}
4829 
4830 		}
4831 
4832 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
4833 
4834 		version(linux) {
4835 			this.mtLock();
4836 			scope(exit) this.mtUnlock();
4837 			version(X11)
4838 				XPending(display); // no, really
4839 		}
4840 
4841 		disposed = false;
4842 	}
4843 
4844 	bool disposed = true;
4845 	version(X11)
4846 		int displayFd = -1;
4847 
4848 	ICoreEventLoop.UnregisterToken[] unregisters;
4849 
4850 	version(with_eventloop)
4851 	void dispose() {}
4852 	else
4853 	void dispose() @system {
4854 		disposed = true;
4855 		loopInitialized = false;
4856 
4857 		foreach(urt; unregisters)
4858 			urt.unregister();
4859 		unregisters = null;
4860 
4861 		version(X11) {
4862 			if(pulseFd != -1) {
4863 				import unix = core.sys.posix.unistd;
4864 				unix.close(pulseFd);
4865 				pulseFd = -1;
4866 			}
4867 			if(customSignalFD != -1) {
4868 				import unix = core.sys.posix.unistd;
4869 				unix.close(customSignalFD);
4870 				customSignalFD = -1;
4871 			}
4872 
4873 			version(Emscripten) {} else
4874 			version(linux)
4875 			if(displayFd != -1) {
4876 				// 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
4877 				ep.epoll_event ev = void;
4878 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
4879 				ev.events = ep.EPOLLIN;
4880 				ev.data.fd = displayFd;
4881 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
4882 				displayFd = -1;
4883 			}
4884 		} else version(Windows) {
4885 			if(pulser !is null) {
4886 				pulser.destroy();
4887 				pulser = null;
4888 			}
4889 			if (customEventH !is null) {
4890 				CloseHandle(customEventH);
4891 				customEventH = null;
4892 			}
4893 		}
4894 	}
4895 
4896 	this(long pulseTimeout, void delegate() handlePulse) {
4897 		this.pulseTimeout = pulseTimeout;
4898 		this.handlePulse = handlePulse;
4899 		initialize(pulseTimeout);
4900 	}
4901 
4902 	private long pulseTimeout;
4903 	void delegate() handlePulse;
4904 
4905 	~this() {
4906 		dispose();
4907 	}
4908 
4909 	version(Posix)
4910 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
4911 	version(Posix)
4912 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
4913 	version(Posix)
4914 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
4915 	version(Windows)
4916 	ref auto customEventH() { return SimpleWindow.customEventH; }
4917 
4918 	version(X11) {
4919 		bool doXPending() {
4920 			bool done = false;
4921 
4922 			this.mtLock();
4923 			scope(exit) this.mtUnlock();
4924 			//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
4925 			while(!done && XPending(display)) {
4926 				done = doXNextEvent(this.display);
4927 			}
4928 
4929 			return done;
4930 		}
4931 		void doXNextEventVoid() {
4932 			doXPending();
4933 		}
4934 	}
4935 
4936 	static bool loopInitialized = false;
4937 
4938 	version(with_eventloop) {
4939 		int loopHelper(bool delegate() whileCondition) {
4940 			// FIXME: whileCondition
4941 			import arsd.eventloop;
4942 			loop();
4943 			return 0;
4944 		}
4945 	} else
4946 	int loopHelper(bool delegate() whileCondition) {
4947 		version(X11) {
4948 			bool done = false;
4949 
4950 			XFlush(display);
4951 			insideXEventLoop = true;
4952 			scope(exit) insideXEventLoop = false;
4953 
4954 			static if(use_arsd_core) {
4955 				import arsd.core;
4956 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
4957 
4958 				if(!loopInitialized) {
4959 					el.getTimeout = () { auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); return (wto == 0 || wto >= int.max ? -1 : cast(int)wto); };
4960 					unregisters ~= el.addDelegateOnLoopIteration(&doXNextEventVoid, 3);
4961 					unregisters ~= el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3);
4962 
4963 					if(customSignalFD != -1)
4964 					unregisters ~= el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() {
4965 						version(linux) {
4966 							import core.sys.linux.sys.signalfd;
4967 							import core.sys.posix.unistd : read;
4968 							signalfd_siginfo info;
4969 							read(customSignalFD, &info, info.sizeof);
4970 
4971 							auto sig = info.ssi_signo;
4972 
4973 							if(EventLoop.get.signalHandler !is null) {
4974 								EventLoop.get.signalHandler()(sig);
4975 							} else {
4976 								EventLoop.get.exit();
4977 							}
4978 						}
4979 					}));
4980 
4981 					if(displayFd != -1)
4982 					unregisters ~= el.addCallbackOnFdReadable(displayFd, new CallbackHelper(() {
4983 						this.mtLock();
4984 						scope(exit) this.mtUnlock();
4985 						while(!done && XPending(display)) {
4986 							done = doXNextEvent(this.display);
4987 						}
4988 					}));
4989 
4990 					if(pulseFd != -1)
4991 					unregisters ~= el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() {
4992 						long expirationCount;
4993 						// 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...
4994 
4995 						handlePulse();
4996 
4997 						// read just to clear the buffer so poll doesn't trigger again
4998 						// BTW I read AFTER the pulse because if the pulse handler takes
4999 						// a lot of time to execute, we don't want the app to get stuck
5000 						// in a loop of timer hits without a chance to do anything else
5001 						//
5002 						// IOW handlePulse happens at most once per pulse interval.
5003 						unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
5004 					}));
5005 
5006 					if(customEventFDRead != -1)
5007 					unregisters ~= el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() {
5008 						// we have some custom events; process 'em
5009 						import core.sys.posix.unistd : read;
5010 						ulong n;
5011 						read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
5012 						//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
5013 						//SimpleWindow.processAllCustomEvents();
5014 					}));
5015 
5016 					// FIXME: posix fds
5017 					// FIXME up?
5018 
5019 
5020 					loopInitialized = true;
5021 				}
5022 
5023 				if(whileCondition is null)
5024 					whileCondition = () => true;
5025 
5026 				el.run(() => !whileCondition());
5027 			} else version(linux) {
5028 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
5029 					bool forceXPending = false;
5030 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
5031 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
5032 					{
5033 						this.mtLock();
5034 						scope(exit) this.mtUnlock();
5035 						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
5036 					}
5037 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
5038 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
5039 					if(nfds == -1) {
5040 						if(err.errno == err.EINTR) {
5041 							//if(forceXPending) goto xpending;
5042 							continue; // interrupted by signal, just try again
5043 						}
5044 						throw new Exception("epoll wait failure");
5045 					}
5046 					// writeln(nfds, " ", events[0].data.fd);
5047 
5048 					SimpleWindow.processAllCustomEvents(); // anyway
5049 					//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
5050 					foreach(idx; 0 .. nfds) {
5051 						if(done) break;
5052 						auto fd = events[idx].data.fd;
5053 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
5054 						auto flags = events[idx].events;
5055 						if(flags & ep.EPOLLIN) {
5056 							if (fd == customSignalFD) {
5057 								version(linux) {
5058 									import core.sys.linux.sys.signalfd;
5059 									import core.sys.posix.unistd : read;
5060 									signalfd_siginfo info;
5061 									read(customSignalFD, &info, info.sizeof);
5062 
5063 									auto sig = info.ssi_signo;
5064 
5065 									if(EventLoop.get.signalHandler !is null) {
5066 										EventLoop.get.signalHandler()(sig);
5067 									} else {
5068 										EventLoop.get.exit();
5069 									}
5070 								}
5071 							} else if(fd == display.fd) {
5072 								version(sdddd) { writeln("X EVENT PENDING!"); }
5073 								this.mtLock();
5074 								scope(exit) this.mtUnlock();
5075 								while(!done && XPending(display)) {
5076 									done = doXNextEvent(this.display);
5077 								}
5078 								forceXPending = false;
5079 							} else if(fd == pulseFd) {
5080 								long expirationCount;
5081 								// 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...
5082 
5083 								handlePulse();
5084 
5085 								// read just to clear the buffer so poll doesn't trigger again
5086 								// BTW I read AFTER the pulse because if the pulse handler takes
5087 								// a lot of time to execute, we don't want the app to get stuck
5088 								// in a loop of timer hits without a chance to do anything else
5089 								//
5090 								// IOW handlePulse happens at most once per pulse interval.
5091 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
5092 								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
5093 							} else if (fd == customEventFDRead) {
5094 								// we have some custom events; process 'em
5095 								import core.sys.posix.unistd : read;
5096 								ulong n;
5097 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
5098 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
5099 								//SimpleWindow.processAllCustomEvents();
5100 
5101 								forceXPending = true;
5102 							} else {
5103 								// some other timer
5104 								version(sdddd) { writeln("unknown fd: ", fd); }
5105 
5106 								if(Timer* t = fd in Timer.mapping)
5107 									(*t).trigger();
5108 
5109 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
5110 									(*pfr).ready(flags);
5111 
5112 								// we don't know what the user did in this timer, so we need to assume that
5113 								// there's X data to be flushed and potentially processed
5114 								forceXPending = true;
5115 
5116 								// or i might add support for other FDs too
5117 								// but for now it is just timer
5118 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
5119 							}
5120 						}
5121 						if(flags & ep.EPOLLHUP) {
5122 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
5123 								(*pfr).hup(flags);
5124 							if(globalHupHandler)
5125 								globalHupHandler(fd, flags);
5126 						}
5127 						/+
5128 						} else {
5129 							// not interested in OUT, we are just reading here.
5130 							//
5131 							// error or hup might also be reported
5132 							// but it shouldn't here since we are only
5133 							// using a few types of FD and Xlib will report
5134 							// if it dies.
5135 							// so instead of thoughtfully handling it, I'll
5136 							// just throw. for now at least
5137 
5138 							throw new Exception("epoll did something else");
5139 						}
5140 						+/
5141 					}
5142 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
5143 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
5144 					xpending:
5145 					if (!done && forceXPending) {
5146 						done = doXPending();
5147 					}
5148 				}
5149 			} else {
5150 				// Generic fallback: yes to simple pulse support,
5151 				// but NO timer support!
5152 
5153 				// FIXME: we could probably support the POSIX timer_create
5154 				// signal-based option, but I'm in no rush to write it since
5155 				// I prefer the fd-based functions.
5156 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
5157 
5158 					import core.sys.posix.poll;
5159 
5160 					pollfd[] pfds;
5161 					pollfd[32] pfdsBuffer;
5162 					auto len = PosixFdReader.mapping.length + 2;
5163 					// FIXME: i should just reuse the buffer
5164 					if(len < pfdsBuffer.length)
5165 						pfds = pfdsBuffer[0 .. len];
5166 					else
5167 						pfds = new pollfd[](len);
5168 
5169 					pfds[0].fd = display.fd;
5170 					pfds[0].events = POLLIN;
5171 					pfds[0].revents = 0;
5172 
5173 					int slot = 1;
5174 
5175 					if(customEventFDRead != -1) {
5176 						pfds[slot].fd = customEventFDRead;
5177 						pfds[slot].events = POLLIN;
5178 						pfds[slot].revents = 0;
5179 
5180 						slot++;
5181 					}
5182 
5183 					foreach(fd, obj; PosixFdReader.mapping) {
5184 						if(!obj.enabled) continue;
5185 						pfds[slot].fd = fd;
5186 						pfds[slot].events = POLLIN;
5187 						pfds[slot].revents = 0;
5188 
5189 						slot++;
5190 					}
5191 
5192 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
5193 					if(ret == -1) throw new Exception("poll");
5194 
5195 					if(ret == 0) {
5196 						// FIXME it may not necessarily time out if events keep coming
5197 						if(handlePulse !is null)
5198 							handlePulse();
5199 					} else {
5200 						foreach(s; 0 .. slot) {
5201 							if(pfds[s].revents == 0) continue;
5202 
5203 							if(pfds[s].fd == display.fd) {
5204 								while(!done && XPending(display)) {
5205 									this.mtLock();
5206 									scope(exit) this.mtUnlock();
5207 									done = doXNextEvent(this.display);
5208 								}
5209 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
5210 
5211 								import core.sys.posix.unistd : read;
5212 								ulong n;
5213 								read(customEventFDRead, &n, n.sizeof);
5214 								SimpleWindow.processAllCustomEvents();
5215 							} else {
5216 								auto obj = PosixFdReader.mapping[pfds[s].fd];
5217 								if(pfds[s].revents & POLLNVAL) {
5218 									obj.dispose();
5219 								} else {
5220 									obj.ready(pfds[s].revents);
5221 								}
5222 							}
5223 
5224 							ret--;
5225 							if(ret == 0) break;
5226 						}
5227 					}
5228 				}
5229 			}
5230 		} else
5231 		version(Windows) {
5232 
5233 			static if(use_arsd_core) {
5234 				import arsd.core;
5235 				auto el = getThisThreadEventLoop(EventLoopType.Ui);
5236 				if(!loopInitialized) {
5237 					el.getTimeout = () { auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); return (wto == 0 || wto >= int.max ? INFINITE : cast(int)wto); };
5238 					unregisters ~= el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3);
5239 					unregisters ~= el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 3);
5240 					loopInitialized = true;
5241 				}
5242 				el.run(() => !whileCondition());
5243 			} else {
5244 				int ret = -1;
5245 				MSG message;
5246 				while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
5247 					eventLoopRound++;
5248 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
5249 					auto waitResult = MsgWaitForMultipleObjectsEx(
5250 						cast(int) handles.length, handles.ptr,
5251 						(wto == 0 ? INFINITE : wto), /* timeout */
5252 						0x04FF, /* QS_ALLINPUT */
5253 						0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
5254 
5255 					SimpleWindow.processAllCustomEvents(); // anyway
5256 					enum WAIT_OBJECT_0 = 0;
5257 					if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
5258 						auto h = handles[waitResult - WAIT_OBJECT_0];
5259 						if(auto e = h in WindowsHandleReader.mapping) {
5260 							(*e).ready();
5261 						}
5262 					} else if(waitResult == handles.length + WAIT_OBJECT_0) {
5263 						// message ready
5264 						int count;
5265 						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
5266 							ret = GetMessage(&message, null, 0, 0);
5267 							if(ret == -1)
5268 								throw new WindowsApiException("GetMessage", GetLastError());
5269 							TranslateMessage(&message);
5270 							DispatchMessage(&message);
5271 
5272 							count++;
5273 							if(count > 10)
5274 								break; // take the opportunity to catch up on other events
5275 
5276 							if(ret == 0) { // WM_QUIT
5277 								EventLoop.quitApplication();
5278 								break;
5279 							}
5280 						}
5281 					} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
5282 						SleepEx(0, true); // I call this to give it a chance to do stuff like async io
5283 					} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
5284 						// timeout, should never happen since we aren't using it
5285 					} else if(waitResult == 0xFFFFFFFF) {
5286 							// failed
5287 							throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError());
5288 					} else {
5289 						// idk....
5290 					}
5291 				}
5292 			}
5293 
5294 			// return message.wParam;
5295 			return 0;
5296 		} version (OSXCocoa) {
5297 
5298 			static assert(use_arsd_core);
5299 
5300 			/+
5301 			if (handlePulse !is null && pulseTimeout != 0) {
5302 				NSTimer timer = NSTimer.schedule(pulseTimeout*1e-3,
5303 					cast(NSid) view, sel_registerName("simpledisplay_pulse:"),
5304 					null, true);
5305 
5306 
5307 			if(timer)
5308 				timer.invalidate();
5309 			}
5310 			+/
5311 
5312 			import arsd.core;
5313 			auto el = getThisThreadEventLoop(EventLoopType.Ui);
5314 			if(!loopInitialized) {
5315 				el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3);
5316 				loopInitialized = true;
5317 				sdpyPrintDebugString("one");
5318 				NSApp.run();
5319 				sdpyPrintDebugString("here");
5320 			}
5321 
5322 				sdpyPrintDebugString("arsd.core loop starting");
5323 			el.run(() => !whileCondition());
5324 
5325 				sdpyPrintDebugString("kiio all done");
5326 
5327 			return 0;
5328 		} else {
5329 			return 0;
5330 		}
5331 	}
5332 
5333 	int run(bool delegate() whileCondition = null) {
5334 		if(disposed)
5335 			initialize(this.pulseTimeout);
5336 
5337 		version(X11) {
5338 			try {
5339 				return loopHelper(whileCondition);
5340 			} catch(XDisconnectException e) {
5341 				if(e.userRequested) {
5342 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
5343 						item.discardConnectionState();
5344 					XCloseDisplay(XDisplayConnection.display);
5345 				}
5346 
5347 				XDisplayConnection.display = null;
5348 
5349 				this.dispose();
5350 
5351 				throw e;
5352 			}
5353 		} else {
5354 			return loopHelper(whileCondition);
5355 		}
5356 	}
5357 }
5358 
5359 
5360 /++
5361 	Provides an icon on the system notification area (also known as the system tray).
5362 
5363 
5364 	If a notification area is not available with the NotificationIcon object is created,
5365 	it will silently succeed and simply attempt to create one when an area becomes available.
5366 
5367 
5368 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for
5369 	Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency
5370 	with true color was added at that time. I was just too lazy to write the fallback.
5371 
5372 	If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest
5373 	you use arsd 10.x when targeting Windows XP.
5374 +/
5375 version(Emscripten) {} else
5376 version(OSXCocoa) {} else // NotYetImplementedException
5377 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
5378 
5379 	version(X11) {
5380 		void recreateAfterDisconnect() {
5381 			stateDiscarded = false;
5382 			clippixmap = None;
5383 			throw new Exception("NOT IMPLEMENTED");
5384 		}
5385 
5386 		bool stateDiscarded;
5387 		void discardConnectionState() {
5388 			stateDiscarded = true;
5389 		}
5390 	}
5391 
5392 
5393 	version(X11) {
5394 		Image img;
5395 
5396 		NativeEventHandler getNativeEventHandler() {
5397 			return delegate int(XEvent e) {
5398 				switch(e.type) {
5399 					case EventType.Expose:
5400 					//case EventType.VisibilityNotify:
5401 						redraw();
5402 					break;
5403 					case EventType.ClientMessage:
5404 						version(sddddd) {
5405 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
5406 						writeln("\t", e.xclient.format);
5407 						writeln("\t", e.xclient.data.l);
5408 						}
5409 					break;
5410 					case EventType.ButtonPress:
5411 						auto event = e.xbutton;
5412 						if (onClick !is null || onClickEx !is null) {
5413 							MouseButton mb = cast(MouseButton)0;
5414 							switch (event.button) {
5415 								case 1: mb = MouseButton.left; break; // left
5416 								case 2: mb = MouseButton.middle; break; // middle
5417 								case 3: mb = MouseButton.right; break; // right
5418 								case 4: mb = MouseButton.wheelUp; break; // scroll up
5419 								case 5: mb = MouseButton.wheelDown; break; // scroll down
5420 								case 6: mb = MouseButton.wheelLeft; break; // scroll left...
5421 								case 7: mb = MouseButton.wheelRight; break; // scroll right...
5422 								case 8: mb = MouseButton.backButton; break;
5423 								case 9: mb = MouseButton.forwardButton; break;
5424 								default:
5425 							}
5426 							if (mb) {
5427 								try { onClick()(mb); } catch (Exception) {}
5428 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
5429 							}
5430 						}
5431 					break;
5432 					case EventType.EnterNotify:
5433 						if (onEnter !is null) {
5434 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
5435 						}
5436 						break;
5437 					case EventType.LeaveNotify:
5438 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
5439 						break;
5440 					case EventType.DestroyNotify:
5441 						active = false;
5442 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
5443 					break;
5444 					case EventType.ConfigureNotify:
5445 						auto event = e.xconfigure;
5446 						this.width = event.width;
5447 						this.height = event.height;
5448 						// writeln(width, " x " , height, " @ ", event.x, " ", event.y);
5449 						redraw();
5450 					break;
5451 					default: return 1;
5452 				}
5453 				return 1;
5454 			};
5455 		}
5456 
5457 		/* private */ void hideBalloon() {
5458 			balloon.close();
5459 			version(with_timer)
5460 				timer.destroy();
5461 			balloon = null;
5462 			version(with_timer)
5463 				timer = null;
5464 		}
5465 
5466 		void redraw() {
5467 			if (!active) return;
5468 
5469 			auto display = XDisplayConnection.get;
5470 			GC gc;
5471 
5472 		// from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap
5473 
5474 			int gc_depth(int depth, Display *dpy, Window root, GC *gc) {
5475 				Visual *visual;
5476 				XVisualInfo vis_info;
5477 				XSetWindowAttributes win_attr;
5478 				c_ulong win_mask;
5479 
5480 				if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) {
5481 					assert(0);
5482 					// return 1;
5483 				}
5484 
5485 				visual = vis_info.visual;
5486 
5487 				win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
5488 				win_attr.background_pixel = 0;
5489 				win_attr.border_pixel = 0;
5490 
5491 				win_mask = CWBackPixel | CWColormap | CWBorderPixel;
5492 
5493 				*gc = XCreateGC(dpy, nativeHandle, 0, null);
5494 
5495 				return 0;
5496 			}
5497 
5498 			if(useAlpha)
5499 				gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc);
5500 			else
5501 				gc = DefaultGC(display, DefaultScreen(display));
5502 
5503 			XClearWindow(display, nativeHandle);
5504 
5505 			if(!useAlpha && img !is null)
5506 				XSetClipMask(display, gc, clippixmap);
5507 
5508 			/+
5509 			XSetForeground(display, gc,
5510 				cast(uint) 0 << 16 |
5511 				cast(uint) 0 << 8 |
5512 				cast(uint) 0);
5513 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
5514 			+/
5515 
5516 			if (img is null) {
5517 				XSetForeground(display, gc,
5518 					cast(uint) 0 << 16 |
5519 					cast(uint) 127 << 8 |
5520 					cast(uint) 0);
5521 				XFillArc(display, nativeHandle,
5522 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
5523 			} else {
5524 				int dx = 0;
5525 				int dy = 0;
5526 				if(width > img.width)
5527 					dx = (width - img.width) / 2;
5528 				if(height > img.height)
5529 					dy = (height - img.height) / 2;
5530 				// writeln(img.width, " ", img.height, " vs ", width, " ", height);
5531 				XSetClipOrigin(display, gc, dx, dy);
5532 
5533 				int max(int a, int b) {
5534 					if(a > b) return a; else return b;
5535 				}
5536 
5537 				if (img.usingXshm)
5538 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false);
5539 				else
5540 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height));
5541 			}
5542 			XSetClipMask(display, gc, None);
5543 			flushGui();
5544 		}
5545 
5546 		static Window getTrayOwner() {
5547 			auto display = XDisplayConnection.get;
5548 			auto i = cast(int) DefaultScreen(display);
5549 			if(i < 10 && i >= 0) {
5550 				static Atom atom;
5551 				if(atom == None)
5552 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
5553 				return XGetSelectionOwner(display, atom);
5554 			}
5555 			return None;
5556 		}
5557 
5558 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
5559 			auto to = getTrayOwner();
5560 			auto display = XDisplayConnection.get;
5561 			XEvent ev;
5562 			ev.xclient.type = EventType.ClientMessage;
5563 			ev.xclient.window = to;
5564 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
5565 			ev.xclient.format = 32;
5566 			ev.xclient.data.l[0] = CurrentTime;
5567 			ev.xclient.data.l[1] = message;
5568 			ev.xclient.data.l[2] = d1;
5569 			ev.xclient.data.l[3] = d2;
5570 			ev.xclient.data.l[4] = d3;
5571 
5572 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
5573 		}
5574 
5575 		private static NotificationAreaIcon[] activeIcons;
5576 
5577 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
5578 		private void newManager() {
5579 			close();
5580 			createXWin();
5581 
5582 			if(this.clippixmap)
5583 				XFreePixmap(XDisplayConnection.get, clippixmap);
5584 			if(this.originalMemoryImage)
5585 				this.icon = this.originalMemoryImage;
5586 			else if(this.img)
5587 				this.icon = this.img;
5588 		}
5589 
5590 		private bool useAlpha = false;
5591 
5592 		private void createXWin () {
5593 			// create window
5594 			auto display = XDisplayConnection.get;
5595 
5596 			// to check for MANAGER on root window to catch new/changed tray owners
5597 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
5598 			// so if a thing does appear, we can handle it
5599 			foreach(ai; activeIcons)
5600 				if(ai is this)
5601 					goto alreadythere;
5602 			activeIcons ~= this;
5603 			alreadythere:
5604 
5605 			// and check for an existing tray
5606 			auto trayOwner = getTrayOwner();
5607 			if(trayOwner == None)
5608 				return;
5609 				//throw new Exception("No notification area found");
5610 
5611 			Visual* v = cast(Visual*) CopyFromParent;
5612 
5613 			// GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales
5614 			// from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send
5615 			// a resize event later.
5616 			width = 22;
5617 			height = 22;
5618 
5619 			// if they system gave us a 32 bit visual we need to switch to it too
5620 			int depth = 24;
5621 
5622 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
5623 			if(visualProp !is null) {
5624 				c_ulong[] info = cast(c_ulong[]) visualProp;
5625 				if(info.length == 1) {
5626 					auto vid = info[0];
5627 					int returned;
5628 					XVisualInfo t;
5629 					t.visualid = vid;
5630 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
5631 					if(got !is null) {
5632 						if(returned == 1) {
5633 							v = got.visual;
5634 							depth = got.depth;
5635 							// writeln("using special visual ", got.depth);
5636 							// writeln(depth);
5637 						}
5638 						XFree(got);
5639 					}
5640 				}
5641 			}
5642 
5643 			int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect;
5644 			XSetWindowAttributes attr;
5645 			attr.background_pixel = 0;
5646 			attr.border_pixel = 0;
5647 			attr.override_redirect = 0;
5648 			if(v !is cast(Visual*) CopyFromParent) {
5649 				attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone);
5650 				CWFlags |= CWColormap;
5651 				if(depth == 32)
5652 					useAlpha = true;
5653 				else
5654 					goto plain;
5655 			} else {
5656 				plain:
5657 				attr.background_pixmap = 1 /* ParentRelative */;
5658 				CWFlags |= CWBackPixmap;
5659 			}
5660 
5661 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr);
5662 
5663 			assert(nativeWindow);
5664 
5665 			if(!useAlpha)
5666 				XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
5667 
5668 			nativeHandle = nativeWindow;
5669 
5670 			///+
5671 			arch_ulong[2] info;
5672 			info[0] = 0;
5673 			info[1] = 1;
5674 
5675 			string title = this.name is null ? "simpledisplay.d program" : this.name;
5676 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
5677 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
5678 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
5679 
5680 			XChangeProperty(
5681 				display,
5682 				nativeWindow,
5683 				GetAtom!("_XEMBED_INFO", true)(display),
5684 				GetAtom!("_XEMBED_INFO", true)(display),
5685 				32 /* bits */,
5686 				0 /*PropModeReplace*/,
5687 				info.ptr,
5688 				2);
5689 
5690 			import core.sys.posix.unistd;
5691 			arch_ulong pid = getpid();
5692 
5693 			// XSetCommand(display, nativeWindow, ["sdpy".ptr].ptr, 1);
5694 
5695 			XChangeProperty(
5696 				display,
5697 				nativeWindow,
5698 				GetAtom!("_NET_WM_PID", true)(display),
5699 				XA_CARDINAL,
5700 				32 /* bits */,
5701 				0 /*PropModeReplace*/,
5702 				&pid,
5703 				1);
5704 
5705 			updateNetWmIcon();
5706 
5707 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
5708 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
5709 				XClassHint klass;
5710 				XWMHints wh;
5711 				XSizeHints size;
5712 				klass.res_name = sdpyWindowClassStr;
5713 				klass.res_class = sdpyWindowClassStr;
5714 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
5715 			}
5716 
5717 				// believe it or not, THIS is what xfce needed for the 9999 issue
5718 				XSizeHints sh;
5719 				c_long spr;
5720 				XGetWMNormalHints(display, nativeWindow, &sh, &spr);
5721 				sh.flags |= PMaxSize | PMinSize;
5722 				// FIXME maybe nicer resizing
5723 				sh.min_width = 16;
5724 				sh.min_height = 16;
5725 				sh.max_width = 22;
5726 				sh.max_height = 22;
5727 				XSetWMNormalHints(display, nativeWindow, &sh);
5728 
5729 
5730 			//+/
5731 
5732 
5733 			XSelectInput(display, nativeWindow,
5734 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
5735 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
5736 
5737 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
5738 			// XMapWindow(display, nativeWindow); // to demo it w/o a tray
5739 
5740 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
5741 			active = true;
5742 		}
5743 
5744 		void updateNetWmIcon() {
5745 			if(img is null) return;
5746 			auto display = XDisplayConnection.get;
5747 			// FIXME: ensure this is correct
5748 			arch_ulong[] buffer;
5749 			auto imgMi = img.toTrueColorImage;
5750 			buffer ~= imgMi.width;
5751 			buffer ~= imgMi.height;
5752 			foreach(c; imgMi.imageData.colors) {
5753 				arch_ulong b;
5754 				b |= c.a << 24;
5755 				b |= c.r << 16;
5756 				b |= c.g << 8;
5757 				b |= c.b;
5758 				buffer ~= b;
5759 			}
5760 
5761 			XChangeProperty(
5762 				display,
5763 				nativeHandle,
5764 				GetAtom!"_NET_WM_ICON"(display),
5765 				GetAtom!"CARDINAL"(display),
5766 				32 /* bits */,
5767 				0 /*PropModeReplace*/,
5768 				buffer.ptr,
5769 				cast(int) buffer.length);
5770 		}
5771 
5772 
5773 
5774 		private SimpleWindow balloon;
5775 		version(with_timer)
5776 		private Timer timer;
5777 
5778 		private Window nativeHandle;
5779 		private Pixmap clippixmap = None;
5780 		private int width = 16;
5781 		private int height = 16;
5782 		private bool active = false;
5783 
5784 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
5785 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
5786 		void delegate () onLeave; /// X11 only.
5787 
5788 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
5789 
5790 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
5791 		void getWindowRect (out int x, out int y, out int width, out int height) {
5792 			if (!active) { width = 1; height = 1; return; } // 1: just in case
5793 			Window dummyw;
5794 			auto dpy = XDisplayConnection.get;
5795 			//XWindowAttributes xwa;
5796 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
5797 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
5798 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
5799 			width = this.width;
5800 			height = this.height;
5801 		}
5802 	}
5803 
5804 	/+
5805 		What I actually want from this:
5806 
5807 		* set / change: icon, tooltip
5808 		* handle: mouse click, right click
5809 		* show: notification bubble.
5810 	+/
5811 
5812 	version(Windows) {
5813 		WindowsIcon win32Icon;
5814 		HWND hwnd;
5815 
5816 		NOTIFYICONDATAW data;
5817 
5818 		NativeEventHandler getNativeEventHandler() {
5819 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
5820 				if(msg == WM_USER) {
5821 					auto event = LOWORD(lParam);
5822 					auto iconId = HIWORD(lParam);
5823 					//auto x = GET_X_LPARAM(wParam);
5824 					//auto y = GET_Y_LPARAM(wParam);
5825 					switch(event) {
5826 						case WM_LBUTTONDOWN:
5827 							onClick()(MouseButton.left);
5828 						break;
5829 						case WM_RBUTTONDOWN:
5830 							onClick()(MouseButton.right);
5831 						break;
5832 						case WM_MBUTTONDOWN:
5833 							onClick()(MouseButton.middle);
5834 						break;
5835 						case WM_MOUSEMOVE:
5836 							// sent, we could use it.
5837 						break;
5838 						case WM_MOUSEWHEEL:
5839 							// NOT SENT
5840 						break;
5841 						//case NIN_KEYSELECT:
5842 						//case NIN_SELECT:
5843 						//break;
5844 						default: {}
5845 					}
5846 				}
5847 				return 0;
5848 			};
5849 		}
5850 
5851 		enum NIF_SHOWTIP = 0x00000080;
5852 
5853 		private static struct NOTIFYICONDATAW {
5854 			DWORD cbSize;
5855 			HWND  hWnd;
5856 			UINT  uID;
5857 			UINT  uFlags;
5858 			UINT  uCallbackMessage;
5859 			HICON hIcon;
5860 			WCHAR[128] szTip;
5861 			DWORD dwState;
5862 			DWORD dwStateMask;
5863 			WCHAR[256] szInfo;
5864 			union {
5865 				UINT uTimeout;
5866 				UINT uVersion;
5867 			}
5868 			WCHAR[64] szInfoTitle;
5869 			DWORD dwInfoFlags;
5870 			GUID  guidItem;
5871 			HICON hBalloonIcon;
5872 		}
5873 
5874 	}
5875 
5876 	/++
5877 		Note that on Windows, only left, right, and middle buttons are sent.
5878 		Mouse wheel buttons are NOT set, so don't rely on those events if your
5879 		program is meant to be used on Windows too.
5880 	+/
5881 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
5882 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
5883 		// but on X, we need an Image, so its canonical ctor is there. They should
5884 		// forward to each other though.
5885 		version(X11) {
5886 			this.name = name;
5887 			this.onClick = onClick;
5888 			createXWin();
5889 			this.icon = icon;
5890 		} else version(Windows) {
5891 			this.onClick = onClick;
5892 			this.win32Icon = new WindowsIcon(icon);
5893 
5894 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
5895 
5896 			static bool registered = false;
5897 			if(!registered) {
5898 				WNDCLASSEX wc;
5899 				wc.cbSize = wc.sizeof;
5900 				wc.hInstance = hInstance;
5901 				wc.lpfnWndProc = &WndProc;
5902 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
5903 				if(!RegisterClassExW(&wc))
5904 					throw new WindowsApiException("RegisterClass", GetLastError());
5905 				registered = true;
5906 			}
5907 
5908 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
5909 			if(hwnd is null)
5910 				throw new WindowsApiException("CreateWindow", GetLastError());
5911 
5912 			data.cbSize = data.sizeof;
5913 			data.hWnd = hwnd;
5914 			data.uID = cast(uint) cast(void*) this;
5915 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
5916 				// NIF_INFO means show balloon
5917 			data.uCallbackMessage = WM_USER;
5918 			data.hIcon = this.win32Icon.hIcon;
5919 			data.szTip = ""; // FIXME
5920 			data.dwState = 0; // NIS_HIDDEN; // windows vista
5921 			data.dwStateMask = NIS_HIDDEN; // windows vista
5922 
5923 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
5924 
5925 
5926 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
5927 
5928 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
5929 		} else version(OSXCocoa) {
5930 			throw new NotYetImplementedException();
5931 		} else static assert(0);
5932 	}
5933 
5934 	/// ditto
5935 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
5936 		version(X11) {
5937 			this.onClick = onClick;
5938 			this.name = name;
5939 			createXWin();
5940 			this.icon = icon;
5941 		} else version(Windows) {
5942 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
5943 		} else version(OSXCocoa) {
5944 			throw new NotYetImplementedException();
5945 		} else static assert(0);
5946 	}
5947 
5948 	version(X11) {
5949 		/++
5950 			X-specific extension (for now at least)
5951 		+/
5952 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5953 			this.onClickEx = onClickEx;
5954 			createXWin();
5955 			if (icon !is null) this.icon = icon;
5956 		}
5957 
5958 		/// ditto
5959 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
5960 			this.onClickEx = onClickEx;
5961 			createXWin();
5962 			this.icon = icon;
5963 		}
5964 	}
5965 
5966 	private void delegate (MouseButton button) onClick_;
5967 
5968 	///
5969 	@property final void delegate(MouseButton) onClick() {
5970 		if(onClick_ is null)
5971 			onClick_ = delegate void(MouseButton) {};
5972 		return onClick_;
5973 	}
5974 
5975 	/// ditto
5976 	@property final void onClick(void delegate(MouseButton) handler) {
5977 		// I made this a property setter so we can wrap smaller arg
5978 		// delegates and just forward all to onClickEx or something.
5979 		onClick_ = handler;
5980 	}
5981 
5982 
5983 	string name_;
5984 	@property void name(string n) {
5985 		name_ = n;
5986 	}
5987 
5988 	@property string name() {
5989 		return name_;
5990 	}
5991 
5992 	private MemoryImage originalMemoryImage;
5993 
5994 	///
5995 	@property void icon(MemoryImage i) {
5996 		version(X11) {
5997 			this.originalMemoryImage = i;
5998 			if (!active) return;
5999 			if (i !is null) {
6000 				this.img = Image.fromMemoryImage(i, useAlpha, false);
6001 				if(!useAlpha)
6002 					this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
6003 				// writeln("using pixmap ", clippixmap);
6004 				updateNetWmIcon();
6005 				redraw();
6006 			} else {
6007 				if (this.img !is null) {
6008 					this.img = null;
6009 					redraw();
6010 				}
6011 			}
6012 		} else version(Windows) {
6013 			this.win32Icon = new WindowsIcon(i);
6014 
6015 			data.uFlags = NIF_ICON;
6016 			data.hIcon = this.win32Icon.hIcon;
6017 
6018 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
6019 		} else version(OSXCocoa) {
6020 			throw new NotYetImplementedException();
6021 		} else static assert(0);
6022 	}
6023 
6024 	/// ditto
6025 	@property void icon (Image i) {
6026 		version(X11) {
6027 			if (!active) return;
6028 			if (i !is img) {
6029 				originalMemoryImage = null;
6030 				img = i;
6031 				redraw();
6032 			}
6033 		} else version(Windows) {
6034 			this.icon(i is null ? null : i.toTrueColorImage());
6035 		} else version(OSXCocoa) {
6036 			throw new NotYetImplementedException();
6037 		} else static assert(0);
6038 	}
6039 
6040 	/++
6041 		Shows a balloon notification. You can only show one balloon at a time, if you call
6042 		it twice while one is already up, the first balloon will be replaced.
6043 
6044 
6045 		The user is free to block notifications and they will automatically disappear after
6046 		a timeout period.
6047 
6048 		Params:
6049 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
6050 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
6051 			icon = the icon to display with the notification. If null, it uses your existing icon.
6052 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
6053 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
6054 	+/
6055 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
6056 		bool useCustom = true;
6057 		version(libnotify) {
6058 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
6059 			try {
6060 				if(!active) return;
6061 
6062 				if(libnotify is null) {
6063 					libnotify = new C_DynamicLibrary("libnotify.so");
6064 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
6065 				}
6066 
6067 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
6068 
6069 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
6070 
6071 				if(onclick) {
6072 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
6073 					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);
6074 					libnotify_action_delegates_count++;
6075 				}
6076 
6077 				// FIXME icon
6078 
6079 				// set hint image-data
6080 				// set default action for onclick
6081 
6082 				void* error;
6083 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
6084 
6085 				useCustom = false;
6086 			} catch(Exception e) {
6087 
6088 			}
6089 		}
6090 
6091 		version(X11) {
6092 		if(useCustom) {
6093 			if(!active) return;
6094 			if(balloon) {
6095 				hideBalloon();
6096 			}
6097 			// I know there are two specs for this, but one is never
6098 			// implemented by any window manager I have ever seen, and
6099 			// the other is a bloated mess and too complicated for simpledisplay...
6100 			// so doing my own little window instead.
6101 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
6102 
6103 			int x, y, width, height;
6104 			getWindowRect(x, y, width, height);
6105 
6106 			int bx = x - balloon.width;
6107 			int by = y - balloon.height;
6108 			if(bx < 0)
6109 				bx = x + width + balloon.width;
6110 			if(by < 0)
6111 				by = y + height;
6112 
6113 			// just in case, make sure it is actually on scren
6114 			if(bx < 0)
6115 				bx = 0;
6116 			if(by < 0)
6117 				by = 0;
6118 
6119 			balloon.move(bx, by);
6120 			auto painter = balloon.draw();
6121 			painter.fillColor = Color(220, 220, 220);
6122 			painter.outlineColor = Color.black;
6123 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
6124 			auto iconWidth = icon is null ? 0 : icon.width;
6125 			if(icon)
6126 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
6127 			iconWidth += 6; // margin around the icon
6128 
6129 			// draw a close button
6130 			painter.outlineColor = Color(44, 44, 44);
6131 			painter.fillColor = Color(255, 255, 255);
6132 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
6133 			painter.pen = Pen(Color.black, 3);
6134 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
6135 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
6136 			painter.pen = Pen(Color.black, 1);
6137 			painter.fillColor = Color(220, 220, 220);
6138 
6139 			// Draw the title and message
6140 			painter.drawText(Point(4 + iconWidth, 4), title);
6141 			painter.drawLine(
6142 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
6143 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
6144 			);
6145 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
6146 
6147 			balloon.setEventHandlers(
6148 				(MouseEvent ev) {
6149 					if(ev.type == MouseEventType.buttonPressed) {
6150 						if(ev.x > balloon.width - 16 && ev.y < 16)
6151 							hideBalloon();
6152 						else if(onclick)
6153 							onclick();
6154 					}
6155 				}
6156 			);
6157 			balloon.show();
6158 
6159 			version(with_timer)
6160 			timer = new Timer(timeout, &hideBalloon);
6161 			else {} // FIXME
6162 		}
6163 		} else version(Windows) {
6164 			enum NIF_INFO = 0x00000010;
6165 
6166 			data.uFlags = NIF_INFO;
6167 
6168 			// FIXME: go back to the last valid unicode code point
6169 			if(title.length > 40)
6170 				title = title[0 .. 40];
6171 			if(message.length > 220)
6172 				message = message[0 .. 220];
6173 
6174 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
6175 			enum NIIF_LARGE_ICON  = 0x00000020;
6176 			enum NIIF_NOSOUND = 0x00000010;
6177 			enum NIIF_USER = 0x00000004;
6178 			enum NIIF_ERROR = 0x00000003;
6179 			enum NIIF_WARNING = 0x00000002;
6180 			enum NIIF_INFO = 0x00000001;
6181 			enum NIIF_NONE = 0;
6182 
6183 			WCharzBuffer t = WCharzBuffer(title);
6184 			WCharzBuffer m = WCharzBuffer(message);
6185 
6186 			t.copyInto(data.szInfoTitle);
6187 			m.copyInto(data.szInfo);
6188 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
6189 
6190 			if(icon !is null) {
6191 				auto i = new WindowsIcon(icon);
6192 				data.hBalloonIcon = i.hIcon;
6193 				data.dwInfoFlags |= NIIF_USER;
6194 			}
6195 
6196 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
6197 		} else version(OSXCocoa) {
6198 			throw new NotYetImplementedException();
6199 		} else static assert(0);
6200 	}
6201 
6202 	///
6203 	//version(Windows)
6204 	void show() {
6205 		version(X11) {
6206 			if(!hidden)
6207 				return;
6208 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
6209 			hidden = false;
6210 		} else version(Windows) {
6211 			data.uFlags = NIF_STATE;
6212 			data.dwState = 0; // NIS_HIDDEN; // windows vista
6213 			data.dwStateMask = NIS_HIDDEN; // windows vista
6214 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
6215 		} else version(OSXCocoa) {
6216 			throw new NotYetImplementedException();
6217 		} else static assert(0);
6218 	}
6219 
6220 	version(X11)
6221 		bool hidden = false;
6222 
6223 	///
6224 	//version(Windows)
6225 	void hide() {
6226 		version(X11) {
6227 			if(hidden)
6228 				return;
6229 			hidden = true;
6230 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
6231 		} else version(Windows) {
6232 			data.uFlags = NIF_STATE;
6233 			data.dwState = NIS_HIDDEN; // windows vista
6234 			data.dwStateMask = NIS_HIDDEN; // windows vista
6235 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
6236 		} else version(OSXCocoa) {
6237 			throw new NotYetImplementedException();
6238 		} else static assert(0);
6239 	}
6240 
6241 	///
6242 	void close () {
6243 		version(X11) {
6244 			if (active) {
6245 				active = false; // event handler will set this too, but meh
6246 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
6247 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
6248 				flushGui();
6249 			}
6250 		} else version(Windows) {
6251 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
6252 		} else version(OSXCocoa) {
6253 			throw new NotYetImplementedException();
6254 		} else static assert(0);
6255 	}
6256 
6257 	~this() {
6258 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
6259 		version(X11)
6260 			if(clippixmap != None)
6261 				XFreePixmap(XDisplayConnection.get, clippixmap);
6262 		close();
6263 	}
6264 }
6265 
6266 version(X11)
6267 /// Call `XFreePixmap` on the return value.
6268 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
6269 	char[] data = new char[](i.width * i.height / 8 + 2);
6270 	data[] = 0;
6271 
6272 	int bitOffset = 0;
6273 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
6274 		ubyte v = c.a > 128 ? 1 : 0;
6275 		data[bitOffset / 8] |= v << (bitOffset%8);
6276 		bitOffset++;
6277 	}
6278 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
6279 	return handle;
6280 }
6281 
6282 
6283 // basic functions to make timers
6284 /**
6285 	A timer that will trigger your function on a given interval.
6286 
6287 
6288 	You create a timer with an interval and a callback. It will continue
6289 	to fire on the interval until it is destroyed.
6290 
6291 	There are currently no one-off timers (instead, just create one and
6292 	destroy it when it is triggered) nor are there pause/resume functions -
6293 	the timer must again be destroyed and recreated if you want to pause it.
6294 
6295 	---
6296 	auto timer = new Timer(50, { it happened!; });
6297 	timer.destroy();
6298 	---
6299 
6300 	Timers can only be expected to fire when the event loop is running and only
6301 	once per iteration through the event loop.
6302 
6303 	History:
6304 		Prior to December 9, 2020, a timer pulse set too high with a handler too
6305 		slow could lock up the event loop. It now guarantees other things will
6306 		get a chance to run between timer calls, even if that means not keeping up
6307 		with the requested interval.
6308 */
6309 version(with_timer) {
6310 static if(use_arsd_core) {
6311 	alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api
6312 } else
6313 class Timer {
6314 // FIXME: needs pause and unpause
6315 	// FIXME: I might add overloads for ones that take a count of
6316 	// how many elapsed since last time (on Windows, it will divide
6317 	// the ticks thing given, on Linux it is just available) and
6318 	// maybe one that takes an instance of the Timer itself too
6319 	/// Create a timer with a callback when it triggers.
6320 	this(int intervalInMilliseconds, void delegate() onPulse) @trusted {
6321 		assert(onPulse !is null);
6322 
6323 		this.intervalInMilliseconds = intervalInMilliseconds;
6324 		this.onPulse = onPulse;
6325 
6326 		version(Windows) {
6327 			/*
6328 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6329 			if(handle == 0)
6330 				throw new WindowsApiException("SetTimer", GetLastError());
6331 			*/
6332 
6333 			// thanks to Archival 998 for the WaitableTimer blocks
6334 			handle = CreateWaitableTimer(null, false, null);
6335 			long initialTime = -intervalInMilliseconds;
6336 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6337 				throw new WindowsApiException("SetWaitableTimer", GetLastError());
6338 
6339 			mapping[handle] = this;
6340 
6341 		} else version(Emscripten) {
6342 		} else version(linux) {
6343 			static import ep = core.sys.linux.epoll;
6344 
6345 			import core.sys.linux.timerfd;
6346 
6347 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
6348 			if(fd == -1)
6349 				throw new Exception("timer create failed");
6350 
6351 			mapping[fd] = this;
6352 
6353 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6354 
6355 			if(timerfd_settime(fd, 0, &value, null) == -1)
6356 				throw new Exception("couldn't make pulse timer");
6357 
6358 			version(with_eventloop) {
6359 				import arsd.eventloop;
6360 				addFileEventListeners(fd, &trigger, null, null);
6361 			} else {
6362 				prepareEventLoop();
6363 
6364 				ep.epoll_event ev = void;
6365 				ev.events = ep.EPOLLIN;
6366 				ev.data.fd = fd;
6367 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6368 			}
6369 		} else featureNotImplemented();
6370 	}
6371 
6372 	private int intervalInMilliseconds;
6373 
6374 	// just cuz I sometimes call it this.
6375 	alias dispose = destroy;
6376 
6377 	/// Stop and destroy the timer object.
6378 	void destroy() {
6379 		version(Windows) {
6380 			staticDestroy(handle);
6381 			handle = null;
6382 		} else version(linux) {
6383 			staticDestroy(fd);
6384 			fd = -1;
6385 		} else featureNotImplemented();
6386 	}
6387 
6388 	version(Windows)
6389 	static void staticDestroy(HANDLE handle) {
6390 		if(handle) {
6391 			// KillTimer(null, handle);
6392 			CancelWaitableTimer(cast(void*)handle);
6393 			mapping.remove(handle);
6394 			CloseHandle(handle);
6395 		}
6396 	}
6397 	else version(Emscripten)
6398 	static void staticDestroy(int fd) @system {
6399 		assert(0);
6400 	}
6401 	else version(linux)
6402 	static void staticDestroy(int fd) @system {
6403 		if(fd != -1) {
6404 			import unix = core.sys.posix.unistd;
6405 			static import ep = core.sys.linux.epoll;
6406 
6407 			version(with_eventloop) {
6408 				import arsd.eventloop;
6409 				removeFileEventListeners(fd);
6410 			} else {
6411 				ep.epoll_event ev = void;
6412 				ev.events = ep.EPOLLIN;
6413 				ev.data.fd = fd;
6414 
6415 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6416 			}
6417 			unix.close(fd);
6418 			mapping.remove(fd);
6419 		}
6420 	}
6421 
6422 	~this() {
6423 		version(Windows) { if(handle)
6424 			cleanupQueue.queue!staticDestroy(handle);
6425 		} else version(linux) { if(fd != -1)
6426 			cleanupQueue.queue!staticDestroy(fd);
6427 		}
6428 	}
6429 
6430 	void changeTime(int intervalInMilliseconds)
6431 	{
6432 		this.intervalInMilliseconds = intervalInMilliseconds;
6433 		version(Windows)
6434 		{
6435 			if(handle)
6436 			{
6437 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
6438 				long initialTime = -intervalInMilliseconds;
6439 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
6440 					throw new WindowsApiException("couldn't change pulse timer", GetLastError());
6441 			}
6442 		} else version(linux) {
6443 			import core.sys.linux.timerfd;
6444 
6445 			itimerspec value = makeItimerspec(intervalInMilliseconds);
6446 			if(timerfd_settime(fd, 0, &value, null) == -1) {
6447 				throw new Exception("couldn't change pulse timer");
6448 			}
6449 		} else {
6450 			assert(false, "Timer.changeTime(int) is not implemented for this platform");
6451 		}
6452 	}
6453 
6454 
6455 	private:
6456 
6457 	void delegate() onPulse;
6458 
6459 	int lastEventLoopRoundTriggered;
6460 
6461 	version(linux) {
6462 		static auto makeItimerspec(int intervalInMilliseconds) {
6463 			import core.sys.linux.timerfd;
6464 
6465 			itimerspec value;
6466 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6467 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6468 
6469 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
6470 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
6471 
6472 			return value;
6473 		}
6474 	}
6475 
6476 	void trigger() {
6477 		version(linux) {
6478 			import unix = core.sys.posix.unistd;
6479 			long val;
6480 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
6481 		} else version(Windows) {
6482 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
6483 				return; // never try to actually run faster than the event loop
6484 			lastEventLoopRoundTriggered = eventLoopRound;
6485 		} else featureNotImplemented();
6486 
6487 		onPulse();
6488 	}
6489 
6490 	version(Windows)
6491 	void rearm() {
6492 
6493 	}
6494 
6495 	version(Windows)
6496 		extern(Windows)
6497 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
6498 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
6499 			if(Timer* t = timer in mapping) {
6500 				try
6501 				(*t).trigger();
6502 				catch(Exception e) { sdpy_abort(e); assert(0); }
6503 			}
6504 		}
6505 
6506 	version(Windows) {
6507 		//UINT_PTR handle;
6508 		//static Timer[UINT_PTR] mapping;
6509 		HANDLE handle;
6510 		__gshared Timer[HANDLE] mapping;
6511 	} else version(linux) {
6512 		int fd = -1;
6513 		__gshared Timer[int] mapping;
6514 	} else version(OSXCocoa) {
6515 	} else static assert(0, "timer not supported");
6516 }
6517 }
6518 
6519 version(Windows)
6520 private int eventLoopRound;
6521 
6522 version(Windows)
6523 /// 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
6524 class WindowsHandleReader {
6525 	///
6526 	this(void delegate() onReady, HANDLE handle) {
6527 		this.onReady = onReady;
6528 		this.handle = handle;
6529 
6530 		mapping[handle] = this;
6531 
6532 		enable();
6533 	}
6534 
6535 	static if(use_arsd_core)
6536 		ICoreEventLoop.UnregisterToken unregisterToken;
6537 
6538 	///
6539 	void enable() {
6540 		static if(use_arsd_core) {
6541 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready));
6542 		} else {
6543 			auto el = EventLoop.get().impl;
6544 			el.handles ~= handle;
6545 		}
6546 	}
6547 
6548 	///
6549 	void disable() {
6550 		static if(use_arsd_core) {
6551 			unregisterToken.unregister();
6552 		} else {
6553 			auto el = EventLoop.get().impl;
6554 			for(int i = 0; i < el.handles.length; i++) {
6555 				if(el.handles[i] is handle) {
6556 					el.handles[i] = el.handles[$-1];
6557 					el.handles = el.handles[0 .. $-1];
6558 					return;
6559 				}
6560 			}
6561 		}
6562 	}
6563 
6564 	void dispose() {
6565 		disable();
6566 		if(handle)
6567 			mapping.remove(handle);
6568 		handle = null;
6569 	}
6570 
6571 	void ready() {
6572 		if(onReady)
6573 			onReady();
6574 	}
6575 
6576 	HANDLE handle;
6577 	void delegate() onReady;
6578 
6579 	__gshared WindowsHandleReader[HANDLE] mapping;
6580 }
6581 
6582 version(Posix)
6583 /// Lets you add files to the event loop for reading. Use at your own risk.
6584 class PosixFdReader {
6585 	///
6586 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6587 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
6588 	}
6589 
6590 	///
6591 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6592 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
6593 	}
6594 
6595 	///
6596 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
6597 		this.onReady = onReady;
6598 		this.fd = fd;
6599 		this.captureWrites = captureWrites;
6600 		this.captureReads = captureReads;
6601 
6602 		mapping[fd] = this;
6603 
6604 		version(with_eventloop) {
6605 			import arsd.eventloop;
6606 			addFileEventListeners(fd, &readyel);
6607 		} else {
6608 			enable();
6609 		}
6610 	}
6611 
6612 	bool captureReads;
6613 	bool captureWrites;
6614 
6615 	static if(use_arsd_core) {
6616 		import arsd.core;
6617 		ICoreEventLoop.UnregisterToken unregisterToken;
6618 	}
6619 
6620 	version(with_eventloop) {} else
6621 	///
6622 	void enable() @system {
6623 		enabled = true;
6624 
6625 		static if(use_arsd_core) {
6626 			unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper(
6627 				() { onReady(fd, true, false); }
6628 			));
6629 			// FIXME: what if it is writeable?
6630 
6631 		} else version(linux) {
6632 			prepareEventLoop();
6633 			static import ep = core.sys.linux.epoll;
6634 			ep.epoll_event ev = void;
6635 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6636 			// writeln("enable ", fd, " ", captureReads, " ", captureWrites);
6637 			ev.data.fd = fd;
6638 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
6639 		} else {
6640 
6641 		}
6642 	}
6643 
6644 	version(with_eventloop) {} else
6645 	///
6646 	void disable() @system {
6647 		enabled = false;
6648 
6649 		static if(use_arsd_core) {
6650 			unregisterToken.unregister();
6651 		} else
6652 		version(linux) {
6653 			prepareEventLoop();
6654 			static import ep = core.sys.linux.epoll;
6655 			ep.epoll_event ev = void;
6656 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
6657 			// writeln("disable ", fd, " ", captureReads, " ", captureWrites);
6658 			ev.data.fd = fd;
6659 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
6660 		}
6661 	}
6662 
6663 	version(with_eventloop) {} else
6664 	///
6665 	void dispose() {
6666 		if(enabled)
6667 			disable();
6668 		if(fd != -1)
6669 			mapping.remove(fd);
6670 		fd = -1;
6671 	}
6672 
6673 	void delegate(int, bool, bool) onReady;
6674 
6675 	version(with_eventloop)
6676 	void readyel() {
6677 		onReady(fd, true, true);
6678 	}
6679 
6680 	void ready(uint flags) {
6681 		version(Emscripten) {
6682 			assert(0);
6683 		} else version(linux) {
6684 			static import ep = core.sys.linux.epoll;
6685 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
6686 		} else {
6687 			import core.sys.posix.poll;
6688 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
6689 		}
6690 	}
6691 
6692 	void hup(uint flags) {
6693 		if(onHup)
6694 			onHup();
6695 	}
6696 
6697 	void delegate() onHup;
6698 
6699 	int fd = -1;
6700 	private bool enabled;
6701 	__gshared PosixFdReader[int] mapping;
6702 }
6703 
6704 // basic functions to access the clipboard
6705 /+
6706 
6707 
6708 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
6709 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
6710 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6711 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
6712 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
6713 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
6714 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
6715 
6716 +/
6717 
6718 /++
6719 	this does a delegate because it is actually an async call on X...
6720 	the receiver may never be called if the clipboard is empty or unavailable
6721 	gets plain text from the clipboard.
6722 +/
6723 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system {
6724 	version(Windows) {
6725 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6726 		if(OpenClipboard(hwndOwner) == 0)
6727 			throw new WindowsApiException("OpenClipboard", GetLastError());
6728 		scope(exit)
6729 			CloseClipboard();
6730 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
6731 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
6732 
6733 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
6734 				scope(exit)
6735 					GlobalUnlock(dataHandle);
6736 
6737 				// FIXME: CR/LF conversions
6738 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
6739 				int len = 0;
6740 				auto d = data;
6741 				while(*d) {
6742 					d++;
6743 					len++;
6744 				}
6745 				string s;
6746 				s.reserve(len);
6747 				foreach(dchar ch; data[0 .. len]) {
6748 					s ~= ch;
6749 				}
6750 				receiver(s);
6751 			}
6752 		}
6753 	} else version(X11) {
6754 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6755 	} else version(OSXCocoa) {
6756 		throw new NotYetImplementedException();
6757 	} else version(Emscripten) {
6758 		throw new NotYetImplementedException();
6759 	} else static assert(0);
6760 }
6761 
6762 // FIXME: a clipboard listener might be cool btw
6763 
6764 /++
6765 	this does a delegate because it is actually an async call on X...
6766 	the receiver may never be called if the clipboard is empty or unavailable
6767 	gets image from the clipboard.
6768 
6769 	templated because it introduces an optional dependency on arsd.bmp
6770 +/
6771 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
6772 	version(Windows) {
6773 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
6774 		if(OpenClipboard(hwndOwner) == 0)
6775 			throw new WindowsApiException("OpenClipboard", GetLastError());
6776 		scope(exit)
6777 			CloseClipboard();
6778 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
6779 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
6780 				scope(exit)
6781 					GlobalUnlock(dataHandle);
6782 
6783 				auto len = GlobalSize(dataHandle);
6784 
6785 				import arsd.bmp;
6786 				auto img = readBmp(data[0 .. len], false);
6787 				receiver(img);
6788 			}
6789 		}
6790 	} else version(X11) {
6791 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
6792 	} else version(OSXCocoa) {
6793 		throw new NotYetImplementedException();
6794 	} else version(Emscripten) {
6795 		throw new NotYetImplementedException();
6796 	} else static assert(0);
6797 }
6798 
6799 /// Copies some text to the clipboard.
6800 void setClipboardText(SimpleWindow clipboardOwner, string text) {
6801 	assert(clipboardOwner !is null);
6802 	version(Windows) {
6803 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6804 			throw new WindowsApiException("OpenClipboard", GetLastError());
6805 		scope(exit)
6806 			CloseClipboard();
6807 		EmptyClipboard();
6808 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6809 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
6810 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6811 		if(auto data = cast(wchar*) GlobalLock(handle)) {
6812 			auto slice = data[0 .. sz];
6813 			scope(failure)
6814 				GlobalUnlock(handle);
6815 
6816 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
6817 
6818 			GlobalUnlock(handle);
6819 			SetClipboardData(CF_UNICODETEXT, handle);
6820 		}
6821 	} else version(X11) {
6822 		// we set BOTH clipboard and primary on an explicit action
6823 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
6824 		setX11Selection!"PRIMARY"(clipboardOwner, text);
6825 	} else version(OSXCocoa) {
6826 		throw new NotYetImplementedException();
6827 	} else version(Emscripten) {
6828 		throw new NotYetImplementedException();
6829 	} else static assert(0);
6830 }
6831 
6832 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
6833 	assert(clipboardOwner !is null);
6834 	version(Windows) {
6835 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
6836 			throw new WindowsApiException("OpenClipboard", GetLastError());
6837 		scope(exit)
6838 			CloseClipboard();
6839 		EmptyClipboard();
6840 
6841 
6842 		import arsd.bmp;
6843 		ubyte[] mdata;
6844 		mdata.reserve(img.width * img.height);
6845 		void sink(ubyte b) {
6846 			mdata ~= b;
6847 		}
6848 		writeBmpIndirect(img, &sink, false);
6849 
6850 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
6851 		if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
6852 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
6853 			auto slice = data[0 .. mdata.length];
6854 			scope(failure)
6855 				GlobalUnlock(handle);
6856 
6857 			slice[] = mdata[];
6858 
6859 			GlobalUnlock(handle);
6860 			SetClipboardData(CF_DIB, handle);
6861 		}
6862 	} else version(X11) {
6863 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
6864 			mixin X11SetSelectionHandler_Basics;
6865 			private const(ubyte)[] mdata;
6866 			private const(ubyte)[] mdata_original;
6867 			this(MemoryImage img) {
6868 				import arsd.bmp;
6869 
6870 				mdata.reserve(img.width * img.height);
6871 				void sink(ubyte b) {
6872 					mdata ~= b;
6873 				}
6874 				writeBmpIndirect(img, &sink, true);
6875 
6876 				mdata_original = mdata;
6877 			}
6878 
6879 			Atom[] availableFormats() {
6880 				auto display = XDisplayConnection.get;
6881 				return [
6882 					GetAtom!"image/bmp"(display),
6883 					GetAtom!"TARGETS"(display)
6884 				];
6885 			}
6886 
6887 			ubyte[] getData(Atom format, return scope ubyte[] data) {
6888 				if(mdata.length < data.length) {
6889 					data[0 .. mdata.length] = mdata[];
6890 					auto ret = data[0 .. mdata.length];
6891 					mdata = mdata[$..$];
6892 					return ret;
6893 				} else {
6894 					data[] = mdata[0 .. data.length];
6895 					mdata = mdata[data.length .. $];
6896 					return data[];
6897 				}
6898 			}
6899 
6900 			void done() {
6901 				mdata = mdata_original;
6902 			}
6903 		}
6904 
6905 		auto handler = new X11SetSelectionHandler_Image(img);
6906 		setX11Selection!"CLIPBOARD"(clipboardOwner, handler);
6907 		setX11Selection!"PRIMARY"(clipboardOwner, handler);
6908 	} else version(OSXCocoa) {
6909 		throw new NotYetImplementedException();
6910 	} else version(Emscripten) {
6911 		throw new NotYetImplementedException();
6912 	} else static assert(0);
6913 }
6914 
6915 
6916 version(X11) {
6917 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
6918 
6919 	private __gshared Atom*[] interredAtoms; // for discardAndRecreate
6920 
6921 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
6922 	/// Platform-specific for X11.
6923 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
6924 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
6925 		__gshared static Atom a;
6926 		if(!a) {
6927 			a = XInternAtom(display, name, !create);
6928 			// FIXME: might need to synchronize this and attach it to the actual object
6929 			interredAtoms ~= &a;
6930 		}
6931 		if(a == None)
6932 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
6933 		return a;
6934 	}
6935 
6936 	/// Platform-specific for X11 - gets atom names as a string.
6937 	string getAtomName(Atom atom, Display* display) {
6938 		if(atom == None)
6939 			return "None";
6940 		auto got = XGetAtomName(display, atom);
6941 		if(got is null)
6942 			return null;
6943 		scope(exit) XFree(got);
6944 		import core.stdc.string;
6945 		string s = got[0 .. strlen(got)].idup;
6946 		return s;
6947 	}
6948 
6949 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
6950 	void setPrimarySelection(SimpleWindow window, string text) {
6951 		setX11Selection!"PRIMARY"(window, text);
6952 	}
6953 
6954 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
6955 	void setSecondarySelection(SimpleWindow window, string text) {
6956 		setX11Selection!"SECONDARY"(window, text);
6957 	}
6958 
6959 	interface X11SetSelectionHandler {
6960 		// should include TARGETS right now
6961 		Atom[] availableFormats();
6962 		// Return the slice of data you filled, empty slice if done.
6963 		// this is to support the incremental thing
6964 		ubyte[] getData(Atom format, return scope ubyte[] data);
6965 
6966 		void done();
6967 
6968 		void handleRequest(XEvent);
6969 
6970 		bool matchesIncr(Window, Atom);
6971 		void sendMoreIncr(XPropertyEvent*);
6972 	}
6973 
6974 	mixin template X11SetSelectionHandler_Basics() {
6975 		Window incrWindow;
6976 		Atom incrAtom;
6977 		Atom selectionAtom;
6978 		Atom formatAtom;
6979 		ubyte[] toSend;
6980 		bool matchesIncr(Window w, Atom a) {
6981 			return incrAtom && incrAtom == a && w == incrWindow;
6982 		}
6983 		void sendMoreIncr(XPropertyEvent* event) {
6984 			auto display = XDisplayConnection.get;
6985 
6986 			XChangeProperty (display,
6987 				incrWindow,
6988 				incrAtom,
6989 				formatAtom,
6990 				8 /* bits */, PropModeReplace,
6991 				toSend.ptr, cast(int) toSend.length);
6992 
6993 			if(toSend.length != 0) {
6994 				toSend = this.getData(formatAtom, toSend[]);
6995 			} else {
6996 				this.done();
6997 				incrWindow = None;
6998 				incrAtom = None;
6999 				selectionAtom = None;
7000 				formatAtom = None;
7001 				toSend = null;
7002 			}
7003 		}
7004 		void handleRequest(XEvent ev) {
7005 
7006 			auto display = XDisplayConnection.get;
7007 
7008 			XSelectionRequestEvent* event = &ev.xselectionrequest;
7009 			XSelectionEvent selectionEvent;
7010 			selectionEvent.type = EventType.SelectionNotify;
7011 			selectionEvent.display = event.display;
7012 			selectionEvent.requestor = event.requestor;
7013 			selectionEvent.selection = event.selection;
7014 			selectionEvent.time = event.time;
7015 			selectionEvent.target = event.target;
7016 
7017 			bool supportedType() {
7018 				foreach(t; this.availableFormats())
7019 					if(t == event.target)
7020 						return true;
7021 				return false;
7022 			}
7023 
7024 			if(event.property == None) {
7025 				selectionEvent.property = event.target;
7026 
7027 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
7028 				XFlush(display);
7029 			} if(event.target == GetAtom!"TARGETS"(display)) {
7030 				/* respond with the supported types */
7031 				auto tlist = this.availableFormats();
7032 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
7033 				selectionEvent.property = event.property;
7034 
7035 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
7036 				XFlush(display);
7037 			} else if(supportedType()) {
7038 				auto buffer = new ubyte[](1024 * 64);
7039 				auto toSend = this.getData(event.target, buffer[]);
7040 
7041 				if(toSend.length < 32 * 1024) {
7042 					// small enough to send directly...
7043 					selectionEvent.property = event.property;
7044 					XChangeProperty (display,
7045 						selectionEvent.requestor,
7046 						selectionEvent.property,
7047 						event.target,
7048 						8 /* bits */, 0 /* PropModeReplace */,
7049 						toSend.ptr, cast(int) toSend.length);
7050 
7051 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
7052 					XFlush(display);
7053 				} else {
7054 					// large, let's send incrementally
7055 					arch_ulong l = toSend.length;
7056 
7057 					// if I wanted other events from this window don't want to clear that out....
7058 					XWindowAttributes xwa;
7059 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
7060 
7061 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
7062 
7063 					incrWindow = event.requestor;
7064 					incrAtom = event.property;
7065 					formatAtom = event.target;
7066 					selectionAtom = event.selection;
7067 					this.toSend = toSend;
7068 
7069 					selectionEvent.property = event.property;
7070 					XChangeProperty (display,
7071 						selectionEvent.requestor,
7072 						selectionEvent.property,
7073 						GetAtom!"INCR"(display),
7074 						32 /* bits */, PropModeReplace,
7075 						&l, 1);
7076 
7077 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
7078 					XFlush(display);
7079 				}
7080 				//if(after)
7081 					//after();
7082 			} else {
7083 				debug(sdpy_clip) {
7084 					writeln("Unsupported data ", getAtomName(event.target, display));
7085 				}
7086 				selectionEvent.property = None; // I don't know how to handle this type...
7087 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
7088 				XFlush(display);
7089 			}
7090 		}
7091 	}
7092 
7093 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
7094 		mixin X11SetSelectionHandler_Basics;
7095 		private const(ubyte)[] text;
7096 		private const(ubyte)[] text_original;
7097 		this(string text) {
7098 			this.text = cast(const ubyte[]) text;
7099 			this.text_original = this.text;
7100 		}
7101 		Atom[] availableFormats() {
7102 			auto display = XDisplayConnection.get;
7103 			return [
7104 				GetAtom!"UTF8_STRING"(display),
7105 				GetAtom!"text/plain"(display),
7106 				XA_STRING,
7107 				GetAtom!"TARGETS"(display)
7108 			];
7109 		}
7110 
7111 		ubyte[] getData(Atom format, return scope ubyte[] data) {
7112 			if(text.length < data.length) {
7113 				data[0 .. text.length] = text[];
7114 				return data[0 .. text.length];
7115 			} else {
7116 				data[] = text[0 .. data.length];
7117 				text = text[data.length .. $];
7118 				return data[];
7119 			}
7120 		}
7121 
7122 		void done() {
7123 			text = text_original;
7124 		}
7125 	}
7126 
7127 	/// 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?!)
7128 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
7129 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
7130 	}
7131 
7132 	private __gshared bool mightShortCircuitClipboard;
7133 
7134 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
7135 		assert(window !is null);
7136 
7137 		auto display = XDisplayConnection.get();
7138 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
7139 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
7140 		else Atom a = GetAtom!atomName(display);
7141 
7142 		if(a == XA_PRIMARY && mightShortCircuitClipboard)
7143 		if(auto ptr = a in window.impl.setSelectionHandlers) {
7144 			// we already have it, don't even need to inform the X server
7145 			// sdpyPrintDebugString("short circuit in set");
7146 			*ptr = data;
7147 			return;
7148 		}
7149 
7150 		// we don't have it, tell X we want it
7151 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
7152 		window.impl.setSelectionHandlers[a] = data;
7153 		if(a == XA_PRIMARY)
7154 		mightShortCircuitClipboard = true;
7155 	}
7156 
7157 	/+
7158 	/++
7159 		History:
7160 			Added September 28, 2024
7161 	+/
7162 	bool hasX11Selection(string atomName)(SimpleWindow window) {
7163 		auto display = XDisplayConnection.get();
7164 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
7165 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
7166 		else Atom a = GetAtom!atomName(display);
7167 
7168 		if(a in window.impl.setSelectionHandlers)
7169 			return true;
7170 		else
7171 			return false;
7172 	}
7173 	+/
7174 
7175 	///
7176 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
7177 		getX11Selection!"PRIMARY"(window, handler);
7178 	}
7179 
7180 	// added July 28, 2020
7181 	// undocumented as experimental tho
7182 	interface X11GetSelectionHandler {
7183 		void handleData(Atom target, in ubyte[] data);
7184 		Atom findBestFormat(Atom[] answer);
7185 
7186 		void prepareIncremental(Window, Atom);
7187 		bool matchesIncr(Window, Atom);
7188 		void handleIncrData(Atom, in ubyte[] data);
7189 	}
7190 
7191 	mixin template X11GetSelectionHandler_Basics() {
7192 		Window incrWindow;
7193 		Atom incrAtom;
7194 
7195 		void prepareIncremental(Window w, Atom a) {
7196 			incrWindow = w;
7197 			incrAtom = a;
7198 		}
7199 		bool matchesIncr(Window w, Atom a) {
7200 			return incrWindow == w && incrAtom == a;
7201 		}
7202 
7203 		Atom incrFormatAtom;
7204 		ubyte[] incrData;
7205 		void handleIncrData(Atom format, in ubyte[] data) {
7206 			incrFormatAtom = format;
7207 
7208 			if(data.length)
7209 				incrData ~= data;
7210 			else
7211 				handleData(incrFormatAtom, incrData);
7212 
7213 		}
7214 	}
7215 
7216 	static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
7217 		this(void delegate(in char[]) handler) {
7218 			this.handler = handler;
7219 		}
7220 
7221 		mixin X11GetSelectionHandler_Basics;
7222 
7223 		void delegate(in char[]) handler;
7224 
7225 		void handleData(Atom target, in ubyte[] data) {
7226 			// import std.stdio; writeln(target, " ", data);
7227 			if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
7228 				handler(cast(const char[]) data);
7229 			else if(target == None && data is null)
7230 				handler(null); // no suitable selection exists
7231 		}
7232 
7233 		Atom findBestFormat(Atom[] answer) {
7234 			Atom best = None;
7235 			foreach(option; answer) {
7236 				if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
7237 					best = option;
7238 					break;
7239 				} else if(option == XA_STRING) {
7240 					best = option;
7241 				} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
7242 					best = option;
7243 				}
7244 			}
7245 			return best;
7246 		}
7247 	}
7248 
7249 	///
7250 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
7251 		assert(window !is null);
7252 
7253 		auto display = XDisplayConnection.get();
7254 
7255 		static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY;
7256 		else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY;
7257 		else Atom atom = GetAtom!atomName(display);
7258 
7259 		if(atom == XA_PRIMARY && mightShortCircuitClipboard)
7260 		if(auto ptr = atom in window.impl.setSelectionHandlers) {
7261 			if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) {
7262 				// we already have it! short circuit everything
7263 
7264 				// sdpyPrintDebugString("short circuit in get");
7265 				handler(cast(char[]) txt.text_original);
7266 				return;
7267 			}
7268 		}
7269 
7270 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
7271 
7272 		auto target = GetAtom!"TARGETS"(display);
7273 
7274 		// SDD_DATA is "simpledisplay.d data"
7275 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
7276 	}
7277 
7278 	/// Gets the image on the clipboard, if there is one. Added July 2020.
7279 	/// only supports bmps. using this function will import arsd.bmp.
7280 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
7281 		assert(window !is null);
7282 
7283 		auto display = XDisplayConnection.get();
7284 		auto atom = GetAtom!atomName(display);
7285 
7286 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
7287 			this(void delegate(MemoryImage) handler) {
7288 				this.handler = handler;
7289 			}
7290 
7291 			mixin X11GetSelectionHandler_Basics;
7292 
7293 			void delegate(MemoryImage) handler;
7294 
7295 			void handleData(Atom target, in ubyte[] data) {
7296 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
7297 					import arsd.bmp;
7298 					handler(readBmp(data));
7299 				}
7300 			}
7301 
7302 			Atom findBestFormat(Atom[] answer) {
7303 				Atom best = None;
7304 				foreach(option; answer) {
7305 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
7306 						best = option;
7307 					}
7308 				}
7309 				return best;
7310 			}
7311 
7312 		}
7313 
7314 
7315 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
7316 
7317 		auto target = GetAtom!"TARGETS"(display);
7318 
7319 		// SDD_DATA is "simpledisplay.d data"
7320 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
7321 	}
7322 
7323 
7324 	///
7325 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
7326 		Atom actualType;
7327 		int actualFormat;
7328 		arch_ulong actualItems;
7329 		arch_ulong bytesRemaining;
7330 		void* data;
7331 
7332 		auto display = XDisplayConnection.get();
7333 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
7334 			if(actualFormat == 0)
7335 				return null;
7336 			else {
7337 				int byteLength;
7338 				if(actualFormat == 32) {
7339 					// 32 means it is a C long... which is variable length
7340 					actualFormat = cast(int) arch_long.sizeof * 8;
7341 				}
7342 
7343 				// then it is just a bit count
7344 				byteLength = cast(int) (actualItems * actualFormat / 8);
7345 
7346 				auto d = new ubyte[](byteLength);
7347 				d[] = cast(ubyte[]) data[0 .. byteLength];
7348 				XFree(data);
7349 				return d;
7350 			}
7351 		}
7352 		return null;
7353 	}
7354 
7355 	/* defined in the systray spec */
7356 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
7357 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
7358 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
7359 
7360 
7361 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
7362 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
7363 	public class GlobalHotkey {
7364 		KeyEvent key;
7365 		void delegate () handler;
7366 
7367 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
7368 
7369 		/// Create from initialzed KeyEvent object
7370 		this (KeyEvent akey, void delegate () ahandler=null) {
7371 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
7372 			key = akey;
7373 			handler = ahandler;
7374 		}
7375 
7376 		/// Create from emacs-like key name ("C-M-Y", etc.)
7377 		this (const(char)[] akey, void delegate () ahandler=null) {
7378 			key = KeyEvent.parse(akey);
7379 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
7380 			handler = ahandler;
7381 		}
7382 
7383 	}
7384 
7385 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7386 		//conwriteln("failed to grab key");
7387 		GlobalHotkeyManager.ghfailed = true;
7388 		return 0;
7389 	}
7390 
7391 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7392 		Image.impl.xshmfailed = true;
7393 		return 0;
7394 	}
7395 
7396 	private __gshared int errorHappened;
7397 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
7398 		import core.stdc.stdio;
7399 		char[265] buffer;
7400 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
7401 		debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, cast(long) evt.serial, evt.request_code, evt.minor_code, cast(long) evt.resourceid);
7402 		errorHappened = true;
7403 		return 0;
7404 	}
7405 
7406 	/++
7407 		Global hotkey manager. It contains static methods to manage global hotkeys.
7408 
7409 		---
7410 		 try {
7411 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
7412 		} catch (Exception e) {
7413 			conwriteln("ERROR registering hotkey!");
7414 		}
7415 		EventLoop.get.run();
7416 		---
7417 
7418 		The key strings are based on Emacs. In practical terms,
7419 		`M` means `alt` and `H` means the Windows logo key. `C`
7420 		is `ctrl`.
7421 
7422 		$(WARNING
7423 			This is X-specific right now. If you are on
7424 			Windows, try [registerHotKey] instead.
7425 
7426 			We will probably merge these into a single
7427 			interface later.
7428 		)
7429 	+/
7430 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
7431 		version(X11) {
7432 			void recreateAfterDisconnect() {
7433 				throw new Exception("NOT IMPLEMENTED");
7434 			}
7435 			void discardConnectionState() {
7436 				throw new Exception("NOT IMPLEMENTED");
7437 			}
7438 		}
7439 
7440 		private static immutable uint[8] masklist = [ 0,
7441 			KeyOrButtonMask.LockMask,
7442 			KeyOrButtonMask.Mod2Mask,
7443 			KeyOrButtonMask.Mod3Mask,
7444 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
7445 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
7446 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7447 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
7448 		];
7449 		private __gshared GlobalHotkeyManager ghmanager;
7450 		private __gshared bool ghfailed = false;
7451 
7452 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
7453 			if (modmask == 0) return false;
7454 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
7455 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
7456 			return true;
7457 		}
7458 
7459 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
7460 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
7461 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
7462 			return modmask;
7463 		}
7464 
7465 		private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) {
7466 			uint keycode = cast(uint)ke.key;
7467 			auto dpy = XDisplayConnection.get;
7468 			return XKeysymToKeycode(dpy, keycode);
7469 		}
7470 
7471 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
7472 
7473 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
7474 
7475 		NativeEventHandler getNativeEventHandler () {
7476 			return delegate int (XEvent e) {
7477 				if (e.type != EventType.KeyPress) return 1;
7478 				auto kev = cast(const(XKeyEvent)*)&e;
7479 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
7480 				if (auto ghkp = hash in globalHotkeyList) {
7481 					try {
7482 						ghkp.doHandle();
7483 					} catch (Exception e) {
7484 						import core.stdc.stdio : stderr, fprintf;
7485 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
7486 					}
7487 				}
7488 				return 1;
7489 			};
7490 		}
7491 
7492 		private this () {
7493 			auto dpy = XDisplayConnection.get;
7494 			auto root = RootWindow(dpy, DefaultScreen(dpy));
7495 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
7496 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
7497 		}
7498 
7499 		/// Register new global hotkey with initialized `GlobalHotkey` object.
7500 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
7501 		static void register (GlobalHotkey gh) {
7502 			if (gh is null) return;
7503 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
7504 
7505 			auto dpy = XDisplayConnection.get;
7506 			immutable keycode = keyEvent2KeyCode(gh.key);
7507 
7508 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
7509 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
7510 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
7511 			XSync(dpy, 0/*False*/);
7512 
7513 			Window root = RootWindow(dpy, DefaultScreen(dpy));
7514 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7515 			ghfailed = false;
7516 			foreach (immutable uint ormask; masklist[]) {
7517 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
7518 			}
7519 			XSync(dpy, 0/*False*/);
7520 			XSetErrorHandler(savedErrorHandler);
7521 
7522 			if (ghfailed) {
7523 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7524 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
7525 				XSync(dpy, 0/*False*/);
7526 				XSetErrorHandler(savedErrorHandler);
7527 				throw new Exception("cannot register global hotkey");
7528 			}
7529 
7530 			globalHotkeyList[hash] = gh;
7531 		}
7532 
7533 		/// Ditto
7534 		static void register (const(char)[] akey, void delegate () ahandler) {
7535 			register(new GlobalHotkey(akey, ahandler));
7536 		}
7537 
7538 		private static void removeByHash (ulong hash) {
7539 			if (auto ghp = hash in globalHotkeyList) {
7540 				auto dpy = XDisplayConnection.get;
7541 				immutable keycode = keyEvent2KeyCode(ghp.key);
7542 				Window root = RootWindow(dpy, DefaultScreen(dpy));
7543 				XSync(dpy, 0/*False*/);
7544 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
7545 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
7546 				XSync(dpy, 0/*False*/);
7547 				XSetErrorHandler(savedErrorHandler);
7548 				globalHotkeyList.remove(hash);
7549 			}
7550 		}
7551 
7552 		/// Register new global hotkey with previously used `GlobalHotkey` object.
7553 		/// It is safe to unregister unknown or invalid hotkey.
7554 		static void unregister (GlobalHotkey gh) {
7555 			//TODO: add second AA for faster search? prolly doesn't worth it.
7556 			if (gh is null) return;
7557 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
7558 				if (kv.value is gh) {
7559 					removeByHash(kv.key);
7560 					return;
7561 				}
7562 			}
7563 		}
7564 
7565 		/// Ditto.
7566 		static void unregister (const(char)[] key) {
7567 			auto kev = KeyEvent.parse(key);
7568 			immutable keycode = keyEvent2KeyCode(kev);
7569 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
7570 		}
7571 	}
7572 }
7573 
7574 version(Windows) {
7575 	/++
7576 		See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
7577 
7578 		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).
7579 	+/
7580 	void sendSyntheticInput(wstring s) {
7581 			INPUT[] inputs;
7582 			inputs.reserve(s.length * 2);
7583 
7584 			foreach(wchar c; s) {
7585 				INPUT input;
7586 				input.type = INPUT_KEYBOARD;
7587 				input.ki.wScan = c;
7588 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7589 				inputs ~= input;
7590 
7591 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7592 				inputs ~= input;
7593 			}
7594 
7595 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7596 				throw new WindowsApiException("SendInput", GetLastError());
7597 			}
7598 
7599 	}
7600 
7601 
7602 	// global hotkey helper function
7603 
7604 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
7605 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system {
7606 		__gshared int hotkeyId = 0;
7607 		int id = ++hotkeyId;
7608 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
7609 			throw new Exception("RegisterHotKey");
7610 
7611 		__gshared void delegate()[WPARAM][HWND] handlers;
7612 
7613 		handlers[window.impl.hwnd][id] = handler;
7614 
7615 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
7616 
7617 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7618 			switch(msg) {
7619 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
7620 				case WM_HOTKEY:
7621 					if(auto list = hwnd in handlers) {
7622 						if(auto h = wParam in *list) {
7623 							(*h)();
7624 							return 0;
7625 						}
7626 					}
7627 				goto default;
7628 				default:
7629 			}
7630 			if(oldHandler)
7631 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
7632 			return 1; // pass it on
7633 		};
7634 
7635 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
7636 			oldHandler = window.handleNativeEvent;
7637 			window.handleNativeEvent = nativeEventHandler;
7638 		}
7639 
7640 		return id;
7641 	}
7642 
7643 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
7644 	void unregisterHotKey(SimpleWindow window, int id) {
7645 		if(!UnregisterHotKey(window.impl.hwnd, id))
7646 			throw new WindowsApiException("UnregisterHotKey", GetLastError());
7647 	}
7648 }
7649 
7650 version (X11) {
7651 	//pragma(lib, "dl"); // already done by the standard compiler build and specifying it again messes up zig cross compile
7652 	import core.sys.posix.dlfcn;
7653 }
7654 
7655 /++
7656 	Allows for sending synthetic input to the X server via the Xtst
7657 	extension or on Windows using SendInput.
7658 
7659 	Please remember user input is meant to be user - don't use this
7660 	if you have some other alternative!
7661 
7662 	History:
7663 		Added May 17, 2020 with the X implementation.
7664 
7665 		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.)
7666 	Bugs:
7667 		All methods on OSX Cocoa will throw not yet implemented exceptions.
7668 +/
7669 struct SyntheticInput {
7670 	@disable this();
7671 
7672 	private int* refcount;
7673 
7674 	version(X11) {
7675 		private void* lib;
7676 
7677 		private extern(C) {
7678 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
7679 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
7680 		}
7681 	}
7682 
7683 	/// The dummy param must be 0.
7684 	this(int dummy) {
7685 		version(X11) {
7686 			lib = dlopen("libXtst.so", RTLD_NOW);
7687 			if(lib is null)
7688 				throw new Exception("cannot load xtest lib extension");
7689 			scope(failure)
7690 				dlclose(lib);
7691 
7692 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
7693 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
7694 
7695 			if(XTestFakeKeyEvent is null)
7696 				throw new Exception("No XTestFakeKeyEvent");
7697 			if(XTestFakeButtonEvent is null)
7698 				throw new Exception("No XTestFakeButtonEvent");
7699 		}
7700 
7701 		refcount = new int;
7702 		*refcount = 1;
7703 	}
7704 
7705 	this(this) {
7706 		if(refcount)
7707 			*refcount += 1;
7708 	}
7709 
7710 	~this() {
7711 		if(refcount) {
7712 			*refcount -= 1;
7713 			if(*refcount == 0)
7714 				// I commented this because if I close the lib before
7715 				// XCloseDisplay, it is liable to segfault... so just
7716 				// gonna keep it loaded if it is loaded, no big deal
7717 				// anyway.
7718 				{} // dlclose(lib);
7719 		}
7720 	}
7721 
7722 	/++
7723 		Simulates typing a string into the keyboard.
7724 
7725 		Bugs:
7726 			On X11, this ONLY works with basic ascii! On Windows, it can handle more.
7727 
7728 			Not implemented except on Windows and X11.
7729 	+/
7730 	void sendSyntheticInput(string s) {
7731 		version(Windows) {
7732 			INPUT[] inputs;
7733 			inputs.reserve(s.length * 2);
7734 
7735 			auto ei = GetMessageExtraInfo();
7736 
7737 			foreach(wchar c; s) {
7738 				INPUT input;
7739 				input.type = INPUT_KEYBOARD;
7740 				input.ki.wScan = c;
7741 				input.ki.dwFlags = KEYEVENTF_UNICODE;
7742 				input.ki.dwExtraInfo = ei;
7743 				inputs ~= input;
7744 
7745 				input.ki.dwFlags |= KEYEVENTF_KEYUP;
7746 				inputs ~= input;
7747 			}
7748 
7749 			if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
7750 				throw new WindowsApiException("SendInput", GetLastError());
7751 			}
7752 		} else version(X11) {
7753 			int delay = 0;
7754 			foreach(ch; s) {
7755 				pressKey(cast(Key) ch, true, delay);
7756 				pressKey(cast(Key) ch, false, delay);
7757 				delay += 5;
7758 			}
7759 		} else throw new NotYetImplementedException();
7760 	}
7761 
7762 	/++
7763 		Sends a fake press or release key event.
7764 
7765 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7766 
7767 		Bugs:
7768 			The `delay` parameter is not implemented yet on Windows.
7769 
7770 			Not implemented except on Windows and X11.
7771 	+/
7772 	void pressKey(Key key, bool pressed, int delay = 0) {
7773 		version(Windows) {
7774 			INPUT input;
7775 			input.type = INPUT_KEYBOARD;
7776 			input.ki.wVk = cast(ushort) key;
7777 
7778 			input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
7779 			input.ki.dwExtraInfo = GetMessageExtraInfo();
7780 
7781 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7782 				throw new WindowsApiException("SendInput", GetLastError());
7783 			}
7784 		} else version(X11) {
7785 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
7786 		} else throw new NotYetImplementedException();
7787 	}
7788 
7789 	/++
7790 		Sends a fake mouse button press or release event.
7791 
7792 		Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
7793 
7794 		`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
7795 
7796 		Bugs:
7797 			The `delay` parameter is not implemented yet on Windows.
7798 
7799 			The backButton and forwardButton will throw NotYetImplementedException on Windows.
7800 
7801 			All arguments will throw NotYetImplementedException on OSX Cocoa.
7802 	+/
7803 	void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
7804 		version(Windows) {
7805 			INPUT input;
7806 			input.type = INPUT_MOUSE;
7807 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7808 
7809 			// input.mi.mouseData for a wheel event
7810 
7811 			switch(button) {
7812 				case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
7813 				case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
7814 				case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
7815 				case MouseButton.wheelUp:
7816 				case MouseButton.wheelDown:
7817 					input.mi.dwFlags = MOUSEEVENTF_WHEEL;
7818 					input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
7819 				break;
7820 				case MouseButton.wheelLeft: throw new NotYetImplementedException();
7821 				case MouseButton.wheelRight: throw new NotYetImplementedException();
7822 				case MouseButton.backButton: throw new NotYetImplementedException();
7823 				case MouseButton.forwardButton: throw new NotYetImplementedException();
7824 				default:
7825 			}
7826 
7827 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7828 				throw new WindowsApiException("SendInput", GetLastError());
7829 			}
7830 		} else version(X11) {
7831 			int btn;
7832 
7833 			switch(button) {
7834 				case MouseButton.left: btn = 1; break;
7835 				case MouseButton.middle: btn = 2; break;
7836 				case MouseButton.right: btn = 3; break;
7837 				case MouseButton.wheelUp: btn = 4; break;
7838 				case MouseButton.wheelDown: btn = 5; break;
7839 				case MouseButton.wheelLeft: btn = 6; break;
7840 				case MouseButton.wheelRight: btn = 7; break;
7841 				case MouseButton.backButton: btn = 8; break;
7842 				case MouseButton.forwardButton: btn = 9; break;
7843 				default:
7844 			}
7845 
7846 			assert(btn);
7847 
7848 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
7849 		} else throw new NotYetImplementedException();
7850 	}
7851 
7852 	///
7853 	static void moveMouseArrowBy(int dx, int dy) {
7854 		version(Windows) {
7855 			INPUT input;
7856 			input.type = INPUT_MOUSE;
7857 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7858 			input.mi.dx = dx;
7859 			input.mi.dy = dy;
7860 			input.mi.dwFlags = MOUSEEVENTF_MOVE;
7861 
7862 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7863 				throw new WindowsApiException("SendInput", GetLastError());
7864 			}
7865 		} else version(X11) {
7866 			auto disp = XDisplayConnection.get();
7867 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
7868 			XFlush(disp);
7869 		} else throw new NotYetImplementedException();
7870 	}
7871 
7872 	///
7873 	static void moveMouseArrowTo(int x, int y) {
7874 		version(Windows) {
7875 			INPUT input;
7876 			input.type = INPUT_MOUSE;
7877 			input.mi.dwExtraInfo = GetMessageExtraInfo();
7878 			input.mi.dx = x;
7879 			input.mi.dy = y;
7880 			input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
7881 
7882 			if(SendInput(1, &input, INPUT.sizeof) != 1) {
7883 				throw new WindowsApiException("SendInput", GetLastError());
7884 			}
7885 		} else version(X11) {
7886 			auto disp = XDisplayConnection.get();
7887 			auto root = RootWindow(disp, DefaultScreen(disp));
7888 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
7889 			XFlush(disp);
7890 		} else throw new NotYetImplementedException();
7891 	}
7892 }
7893 
7894 
7895 
7896 /++
7897 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
7898 
7899 	See_Also:
7900 	$(LIST
7901 		*[ScreenPainter]
7902 		*[ScreenPainter.rasterOp]
7903 	)
7904 +/
7905 enum RasterOp {
7906 	normal, /// Replaces the pixel.
7907 	xor, /// Uses bitwise xor to draw.
7908 }
7909 
7910 // being phobos-free keeps the size WAY down
7911 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
7912 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
7913 package(arsd) const(wchar)* toWStringz(string s) {
7914 	wstring r;
7915 	foreach(dchar c; s)
7916 		r ~= c;
7917 	r ~= '\0';
7918 	return r.ptr;
7919 }
7920 private string[] split(in void[] a, char c) {
7921 		string[] ret;
7922 		size_t previous = 0;
7923 		foreach(i, char ch; cast(ubyte[]) a) {
7924 			if(ch == c) {
7925 				ret ~= cast(string) a[previous .. i];
7926 				previous = i + 1;
7927 			}
7928 		}
7929 		if(previous != a.length)
7930 			ret ~= cast(string) a[previous .. $];
7931 		return ret;
7932 	}
7933 
7934 version(without_opengl) {
7935 	enum OpenGlOptions {
7936 		no,
7937 	}
7938 } else {
7939 	/++
7940 		Determines if you want an OpenGL context created on the new window.
7941 
7942 
7943 		See more: [#topics-3d|in the 3d topic].
7944 
7945 		---
7946 		import arsd.simpledisplay;
7947 		void main() {
7948 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
7949 
7950 			// Set up the matrix
7951 			window.setAsCurrentOpenGlContext(); // make this window active
7952 
7953 			// This is called on each frame, we will draw our scene
7954 			window.redrawOpenGlScene = delegate() {
7955 
7956 			};
7957 
7958 			window.eventLoop(0);
7959 		}
7960 		---
7961 	+/
7962 	enum OpenGlOptions {
7963 		no, /// No OpenGL context is created
7964 		yes, /// Yes, create an OpenGL context
7965 	}
7966 
7967 	version(X11) {
7968 		static if (!SdpyIsUsingIVGLBinds) {
7969 
7970 
7971 			struct __GLXFBConfigRec {}
7972 			alias GLXFBConfig = __GLXFBConfigRec*;
7973 
7974 			//pragma(lib, "GL");
7975 			//pragma(lib, "GLU");
7976 			interface GLX {
7977 			extern(C) nothrow @nogc {
7978 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
7979 						const int *attrib_list);
7980 
7981 				 void glXCopyContext(Display *dpy, GLXContext src,
7982 						GLXContext dst, arch_ulong mask);
7983 
7984 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
7985 						GLXContext share_list, Bool direct);
7986 
7987 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
7988 						Pixmap pixmap);
7989 
7990 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
7991 
7992 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
7993 
7994 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
7995 						int attrib, int *value);
7996 
7997 				 GLXContext glXGetCurrentContext();
7998 
7999 				 GLXDrawable glXGetCurrentDrawable();
8000 
8001 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
8002 
8003 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
8004 						GLXContext ctx);
8005 
8006 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
8007 
8008 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
8009 
8010 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
8011 
8012 				 void glXUseXFont(Font font, int first, int count, int list_base);
8013 
8014 				 void glXWaitGL();
8015 
8016 				 void glXWaitX();
8017 
8018 
8019 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
8020 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
8021 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
8022 
8023 				char* glXQueryExtensionsString (Display*, int);
8024 				void* glXGetProcAddress (const(char)*);
8025 
8026 			}
8027 			}
8028 
8029 			version(OSX)
8030 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
8031 			else
8032 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
8033 			shared static this() {
8034 				glx.loadDynamicLibrary();
8035 			}
8036 
8037 			alias glbindGetProcAddress = glXGetProcAddress;
8038 		}
8039 	} else version(Windows) {
8040 		/* it is done below by interface GL */
8041 	} else
8042 		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.");
8043 }
8044 
8045 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
8046 alias Resizablity = Resizability;
8047 
8048 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
8049 enum Resizability {
8050 	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.
8051 	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.
8052 	/++
8053 		$(PITFALL
8054 			Planned for the future but not implemented.
8055 		)
8056 
8057 		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.
8058 
8059 		History:
8060 			Added November 11, 2022, but not yet implemented and may not be for some time.
8061 	+/
8062 	/*@__future*/ allowResizingMaintainingAspectRatio,
8063 	/++
8064 		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.
8065 
8066 		History:
8067 			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.
8068 
8069 			Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all.
8070 	+/
8071 	automaticallyScaleIfPossible,
8072 }
8073 /// ditto
8074 alias Resizeability = Resizability;
8075 
8076 
8077 /++
8078 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
8079 +/
8080 enum TextAlignment : uint {
8081 	Left = 0, ///
8082 	Center = 1, ///
8083 	Right = 2, ///
8084 
8085 	VerticalTop = 0, ///
8086 	VerticalCenter = 4, ///
8087 	VerticalBottom = 8, ///
8088 }
8089 
8090 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
8091 alias Rectangle = arsd.color.Rectangle;
8092 
8093 
8094 /++
8095 	Keyboard press and release events.
8096 +/
8097 struct KeyEvent {
8098 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
8099 	Key key;
8100 	ubyte hardwareCode; /// A platform and hardware specific code for the key
8101 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
8102 
8103 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
8104 
8105 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
8106 
8107 	SimpleWindow window; /// associated Window
8108 
8109 	/++
8110 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
8111 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
8112 		to predict if char events are actually coming..
8113 
8114 		Only available on X systems since this information is not given ahead of time elsewhere.
8115 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
8116 
8117 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
8118 		and potential quirks I'd recommend avoiding it.
8119 
8120 		History:
8121 			Added April 26, 2021 (dub v9.5)
8122 	+/
8123 	version(X11)
8124 		dchar[] charsPossible;
8125 
8126 	// convert key event to simplified string representation a-la emacs
8127 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
8128 		uint dpos = 0;
8129 		void put (const(char)[] s...) nothrow @trusted {
8130 			static if (growdest) {
8131 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
8132 			} else {
8133 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
8134 			}
8135 		}
8136 
8137 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
8138 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
8139 		}
8140 
8141 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
8142 
8143 		// put modifiers
8144 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
8145 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
8146 		putMod(ModifierState.alt, Key.Alt, "Alt+");
8147 		putMod(ModifierState.windows, Key.Shift, "Windows+");
8148 		putMod(ModifierState.shift, Key.Shift, "Shift+");
8149 
8150 		if (this.key) {
8151 			foreach (string kn; __traits(allMembers, Key)) {
8152 				if (this.key == __traits(getMember, Key, kn)) {
8153 					// HACK!
8154 					static if (kn == "N0") put("0");
8155 					else static if (kn == "N1") put("1");
8156 					else static if (kn == "N2") put("2");
8157 					else static if (kn == "N3") put("3");
8158 					else static if (kn == "N4") put("4");
8159 					else static if (kn == "N5") put("5");
8160 					else static if (kn == "N6") put("6");
8161 					else static if (kn == "N7") put("7");
8162 					else static if (kn == "N8") put("8");
8163 					else static if (kn == "N9") put("9");
8164 					else put(kn);
8165 					return dest[0..dpos];
8166 				}
8167 			}
8168 			put("Unknown");
8169 		} else {
8170 			if (dpos && dest[dpos-1] == '+') --dpos;
8171 		}
8172 		return dest[0..dpos];
8173 	}
8174 
8175 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
8176 
8177 	/** Parse string into key name with modifiers. It accepts things like:
8178 	 *
8179 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
8180 	 *
8181 	 * Ctrl+Win+1 -- windows style
8182 	 *
8183 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
8184 	 *
8185 	 * Ctrl Win 1 -- and space
8186 	 *
8187 	 * and even "Win + 1 + Ctrl".
8188 	 */
8189 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
8190 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
8191 
8192 		// remove trailing spaces
8193 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
8194 
8195 		// tokens delimited by blank, '+', or '-'
8196 		// null on eol
8197 		const(char)[] getToken () nothrow @trusted @nogc {
8198 			// remove leading spaces and delimiters
8199 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
8200 			if (name.length == 0) return null; // oops, no more tokens
8201 			// get token
8202 			size_t epos = 0;
8203 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
8204 			assert(epos > 0 && epos <= name.length);
8205 			auto res = name[0..epos];
8206 			name = name[epos..$];
8207 			return res;
8208 		}
8209 
8210 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
8211 			if (s0.length != s1.length) return false;
8212 			foreach (immutable ci, char c0; s0) {
8213 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
8214 				char c1 = s1[ci];
8215 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
8216 				if (c0 != c1) return false;
8217 			}
8218 			return true;
8219 		}
8220 
8221 		if (ignoreModsOut !is null) *ignoreModsOut = false;
8222 		if (updown !is null) *updown = -1;
8223 		KeyEvent res;
8224 		res.key = cast(Key)0; // just in case
8225 		const(char)[] tk, tkn; // last token
8226 		bool allowEmascStyle = true;
8227 		bool ignoreModifiers = false;
8228 		tokenloop: for (;;) {
8229 			tk = tkn;
8230 			tkn = getToken();
8231 			//k8: yay, i took "Bloody Mess" trait from Fallout!
8232 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
8233 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
8234 			if (allowEmascStyle && tkn.length != 0) {
8235 				if (tk.length == 1) {
8236 					char mdc = tk[0];
8237 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
8238 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
8239 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
8240 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
8241 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
8242 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
8243 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
8244 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
8245 				}
8246 			}
8247 			allowEmascStyle = false;
8248 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
8249 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
8250 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
8251 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
8252 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
8253 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
8254 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
8255 			if (tk.length == 0) continue;
8256 			// try key name
8257 			if (res.key == 0) {
8258 				// little hack
8259 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
8260 					final switch (tk[0]) {
8261 						case '0': tk = "N0"; break;
8262 						case '1': tk = "N1"; break;
8263 						case '2': tk = "N2"; break;
8264 						case '3': tk = "N3"; break;
8265 						case '4': tk = "N4"; break;
8266 						case '5': tk = "N5"; break;
8267 						case '6': tk = "N6"; break;
8268 						case '7': tk = "N7"; break;
8269 						case '8': tk = "N8"; break;
8270 						case '9': tk = "N9"; break;
8271 					}
8272 				}
8273 				foreach (string kn; __traits(allMembers, Key)) {
8274 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
8275 				}
8276 			}
8277 			// unknown or duplicate key name, get out of here
8278 			break;
8279 		}
8280 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
8281 		return res; // something
8282 	}
8283 
8284 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
8285 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
8286 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
8287 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
8288 		}
8289 		bool ignoreMods;
8290 		int updown;
8291 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
8292 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
8293 		if (this.key != ke.key) {
8294 			// things like "ctrl+alt" are complicated
8295 			uint tkm = this.modifierState&modmask;
8296 			uint kkm = ke.modifierState&modmask;
8297 			Key tk = this.key;
8298 			// ke
8299 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
8300 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
8301 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
8302 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
8303 			// this
8304 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
8305 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
8306 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
8307 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
8308 			return (tk == ke.key && tkm == kkm);
8309 		}
8310 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
8311 	}
8312 }
8313 
8314 /// Sets the application name.
8315 @property string ApplicationName(string name) {
8316 	return _applicationName = name;
8317 }
8318 
8319 string _applicationName;
8320 
8321 /// ditto
8322 @property string ApplicationName() {
8323 	if(_applicationName is null) {
8324 		import core.runtime;
8325 		return Runtime.args[0];
8326 	}
8327 	return _applicationName;
8328 }
8329 
8330 
8331 /// Type of a [MouseEvent].
8332 enum MouseEventType : int {
8333 	motion = 0, /// The mouse moved inside the window
8334 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
8335 	buttonReleased = 2, /// A mouse button was released
8336 }
8337 
8338 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
8339 /++
8340 	Listen for this on your event listeners if you are interested in mouse action.
8341 
8342 	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.
8343 
8344 	Examples:
8345 
8346 	This will draw boxes on the window with the mouse as you hold the left button.
8347 	---
8348 	import arsd.simpledisplay;
8349 
8350 	void main() {
8351 		auto window = new SimpleWindow();
8352 
8353 		window.eventLoop(0,
8354 			(MouseEvent ev) {
8355 				if(ev.modifierState & ModifierState.leftButtonDown) {
8356 					auto painter = window.draw();
8357 					painter.fillColor = Color.red;
8358 					painter.outlineColor = Color.black;
8359 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
8360 				}
8361 			}
8362 		);
8363 	}
8364 	---
8365 +/
8366 struct MouseEvent {
8367 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
8368 
8369 	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.
8370 	int y; /// Current Y position of the cursor when the event fired.
8371 
8372 	int dx; /// Change in X position since last report
8373 	int dy; /// Change in Y position since last report
8374 
8375 	MouseButton button; /// See [MouseButton]
8376 	int modifierState; /// See [ModifierState]
8377 
8378 	version(X11)
8379 		private Time timestamp;
8380 
8381 	/++
8382 		Returns true if this is a mouse wheel/touchpad scroll event.
8383 
8384 		History:
8385 			Added December 21, 2025
8386 	+/
8387 	bool isMouseWheel() const {
8388 		return button == MouseButton.wheelLeft || button == MouseButton.wheelRight || button == MouseButton.wheelUp || button == MouseButton.wheelDown;
8389 	}
8390 
8391 	/// Returns a linear representation of mouse button,
8392 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
8393 	///
8394 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
8395 	@property ubyte buttonLinear() const {
8396 		import core.bitop;
8397 		if(button == 0)
8398 			return 0;
8399 		return (bsf(button) + 1) & 0b1111;
8400 	}
8401 
8402 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
8403 
8404 	SimpleWindow window; /// The window in which the event happened.
8405 
8406 	Point globalCoordinates() {
8407 		Point p;
8408 		if(window is null)
8409 			throw new Exception("wtf");
8410 		static if(UsingSimpledisplayX11) {
8411 			Window child;
8412 			XTranslateCoordinates(
8413 				XDisplayConnection.get,
8414 				window.impl.window,
8415 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
8416 				x, y, &p.x, &p.y, &child);
8417 			return p;
8418 		} else version(Windows) {
8419 			POINT[1] points;
8420 			points[0].x = x;
8421 			points[0].y = y;
8422 			MapWindowPoints(
8423 				window.impl.hwnd,
8424 				null,
8425 				points.ptr,
8426 				points.length
8427 			);
8428 			p.x = points[0].x;
8429 			p.y = points[0].y;
8430 
8431 			return p;
8432 		} else version(OSXCocoa) {
8433 			auto rect = window.window.frame;
8434 			// FIXME: mapped right?
8435 			return Point(cast(int) rect.origin.x + x, cast(int) rect.origin.y + y);
8436 		} else version(Emscripten) {
8437 			throw new NotYetImplementedException();
8438 		} else static assert(0);
8439 	}
8440 
8441 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
8442 
8443 	/**
8444 	can contain emacs-like modifier prefix
8445 	case-insensitive names:
8446 		lmbX/leftX
8447 		rmbX/rightX
8448 		mmbX/middleX
8449 		wheelX
8450 		motion (no prefix allowed)
8451 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
8452 	*/
8453 	static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
8454 		if (str.length == 0) return false; // just in case
8455 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
8456 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
8457 		auto anchor = str;
8458 		uint mods = 0; // uint.max == any
8459 		// interesting bits in kmod
8460 		uint kmodmask =
8461 			ModifierState.shift|
8462 			ModifierState.ctrl|
8463 			ModifierState.alt|
8464 			ModifierState.windows|
8465 			ModifierState.leftButtonDown|
8466 			ModifierState.middleButtonDown|
8467 			ModifierState.rightButtonDown|
8468 			0;
8469 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
8470 		bool wasButtons = false;
8471 		while (str.length) {
8472 			if (str.ptr[0] <= ' ') {
8473 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
8474 				continue;
8475 			}
8476 			// one-letter modifier?
8477 			if (str.length >= 2 && str.ptr[1] == '-') {
8478 				switch (str.ptr[0]) {
8479 					case '*': // "any" modifier (cannot be undone)
8480 						mods = mods.max;
8481 						break;
8482 					case 'C': case 'c': // emacs "ctrl"
8483 						if (mods != mods.max) mods |= ModifierState.ctrl;
8484 						break;
8485 					case 'M': case 'm': // emacs "meta"
8486 						if (mods != mods.max) mods |= ModifierState.alt;
8487 						break;
8488 					case 'S': case 's': // emacs "shift"
8489 						if (mods != mods.max) mods |= ModifierState.shift;
8490 						break;
8491 					case 'H': case 'h': // emacs "hyper" (aka winkey)
8492 						if (mods != mods.max) mods |= ModifierState.windows;
8493 						break;
8494 					default:
8495 						return false; // unknown modifier
8496 				}
8497 				str = str[2..$];
8498 				continue;
8499 			}
8500 			// word
8501 			char[16] buf = void; // locased
8502 			auto wep = 0;
8503 			while (str.length) {
8504 				immutable char ch = str.ptr[0];
8505 				if (ch <= ' ' || ch == '-') break;
8506 				str = str[1..$];
8507 				if (wep > buf.length) return false; // too long
8508 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8509 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8510 				else return false; // invalid char
8511 			}
8512 			if (wep == 0) return false; // just in case
8513 			uint bnum;
8514 			enum UpDown { None = -1, Up, Down, Any }
8515 			auto updown = UpDown.None; // 0: up; 1: down
8516 			switch (buf[0..wep]) {
8517 				// left button
8518 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
8519 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
8520 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
8521 				case "lmb": case "left": bnum = 0; break;
8522 				// middle button
8523 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
8524 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
8525 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
8526 				case "mmb": case "middle": bnum = 1; break;
8527 				// right button
8528 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
8529 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
8530 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
8531 				case "rmb": case "right": bnum = 2; break;
8532 				// wheel
8533 				case "wheelup": updown = UpDown.Up; goto case "wheel";
8534 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
8535 				case "wheelany": updown = UpDown.Any; goto case "wheel";
8536 				case "wheel": bnum = 3; break;
8537 				// motion
8538 				case "motion": bnum = 7; break;
8539 				// unknown
8540 				default: return false;
8541 			}
8542 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8543 			// parse possible "-up" or "-down"
8544 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
8545 				wep = 0;
8546 				foreach (immutable idx, immutable char ch; str[1..$]) {
8547 					if (ch <= ' ' || ch == '-') break;
8548 					assert(idx == wep); // for now; trick
8549 					if (wep > buf.length) { wep = 0; break; } // too long
8550 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
8551 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
8552 					else { wep = 0; break; } // invalid char
8553 				}
8554 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
8555 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
8556 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
8557 				// remove parsed part
8558 				if (updown != UpDown.None) str = str[wep+1..$];
8559 			}
8560 			if (updown == UpDown.None) {
8561 				updown = UpDown.Down;
8562 			}
8563 			wasButtons = wasButtons || (bnum <= 2);
8564 			//assert(updown != UpDown.None);
8565 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
8566 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
8567 			if (lastButt != lastButt.max) {
8568 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
8569 				if (mods != mods.max) {
8570 					uint butbit = 0;
8571 					final switch (lastButt&0x03) {
8572 						case 0: butbit = ModifierState.leftButtonDown; break;
8573 						case 1: butbit = ModifierState.middleButtonDown; break;
8574 						case 2: butbit = ModifierState.rightButtonDown; break;
8575 					}
8576 					     if (lastButt&Flag.Down) mods |= butbit;
8577 					else if (lastButt&Flag.Up) mods &= ~butbit;
8578 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
8579 				}
8580 			}
8581 			// remember last button
8582 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
8583 		}
8584 		// no button -- nothing to do
8585 		if (lastButt == lastButt.max) return false;
8586 		// done parsing, check if something's left
8587 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
8588 		// remove action button from mask
8589 		if ((lastButt&0xff) < 3) {
8590 			final switch (lastButt&0x03) {
8591 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
8592 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
8593 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
8594 			}
8595 		}
8596 		// special case: "Motion" means "ignore buttons"
8597 		if ((lastButt&0xff) == 7 && !wasButtons) {
8598 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
8599 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
8600 		}
8601 		uint kmod = event.modifierState&kmodmask;
8602 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
8603 		// check modifier state
8604 		if (mods != mods.max) {
8605 			if (kmod != mods) return false;
8606 		}
8607 		// now check type
8608 		if ((lastButt&0xff) == 7) {
8609 			// motion
8610 			if (event.type != MouseEventType.motion) return false;
8611 		} else if ((lastButt&0xff) == 3) {
8612 			// wheel
8613 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
8614 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
8615 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
8616 			return false;
8617 		} else {
8618 			// buttons
8619 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
8620 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
8621 			{
8622 				return false;
8623 			}
8624 			// button number
8625 			switch (lastButt&0x03) {
8626 				case 0: if (event.button != MouseButton.left) return false; break;
8627 				case 1: if (event.button != MouseButton.middle) return false; break;
8628 				case 2: if (event.button != MouseButton.right) return false; break;
8629 				default: return false;
8630 			}
8631 		}
8632 		return true;
8633 	}
8634 }
8635 
8636 version(arsd_mevent_strcmp_test) unittest {
8637 	MouseEvent event;
8638 	event.type = MouseEventType.buttonPressed;
8639 	event.button = MouseButton.left;
8640 	event.modifierState = ModifierState.ctrl;
8641 	assert(event == "C-LMB");
8642 	assert(event != "C-LMBUP");
8643 	assert(event != "C-LMB-UP");
8644 	assert(event != "C-S-LMB");
8645 	assert(event == "*-LMB");
8646 	assert(event != "*-LMB-UP");
8647 
8648 	event.type = MouseEventType.buttonReleased;
8649 	assert(event != "C-LMB");
8650 	assert(event == "C-LMBUP");
8651 	assert(event == "C-LMB-UP");
8652 	assert(event != "C-S-LMB");
8653 	assert(event != "*-LMB");
8654 	assert(event == "*-LMB-UP");
8655 
8656 	event.button = MouseButton.right;
8657 	event.modifierState |= ModifierState.shift;
8658 	event.type = MouseEventType.buttonPressed;
8659 	assert(event != "C-LMB");
8660 	assert(event != "C-LMBUP");
8661 	assert(event != "C-LMB-UP");
8662 	assert(event != "C-S-LMB");
8663 	assert(event != "*-LMB");
8664 	assert(event != "*-LMB-UP");
8665 
8666 	assert(event != "C-RMB");
8667 	assert(event != "C-RMBUP");
8668 	assert(event != "C-RMB-UP");
8669 	assert(event == "C-S-RMB");
8670 	assert(event == "*-RMB");
8671 	assert(event != "*-RMB-UP");
8672 }
8673 
8674 /// This gives a few more options to drawing lines and such
8675 struct Pen {
8676 	Color color; /// the foreground color
8677 	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.
8678 	Style style; /// See [Style]
8679 /+
8680 // From X.h
8681 
8682 #define LineSolid		0
8683 #define LineOnOffDash		1
8684 #define LineDoubleDash		2
8685        LineDou-        The full path of the line is drawn, but the
8686        bleDash         even dashes are filled differently from the
8687                        odd dashes (see fill-style) with CapButt
8688                        style used where even and odd dashes meet.
8689 
8690 
8691 
8692 /* capStyle */
8693 
8694 #define CapNotLast		0
8695 #define CapButt			1
8696 #define CapRound		2
8697 #define CapProjecting		3
8698 
8699 /* joinStyle */
8700 
8701 #define JoinMiter		0
8702 #define JoinRound		1
8703 #define JoinBevel		2
8704 
8705 /* fillStyle */
8706 
8707 #define FillSolid		0
8708 #define FillTiled		1
8709 #define FillStippled		2
8710 #define FillOpaqueStippled	3
8711 
8712 
8713 +/
8714 	/// Style of lines drawn
8715 	enum Style {
8716 		Solid, /// a solid line
8717 		Dashed, /// a dashed line
8718 		Dotted, /// a dotted line
8719 	}
8720 }
8721 
8722 
8723 /++
8724 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
8725 
8726 	You can load an image with an alpha channel, but you cannot draw that in the current implementation. If you want alpha blending when drawing, use [Sprite] instead.
8727 
8728 
8729 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
8730 
8731 	$(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.)
8732 
8733 	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.
8734 
8735 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
8736 
8737 	$(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.
8738 
8739 	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!
8740 
8741 	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!)
8742 
8743 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
8744 
8745 	---
8746 		auto image = new Image(256, 256);
8747 		scope(exit) destroy(image);
8748 	---
8749 
8750 	As long as you don't hold on to it outside the scope.
8751 
8752 	I might change it to be an owned pointer at some point in the future.
8753 
8754 	)
8755 
8756 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
8757 	you can also often get a fair amount of speedup by getting the raw data format and
8758 	writing some custom code.
8759 
8760 	FIXME INSERT EXAMPLES HERE
8761 
8762 
8763 +/
8764 final class Image {
8765 	///
8766 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
8767 		this.width = width;
8768 		this.height = height;
8769 		this.enableAlpha = enableAlpha;
8770 
8771 		impl.createImage(width, height, forcexshm, enableAlpha);
8772 	}
8773 
8774 	///
8775 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
8776 		this(size.width, size.height, forcexshm, enableAlpha);
8777 	}
8778 
8779 	private bool suppressDestruction;
8780 
8781 	version(X11)
8782 	this(XImage* handle) {
8783 		this.handle = handle;
8784 		this.rawData = cast(ubyte*) handle.data;
8785 		this.width = handle.width;
8786 		this.height = handle.height;
8787 		this.enableAlpha = handle.depth == 32;
8788 		suppressDestruction = true;
8789 	}
8790 
8791 	~this() {
8792 		if(suppressDestruction) return;
8793 		impl.dispose();
8794 	}
8795 
8796 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
8797 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
8798 	pure const @system nothrow {
8799 		/*
8800 			To use these to draw a blue rectangle with size WxH at position X,Y...
8801 
8802 			// make certain that it will fit before we proceed
8803 			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!
8804 
8805 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
8806 			// (though calculating them isn't really that expensive).
8807 			auto nextLineAdjustment = img.adjustmentForNextLine();
8808 			auto offR = img.redByteOffset();
8809 			auto offB = img.blueByteOffset();
8810 			auto offG = img.greenByteOffset();
8811 			auto bpp = img.bytesPerPixel();
8812 
8813 			auto data = img.getDataPointer();
8814 
8815 			// figure out the starting byte offset
8816 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
8817 
8818 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
8819 
8820 			// and now our drawing loop for the rectangle
8821 			foreach(y; 0 .. H) {
8822 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
8823 				foreach(x; 0 .. W) {
8824 					// write our color
8825 					data[offR] = 0;
8826 					data[offG] = 0;
8827 					data[offB] = 255;
8828 
8829 					data += bpp; // moving to the next pixel is just an addition...
8830 				}
8831 				startOfLine += nextLineAdjustment;
8832 			}
8833 
8834 
8835 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
8836 
8837 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
8838 			can be made into a bitmask or something so we can write them as *uint...
8839 		*/
8840 
8841 		///
8842 		int offsetForTopLeftPixel() {
8843 			version(X11) {
8844 				return 0;
8845 			} else version(Windows) {
8846 				if(enableAlpha) {
8847 					return (width * 4) * (height - 1);
8848 				} else {
8849 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
8850 				}
8851 			} else version(OSXCocoa) {
8852 				return 0 ; //throw new NotYetImplementedException();
8853 			} else version(Emscripten) {
8854 				return 0;
8855 			} else static assert(0, "fill in this info for other OSes");
8856 		}
8857 
8858 		///
8859 		int offsetForPixel(int x, int y) {
8860 			version(X11) {
8861 				auto offset = (y * width + x) * 4;
8862 				return offset;
8863 			} else version(Windows) {
8864 				if(enableAlpha) {
8865 					auto itemsPerLine = width * 4;
8866 					// remember, bmps are upside down
8867 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
8868 					return offset;
8869 				} else {
8870 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8871 					// remember, bmps are upside down
8872 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
8873 					return offset;
8874 				}
8875 			} else version(OSXCocoa) {
8876 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8877 			} else version(Emscripten) {
8878 				return (y * width + x) * 4 ; //throw new NotYetImplementedException();
8879 			} else static assert(0, "fill in this info for other OSes");
8880 		}
8881 
8882 		///
8883 		int adjustmentForNextLine() {
8884 			version(X11) {
8885 				return width * 4;
8886 			} else version(Windows) {
8887 				// windows bmps are upside down, so the adjustment is actually negative
8888 				if(enableAlpha)
8889 					return - (cast(int) width * 4);
8890 				else
8891 					return -((cast(int) width * 3 + 3) / 4) * 4;
8892 			} else version(OSXCocoa) {
8893 				return width * 4 ; //throw new NotYetImplementedException();
8894 			} else version(Emscripten) {
8895 				return width * 4 ; //throw new NotYetImplementedException();
8896 			} else static assert(0, "fill in this info for other OSes");
8897 		}
8898 
8899 		/// once you have the position of a pixel, use these to get to the proper color
8900 		int redByteOffset() {
8901 			version(X11) {
8902 				return 2;
8903 			} else version(Windows) {
8904 				return 2;
8905 			} else version(OSXCocoa) {
8906 				return 2 ; //throw new NotYetImplementedException();
8907 			} else version(Emscripten) {
8908 				return 2 ; //throw new NotYetImplementedException();
8909 			} else static assert(0, "fill in this info for other OSes");
8910 		}
8911 
8912 		///
8913 		int greenByteOffset() {
8914 			version(X11) {
8915 				return 1;
8916 			} else version(Windows) {
8917 				return 1;
8918 			} else version(OSXCocoa) {
8919 				return 1 ; //throw new NotYetImplementedException();
8920 			} else version(Emscripten) {
8921 				return 1 ; //throw new NotYetImplementedException();
8922 			} else static assert(0, "fill in this info for other OSes");
8923 		}
8924 
8925 		///
8926 		int blueByteOffset() {
8927 			version(X11) {
8928 				return 0;
8929 			} else version(Windows) {
8930 				return 0;
8931 			} else version(OSXCocoa) {
8932 				return 0 ; //throw new NotYetImplementedException();
8933 			} else version(Emscripten) {
8934 				return 0 ; //throw new NotYetImplementedException();
8935 			} else static assert(0, "fill in this info for other OSes");
8936 		}
8937 
8938 		/// Only valid if [enableAlpha] is true
8939 		int alphaByteOffset() {
8940 			version(X11) {
8941 				return 3;
8942 			} else version(Windows) {
8943 				return 3;
8944 			} else version(OSXCocoa) {
8945 				return 3; //throw new NotYetImplementedException();
8946 			} else version(Emscripten) {
8947 				return 3 ; //throw new NotYetImplementedException();
8948 			} else static assert(0, "fill in this info for other OSes");
8949 		}
8950 	}
8951 
8952 	///
8953 	final void putPixel(int x, int y, Color c) {
8954 		if(x < 0 || x >= width)
8955 			return;
8956 		if(y < 0 || y >= height)
8957 			return;
8958 
8959 		impl.setPixel(x, y, c);
8960 	}
8961 
8962 	///
8963 	final Color getPixel(int x, int y) {
8964 		if(x < 0 || x >= width)
8965 			return Color.transparent;
8966 		if(y < 0 || y >= height)
8967 			return Color.transparent;
8968 
8969 		version(OSXCocoa) throw new NotYetImplementedException(); else
8970 		return impl.getPixel(x, y);
8971 	}
8972 
8973 	///
8974 	final void opIndexAssign(Color c, int x, int y) {
8975 		putPixel(x, y, c);
8976 	}
8977 
8978 	///
8979 	TrueColorImage toTrueColorImage() {
8980 		auto tci = new TrueColorImage(width, height);
8981 		convertToRgbaBytes(tci.imageData.bytes);
8982 		return tci;
8983 	}
8984 
8985 	///
8986 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) {
8987 		auto tci = i.getAsTrueColorImage();
8988 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
8989 		static if(UsingSimpledisplayX11)
8990 			img.premultiply = premultiply;
8991 		img.setRgbaBytes(tci.imageData.bytes);
8992 		return img;
8993 	}
8994 
8995 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
8996 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
8997 	/// if you pass null, it will allocate a new one.
8998 	ubyte[] getRgbaBytes(ubyte[] where = null) {
8999 		if(where is null)
9000 			where = new ubyte[this.width*this.height*4];
9001 		convertToRgbaBytes(where);
9002 		return where;
9003 	}
9004 
9005 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
9006 	void setRgbaBytes(in ubyte[] from ) {
9007 		assert(from.length == this.width * this.height * 4);
9008 		setFromRgbaBytes(from);
9009 	}
9010 
9011 	// FIXME: make properly cross platform by getting rgba right
9012 
9013 	/// warning: this is not portable across platforms because the data format can change
9014 	ubyte* getDataPointer() {
9015 		return impl.rawData;
9016 	}
9017 
9018 	/// for use with getDataPointer
9019 	final int bytesPerLine() const pure @safe nothrow {
9020 		version(Windows)
9021 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
9022 		else version(X11)
9023 			return 4 * width;
9024 		else version(OSXCocoa)
9025 			return 4 * width;
9026 		else static assert(0);
9027 	}
9028 
9029 	/// for use with getDataPointer
9030 	final int bytesPerPixel() const pure @safe nothrow {
9031 		version(Windows)
9032 			return enableAlpha ? 4 : 3;
9033 		else version(X11)
9034 			return 4;
9035 		else version(OSXCocoa)
9036 			return 4;
9037 		else static assert(0);
9038 	}
9039 
9040 	///
9041 	immutable int width;
9042 
9043 	///
9044 	immutable int height;
9045 
9046 	///
9047 	immutable bool enableAlpha;
9048     //private:
9049 	mixin NativeImageImplementation!() impl;
9050 }
9051 
9052 /++
9053 	A convenience function to pop up a window displaying the image.
9054 	If you pass a win, it will draw the image in it. Otherwise, it will
9055 	create a window with the size of the image and run its event loop, closing
9056 	when a key is pressed.
9057 
9058 	History:
9059 		`BlockingMode` parameter added on December 8, 2021. Previously, it would
9060 		always block until the application quit which could cause bizarre behavior
9061 		inside a more complex application. Now, the default is to block until
9062 		this window closes if it is the only event loop running, and otherwise,
9063 		not to block at all and just pop up the display window asynchronously.
9064 +/
9065 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) {
9066 	if(win is null) {
9067 		win = new SimpleWindow(image);
9068 		{
9069 			auto p = win.draw;
9070 			p.drawImage(Point(0, 0), image);
9071 		}
9072 		win.eventLoopWithBlockingMode(
9073 			bm, 0,
9074 			(KeyEvent ev) {
9075 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
9076 			} );
9077 	} else {
9078 		win.image = image;
9079 	}
9080 }
9081 
9082 enum FontWeight : int {
9083 	dontcare = 0,
9084 	thin = 100,
9085 	extralight = 200,
9086 	light = 300,
9087 	regular = 400,
9088 	medium = 500,
9089 	semibold = 600,
9090 	bold = 700,
9091 	extrabold = 800,
9092 	heavy = 900
9093 }
9094 
9095 /++
9096 	Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont].
9097 
9098 	History:
9099 		Added October 24, 2022. The methods were already on [OperatingSystemFont] before that.
9100 +/
9101 interface MeasurableFont {
9102 	/++
9103 		History:
9104 			Added April 12, 2025
9105 	+/
9106 	//version(OSXCocoa)
9107 		alias fnum = float;
9108 	//else
9109 		//alias fnum = int;
9110 
9111 
9112 	/++
9113 		Returns true if it is a monospace font, meaning each of the
9114 		glyphs (at least the ascii characters) have matching width
9115 		and no kerning, so you can determine the display width of some
9116 		strings by simply multiplying the string width by [averageWidth].
9117 
9118 		(Please note that multiply doesn't $(I actually) work in general,
9119 		consider characters like tab and newline, but it does sometimes.)
9120 	+/
9121 	bool isMonospace();
9122 
9123 	/++
9124 		The average width of glyphs in the font, traditionally equal to the
9125 		width of the lowercase x. Can be used to estimate bounding boxes,
9126 		especially if the font [isMonospace].
9127 
9128 		Given in pixels.
9129 	+/
9130 	fnum averageWidth();
9131 	/++
9132 		The height of the bounding box of a line.
9133 	+/
9134 	fnum height();
9135 	/++
9136 		The maximum ascent of a glyph above the baseline.
9137 
9138 		Given in pixels.
9139 	+/
9140 	fnum ascent();
9141 	/++
9142 		The maximum descent of a glyph below the baseline. For example, how low the g might go.
9143 
9144 		Given in pixels.
9145 	+/
9146 	fnum descent();
9147 	/++
9148 		The display width of the given string, and if you provide a window, it will use it to
9149 		make the pixel count on screen more accurate too, but this shouldn't generally be necessary.
9150 
9151 		Given in pixels.
9152 	+/
9153 	fnum stringWidth(scope const(char)[] s, SimpleWindow window = null);
9154 
9155 }
9156 
9157 int castFnumToCnum(MeasurableFont.fnum i) {
9158 	static if(is(MeasurableFont.fnum : long))
9159 		return cast(int) i;
9160 	else
9161 		return cast(int) (i + 0.9);
9162 }
9163 
9164 // FIXME: i need a font cache and it needs to handle disconnects.
9165 
9166 /++
9167 	Represents a font loaded off the operating system or the X server.
9168 
9169 
9170 	While the api here is unified cross platform, the fonts are not necessarily
9171 	available, even across machines of the same platform, so be sure to always check
9172 	for null (using [isNull]) and have a fallback plan.
9173 
9174 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
9175 
9176 	Worst case, a null font will automatically fall back to the default font loaded
9177 	for your system.
9178 +/
9179 class OperatingSystemFont : MeasurableFont {
9180 	// FIXME: when the X Connection is lost, these need to be invalidated!
9181 	// that means I need to store the original stuff again to reconstruct it too.
9182 
9183 	version(Emscripten) {
9184 		void* font;
9185 	} else version(X11) {
9186 		XFontStruct* font;
9187 		XFontSet fontset;
9188 
9189 		version(with_xft) {
9190 			XftFont* xftFont;
9191 			bool isXft;
9192 		}
9193 	} else version(Windows) {
9194 		HFONT font;
9195 		int width_;
9196 		int height_;
9197 	} else version(OSXCocoa) {
9198 		NSFont font;
9199 	} else static assert(0);
9200 
9201 	/++
9202 		Constructs the class and immediately calls [load].
9203 	+/
9204 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9205 		load(name, size, weight, italic);
9206 	}
9207 
9208 	/++
9209 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
9210 
9211 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
9212 
9213 		History:
9214 			Added January 24, 2021.
9215 	+/
9216 	this() {
9217 		// this space intentionally left blank
9218 	}
9219 
9220 	/++
9221 		Constructs a copy of the given font object.
9222 
9223 		History:
9224 			Added January 7, 2023.
9225 	+/
9226 	this(OperatingSystemFont font) {
9227 		if(font is null || font.loadedInfo is LoadedInfo.init)
9228 			loadDefault();
9229 		else
9230 			load(font.loadedInfo.tupleof);
9231 	}
9232 
9233 	/++
9234 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
9235 
9236 		History:
9237 			Added November 13, 2020.
9238 	+/
9239 	version(with_xft)
9240 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9241 		unload();
9242 
9243 		if(!XftLibrary.attempted) {
9244 			XftLibrary.loadDynamicLibrary();
9245 		}
9246 
9247 		if(!XftLibrary.loadSuccessful)
9248 			return false;
9249 
9250 		auto display = XDisplayConnection.get;
9251 
9252 		char[256] nameBuffer = void;
9253 		int nbp = 0;
9254 
9255 		void add(in char[] a) {
9256 			nameBuffer[nbp .. nbp + a.length] = a[];
9257 			nbp += a.length;
9258 		}
9259 		add(name);
9260 
9261 		if(size) {
9262 			add(":size=");
9263 			add(toInternal!string(size));
9264 		}
9265 		if(weight != FontWeight.dontcare && weight != 400) {
9266 			if(weight < 400)
9267 				add(":style=Light");
9268 			else
9269 				add(":style=Bold");
9270 			add(":weight=");
9271 			add(weightToString(weight));
9272 		}
9273 		if(italic) {
9274 			if(weight == FontWeight.dontcare)
9275 				add(":style=Italic");
9276 			add(":slant=100");
9277 		}
9278 
9279 		nameBuffer[nbp] = 0;
9280 
9281 		this.xftFont = XftFontOpenName(
9282 			display,
9283 			DefaultScreen(display),
9284 			nameBuffer.ptr
9285 		);
9286 
9287 		this.isXft = true;
9288 
9289 		if(xftFont !is null) {
9290 			isMonospace_ = stringWidth("x") == stringWidth("M");
9291 			ascent_ = xftFont.ascent;
9292 			descent_ = xftFont.descent;
9293 		}
9294 
9295 		return !isNull();
9296 	}
9297 
9298 	/++
9299 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
9300 
9301 
9302 		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.
9303 
9304 		If `pattern` is null, it returns all available font families.
9305 
9306 		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.
9307 
9308 		The format of the pattern is platform-specific.
9309 
9310 		History:
9311 			Added May 1, 2021 (dub v9.5)
9312 	+/
9313 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
9314 		version(Windows) {
9315 			auto hdc = GetDC(null);
9316 			scope(exit) ReleaseDC(null, hdc);
9317 			LOGFONT logfont;
9318 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
9319 				auto localHandler = *(cast(typeof(handler)*) p);
9320 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
9321 			}
9322 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
9323 		} else version(X11) {
9324 			//import core.stdc.stdio;
9325 			bool done = false;
9326 			version(with_xft) {
9327 				if(!XftLibrary.attempted) {
9328 					XftLibrary.loadDynamicLibrary();
9329 				}
9330 
9331 				if(!XftLibrary.loadSuccessful)
9332 					goto skipXft;
9333 
9334 				if(!FontConfigLibrary.attempted)
9335 					FontConfigLibrary.loadDynamicLibrary();
9336 				if(!FontConfigLibrary.loadSuccessful)
9337 					goto skipXft;
9338 
9339 				{
9340 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
9341 					if(got is null)
9342 						goto skipXft;
9343 					scope(exit) FcFontSetDestroy(got);
9344 
9345 					auto fontPatterns = got.fonts[0 .. got.nfont];
9346 					foreach(candidate; fontPatterns) {
9347 						char* where, whereStyle;
9348 
9349 						char* pmg = FcNameUnparse(candidate);
9350 
9351 						//FcPatternGetString(candidate, "family", 0, &where);
9352 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
9353 						//if(where && whereStyle) {
9354 						if(pmg) {
9355 							if(!handler(pmg.sliceCString))
9356 								return;
9357 							//printf("%s || %s %s\n", pmg, where, whereStyle);
9358 						}
9359 					}
9360 				}
9361 			}
9362 
9363 			skipXft:
9364 
9365 			if(pattern is null)
9366 				pattern = "*";
9367 
9368 			int count;
9369 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
9370 			scope(exit) XFreeFontNames(coreFontsRaw);
9371 
9372 			auto coreFonts = coreFontsRaw[0 .. count];
9373 
9374 			foreach(font; coreFonts) {
9375 				char[128] tmp;
9376 				tmp[0 ..5] = "core:";
9377 				auto cf = font.sliceCString;
9378 				if(5 + cf.length > tmp.length)
9379 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
9380 				tmp[5 .. 5 + cf.length] = cf;
9381 				if(!handler(tmp[0 .. 5 + cf.length]))
9382 					return;
9383 			}
9384 		}
9385 	}
9386 
9387 	/++
9388 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
9389 		to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega].
9390 
9391 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
9392 		underlying system doesn't support returning the raw bytes.
9393 
9394 		History:
9395 			Added September 10, 2021 (dub v10.3)
9396 	+/
9397 	ubyte[] getTtfBytes() {
9398 		if(isNull)
9399 			return null;
9400 
9401 		version(Windows) {
9402 			auto dc = GetDC(null);
9403 			auto orig = SelectObject(dc, font);
9404 
9405 			scope(exit) {
9406 				SelectObject(dc, orig);
9407 				ReleaseDC(null, dc);
9408 			}
9409 
9410 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
9411 			if(res == GDI_ERROR)
9412 				return null;
9413 
9414 			ubyte[] buffer = new ubyte[](res);
9415 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
9416 			if(res == GDI_ERROR)
9417 				return null; // wtf really tbh
9418 
9419 			return buffer;
9420 		} else version(with_xft) {
9421 			if(isXft && xftFont) {
9422 				if(!FontConfigLibrary.attempted)
9423 					FontConfigLibrary.loadDynamicLibrary();
9424 				if(!FontConfigLibrary.loadSuccessful)
9425 					return null;
9426 
9427 				char* file;
9428 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
9429 					if (file !is null && file[0]) {
9430 						import core.stdc.stdio;
9431 						auto fp = fopen(file, "rb");
9432 						if(fp is null)
9433 							return null;
9434 						scope(exit)
9435 							fclose(fp);
9436 						fseek(fp, 0, SEEK_END);
9437 						ubyte[] buffer = new ubyte[](ftell(fp));
9438 						fseek(fp, 0, SEEK_SET);
9439 
9440 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
9441 						if(got != buffer.length)
9442 							return null;
9443 
9444 						return buffer;
9445 					}
9446 				}
9447 			}
9448 			return null;
9449 		} else throw new NotYetImplementedException();
9450 	}
9451 
9452 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
9453 
9454 	private string weightToString(FontWeight weight) {
9455 		with(FontWeight)
9456 		final switch(weight) {
9457 			case dontcare: return "*";
9458 			case thin: return "extralight";
9459 			case extralight: return "extralight";
9460 			case light: return "light";
9461 			case regular: return "regular";
9462 			case medium: return "medium";
9463 			case semibold: return "demibold";
9464 			case bold: return "bold";
9465 			case extrabold: return "demibold";
9466 			case heavy: return "black";
9467 		}
9468 	}
9469 
9470 	/++
9471 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
9472 
9473 		History:
9474 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9475 	+/
9476 	version(X11)
9477 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9478 		unload();
9479 
9480 		string xfontstr;
9481 
9482 		if(name.length > 3 && name[0 .. 3] == "-*-") {
9483 			// this is kinda a disgusting hack but if the user sends an exact
9484 			// string I'd like to honor it...
9485 			xfontstr = name;
9486 		} else {
9487 			string weightstr = weightToString(weight);
9488 			string sizestr;
9489 			if(size == 0)
9490 				sizestr = "*";
9491 			else
9492 				sizestr = toInternal!string(size);
9493 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
9494 		}
9495 
9496 		// writeln(xfontstr);
9497 
9498 		auto display = XDisplayConnection.get;
9499 
9500 		font = XLoadQueryFont(display, xfontstr.ptr);
9501 		if(font is null)
9502 			return false;
9503 
9504 		char** lol;
9505 		int lol2;
9506 		char* lol3;
9507 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
9508 
9509 		prepareFontInfo();
9510 
9511 		return !isNull();
9512 	}
9513 
9514 	version(X11)
9515 	private void prepareFontInfo() {
9516 		if(font !is null) {
9517 			isMonospace_ = stringWidth("l") == stringWidth("M");
9518 			ascent_ = font.max_bounds.ascent;
9519 			descent_ = font.max_bounds.descent;
9520 		}
9521 	}
9522 
9523 	version(OSXCocoa)
9524 	private void prepareFontInfo() {
9525 		if(font !is null) {
9526 			isMonospace_ = font.isFixedPitch;
9527 			ascent_ = cast(int) font.ascender;
9528 			descent_ = cast(int) - font.descender;
9529 		}
9530 	}
9531 
9532 
9533 	/++
9534 		Loads a Windows font. You probably want to use [load] instead to be more generic.
9535 
9536 		History:
9537 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
9538 	+/
9539 	version(Windows)
9540 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
9541 		unload();
9542 
9543 		WCharzBuffer buffer = WCharzBuffer(name);
9544 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
9545 
9546 		prepareFontInfo(hdc);
9547 
9548 		return !isNull();
9549 	}
9550 
9551 	version(Windows)
9552 	void prepareFontInfo(HDC hdc = null) {
9553 		if(font is null)
9554 			return;
9555 
9556 		TEXTMETRIC tm;
9557 		auto dc = hdc ? hdc : GetDC(null);
9558 		auto orig = SelectObject(dc, font);
9559 		GetTextMetrics(dc, &tm);
9560 		SelectObject(dc, orig);
9561 		if(hdc is null)
9562 			ReleaseDC(null, dc);
9563 
9564 		width_ = tm.tmAveCharWidth;
9565 		height_ = tm.tmHeight;
9566 		ascent_ = tm.tmAscent;
9567 		descent_ = tm.tmDescent;
9568 		// 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.
9569 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
9570 	}
9571 
9572 
9573 	/++
9574 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
9575 
9576 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
9577 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
9578 
9579 		On Windows, it forwards directly to [loadWin32].
9580 
9581 		Params:
9582 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
9583 			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.
9584 			weight = approximate boldness, results may vary.
9585 			italic = try to get a slanted version of the given font.
9586 
9587 		History:
9588 			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.
9589 	+/
9590 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
9591 		this.loadedInfo = LoadedInfo(name, size, weight, italic);
9592 		version(X11) {
9593 			version(with_xft) {
9594 				if(name.length > 5 && name[0 .. 5] == "core:") {
9595 					goto core;
9596 				}
9597 
9598 				if(loadXft(name, size, weight, italic))
9599 					return true;
9600 				// if xft fails, fallback to core to avoid breaking
9601 				// code that already depended on this.
9602 			}
9603 
9604 			core:
9605 
9606 			if(name.length > 5 && name[0 .. 5] == "core:") {
9607 				name = name[5 .. $];
9608 			}
9609 
9610 			return loadCoreX(name, size, weight, italic);
9611 		} else version(Windows) {
9612 			return loadWin32(name, size, weight, italic);
9613 		} else version(OSXCocoa) {
9614 			return loadCocoa(name, size, weight, italic);
9615 		} else static assert(0);
9616 	}
9617 
9618 	version(OSXCocoa)
9619 	bool loadCocoa(string name, int size, FontWeight weight, bool italic) {
9620 		unload();
9621 
9622 		font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic?
9623 		font.retain();
9624 		prepareFontInfo();
9625 
9626 		return !isNull();
9627 	}
9628 
9629 	private struct LoadedInfo {
9630 		string name;
9631 		int size;
9632 		FontWeight weight;
9633 		bool italic;
9634 	}
9635 	private LoadedInfo loadedInfo;
9636 
9637 	// int size() { return loadedInfo.size; }
9638 
9639 	///
9640 	void unload() {
9641 		if(isNull())
9642 			return;
9643 
9644 		version(X11) {
9645 			auto display = XDisplayConnection.display;
9646 
9647 			if(display is null)
9648 				return;
9649 
9650 			version(with_xft) {
9651 				if(isXft) {
9652 					if(xftFont)
9653 						XftFontClose(display, xftFont);
9654 					isXft = false;
9655 					xftFont = null;
9656 					return;
9657 				}
9658 			}
9659 
9660 			if(font && font !is ScreenPainterImplementation.defaultfont)
9661 				XFreeFont(display, font);
9662 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
9663 				XFreeFontSet(display, fontset);
9664 
9665 			font = null;
9666 			fontset = null;
9667 		} else version(Windows) {
9668 			DeleteObject(font);
9669 			font = null;
9670 		} else version(OSXCocoa) {
9671 			font.release();
9672 			font = null;
9673 		} else static assert(0);
9674 	}
9675 
9676 	private bool isMonospace_;
9677 
9678 	/++
9679 		History:
9680 			Added January 16, 2021
9681 	+/
9682 	bool isMonospace() {
9683 		return isMonospace_;
9684 	}
9685 
9686 	/++
9687 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
9688 
9689 		History:
9690 			Added March 26, 2020
9691 			Documented January 16, 2021
9692 	+/
9693 	fnum averageWidth() {
9694 		version(X11) {
9695 			return stringWidth("x");
9696 		} version(OSXCocoa) {
9697 			return stringWidth("x");
9698 		} else version(Windows)
9699 			return width_;
9700 		else assert(0);
9701 	}
9702 
9703 	/++
9704 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
9705 
9706 		History:
9707 			Added January 16, 2021
9708 	+/
9709 	fnum stringWidth(scope const(char)[] s, SimpleWindow window = null) {
9710 	// FIXME: what about tab?
9711 		if(isNull)
9712 			return 0;
9713 
9714 		version(X11) {
9715 			version(with_xft)
9716 				if(isXft && xftFont !is null) {
9717 					//return xftFont.max_advance_width;
9718 					XGlyphInfo extents;
9719 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
9720 					// writeln(extents);
9721 					return extents.xOff;
9722 				}
9723 			if(font is null)
9724 				return 0;
9725 			else if(fontset) {
9726 				XRectangle rect;
9727 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
9728 
9729 				return rect.width;
9730 			} else {
9731 				return XTextWidth(font, s.ptr, cast(int) s.length);
9732 			}
9733 		} else version(Windows) {
9734 			WCharzBuffer buffer = WCharzBuffer(s);
9735 
9736 			return stringWidth(buffer.slice, window);
9737 		} else version(OSXCocoa) {
9738 			/+
9739 			int charCount = [string length];
9740 			CGGlyph glyphs[charCount];
9741 			CGRect rects[charCount];
9742 
9743 			CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
9744 			CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);
9745 
9746 			int totalwidth = 0, maxheight = 0;
9747 			for (int i=0; i < charCount; i++)
9748 			{
9749 				totalwidth += rects[i].size.width;
9750 				maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
9751 			}
9752 
9753 			dim = CGSizeMake(totalwidth, maxheight);
9754 			+/
9755 			MacString str = MacString(s);
9756 			NSDictionary dict = NSDictionary.dictionaryWithObject(
9757 				font,
9758 				/*forKey:*/cast(void*) NSFontAttributeName
9759 			);
9760 			// scope(exit) dict.release();
9761 			NSSize size = str.borrow.sizeWithAttributes(dict);
9762 
9763 			// import std.stdio; writeln(s, " ", size);
9764 
9765 			return size.width; // cast(int) (size.width + 0.9 /* to round up */); // FIXME
9766 		}
9767 		else assert(0);
9768 	}
9769 
9770 	version(Windows)
9771 	/// ditto
9772 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
9773 		if(isNull)
9774 			return 0;
9775 		version(Windows) {
9776 			SIZE size;
9777 
9778 			prepareContext(window);
9779 			scope(exit) releaseContext();
9780 
9781 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
9782 
9783 			return size.cx;
9784 		} else {
9785 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
9786 			static assert(0, "not implemented yet");
9787 			//return stringWidth(s, window);
9788 		}
9789 	}
9790 
9791 	private {
9792 		int prepRefcount;
9793 
9794 		version(Windows) {
9795 			HDC dc;
9796 			HANDLE orig;
9797 			HWND hwnd;
9798 		}
9799 	}
9800 	/++
9801 		[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.
9802 
9803 		History:
9804 			Added January 23, 2021
9805 	+/
9806 	void prepareContext(SimpleWindow window = null) {
9807 		prepRefcount++;
9808 		if(prepRefcount == 1) {
9809 			version(Windows) {
9810 				hwnd = window is null ? null : window.impl.hwnd;
9811 				dc = GetDC(hwnd);
9812 				orig = SelectObject(dc, font);
9813 			}
9814 		}
9815 	}
9816 	/// ditto
9817 	void releaseContext() {
9818 		prepRefcount--;
9819 		if(prepRefcount == 0) {
9820 			version(Windows) {
9821 				SelectObject(dc, orig);
9822 				ReleaseDC(hwnd, dc);
9823 				hwnd = null;
9824 				dc = null;
9825 				orig = null;
9826 			}
9827 		}
9828 	}
9829 
9830 	/+
9831 		FIXME: I think I need advance and kerning pair
9832 
9833 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
9834 	+/
9835 
9836 	/++
9837 		Returns the height of the font.
9838 
9839 		History:
9840 			Added March 26, 2020
9841 			Documented January 16, 2021
9842 	+/
9843 	fnum height() {
9844 		version(X11) {
9845 			version(with_xft)
9846 				if(isXft && xftFont !is null) {
9847 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
9848 				}
9849 			if(font is null)
9850 				return 0;
9851 			return font.max_bounds.ascent + font.max_bounds.descent;
9852 		} else version(Windows) {
9853 			return height_;
9854 		} else version(OSXCocoa) {
9855 			if(font is null)
9856 				return 0;
9857 			// the descender likely negative so minus means we actually add
9858 			return cast(int) (font.ascender - font.descender + 0.9 /* to round up */);
9859 			// return cast(int) font.capHeight;
9860 		}
9861 		else assert(0);
9862 	}
9863 
9864 	private fnum ascent_;
9865 	private fnum descent_;
9866 
9867 	/++
9868 		Max ascent above the baseline.
9869 
9870 		History:
9871 			Added January 22, 2021
9872 	+/
9873 	fnum ascent() {
9874 		return ascent_;
9875 	}
9876 
9877 	/++
9878 		Max descent below the baseline.
9879 
9880 		History:
9881 			Added January 22, 2021
9882 	+/
9883 	fnum descent() {
9884 		return descent_;
9885 	}
9886 
9887 	/++
9888 		Loads the default font used by [ScreenPainter] if none others are loaded.
9889 
9890 		Returns:
9891 			This method mutates the `this` object, but then returns `this` for
9892 			easy chaining like:
9893 
9894 			---
9895 			auto font = foo.isNull ? foo : foo.loadDefault
9896 			---
9897 
9898 		History:
9899 			Added previously, but left unimplemented until January 24, 2021.
9900 	+/
9901 	OperatingSystemFont loadDefault() {
9902 		unload();
9903 
9904 		loadedInfo = LoadedInfo.init;
9905 
9906 		version(X11) {
9907 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
9908 			// but meh since sdpy does its own thing, this should be ok too
9909 
9910 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9911 			this.font = ScreenPainterImplementation.defaultfont;
9912 			this.fontset = ScreenPainterImplementation.defaultfontset;
9913 
9914 			prepareFontInfo();
9915 			return this;
9916 		} else version(Windows) {
9917 			ScreenPainterImplementation.ensureDefaultFontLoaded();
9918 			this.font = ScreenPainterImplementation.defaultGuiFont;
9919 
9920 			prepareFontInfo();
9921 			return this;
9922 		} else version(OSXCocoa) {
9923 			this.font = NSFont.systemFontOfSize(15);
9924 			font.retain();
9925 
9926 			prepareFontInfo();
9927 
9928 			// import std.stdio; writeln("Load default: ", this.height());
9929 			return this;
9930 		} else throw new NotYetImplementedException();
9931 	}
9932 
9933 	///
9934 	bool isNull() {
9935 		version(with_xft)
9936 			if(isXft)
9937 				return xftFont is null;
9938 		return font is null;
9939 	}
9940 
9941 	/* Metrics */
9942 	/+
9943 		GetABCWidth
9944 		GetKerningPairs
9945 
9946 		if I do it right, I can size it all here, and match
9947 		what happens when I draw the full string with the OS functions.
9948 
9949 		subclasses might do the same thing while getting the glyphs on images
9950 	struct GlyphInfo {
9951 		int glyph;
9952 
9953 		size_t stringIdxStart;
9954 		size_t stringIdxEnd;
9955 
9956 		Rectangle boundingBox;
9957 	}
9958 	GlyphInfo[] getCharBoxes() {
9959 		// XftTextExtentsUtf8
9960 		return null;
9961 
9962 	}
9963 	+/
9964 
9965 	~this() {
9966 		if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
9967 		unload();
9968 	}
9969 }
9970 
9971 version(Windows)
9972 private string sliceCString(const(wchar)[] w) {
9973 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
9974 }
9975 
9976 private inout(char)[] sliceCString(inout(char)* s) {
9977 	import core.stdc.string;
9978 	auto len = strlen(s);
9979 	return s[0 .. len];
9980 }
9981 
9982 version(OSXCocoa)
9983 	alias PaintingHandle = NSObject;
9984 else
9985 	alias PaintingHandle = NativeWindowHandle;
9986 
9987 /**
9988 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
9989 	than constructing it directly. Then, it is reference counted so you can pass it
9990 	at around and when the last ref goes out of scope, the buffered drawing activities
9991 	are all carried out.
9992 
9993 
9994 	Most functions use the outlineColor instead of taking a color themselves.
9995 	ScreenPainter is reference counted and draws its buffer to the screen when its
9996 	final reference goes out of scope.
9997 */
9998 struct ScreenPainter {
9999 	CapableOfBeingDrawnUpon window;
10000 	this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) {
10001 		this.window = window;
10002 		if(window.closed)
10003 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
10004 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
10005 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
10006 		if(window.activeScreenPainter !is null) {
10007 			impl = window.activeScreenPainter;
10008 			if(impl.referenceCount == 0) {
10009 				impl.window = window;
10010 				impl.create(handle);
10011 			}
10012 			impl.manualInvalidations = manualInvalidations;
10013 			impl.referenceCount++;
10014 		//	writeln("refcount ++ ", impl.referenceCount);
10015 		} else {
10016 			impl = new ScreenPainterImplementation;
10017 			impl.window = window;
10018 			impl.create(handle);
10019 			impl.referenceCount = 1;
10020 			impl.manualInvalidations = manualInvalidations;
10021 			window.activeScreenPainter = impl;
10022 			// writeln("constructed");
10023 		}
10024 
10025 		copyActiveOriginals();
10026 	}
10027 
10028 	/++
10029 		EXPERIMENTAL. subject to change.
10030 
10031 		When you draw a cursor, you can draw this to notify your window of where it is,
10032 		for IME systems to use.
10033 	+/
10034 	void notifyCursorPosition(int x, int y, int width, int height) {
10035 		if(auto w = cast(SimpleWindow) window) {
10036 			w.setIMEPopupLocation(x + _originX + width, y + _originY + height);
10037 		}
10038 	}
10039 
10040 	/++
10041 		If you are using manual invalidations, this informs the
10042 		window system that a section needs to be redrawn.
10043 
10044 		If you didn't opt into manual invalidation, you don't
10045 		have to call this.
10046 
10047 		History:
10048 			Added December 30, 2021 (dub v10.5)
10049 	+/
10050 	void invalidateRect(Rectangle rect) {
10051 		if(impl is null) return;
10052 
10053 		// transform(rect)
10054 		rect.left += _originX;
10055 		rect.right += _originX;
10056 		rect.top += _originY;
10057 		rect.bottom += _originY;
10058 
10059 		impl.invalidateRect(rect);
10060 	}
10061 
10062 	private Pen originalPen;
10063 	private Color originalFillColor;
10064 	private arsd.color.Rectangle originalClipRectangle;
10065 	private OperatingSystemFont originalFont;
10066 	void copyActiveOriginals() {
10067 		if(impl is null) return;
10068 		originalPen = impl._activePen;
10069 		originalFillColor = impl._fillColor;
10070 		originalClipRectangle = impl._clipRectangle;
10071 		version(OSXCocoa) {} else
10072 		originalFont = impl._activeFont;
10073 	}
10074 
10075 	~this() {
10076 		if(impl is null) return;
10077 		impl.referenceCount--;
10078 		//writeln("refcount -- ", impl.referenceCount);
10079 		if(impl.referenceCount == 0) {
10080 			// writeln("destructed");
10081 			impl.dispose();
10082 			*window.activeScreenPainter = ScreenPainterImplementation.init;
10083 			// writeln("paint finished");
10084 		} else {
10085 			// there is still an active reference, reset stuff so the
10086 			// next user doesn't get weirdness via the reference
10087 			this.rasterOp = RasterOp.normal;
10088 			pen = originalPen;
10089 			fillColor = originalFillColor;
10090 			if(originalFont)
10091 				setFont(originalFont);
10092 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
10093 		}
10094 	}
10095 
10096 	this(this) {
10097 		if(impl is null) return;
10098 		impl.referenceCount++;
10099 		//writeln("refcount ++ ", impl.referenceCount);
10100 
10101 		copyActiveOriginals();
10102 	}
10103 
10104 	private int _originX;
10105 	private int _originY;
10106 	@property int originX() { return _originX; }
10107 	@property int originY() { return _originY; }
10108 	@property int originX(int a) {
10109 		_originX = a;
10110 		return _originX;
10111 	}
10112 	@property int originY(int a) {
10113 		_originY = a;
10114 		return _originY;
10115 	}
10116 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
10117 	private void transform(ref Point p) {
10118 		if(impl is null) return;
10119 		p.x += _originX;
10120 		p.y += _originY;
10121 	}
10122 
10123 	// this needs to be checked BEFORE the originX/Y transformation
10124 	private bool isClipped(Point p) {
10125 		return !currentClipRectangle.contains(p);
10126 	}
10127 	private bool isClipped(Point p, int width, int height) {
10128 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
10129 	}
10130 	private bool isClipped(Point p, Size s) {
10131 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
10132 	}
10133 	private bool isClipped(Point p, Point p2) {
10134 		// need to ensure the end points are actually included inside, so the +1 does that
10135 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
10136 	}
10137 
10138 
10139 	/++
10140 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
10141 
10142 		Returns:
10143 			The old clip rectangle.
10144 
10145 		History:
10146 			Return value was `void` prior to May 10, 2021.
10147 
10148 	+/
10149 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
10150 		if(impl is null) return currentClipRectangle;
10151 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
10152 			return currentClipRectangle; // no need to do anything
10153 		auto old = currentClipRectangle;
10154 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
10155 		transform(pt);
10156 
10157 		impl.setClipRectangle(pt.x, pt.y, width, height);
10158 
10159 		return old;
10160 	}
10161 
10162 	/// ditto
10163 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
10164 		if(impl is null) return currentClipRectangle;
10165 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
10166 	}
10167 
10168 	///
10169 	void setFont(OperatingSystemFont font) {
10170 		if(impl is null) return;
10171 		impl.setFont(font);
10172 	}
10173 
10174 	///
10175 	int fontHeight() {
10176 		if(impl is null) return 0;
10177 		return impl.fontHeight();
10178 	}
10179 
10180 	private Pen activePen;
10181 
10182 	///
10183 	@property void pen(Pen p) {
10184 		if(impl is null) return;
10185 		activePen = p;
10186 		impl.pen(p);
10187 	}
10188 
10189 	///
10190 	@scriptable
10191 	@property void outlineColor(Color c) {
10192 		if(impl is null) return;
10193 		if(activePen.color == c)
10194 			return;
10195 		activePen.color = c;
10196 		impl.pen(activePen);
10197 	}
10198 
10199 	///
10200 	@scriptable
10201 	@property void fillColor(Color c) {
10202 		if(impl is null) return;
10203 		impl.fillColor(c);
10204 	}
10205 
10206 	///
10207 	@property void rasterOp(RasterOp op) {
10208 		if(impl is null) return;
10209 		impl.rasterOp(op);
10210 	}
10211 
10212 
10213 	void updateDisplay() {
10214 		// FIXME this should do what the dtor does
10215 	}
10216 
10217 	/// 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)
10218 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
10219 		if(impl is null) return;
10220 		if(isClipped(upperLeft, width, height)) return;
10221 		transform(upperLeft);
10222 		version(Windows) {
10223 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
10224 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
10225 			RECT clip = scroll;
10226 			RECT uncovered;
10227 			HRGN hrgn;
10228 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
10229 				throw new WindowsApiException("ScrollDC", GetLastError());
10230 
10231 		} else version(X11) {
10232 			// FIXME: clip stuff outside this rectangle
10233 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
10234 		} else version(OSXCocoa) {
10235 			throw new NotYetImplementedException();
10236 		} else static assert(0);
10237 	}
10238 
10239 	///
10240 	void clear(Color color = Color.white()) {
10241 		if(impl is null) return;
10242 		fillColor = color;
10243 		outlineColor = color;
10244 		drawRectangle(Point(0, 0), window.width, window.height);
10245 	}
10246 
10247 	/++
10248 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
10249 
10250 		Params:
10251 			upperLeft = point on the window where the upper left corner of the image will be drawn
10252 			imageUpperLeft = point on the image to start the slice to draw
10253 			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.
10254 		History:
10255 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
10256 	+/
10257 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
10258 		if(impl is null) return;
10259 		if(isClipped(upperLeft, s.width, s.height)) return;
10260 		transform(upperLeft);
10261 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
10262 	}
10263 
10264 	/++
10265 		Draws an [Image] to the window.
10266 
10267 		$(WARNING
10268 			Even if the Image was loaded with `enableAlpha`, drawing may not work!
10269 
10270 			Use [Sprite.fromMemoryImage] and [drawPixmap] instead if you want alpha blending to work better.
10271 	+/
10272 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
10273 		if(impl is null) return;
10274 		//if(isClipped(upperLeft, w, h)) return; // FIXME
10275 		transform(upperLeft);
10276 		if(w == 0 || w > i.width)
10277 			w = i.width;
10278 		if(h == 0 || h > i.height)
10279 			h = i.height;
10280 		if(upperLeftOfImage.x < 0)
10281 			upperLeftOfImage.x = 0;
10282 		if(upperLeftOfImage.y < 0)
10283 			upperLeftOfImage.y = 0;
10284 
10285 		assert(i.enableAlpha == false, "Alpha blending is not implemented for Image drawing - use Sprite and drawPixmap instead");
10286 
10287 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
10288 	}
10289 
10290 	///
10291 	Size textSize(in char[] text) {
10292 		if(impl is null) return Size(0, 0);
10293 		return impl.textSize(text);
10294 	}
10295 
10296 	/++
10297 		Draws a string in the window with the set font (see [setFont] to change it).
10298 
10299 		Params:
10300 			upperLeft = the upper left point of the bounding box of the text
10301 			text = the string to draw
10302 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
10303 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
10304 	+/
10305 	@scriptable
10306 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
10307 		if(impl is null) return;
10308 		if(lowerRight.x != 0 || lowerRight.y != 0) {
10309 			if(isClipped(upperLeft, lowerRight)) return;
10310 			transform(lowerRight);
10311 		} else {
10312 			if(isClipped(upperLeft, textSize(text))) return;
10313 		}
10314 		transform(upperLeft);
10315 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
10316 	}
10317 
10318 	/++
10319 		Draws text using a custom font.
10320 
10321 		This is still MAJOR work in progress.
10322 
10323 		Creating a [DrawableFont] can be tricky and require additional dependencies.
10324 	+/
10325 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
10326 		if(impl is null) return;
10327 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
10328 		transform(upperLeft);
10329 		font.drawString(this, upperLeft, text);
10330 	}
10331 
10332 	version(Windows)
10333 	void drawText(Point upperLeft, scope const(wchar)[] text) {
10334 		if(impl is null) return;
10335 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
10336 		transform(upperLeft);
10337 
10338 		if(text.length && text[$-1] == '\n')
10339 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
10340 
10341 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
10342 	}
10343 
10344 	static struct TextDrawingContext {
10345 		Point boundingBoxUpperLeft;
10346 		Point boundingBoxLowerRight;
10347 
10348 		Point currentLocation;
10349 
10350 		Point lastDrewUpperLeft;
10351 		Point lastDrewLowerRight;
10352 
10353 		// how do i do right aligned rich text?
10354 		// i kinda want to do a pre-made drawing then right align
10355 		// draw the whole block.
10356 		//
10357 		// That's exactly the diff: inline vs block stuff.
10358 
10359 		// I need to get coordinates of an inline section out too,
10360 		// not just a bounding box, but a series of bounding boxes
10361 		// should be ok. Consider what's needed to detect a click
10362 		// on a link in the middle of a paragraph breaking a line.
10363 		//
10364 		// Generally, we should be able to get the rectangles of
10365 		// any portion we draw.
10366 		//
10367 		// It also needs to tell what text is left if it overflows
10368 		// out of the box, so we can do stuff like float images around
10369 		// it. It should not attempt to draw a letter that would be
10370 		// clipped.
10371 		//
10372 		// I might also turn off word wrap stuff.
10373 	}
10374 
10375 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
10376 		if(impl is null) return;
10377 		// FIXME
10378 	}
10379 
10380 	/// Drawing an individual pixel is slow. Avoid it if possible.
10381 	void drawPixel(Point where) {
10382 		if(impl is null) return;
10383 		if(isClipped(where)) return;
10384 		transform(where);
10385 		impl.drawPixel(where.x, where.y);
10386 	}
10387 
10388 
10389 	/// Draws a pen using the current pen / outlineColor
10390 	@scriptable
10391 	void drawLine(Point starting, Point ending) {
10392 		if(impl is null) return;
10393 		if(isClipped(starting, ending)) return;
10394 		transform(starting);
10395 		transform(ending);
10396 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
10397 	}
10398 
10399 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
10400 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
10401 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
10402 	@scriptable
10403 	void drawRectangle(Point upperLeft, int width, int height) {
10404 		if(impl is null) return;
10405 		if(isClipped(upperLeft, width, height)) return;
10406 		transform(upperLeft);
10407 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
10408 	}
10409 
10410 	/// ditto
10411 	void drawRectangle(Point upperLeft, Size size) {
10412 		if(impl is null) return;
10413 		if(isClipped(upperLeft, size.width, size.height)) return;
10414 		transform(upperLeft);
10415 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
10416 	}
10417 
10418 	/// ditto
10419 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
10420 		if(impl is null) return;
10421 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
10422 		transform(upperLeft);
10423 		transform(lowerRightInclusive);
10424 		impl.drawRectangle(upperLeft.x, upperLeft.y,
10425 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
10426 	}
10427 
10428 	// overload added on May 12, 2021
10429 	/// ditto
10430 	void drawRectangle(Rectangle rect) {
10431 		drawRectangle(rect.upperLeft, rect.size);
10432 	}
10433 
10434 	/// Arguments are the points of the bounding rectangle
10435 	void drawEllipse(Point upperLeft, Point lowerRight) {
10436 		if(impl is null) return;
10437 		if(isClipped(upperLeft, lowerRight)) return;
10438 		transform(upperLeft);
10439 		transform(lowerRight);
10440 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
10441 	}
10442 
10443 	/++
10444 		Draws an arc inside the bounding box given by `upperLeft`, `width`, and `height`, from the angle (`start` / 64) degrees for (`length` / 64) degrees of rotation.
10445 
10446 
10447 		If `length` is positive, it travels counter-clockwise from `start`. If negative, it goes clockwise.  `start` == 0 at the three o'clock position of the bounding box - the center of the line at the right-hand side of the screen.
10448 
10449 		The arc is outlined with the current pen and filled with the current fill. On Windows, the line segments back to the middle are also drawn unless you have a full length ellipse.
10450 
10451 		Bugs:
10452 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
10453 
10454 			The arc outline on Linux sometimes goes over the target.
10455 
10456 			The fill on Windows sometimes stops short.
10457 
10458 		History:
10459 			This function was broken af, totally inconsistent on platforms until September 24, 2021.
10460 
10461 			The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024.
10462 	+/
10463 	void drawArc(Point upperLeft, int width, int height, int start, int length) {
10464 		if(impl is null) return;
10465 		// FIXME: not actually implemented
10466 		if(isClipped(upperLeft, width, height)) return;
10467 		transform(upperLeft);
10468 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length);
10469 	}
10470 
10471 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
10472 	void drawCircle(Point upperLeft, int diameter) {
10473 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
10474 	}
10475 
10476 	/++
10477 		Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush.
10478 
10479 
10480 		Bugs:
10481 			Not implemented on Mac; it will instead draw a non-rounded rectangle for now.
10482 
10483 		History:
10484 			Added August 3, 2024
10485 	+/
10486 	void drawRectangleRounded(Rectangle rect, int borderRadius) {
10487 		drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius);
10488 	}
10489 
10490 	/// ditto
10491 	void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) {
10492 		drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius);
10493 	}
10494 
10495 	/// ditto
10496 	void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
10497 		if(borderRadius <= 0) {
10498 			drawRectangle(upperLeft, lowerRight);
10499 			return;
10500 		}
10501 
10502 		transform(upperLeft);
10503 		transform(lowerRight);
10504 
10505 		impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius);
10506 	}
10507 
10508 	/// .
10509 	void drawPolygon(Point[] vertexes) {
10510 		if(impl is null) return;
10511 		assert(vertexes.length);
10512 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
10513 		foreach(ref vertex; vertexes) {
10514 			if(vertex.x < minX)
10515 				minX = vertex.x;
10516 			if(vertex.y < minY)
10517 				minY = vertex.y;
10518 			if(vertex.x > maxX)
10519 				maxX = vertex.x;
10520 			if(vertex.y > maxY)
10521 				maxY = vertex.y;
10522 			transform(vertex);
10523 		}
10524 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
10525 		impl.drawPolygon(vertexes);
10526 	}
10527 
10528 	/// ditto
10529 	void drawPolygon(Point[] vertexes...) {
10530 		if(impl is null) return;
10531 		drawPolygon(vertexes);
10532 	}
10533 
10534 
10535 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
10536 
10537 	//mixin NativeScreenPainterImplementation!() impl;
10538 
10539 
10540 	// HACK: if I mixin the impl directly, it won't let me override the copy
10541 	// constructor! The linker complains about there being multiple definitions.
10542 	// I'll make the best of it and reference count it though.
10543 	ScreenPainterImplementation* impl;
10544 }
10545 
10546 	// HACK: I need a pointer to the implementation so it's separate
10547 	struct ScreenPainterImplementation {
10548 		CapableOfBeingDrawnUpon window;
10549 		int referenceCount;
10550 		mixin NativeScreenPainterImplementation!();
10551 	}
10552 
10553 // FIXME: i haven't actually tested the sprite class on MS Windows
10554 
10555 /**
10556 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
10557 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
10558 
10559 
10560 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
10561 	though I'm not sure that's ideal and the implementation might change.
10562 
10563 	You create one by giving a window and an image. It optimizes for that window,
10564 	and copies the image into it to use as the initial picture. Creating a sprite
10565 	can be quite slow (especially over a network connection) so you should do it
10566 	as little as possible and just hold on to your sprite handles after making them.
10567 	simpledisplay does try to do its best though, using the XSHM extension if available,
10568 	but you should still write your code as if it will always be slow.
10569 
10570 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
10571 	a fast operation - much faster than drawing the Image itself every time.
10572 
10573 	`Sprite` represents a scarce resource which should be freed when you
10574 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
10575 	after it has been disposed. If you are unsure about this, don't take chances,
10576 	just let the garbage collector do it for you. But ideally, you can manage its
10577 	lifetime more efficiently.
10578 
10579 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
10580 	support alpha blending in its drawing at this time. That might change in the
10581 	future, but if you need alpha blending right now, use OpenGL instead. See
10582 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
10583 
10584 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
10585 	in by setting the enableAlpha = true in the constructor.
10586 */
10587 class Sprite : CapableOfBeingDrawnUpon {
10588 
10589 	///
10590 	ScreenPainter draw() {
10591 		return ScreenPainter(this, handle, false);
10592 	}
10593 
10594 	/++
10595 		Copies the sprite's current state into a [TrueColorImage].
10596 
10597 		Be warned: this can be a very slow operation
10598 
10599 		History:
10600 			Actually implemented on March 14, 2021
10601 	+/
10602 	TrueColorImage takeScreenshot() {
10603 		return trueColorImageFromNativeHandle(handle, width, height);
10604 	}
10605 
10606 	void delegate() paintingFinishedDg() { return null; }
10607 	bool closed() { return false; }
10608 	ScreenPainterImplementation* activeScreenPainter_;
10609 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
10610 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
10611 
10612 	version(Windows)
10613 		private ubyte* rawData;
10614 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
10615 	// ditto on the XPicture stuff
10616 
10617 	version(X11) {
10618 		private static XRenderPictFormat* RGB24;
10619 		private static XRenderPictFormat* ARGB32;
10620 
10621 		private Picture xrenderPicture;
10622 	}
10623 
10624 	version(X11)
10625 	private static void requireXRender() {
10626 		if(!XRenderLibrary.loadAttempted) {
10627 			XRenderLibrary.loadDynamicLibrary();
10628 		}
10629 
10630 		if(!XRenderLibrary.loadSuccessful)
10631 			throw new Exception("XRender library load failure");
10632 
10633 		auto display = XDisplayConnection.get;
10634 
10635 		// FIXME: if we migrate X displays, these need to be changed
10636 		if(RGB24 is null)
10637 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
10638 		if(ARGB32 is null)
10639 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
10640 	}
10641 
10642 	protected this() {}
10643 
10644 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
10645 		this._width = width;
10646 		this._height = height;
10647 		this.enableAlpha = enableAlpha;
10648 
10649 		version(X11) {
10650 			auto display = XDisplayConnection.get();
10651 
10652 			if(enableAlpha) {
10653 				requireXRender();
10654 			}
10655 
10656 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
10657 
10658 			if(enableAlpha) {
10659 				XRenderPictureAttributes attrs;
10660 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
10661 			}
10662 		} else version(Windows) {
10663 			version(CRuntime_DigitalMars) {
10664 				//if(enableAlpha)
10665 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
10666 			}
10667 
10668 			BITMAPINFO infoheader;
10669 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
10670 			infoheader.bmiHeader.biWidth = width;
10671 			infoheader.bmiHeader.biHeight = height;
10672 			infoheader.bmiHeader.biPlanes = 1;
10673 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
10674 			infoheader.bmiHeader.biCompression = BI_RGB;
10675 
10676 			// FIXME: this should prolly be a device dependent bitmap...
10677 			handle = CreateDIBSection(
10678 				null,
10679 				&infoheader,
10680 				DIB_RGB_COLORS,
10681 				cast(void**) &rawData,
10682 				null,
10683 				0);
10684 
10685 			if(handle is null)
10686 				throw new WindowsApiException("couldn't create pixmap", GetLastError());
10687 		}
10688 	}
10689 
10690 	/// Makes a sprite based on the image with the initial contents from the Image
10691 	this(SimpleWindow win, Image i) {
10692 		this(win, i.width, i.height, i.enableAlpha);
10693 
10694 		version(X11) {
10695 			auto display = XDisplayConnection.get();
10696 			auto gc = XCreateGC(display, this.handle, 0, null);
10697 			scope(exit) XFreeGC(display, gc);
10698 			if(i.usingXshm)
10699 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
10700 			else
10701 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
10702 		} else version(Windows) {
10703 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
10704 			auto arrLength = itemsPerLine * height;
10705 			rawData[0..arrLength] = i.rawData[0..arrLength];
10706 		} else version(OSXCocoa) {
10707 			// FIXME: I have no idea if this is even any good
10708 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
10709 			handle = CGBitmapContextCreate(null, width, height, 8, 4*width,
10710 				colorSpace,
10711 				kCGImageAlphaPremultipliedLast
10712 				|kCGBitmapByteOrder32Big);
10713 			CGColorSpaceRelease(colorSpace);
10714 			auto rawData = CGBitmapContextGetData(handle);
10715 
10716 			auto rdl = (width * height * 4);
10717 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
10718 		} else static assert(0);
10719 	}
10720 
10721 	/++
10722 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
10723 
10724 		Params:
10725 			where = point on the window where the upper left corner of the image will be drawn
10726 			imageUpperLeft = point on the image to start the slice to draw
10727 			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.
10728 		History:
10729 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
10730 	+/
10731 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
10732 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
10733 	}
10734 
10735 	/// Call this when you're ready to get rid of it
10736 	void dispose() {
10737 		version(X11) {
10738 			staticDispose(xrenderPicture, handle);
10739 			xrenderPicture = None;
10740 			handle = None;
10741 		} else version(Windows) {
10742 			staticDispose(handle);
10743 			handle = null;
10744 		} else version(OSXCocoa) {
10745 			staticDispose(handle);
10746 			handle = null;
10747 		} else static assert(0);
10748 
10749 	}
10750 
10751 	version(X11)
10752 	static void staticDispose(Picture xrenderPicture, Pixmap handle) {
10753 		if(xrenderPicture)
10754 			XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
10755 		if(handle)
10756 			XFreePixmap(XDisplayConnection.get(), handle);
10757 	}
10758 	else version(Windows)
10759 	static void staticDispose(HBITMAP handle) {
10760 		if(handle)
10761 			DeleteObject(handle);
10762 	}
10763 	else version(OSXCocoa)
10764 	static void staticDispose(CGContextRef context) {
10765 		if(context)
10766 			CGContextRelease(context);
10767 	}
10768 
10769 	~this() {
10770 		version(X11) { if(xrenderPicture || handle)
10771 			cleanupQueue.queue!staticDispose(xrenderPicture, handle);
10772 		} else version(Windows) { if(handle)
10773 			cleanupQueue.queue!staticDispose(handle);
10774 		} else version(OSXCocoa) { if(handle)
10775 			cleanupQueue.queue!staticDispose(handle);
10776 		} else static assert(0);
10777 	}
10778 
10779 	///
10780 	final @property int width() { return _width; }
10781 
10782 	///
10783 	final @property int height() { return _height; }
10784 
10785 	///
10786 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
10787 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
10788 	}
10789 
10790 	auto nativeHandle() {
10791 		return handle;
10792 	}
10793 
10794 	private:
10795 
10796 	int _width;
10797 	int _height;
10798 	bool enableAlpha;
10799 	version(X11)
10800 		Pixmap handle;
10801 	else version(Windows)
10802 		HBITMAP handle;
10803 	else version(OSXCocoa)
10804 		CGContextRef handle;
10805 	else version(Emscripten)
10806 		void* handle;
10807 	else static assert(0);
10808 }
10809 
10810 /++
10811 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
10812 
10813 	History:
10814 		Added November 20, 2021 (dub v10.4)
10815 +/
10816 version(OSXCocoa) {} else // NotYetImplementedException
10817 abstract class Gradient : Sprite {
10818 	protected this(int w, int h) {
10819 		version(X11) {
10820 			Sprite.requireXRender();
10821 
10822 			super();
10823 			enableAlpha = true;
10824 			_width = w;
10825 			_height = h;
10826 		} else version(Windows) {
10827 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
10828 		}
10829 	}
10830 
10831 	version(Windows)
10832 	final void forEachPixel(scope Color delegate(int x, int y) dg) @system {
10833 		auto ptr = rawData;
10834 		foreach(j; 0 .. _height)
10835 		foreach(i; 0 .. _width) {
10836 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
10837 			*rawData = (color.a * color.b) / 255; rawData++;
10838 			*rawData = (color.a * color.g) / 255; rawData++;
10839 			*rawData = (color.a * color.r) / 255; rawData++;
10840 			*rawData = color.a; rawData++;
10841 		}
10842 	}
10843 
10844 	version(X11)
10845 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
10846 		assert(stops.length > 0);
10847 		assert(stops.length <= 16, "I got lazy with buffers");
10848 
10849 		XFixed[16] stopsPositions = void;
10850 		XRenderColor[16] colors = void;
10851 
10852 		foreach(idx, stop; stops) {
10853 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
10854 			auto c = stop.c;
10855 			colors[idx] = XRenderColor(
10856 				cast(ushort)(c.r * ushort.max / 255),
10857 				cast(ushort)(c.g * ushort.max / 255),
10858 				cast(ushort)(c.b * ushort.max / 255),
10859 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
10860 			);
10861 		}
10862 
10863 		xrenderPicture = dg(stopsPositions, colors);
10864 	}
10865 
10866 	///
10867 	static struct Stop {
10868 		float percentage; /// between 0 and 1.0
10869 		Color c;
10870 	}
10871 }
10872 
10873 /++
10874 	Creates a linear gradient between p1 and p2.
10875 
10876 	X ONLY RIGHT NOW
10877 
10878 	History:
10879 		Added November 20, 2021 (dub v10.4)
10880 
10881 	Bugs:
10882 		Not yet implemented on Windows.
10883 +/
10884 version(OSXCocoa) {} else // NotYetImplementedException
10885 class LinearGradient : Gradient {
10886 	/++
10887 
10888 	+/
10889 	this(Point p1, Point p2, Stop[] stops...) {
10890 		super(p2.x, p2.y);
10891 
10892 		version(X11) {
10893 			XLinearGradient gradient;
10894 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
10895 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
10896 
10897 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10898 				return XRenderCreateLinearGradient(
10899 					XDisplayConnection.get,
10900 					&gradient,
10901 					stopsPositions.ptr,
10902 					colors.ptr,
10903 					cast(int) stops.length);
10904 			});
10905 		} else version(Windows) {
10906 			// FIXME
10907 			forEachPixel((int x, int y) {
10908 				import core.stdc.math;
10909 
10910 				//sqrtf(
10911 
10912 				return Color.transparent;
10913 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10914 			});
10915 		}
10916 	}
10917 }
10918 
10919 /++
10920 	A conical gradient goes from color to color around a circumference from a center point.
10921 
10922 	X ONLY RIGHT NOW
10923 
10924 	History:
10925 		Added November 20, 2021 (dub v10.4)
10926 
10927 	Bugs:
10928 		Not yet implemented on Windows.
10929 +/
10930 version(OSXCocoa) {} else // NotYetImplementedException
10931 class ConicalGradient : Gradient {
10932 	/++
10933 
10934 	+/
10935 	this(Point center, float angleInDegrees, Stop[] stops...) {
10936 		super(center.x * 2, center.y * 2);
10937 
10938 		version(X11) {
10939 			XConicalGradient gradient;
10940 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
10941 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
10942 
10943 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10944 				return XRenderCreateConicalGradient(
10945 					XDisplayConnection.get,
10946 					&gradient,
10947 					stopsPositions.ptr,
10948 					colors.ptr,
10949 					cast(int) stops.length);
10950 			});
10951 		} else version(Windows) {
10952 			// FIXME
10953 			forEachPixel((int x, int y) {
10954 				import core.stdc.math;
10955 
10956 				//sqrtf(
10957 
10958 				return Color.transparent;
10959 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
10960 			});
10961 
10962 		}
10963 	}
10964 }
10965 
10966 /++
10967 	A radial gradient goes from color to color based on distance from the center.
10968 	It is like rings of color.
10969 
10970 	X ONLY RIGHT NOW
10971 
10972 
10973 	More specifically, you create two circles: an inner circle and an outer circle.
10974 	The gradient is only drawn in the area outside the inner circle but inside the outer
10975 	circle. The closest line between those two circles forms the line for the gradient
10976 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
10977 
10978 	History:
10979 		Added November 20, 2021 (dub v10.4)
10980 
10981 	Bugs:
10982 		Not yet implemented on Windows.
10983 +/
10984 version(OSXCocoa) {} else // NotYetImplementedException
10985 class RadialGradient : Gradient {
10986 	/++
10987 
10988 	+/
10989 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
10990 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
10991 
10992 		version(X11) {
10993 			XRadialGradient gradient;
10994 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
10995 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
10996 
10997 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
10998 				return XRenderCreateRadialGradient(
10999 					XDisplayConnection.get,
11000 					&gradient,
11001 					stopsPositions.ptr,
11002 					colors.ptr,
11003 					cast(int) stops.length);
11004 			});
11005 		} else version(Windows) {
11006 			// FIXME
11007 			forEachPixel((int x, int y) {
11008 				import core.stdc.math;
11009 
11010 				//sqrtf(
11011 
11012 				return Color.transparent;
11013 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
11014 			});
11015 		}
11016 	}
11017 }
11018 
11019 
11020 
11021 /+
11022 	NOT IMPLEMENTED
11023 
11024 	A display-stored image optimized for relatively quick drawing, like
11025 	[Sprite], but this one supports alpha channel blending and does NOT
11026 	support direct drawing upon it with a [ScreenPainter].
11027 
11028 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
11029 	plain [ScreenPainter]... sort of.
11030 
11031 	On X11, it requires the Xrender extension and library. This is available
11032 	almost everywhere though.
11033 
11034 	History:
11035 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
11036 +/
11037 version(none)
11038 class AlphaSprite {
11039 	/++
11040 		Copies the given image into it.
11041 	+/
11042 	this(MemoryImage img) {
11043 
11044 		if(!XRenderLibrary.loadAttempted) {
11045 			XRenderLibrary.loadDynamicLibrary();
11046 
11047 			// FIXME: this needs to be reconstructed when the X server changes
11048 			repopulateX();
11049 		}
11050 		if(!XRenderLibrary.loadSuccessful)
11051 			throw new Exception("XRender library load failure");
11052 
11053 		// I probably need to put the alpha mask in a separate Picture
11054 		// ugh
11055 		// maybe the Sprite itself can have an alpha bitmask anyway
11056 
11057 
11058 		auto display = XDisplayConnection.get();
11059 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
11060 
11061 
11062 		XRenderPictureAttributes attrs;
11063 
11064 		handle = XRenderCreatePicture(
11065 			XDisplayConnection.get,
11066 			pixmap,
11067 			RGBA,
11068 			0,
11069 			&attrs
11070 		);
11071 
11072 	}
11073 
11074 	// maybe i'll use the create gradient functions too with static factories..
11075 
11076 	void drawAt(ScreenPainter painter, Point where) {
11077 		//painter.drawPixmap(this, where);
11078 
11079 		XRenderPictureAttributes attrs;
11080 
11081 		auto pic = XRenderCreatePicture(
11082 			XDisplayConnection.get,
11083 			painter.impl.d,
11084 			RGB,
11085 			0,
11086 			&attrs
11087 		);
11088 
11089 		XRenderComposite(
11090 			XDisplayConnection.get,
11091 			3, // PictOpOver
11092 			handle,
11093 			None,
11094 			pic,
11095 			0, // src
11096 			0,
11097 			0, // mask
11098 			0,
11099 			10, // dest
11100 			10,
11101 			100, // width
11102 			100
11103 		);
11104 
11105 		/+
11106 		XRenderFreePicture(
11107 			XDisplayConnection.get,
11108 			pic
11109 		);
11110 
11111 		XRenderFreePicture(
11112 			XDisplayConnection.get,
11113 			fill
11114 		);
11115 		+/
11116 		// on Windows you can stretch but Xrender still can't :(
11117 	}
11118 
11119 	static XRenderPictFormat* RGB;
11120 	static XRenderPictFormat* RGBA;
11121 	static void repopulateX() {
11122 		auto display = XDisplayConnection.get;
11123 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
11124 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
11125 	}
11126 
11127 	XPixmap pixmap;
11128 	Picture handle;
11129 }
11130 
11131 ///
11132 interface CapableOfBeingDrawnUpon {
11133 	///
11134 	ScreenPainter draw();
11135 	///
11136 	int width();
11137 	///
11138 	int height();
11139 	protected ScreenPainterImplementation* activeScreenPainter();
11140 	protected void activeScreenPainter(ScreenPainterImplementation*);
11141 	bool closed();
11142 
11143 	void delegate() paintingFinishedDg();
11144 
11145 	/// Be warned: this can be a very slow operation
11146 	TrueColorImage takeScreenshot();
11147 }
11148 
11149 /// 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].
11150 void flushGui() {
11151 	version(X11) {
11152 		auto dpy = XDisplayConnection.get();
11153 		XLockDisplay(dpy);
11154 		scope(exit) XUnlockDisplay(dpy);
11155 		XFlush(dpy);
11156 	}
11157 }
11158 
11159 /++
11160 	Runs the given code in the GUI thread when its event loop
11161 	is available, blocking until it completes. This allows you
11162 	to create and manipulate windows from another thread without
11163 	invoking undefined behavior.
11164 
11165 	If this is the gui thread, it runs the code immediately.
11166 
11167 	If no gui thread exists yet, the current thread is assumed
11168 	to be it. Attempting to create windows or run the event loop
11169 	in any other thread will cause an assertion failure.
11170 
11171 
11172 	$(TIP
11173 		Did you know you can use UFCS on delegate literals?
11174 
11175 		() {
11176 			// code here
11177 		}.runInGuiThread;
11178 	)
11179 
11180 	Returns:
11181 		`true` if the function was called, `false` if it was not.
11182 		The function may not be called because the gui thread had
11183 		already terminated by the time you called this.
11184 
11185 	History:
11186 		Added April 10, 2020 (v7.2.0)
11187 
11188 		Return value added and implementation tweaked to avoid locking
11189 		at program termination on February 24, 2021 (v9.2.1).
11190 +/
11191 bool runInGuiThread(scope void delegate() dg) @trusted {
11192 	claimGuiThread();
11193 
11194 	if(thisIsGuiThread) {
11195 		dg();
11196 		return true;
11197 	}
11198 
11199 	if(guiThreadTerminating)
11200 		return false;
11201 
11202 	import core.sync.semaphore;
11203 	static Semaphore sc;
11204 	if(sc is null)
11205 		sc = new Semaphore();
11206 
11207 	static RunQueueMember* rqm;
11208 	if(rqm is null)
11209 		rqm = new RunQueueMember;
11210 	rqm.dg = cast(typeof(rqm.dg)) dg;
11211 	rqm.signal = sc;
11212 	rqm.thrown = null;
11213 
11214 	synchronized(runInGuiThreadLock) {
11215 		runInGuiThreadQueue ~= rqm;
11216 	}
11217 
11218 	if(!SimpleWindow.eventWakeUp())
11219 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
11220 
11221 	rqm.signal.wait();
11222 	auto t = rqm.thrown;
11223 
11224 	if(t)
11225 		throw t;
11226 
11227 	return true;
11228 }
11229 
11230 // note it runs sync if this is the gui thread....
11231 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
11232 	claimGuiThread();
11233 
11234 	try {
11235 
11236 		if(thisIsGuiThread) {
11237 			dg();
11238 			return;
11239 		}
11240 
11241 		if(guiThreadTerminating)
11242 			return;
11243 
11244 		RunQueueMember* rqm = new RunQueueMember;
11245 		rqm.dg = cast(typeof(rqm.dg)) dg;
11246 		rqm.signal = null;
11247 		rqm.thrown = null;
11248 
11249 		synchronized(runInGuiThreadLock) {
11250 			runInGuiThreadQueue ~= rqm;
11251 		}
11252 
11253 		if(!SimpleWindow.eventWakeUp())
11254 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
11255 	} catch(Exception e) {
11256 		// try sdpyPrintDebugString(e.toString); catch(Exception wtf) {}
11257 		if(handleError)
11258 			handleError(e);
11259 	}
11260 }
11261 
11262 private void runPendingRunInGuiThreadDelegates() {
11263 	more:
11264 	RunQueueMember* next;
11265 	synchronized(runInGuiThreadLock) {
11266 		if(runInGuiThreadQueue.length) {
11267 			next = runInGuiThreadQueue[0];
11268 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
11269 		} else {
11270 			next = null;
11271 		}
11272 	}
11273 
11274 	if(next) {
11275 		try {
11276 			next.dg();
11277 			next.thrown = null;
11278 		} catch(Throwable t) {
11279 			next.thrown = t;
11280 		}
11281 
11282 		if(next.signal)
11283 			next.signal.notify();
11284 
11285 		goto more;
11286 	}
11287 }
11288 
11289 private void claimGuiThread() nothrow {
11290 	import core.atomic;
11291 	if(cas(&guiThreadExists_, false, true))
11292 		thisIsGuiThread = true;
11293 }
11294 
11295 private struct RunQueueMember {
11296 	void delegate() dg;
11297 	import core.sync.semaphore;
11298 	Semaphore signal;
11299 	Throwable thrown;
11300 }
11301 
11302 private __gshared RunQueueMember*[] runInGuiThreadQueue;
11303 private __gshared SynchronizableObject runInGuiThreadLock = new SynchronizableObject; // intentional CTFE
11304 private bool thisIsGuiThread = false;
11305 private shared bool guiThreadExists_ = false;
11306 private shared bool guiThreadTerminating = false;
11307 
11308 /++
11309 	Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d
11310 	event loop. All windows must be exclusively created and managed by a single thread.
11311 
11312 	If no gui thread exists, simpledisplay.d will automatically adopt the current thread
11313 	when you call one of its constructors.
11314 
11315 	If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this
11316 	one. If so, you can run gui functions on it. If not, don't. The helper functions
11317 	[runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically.
11318 
11319 	The reason this function is available is in case you want to message pass between a gui
11320 	thread and your current thread. If no gui thread exists or if this is the gui thread,
11321 	you're liable to deadlock when trying to communicate since you'd end up talking to yourself.
11322 
11323 	History:
11324 		Added December 3, 2021 (dub v10.5)
11325 +/
11326 public bool guiThreadExists() {
11327 	return guiThreadExists_;
11328 }
11329 
11330 /++
11331 	Returns `true` if this thread is either running or set to be running the
11332 	simpledisplay.d gui core event loop because it owns windows.
11333 
11334 	It is important to keep gui-related functionality in the right thread, so you will
11335 	want to `runInGuiThread` when you call them (with some specific exceptions called
11336 	out in those specific functions' documentation). Notably, all windows must be
11337 	created and managed only from the gui thread.
11338 
11339 	Will return false if simpledisplay's other functions haven't been called
11340 	yet; check [guiThreadExists] in addition to this.
11341 
11342 	History:
11343 		Added December 3, 2021 (dub v10.5)
11344 +/
11345 public bool thisThreadRunningGui() {
11346 	return thisIsGuiThread;
11347 }
11348 
11349 /++
11350 	Function to help temporarily print debugging info. It will bypass any stdout/err redirection
11351 	and go to the controlling tty or console (attaching to the parent and/or allocating one as
11352 	needed on Windows. Please note it may overwrite output from other programs in the parent and the
11353 	allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log
11354 	file instead if you are in one of those situations).
11355 
11356 	It does not support outputting very many types; just strings and ints are likely to actually work.
11357 
11358 	It will perform very slowly and swallows any errors that may occur. Moreover, the specific output
11359 	is unspecified meaning I can change it at any time. The only point of this function is to help
11360 	in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword
11361 	and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use
11362 	in those contexts.
11363 
11364 	$(WARNING
11365 		I reserve the right to change this function at any time. You can use it if it helps you
11366 		but do not rely on it for anything permanent.
11367 	)
11368 
11369 	History:
11370 		Added December 3, 2021. Not formally supported under any stable tag.
11371 +/
11372 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
11373 	try {
11374 		version(Windows) {
11375 			import core.sys.windows.wincon;
11376 			if(!AttachConsole(ATTACH_PARENT_PROCESS))
11377 				AllocConsole();
11378 			const(char)* fn = "CONOUT$";
11379 		} else version(Posix) {
11380 			const(char)* fn = "/dev/tty";
11381 		} else static assert(0, "Function not implemented for your system");
11382 
11383 		if(fileOverride.length)
11384 			fn = fileOverride.ptr;
11385 
11386 		import core.stdc.stdio;
11387 		auto fp = fopen(fn, "wt");
11388 		if(fp is null) return;
11389 		scope(exit) fclose(fp);
11390 
11391 		string str;
11392 		foreach(item; t) {
11393 			static if(is(typeof(item) : const(char)[]))
11394 				str ~= item;
11395 			else
11396 				str ~= toInternal!string(item);
11397 			str ~= " ";
11398 		}
11399 		str ~= "\n";
11400 
11401 		fwrite(str.ptr, 1, str.length, fp);
11402 		fflush(fp);
11403 	} catch(Exception e) {
11404 		// sorry no hope
11405 	}
11406 }
11407 
11408 private void guiThreadFinalize() {
11409 	assert(thisIsGuiThread);
11410 
11411 	guiThreadTerminating = true; // don't add any more from this point on
11412 	runPendingRunInGuiThreadDelegates();
11413 }
11414 
11415 /+
11416 interface IPromise {
11417 	void reportProgress(int current, int max, string message);
11418 
11419 	/+ // not formally in cuz of templates but still
11420 	IPromise Then();
11421 	IPromise Catch();
11422 	IPromise Finally();
11423 	+/
11424 }
11425 
11426 /+
11427 	auto promise = async({ ... });
11428 	promise.Then(whatever).
11429 		Then(whateverelse).
11430 		Catch((exception) { });
11431 
11432 
11433 	A promise is run inside a fiber and it looks something like:
11434 
11435 	try {
11436 		auto res = whatever();
11437 		auto res2 = whateverelse(res);
11438 	} catch(Exception e) {
11439 		{ }(e);
11440 	}
11441 
11442 	When a thing succeeds, it is passed as an arg to the next
11443 +/
11444 class Promise(T) : IPromise {
11445 	auto Then() { return null; }
11446 	auto Catch() { return null; }
11447 	auto Finally() { return null; }
11448 
11449 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
11450 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
11451 	T await();
11452 }
11453 
11454 interface Task {
11455 }
11456 
11457 interface Resolvable(T) : Task {
11458 	void run();
11459 
11460 	void resolve(T);
11461 
11462 	Resolvable!T then(void delegate(T)); // returns a new promise
11463 	Resolvable!T error(Throwable); // js catch
11464 	Resolvable!T completed(); // js finally
11465 
11466 }
11467 
11468 /++
11469 	Runs `work` in a helper thread and sends its return value back to the main gui
11470 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
11471 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
11472 	kill the program.
11473 
11474 	You can call reportProgress(position, max, message) to update your parent window
11475 	on your progress.
11476 
11477 	I should also use `shared` methods. FIXME
11478 
11479 	History:
11480 		Added March 6, 2021 (dub version 9.3).
11481 +/
11482 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
11483 	uponCompletion(work(null));
11484 }
11485 
11486 +/
11487 
11488 /// Used internal to dispatch events to various classes.
11489 interface CapableOfHandlingNativeEvent {
11490 	NativeEventHandler getNativeEventHandler();
11491 
11492 	version(OSXCocoa)
11493 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[void*] nativeHandleMapping; // to avoid typeinfo problems
11494 	else
11495 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
11496 
11497 	version(X11) {
11498 		// if this is impossible, you are allowed to just throw from it
11499 		// Note: if you call it from another object, set a flag cuz the manger will call you again
11500 		void recreateAfterDisconnect();
11501 		// discard any *connection specific* state, but keep enough that you
11502 		// can be recreated if possible. discardConnectionState() is always called immediately
11503 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
11504 		// you need initialization order
11505 		void discardConnectionState();
11506 	}
11507 }
11508 
11509 version(X11)
11510 /++
11511 	State of keys on mouse events, especially motion.
11512 
11513 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
11514 +/
11515 enum ModifierState : uint {
11516 	shift = 1, ///
11517 	capsLock = 2, ///
11518 	ctrl = 4, ///
11519 	alt = 8, /// Not always available on Windows
11520 	windows = 64, /// ditto
11521 	numLock = 16, ///
11522 
11523 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11524 	middleButtonDown = 512, /// ditto
11525 	rightButtonDown = 1024, /// ditto
11526 }
11527 else version(Emscripten)
11528 enum ModifierState : uint {
11529 	shift = 1, ///
11530 	capsLock = 2, ///
11531 	ctrl = 4, ///
11532 	alt = 8, /// Not always available on Windows
11533 	windows = 64, /// ditto
11534 	numLock = 16, ///
11535 
11536 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11537 	middleButtonDown = 512, /// ditto
11538 	rightButtonDown = 1024, /// ditto
11539 }
11540 else version(Windows)
11541 /// ditto
11542 enum ModifierState : uint {
11543 	shift = 4, ///
11544 	ctrl = 8, ///
11545 
11546 	// i'm not sure if the next two are available
11547 	alt = 256, /// not always available on Windows
11548 	windows = 512, /// ditto
11549 
11550 	capsLock = 1024, ///
11551 	numLock = 2048, ///
11552 
11553 	leftButtonDown = 1, /// not available on key events
11554 	middleButtonDown = 16, /// ditto
11555 	rightButtonDown = 2, /// ditto
11556 
11557 	backButtonDown = 0x20, /// not available on X
11558 	forwardButtonDown = 0x40, /// ditto
11559 }
11560 else version(OSXCocoa)
11561 // FIXME FIXME NotYetImplementedException
11562 enum ModifierState : uint {
11563 	shift = 1, ///
11564 	capsLock = 2, ///
11565 	ctrl = 4, ///
11566 	alt = 8, /// Not always available on Windows
11567 	windows = 64, /// ditto
11568 	numLock = 16, ///
11569 
11570 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
11571 	middleButtonDown = 512, /// ditto
11572 	rightButtonDown = 1024, /// ditto
11573 }
11574 
11575 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
11576 enum MouseButton : int {
11577 	none = 0,
11578 	left = 1, ///
11579 	right = 2, ///
11580 	middle = 4, ///
11581 	wheelUp = 8, ///
11582 	wheelDown = 16, ///
11583 	wheelLeft = 32, ///
11584 	wheelRight = 64, ///
11585 	backButton = 128, /// often found on the thumb and used for back in browsers
11586 	forwardButton = 256, /// often found on the thumb and used for forward in browsers
11587 }
11588 
11589 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1`
11590 enum MouseButtonLinear : ubyte {
11591 	left = 1, ///
11592 	right, ///
11593 	middle, ///
11594 	wheelUp, ///
11595 	wheelDown, ///
11596 	wheelLeft, /// Added Dec 21, 2025
11597 	wheelRight, /// ditto
11598 	backButton, /// often found on the thumb and used for back in browsers
11599 	forwardButton, /// often found on the thumb and used for forward in browsers
11600 }
11601 
11602 version(WebAssembly) {
11603 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11604 	enum Key {
11605 		Escape = 0xff1b, ///
11606 		F1 = 0xffbe, ///
11607 		F2 = 0xffbf, ///
11608 		F3 = 0xffc0, ///
11609 		F4 = 0xffc1, ///
11610 		F5 = 0xffc2, ///
11611 		F6 = 0xffc3, ///
11612 		F7 = 0xffc4, ///
11613 		F8 = 0xffc5, ///
11614 		F9 = 0xffc6, ///
11615 		F10 = 0xffc7, ///
11616 		F11 = 0xffc8, ///
11617 		F12 = 0xffc9, ///
11618 		PrintScreen = 0xff61, ///
11619 		ScrollLock = 0xff14, ///
11620 		Pause = 0xff13, ///
11621 		Grave = 0x60, /// The $(BACKTICK) ~ key
11622 		// number keys across the top of the keyboard
11623 		N1 = 0x31, /// Number key atop the keyboard
11624 		N2 = 0x32, ///
11625 		N3 = 0x33, ///
11626 		N4 = 0x34, ///
11627 		N5 = 0x35, ///
11628 		N6 = 0x36, ///
11629 		N7 = 0x37, ///
11630 		N8 = 0x38, ///
11631 		N9 = 0x39, ///
11632 		N0 = 0x30, ///
11633 		Dash = 0x2d, ///
11634 		Equals = 0x3d, ///
11635 		Backslash = 0x5c, /// The \ | key
11636 		Backspace = 0xff08, ///
11637 		Insert = 0xff63, ///
11638 		Home = 0xff50, ///
11639 		PageUp = 0xff55, ///
11640 		Delete = 0xffff, ///
11641 		End = 0xff57, ///
11642 		PageDown = 0xff56, ///
11643 		Up = 0xff52, ///
11644 		Down = 0xff54, ///
11645 		Left = 0xff51, ///
11646 		Right = 0xff53, ///
11647 
11648 		Tab = 0xff09, ///
11649 		Q = 0x71, ///
11650 		W = 0x77, ///
11651 		E = 0x65, ///
11652 		R = 0x72, ///
11653 		T = 0x74, ///
11654 		Y = 0x79, ///
11655 		U = 0x75, ///
11656 		I = 0x69, ///
11657 		O = 0x6f, ///
11658 		P = 0x70, ///
11659 		LeftBracket = 0x5b, /// the [ { key
11660 		RightBracket = 0x5d, /// the ] } key
11661 		CapsLock = 0xffe5, ///
11662 		A = 0x61, ///
11663 		S = 0x73, ///
11664 		D = 0x64, ///
11665 		F = 0x66, ///
11666 		G = 0x67, ///
11667 		H = 0x68, ///
11668 		J = 0x6a, ///
11669 		K = 0x6b, ///
11670 		L = 0x6c, ///
11671 		Semicolon = 0x3b, ///
11672 		Apostrophe = 0x27, ///
11673 		Enter = 0xff0d, ///
11674 		Shift = 0xffe1, ///
11675 		Z = 0x7a, ///
11676 		X = 0x78, ///
11677 		C = 0x63, ///
11678 		V = 0x76, ///
11679 		B = 0x62, ///
11680 		N = 0x6e, ///
11681 		M = 0x6d, ///
11682 		Comma = 0x2c, ///
11683 		Period = 0x2e, ///
11684 		Slash = 0x2f, /// the / ? key
11685 		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
11686 		Ctrl = 0xffe3, ///
11687 		Windows = 0xffeb, ///
11688 		Alt = 0xffe9, ///
11689 		Space = 0x20, ///
11690 		Alt_r = 0xffea, /// ditto of shift_r
11691 		Windows_r = 0xffec, ///
11692 		Menu = 0xff67, ///
11693 		Ctrl_r = 0xffe4, ///
11694 
11695 		NumLock = 0xff7f, ///
11696 		Divide = 0xffaf, /// The / key on the number pad
11697 		Multiply = 0xffaa, /// The * key on the number pad
11698 		Minus = 0xffad, /// The - key on the number pad
11699 		Plus = 0xffab, /// The + key on the number pad
11700 		PadEnter = 0xff8d, /// Numberpad enter key
11701 		Pad1 = 0xff9c, /// Numberpad keys
11702 		Pad2 = 0xff99, ///
11703 		Pad3 = 0xff9b, ///
11704 		Pad4 = 0xff96, ///
11705 		Pad5 = 0xff9d, ///
11706 		Pad6 = 0xff98, ///
11707 		Pad7 = 0xff95, ///
11708 		Pad8 = 0xff97, ///
11709 		Pad9 = 0xff9a, ///
11710 		Pad0 = 0xff9e, ///
11711 		PadDot = 0xff9f, ///
11712 	}
11713 } version(X11) {
11714 	// FIXME: match ASCII whenever we can. Most of it is already there,
11715 	// but there's a few exceptions and mismatches with Windows
11716 
11717 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
11718 	enum Key {
11719 		Escape = 0xff1b, ///
11720 		F1 = 0xffbe, ///
11721 		F2 = 0xffbf, ///
11722 		F3 = 0xffc0, ///
11723 		F4 = 0xffc1, ///
11724 		F5 = 0xffc2, ///
11725 		F6 = 0xffc3, ///
11726 		F7 = 0xffc4, ///
11727 		F8 = 0xffc5, ///
11728 		F9 = 0xffc6, ///
11729 		F10 = 0xffc7, ///
11730 		F11 = 0xffc8, ///
11731 		F12 = 0xffc9, ///
11732 		PrintScreen = 0xff61, ///
11733 		ScrollLock = 0xff14, ///
11734 		Pause = 0xff13, ///
11735 		Grave = 0x60, /// The $(BACKTICK) ~ key
11736 		// number keys across the top of the keyboard
11737 		N1 = 0x31, /// Number key atop the keyboard
11738 		N2 = 0x32, ///
11739 		N3 = 0x33, ///
11740 		N4 = 0x34, ///
11741 		N5 = 0x35, ///
11742 		N6 = 0x36, ///
11743 		N7 = 0x37, ///
11744 		N8 = 0x38, ///
11745 		N9 = 0x39, ///
11746 		N0 = 0x30, ///
11747 		Dash = 0x2d, ///
11748 		Equals = 0x3d, ///
11749 		Backslash = 0x5c, /// The \ | key
11750 		Backspace = 0xff08, ///
11751 		Insert = 0xff63, ///
11752 		Home = 0xff50, ///
11753 		PageUp = 0xff55, ///
11754 		Delete = 0xffff, ///
11755 		End = 0xff57, ///
11756 		PageDown = 0xff56, ///
11757 		Up = 0xff52, ///
11758 		Down = 0xff54, ///
11759 		Left = 0xff51, ///
11760 		Right = 0xff53, ///
11761 
11762 		Tab = 0xff09, ///
11763 		Q = 0x71, ///
11764 		W = 0x77, ///
11765 		E = 0x65, ///
11766 		R = 0x72, ///
11767 		T = 0x74, ///
11768 		Y = 0x79, ///
11769 		U = 0x75, ///
11770 		I = 0x69, ///
11771 		O = 0x6f, ///
11772 		P = 0x70, ///
11773 		LeftBracket = 0x5b, /// the [ { key
11774 		RightBracket = 0x5d, /// the ] } key
11775 		CapsLock = 0xffe5, ///
11776 		A = 0x61, ///
11777 		S = 0x73, ///
11778 		D = 0x64, ///
11779 		F = 0x66, ///
11780 		G = 0x67, ///
11781 		H = 0x68, ///
11782 		J = 0x6a, ///
11783 		K = 0x6b, ///
11784 		L = 0x6c, ///
11785 		Semicolon = 0x3b, ///
11786 		Apostrophe = 0x27, ///
11787 		Enter = 0xff0d, ///
11788 		Shift = 0xffe1, ///
11789 		Z = 0x7a, ///
11790 		X = 0x78, ///
11791 		C = 0x63, ///
11792 		V = 0x76, ///
11793 		B = 0x62, ///
11794 		N = 0x6e, ///
11795 		M = 0x6d, ///
11796 		Comma = 0x2c, ///
11797 		Period = 0x2e, ///
11798 		Slash = 0x2f, /// the / ? key
11799 		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
11800 		Ctrl = 0xffe3, ///
11801 		Windows = 0xffeb, ///
11802 		Alt = 0xffe9, ///
11803 		Space = 0x20, ///
11804 		Alt_r = 0xffea, /// ditto of shift_r
11805 		Windows_r = 0xffec, ///
11806 		Menu = 0xff67, ///
11807 		Ctrl_r = 0xffe4, ///
11808 
11809 		NumLock = 0xff7f, ///
11810 		Divide = 0xffaf, /// The / key on the number pad
11811 		Multiply = 0xffaa, /// The * key on the number pad
11812 		Minus = 0xffad, /// The - key on the number pad
11813 		Plus = 0xffab, /// The + key on the number pad
11814 		PadEnter = 0xff8d, /// Numberpad enter key
11815 		Pad1 = 0xff9c, /// Numberpad keys
11816 		Pad2 = 0xff99, ///
11817 		Pad3 = 0xff9b, ///
11818 		Pad4 = 0xff96, ///
11819 		Pad5 = 0xff9d, ///
11820 		Pad6 = 0xff98, ///
11821 		Pad7 = 0xff95, ///
11822 		Pad8 = 0xff97, ///
11823 		Pad9 = 0xff9a, ///
11824 		Pad0 = 0xff9e, ///
11825 		PadDot = 0xff9f, ///
11826 	}
11827 } else version(Windows) {
11828 	// the character here is for en-us layouts and for illustration only
11829 	// if you actually want to get characters, wait for character events
11830 	// (the argument to your event handler is simply a dchar)
11831 	// those will be converted by the OS for the right locale.
11832 
11833 	enum Key {
11834 		Escape = 0x1b,
11835 		F1 = 0x70,
11836 		F2 = 0x71,
11837 		F3 = 0x72,
11838 		F4 = 0x73,
11839 		F5 = 0x74,
11840 		F6 = 0x75,
11841 		F7 = 0x76,
11842 		F8 = 0x77,
11843 		F9 = 0x78,
11844 		F10 = 0x79,
11845 		F11 = 0x7a,
11846 		F12 = 0x7b,
11847 		PrintScreen = 0x2c,
11848 		ScrollLock = 0x91,
11849 		Pause = 0x13,
11850 		Grave = 0xc0,
11851 		// number keys across the top of the keyboard
11852 		N1 = 0x31,
11853 		N2 = 0x32,
11854 		N3 = 0x33,
11855 		N4 = 0x34,
11856 		N5 = 0x35,
11857 		N6 = 0x36,
11858 		N7 = 0x37,
11859 		N8 = 0x38,
11860 		N9 = 0x39,
11861 		N0 = 0x30,
11862 		Dash = 0xbd,
11863 		Equals = 0xbb,
11864 		Backslash = 0xdc,
11865 		Backspace = 0x08,
11866 		Insert = 0x2d,
11867 		Home = 0x24,
11868 		PageUp = 0x21,
11869 		Delete = 0x2e,
11870 		End = 0x23,
11871 		PageDown = 0x22,
11872 		Up = 0x26,
11873 		Down = 0x28,
11874 		Left = 0x25,
11875 		Right = 0x27,
11876 
11877 		Tab = 0x09,
11878 		Q = 0x51,
11879 		W = 0x57,
11880 		E = 0x45,
11881 		R = 0x52,
11882 		T = 0x54,
11883 		Y = 0x59,
11884 		U = 0x55,
11885 		I = 0x49,
11886 		O = 0x4f,
11887 		P = 0x50,
11888 		LeftBracket = 0xdb,
11889 		RightBracket = 0xdd,
11890 		CapsLock = 0x14,
11891 		A = 0x41,
11892 		S = 0x53,
11893 		D = 0x44,
11894 		F = 0x46,
11895 		G = 0x47,
11896 		H = 0x48,
11897 		J = 0x4a,
11898 		K = 0x4b,
11899 		L = 0x4c,
11900 		Semicolon = 0xba,
11901 		Apostrophe = 0xde,
11902 		Enter = 0x0d,
11903 		Shift = 0x10,
11904 		Z = 0x5a,
11905 		X = 0x58,
11906 		C = 0x43,
11907 		V = 0x56,
11908 		B = 0x42,
11909 		N = 0x4e,
11910 		M = 0x4d,
11911 		Comma = 0xbc,
11912 		Period = 0xbe,
11913 		Slash = 0xbf,
11914 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
11915 		Ctrl = 0x11,
11916 		Windows = 0x5b,
11917 		Alt = -5, // FIXME
11918 		Space = 0x20,
11919 		Alt_r = 0xffea, // ditto of shift_r
11920 		Windows_r = 0x5c, // ditto of shift_r
11921 		Menu = 0x5d,
11922 		Ctrl_r = 0xa3, // ditto of shift_r
11923 
11924 		NumLock = 0x90,
11925 		Divide = 0x6f,
11926 		Multiply = 0x6a,
11927 		Minus = 0x6d,
11928 		Plus = 0x6b,
11929 		PadEnter = -8, // FIXME
11930 		Pad1 = 0x61,
11931 		Pad2 = 0x62,
11932 		Pad3 = 0x63,
11933 		Pad4 = 0x64,
11934 		Pad5 = 0x65,
11935 		Pad6 = 0x66,
11936 		Pad7 = 0x67,
11937 		Pad8 = 0x68,
11938 		Pad9 = 0x69,
11939 		Pad0 = 0x60,
11940 		PadDot = 0x6e,
11941 	}
11942 
11943 	// I'm keeping this around for reference purposes
11944 	// ideally all these buttons will be listed for all platforms,
11945 	// but now now I'm just focusing on my US keyboard
11946 	version(none)
11947 	enum Key {
11948 		LBUTTON = 0x01,
11949 		RBUTTON = 0x02,
11950 		CANCEL = 0x03,
11951 		MBUTTON = 0x04,
11952 		//static if (_WIN32_WINNT > =  0x500) {
11953 		XBUTTON1 = 0x05,
11954 		XBUTTON2 = 0x06,
11955 		//}
11956 		BACK = 0x08,
11957 		TAB = 0x09,
11958 		CLEAR = 0x0C,
11959 		RETURN = 0x0D,
11960 		SHIFT = 0x10,
11961 		CONTROL = 0x11,
11962 		MENU = 0x12,
11963 		PAUSE = 0x13,
11964 		CAPITAL = 0x14,
11965 		KANA = 0x15,
11966 		HANGEUL = 0x15,
11967 		HANGUL = 0x15,
11968 		JUNJA = 0x17,
11969 		FINAL = 0x18,
11970 		HANJA = 0x19,
11971 		KANJI = 0x19,
11972 		ESCAPE = 0x1B,
11973 		CONVERT = 0x1C,
11974 		NONCONVERT = 0x1D,
11975 		ACCEPT = 0x1E,
11976 		MODECHANGE = 0x1F,
11977 		SPACE = 0x20,
11978 		PRIOR = 0x21,
11979 		NEXT = 0x22,
11980 		END = 0x23,
11981 		HOME = 0x24,
11982 		LEFT = 0x25,
11983 		UP = 0x26,
11984 		RIGHT = 0x27,
11985 		DOWN = 0x28,
11986 		SELECT = 0x29,
11987 		PRINT = 0x2A,
11988 		EXECUTE = 0x2B,
11989 		SNAPSHOT = 0x2C,
11990 		INSERT = 0x2D,
11991 		DELETE = 0x2E,
11992 		HELP = 0x2F,
11993 		LWIN = 0x5B,
11994 		RWIN = 0x5C,
11995 		APPS = 0x5D,
11996 		SLEEP = 0x5F,
11997 		NUMPAD0 = 0x60,
11998 		NUMPAD1 = 0x61,
11999 		NUMPAD2 = 0x62,
12000 		NUMPAD3 = 0x63,
12001 		NUMPAD4 = 0x64,
12002 		NUMPAD5 = 0x65,
12003 		NUMPAD6 = 0x66,
12004 		NUMPAD7 = 0x67,
12005 		NUMPAD8 = 0x68,
12006 		NUMPAD9 = 0x69,
12007 		MULTIPLY = 0x6A,
12008 		ADD = 0x6B,
12009 		SEPARATOR = 0x6C,
12010 		SUBTRACT = 0x6D,
12011 		DECIMAL = 0x6E,
12012 		DIVIDE = 0x6F,
12013 		F1 = 0x70,
12014 		F2 = 0x71,
12015 		F3 = 0x72,
12016 		F4 = 0x73,
12017 		F5 = 0x74,
12018 		F6 = 0x75,
12019 		F7 = 0x76,
12020 		F8 = 0x77,
12021 		F9 = 0x78,
12022 		F10 = 0x79,
12023 		F11 = 0x7A,
12024 		F12 = 0x7B,
12025 		F13 = 0x7C,
12026 		F14 = 0x7D,
12027 		F15 = 0x7E,
12028 		F16 = 0x7F,
12029 		F17 = 0x80,
12030 		F18 = 0x81,
12031 		F19 = 0x82,
12032 		F20 = 0x83,
12033 		F21 = 0x84,
12034 		F22 = 0x85,
12035 		F23 = 0x86,
12036 		F24 = 0x87,
12037 		NUMLOCK = 0x90,
12038 		SCROLL = 0x91,
12039 		LSHIFT = 0xA0,
12040 		RSHIFT = 0xA1,
12041 		LCONTROL = 0xA2,
12042 		RCONTROL = 0xA3,
12043 		LMENU = 0xA4,
12044 		RMENU = 0xA5,
12045 		//static if (_WIN32_WINNT > =  0x500) {
12046 		BROWSER_BACK = 0xA6,
12047 		BROWSER_FORWARD = 0xA7,
12048 		BROWSER_REFRESH = 0xA8,
12049 		BROWSER_STOP = 0xA9,
12050 		BROWSER_SEARCH = 0xAA,
12051 		BROWSER_FAVORITES = 0xAB,
12052 		BROWSER_HOME = 0xAC,
12053 		VOLUME_MUTE = 0xAD,
12054 		VOLUME_DOWN = 0xAE,
12055 		VOLUME_UP = 0xAF,
12056 		MEDIA_NEXT_TRACK = 0xB0,
12057 		MEDIA_PREV_TRACK = 0xB1,
12058 		MEDIA_STOP = 0xB2,
12059 		MEDIA_PLAY_PAUSE = 0xB3,
12060 		LAUNCH_MAIL = 0xB4,
12061 		LAUNCH_MEDIA_SELECT = 0xB5,
12062 		LAUNCH_APP1 = 0xB6,
12063 		LAUNCH_APP2 = 0xB7,
12064 		//}
12065 		OEM_1 = 0xBA,
12066 		//static if (_WIN32_WINNT > =  0x500) {
12067 		OEM_PLUS = 0xBB,
12068 		OEM_COMMA = 0xBC,
12069 		OEM_MINUS = 0xBD,
12070 		OEM_PERIOD = 0xBE,
12071 		//}
12072 		OEM_2 = 0xBF,
12073 		OEM_3 = 0xC0,
12074 		OEM_4 = 0xDB,
12075 		OEM_5 = 0xDC,
12076 		OEM_6 = 0xDD,
12077 		OEM_7 = 0xDE,
12078 		OEM_8 = 0xDF,
12079 		//static if (_WIN32_WINNT > =  0x500) {
12080 		OEM_102 = 0xE2,
12081 		//}
12082 		PROCESSKEY = 0xE5,
12083 		//static if (_WIN32_WINNT > =  0x500) {
12084 		PACKET = 0xE7,
12085 		//}
12086 		ATTN = 0xF6,
12087 		CRSEL = 0xF7,
12088 		EXSEL = 0xF8,
12089 		EREOF = 0xF9,
12090 		PLAY = 0xFA,
12091 		ZOOM = 0xFB,
12092 		NONAME = 0xFC,
12093 		PA1 = 0xFD,
12094 		OEM_CLEAR = 0xFE,
12095 	}
12096 
12097 } else version(OSXCocoa) {
12098 	enum Key {
12099 		Escape = 53,
12100 		F1 = 122,
12101 		F2 = 120,
12102 		F3 = 99,
12103 		F4 = 118,
12104 		F5 = 96,
12105 		F6 = 97,
12106 		F7 = 98,
12107 		F8 = 100,
12108 		F9 = 101,
12109 		F10 = 109,
12110 		F11 = 103,
12111 		F12 = 111,
12112 		PrintScreen = 105,
12113 		ScrollLock = 107,
12114 		Pause = 113,
12115 		Grave = 50,
12116 		// number keys across the top of the keyboard
12117 		N1 = 18,
12118 		N2 = 19,
12119 		N3 = 20,
12120 		N4 = 21,
12121 		N5 = 23,
12122 		N6 = 22,
12123 		N7 = 26,
12124 		N8 = 28,
12125 		N9 = 25,
12126 		N0 = 29,
12127 		Dash = 27,
12128 		Equals = 24,
12129 		Backslash = 42,
12130 		Backspace = 51,
12131 		Insert = 114,
12132 		Home = 115,
12133 		PageUp = 116,
12134 		Delete = 117,
12135 		End = 119,
12136 		PageDown = 121,
12137 		Up = 126,
12138 		Down = 125,
12139 		Left = 123,
12140 		Right = 124,
12141 
12142 		Tab = 48,
12143 		Q = 12,
12144 		W = 13,
12145 		E = 14,
12146 		R = 15,
12147 		T = 17,
12148 		Y = 16,
12149 		U = 32,
12150 		I = 34,
12151 		O = 31,
12152 		P = 35,
12153 		LeftBracket = 33,
12154 		RightBracket = 30,
12155 		CapsLock = 57,
12156 		A = 0,
12157 		S = 1,
12158 		D = 2,
12159 		F = 3,
12160 		G = 5,
12161 		H = 4,
12162 		J = 38,
12163 		K = 40,
12164 		L = 37,
12165 		Semicolon = 41,
12166 		Apostrophe = 39,
12167 		Enter = 36,
12168 		Shift = 56,
12169 		Z = 6,
12170 		X = 7,
12171 		C = 8,
12172 		V = 9,
12173 		B = 11,
12174 		N = 45,
12175 		M = 46,
12176 		Comma = 43,
12177 		Period = 47,
12178 		Slash = 44,
12179 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
12180 		Ctrl = 59,
12181 		Windows = 55,
12182 		Alt = 58,
12183 		Space = 49,
12184 		Alt_r = -3, // ditto of shift_r
12185 		Windows_r = -2,
12186 		Menu = 110,
12187 		Ctrl_r = -1,
12188 
12189 		NumLock = 1,
12190 		Divide = 75,
12191 		Multiply = 67,
12192 		Minus = 78,
12193 		Plus = 69,
12194 		PadEnter = 76,
12195 		Pad1 = 83,
12196 		Pad2 = 84,
12197 		Pad3 = 85,
12198 		Pad4 = 86,
12199 		Pad5 = 87,
12200 		Pad6 = 88,
12201 		Pad7 = 89,
12202 		Pad8 = 91,
12203 		Pad9 = 92,
12204 		Pad0 = 82,
12205 		PadDot = 65,
12206 	}
12207 
12208 }
12209 
12210 char keyToLetterCharAssumingLotsOfThingsThatYouMightBetterNotAssume(Key key) {
12211 	version(OSXCocoa) {
12212 		return char.init; // FIXME
12213 	} else {
12214 		return cast(char)(key - Key.A + 'a');
12215 	}
12216 }
12217 
12218 /* Additional utilities */
12219 
12220 
12221 Color fromHsl(real h, real s, real l) {
12222 	return arsd.color.fromHsl([h,s,l]);
12223 }
12224 
12225 
12226 
12227 /* ********** What follows is the system-specific implementations *********/
12228 version(Windows) {
12229 
12230 
12231 	// helpers for making HICONs from MemoryImages
12232 	class WindowsIcon {
12233 		struct Win32Icon {
12234 			align(1):
12235 			uint biSize;
12236 			int biWidth;
12237 			int biHeight;
12238 			ushort biPlanes;
12239 			ushort biBitCount;
12240 			uint biCompression;
12241 			uint biSizeImage;
12242 			int biXPelsPerMeter;
12243 			int biYPelsPerMeter;
12244 			uint biClrUsed;
12245 			uint biClrImportant;
12246 			// RGBQUAD[colorCount] biColors;
12247 			/* Pixels:
12248 			Uint8 pixels[]
12249 			*/
12250 			/* Mask:
12251 			Uint8 mask[]
12252 			*/
12253 		}
12254 
12255 		ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
12256 
12257 			assert(mi.width <= 256, "image too wide");
12258 			assert(mi.height <= 256, "image too tall");
12259 			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
12260 			assert(mi.height % 4 == 0, "image not multiple of 4 height");
12261 
12262 			int icon_plen = mi.width * mi.height * 4;
12263 			int icon_mlen = mi.width * mi.height / 8;
12264 
12265 			int colorCount = 0;
12266 			icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
12267 
12268 			ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen);
12269 			Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr;
12270 
12271 			auto data = memory[Win32Icon.sizeof .. $];
12272 
12273 			width = mi.width;
12274 			height = mi.height;
12275 
12276 			auto trueColorImage = mi.getAsTrueColorImage();
12277 
12278 			icon_win32.biSize = 40;
12279 			icon_win32.biWidth = mi.width;
12280 			icon_win32.biHeight = mi.height*2;
12281 			icon_win32.biPlanes = 1;
12282 			icon_win32.biBitCount = 32;
12283 			icon_win32.biSizeImage = icon_plen + icon_mlen;
12284 
12285 			int offset = 0;
12286 			int andOff = icon_plen * 8; // the and offset is in bits
12287 
12288 			// leaving the and mask as the default 0 so the rgba alpha blend
12289 			// does its thing instead
12290 			for(int y = height - 1; y >= 0; y--) {
12291 				int off2 = y * width * 4;
12292 				foreach(x; 0 .. width) {
12293 					data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0];
12294 					data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1];
12295 					data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2];
12296 					data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3];
12297 
12298 					offset += 4;
12299 					off2 += 4;
12300 				}
12301 			}
12302 
12303 			return memory;
12304 		}
12305 
12306 		this(MemoryImage mi) {
12307 			int icon_len, width, height;
12308 
12309 			auto icon_win32 = fromMemoryImage(mi, icon_len, width, height);
12310 
12311 			/*
12312 			PNG* png = readPnpngData);
12313 			PNGHeader pngh = getHeader(png);
12314 			void* icon_win32;
12315 			if(pngh.depth == 4) {
12316 				auto i = new Win32Icon!(16);
12317 				i.fromPNG(png, pngh, icon_len, width, height);
12318 				icon_win32 = i;
12319 			}
12320 			else if(pngh.depth == 8) {
12321 				auto i = new Win32Icon!(256);
12322 				i.fromPNG(png, pngh, icon_len, width, height);
12323 				icon_win32 = i;
12324 			} else assert(0);
12325 			*/
12326 
12327 			hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0);
12328 
12329 			if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError());
12330 		}
12331 
12332 		~this() {
12333 			if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc
12334 			DestroyIcon(hIcon);
12335 		}
12336 
12337 		HICON hIcon;
12338 	}
12339 
12340 
12341 
12342 
12343 
12344 
12345 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
12346 	alias HWND NativeWindowHandle;
12347 
12348 	extern(Windows)
12349 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
12350 		try {
12351 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
12352 				// it returns zero if the message is handled, so we won't do anything more there
12353 				// do I like that though?
12354 				int mustReturn;
12355 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
12356 				if(mustReturn)
12357 					return ret;
12358 			}
12359 
12360 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
12361 				if(window.getNativeEventHandler !is null) {
12362 					int mustReturn;
12363 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
12364 					if(mustReturn)
12365 						return ret;
12366 				}
12367 				if(auto w = cast(SimpleWindow) (*window))
12368 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
12369 				else
12370 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
12371 			} else {
12372 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
12373 			}
12374 		} catch (Exception e) {
12375 			try {
12376 				sdpy_abort(e);
12377 				return 0;
12378 			} catch(Exception e) { assert(0); }
12379 		}
12380 	}
12381 
12382 	void sdpy_abort(Throwable e) nothrow {
12383 		try
12384 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
12385 		catch(Exception e)
12386 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
12387 		ExitProcess(1);
12388 	}
12389 
12390 	mixin template NativeScreenPainterImplementation() {
12391 		HDC hdc;
12392 		HWND hwnd;
12393 		//HDC windowHdc;
12394 		HBITMAP oldBmp;
12395 
12396 		void create(PaintingHandle window) {
12397 			hwnd = window;
12398 
12399 			if(auto sw = cast(SimpleWindow) this.window) {
12400 				// drawing on a window, double buffer
12401 				auto windowHdc = GetDC(hwnd);
12402 
12403 				auto buffer = sw.impl.buffer;
12404 				if(buffer is null) {
12405 					hdc = windowHdc;
12406 					windowDc = true;
12407 				} else {
12408 					hdc = CreateCompatibleDC(windowHdc);
12409 
12410 					ReleaseDC(hwnd, windowHdc);
12411 
12412 					oldBmp = SelectObject(hdc, buffer);
12413 				}
12414 			} else {
12415 				// drawing on something else, draw directly
12416 				hdc = CreateCompatibleDC(null);
12417 				SelectObject(hdc, window);
12418 			}
12419 
12420 			// X doesn't draw a text background, so neither should we
12421 			SetBkMode(hdc, TRANSPARENT);
12422 
12423 			ensureDefaultFontLoaded();
12424 
12425 			if(defaultGuiFont) {
12426 				SelectObject(hdc, defaultGuiFont);
12427 				// DeleteObject(defaultGuiFont);
12428 			}
12429 		}
12430 
12431 		static HFONT defaultGuiFont;
12432 		static void ensureDefaultFontLoaded() {
12433 			static bool triedDefaultGuiFont = false;
12434 			if(!triedDefaultGuiFont) {
12435 				NONCLIENTMETRICS params;
12436 				params.cbSize = params.sizeof;
12437 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
12438 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
12439 				}
12440 				triedDefaultGuiFont = true;
12441 			}
12442 		}
12443 
12444 		private OperatingSystemFont _activeFont;
12445 
12446 		void setFont(OperatingSystemFont font) {
12447 			_activeFont = font;
12448 			if(font && font.font) {
12449 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
12450 					// error... how to handle tho?
12451 				} else {
12452 
12453 				}
12454 			}
12455 			else if(defaultGuiFont)
12456 				SelectObject(hdc, defaultGuiFont);
12457 		}
12458 
12459 		arsd.color.Rectangle _clipRectangle;
12460 
12461 		void setClipRectangle(int x, int y, int width, int height) {
12462 			auto old = _clipRectangle;
12463 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
12464 			if(old == _clipRectangle)
12465 				return;
12466 
12467 			if(width == 0 || height == 0) {
12468 				SelectClipRgn(hdc, null);
12469 			} else {
12470 				auto region = CreateRectRgn(x, y, x + width, y + height);
12471 				SelectClipRgn(hdc, region);
12472 				DeleteObject(region);
12473 			}
12474 		}
12475 
12476 
12477 		// just because we can on Windows...
12478 		//void create(Image image);
12479 
12480 		void invalidateRect(Rectangle invalidRect) {
12481 			RECT rect;
12482 			rect.left = invalidRect.left;
12483 			rect.right = invalidRect.right;
12484 			rect.top = invalidRect.top;
12485 			rect.bottom = invalidRect.bottom;
12486 			InvalidateRect(hwnd, &rect, false);
12487 		}
12488 		bool manualInvalidations;
12489 
12490 		void dispose() {
12491 			// FIXME: this.window.width/height is probably wrong
12492 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
12493 			// ReleaseDC(hwnd, windowHdc);
12494 
12495 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
12496 			if(cast(SimpleWindow) this.window) {
12497 				if(!manualInvalidations)
12498 					InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
12499 			}
12500 
12501 			if(originalPen !is null)
12502 				SelectObject(hdc, originalPen);
12503 			if(currentPen !is null)
12504 				DeleteObject(currentPen);
12505 			if(originalBrush !is null)
12506 				SelectObject(hdc, originalBrush);
12507 			if(currentBrush !is null)
12508 				DeleteObject(currentBrush);
12509 
12510 			SelectObject(hdc, oldBmp);
12511 
12512 			if(windowDc)
12513 				ReleaseDC(hwnd, hdc);
12514 			else
12515 				DeleteDC(hdc);
12516 
12517 			if(window.paintingFinishedDg !is null)
12518 				window.paintingFinishedDg()();
12519 		}
12520 
12521 		bool windowDc;
12522 		HPEN originalPen;
12523 		HPEN currentPen;
12524 
12525 		Pen _activePen;
12526 
12527 		Color _outlineColor;
12528 
12529 		@property void pen(Pen p) {
12530 			_activePen = p;
12531 			_outlineColor = p.color;
12532 
12533 			HPEN pen;
12534 			if(p.color.a == 0) {
12535 				pen = GetStockObject(NULL_PEN);
12536 			} else {
12537 				int style = PS_SOLID;
12538 				final switch(p.style) {
12539 					case Pen.Style.Solid:
12540 						style = PS_SOLID;
12541 					break;
12542 					case Pen.Style.Dashed:
12543 						style = PS_DASH;
12544 					break;
12545 					case Pen.Style.Dotted:
12546 						style = PS_DOT;
12547 					break;
12548 				}
12549 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
12550 			}
12551 			auto orig = SelectObject(hdc, pen);
12552 			if(originalPen is null)
12553 				originalPen = orig;
12554 
12555 			if(currentPen !is null)
12556 				DeleteObject(currentPen);
12557 
12558 			currentPen = pen;
12559 
12560 			// the outline is like a foreground since it's done that way on X
12561 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
12562 
12563 		}
12564 
12565 		@property void rasterOp(RasterOp op) {
12566 			int mode;
12567 			final switch(op) {
12568 				case RasterOp.normal:
12569 					mode = R2_COPYPEN;
12570 				break;
12571 				case RasterOp.xor:
12572 					mode = R2_XORPEN;
12573 				break;
12574 			}
12575 			SetROP2(hdc, mode);
12576 		}
12577 
12578 		HBRUSH originalBrush;
12579 		HBRUSH currentBrush;
12580 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
12581 		@property void fillColor(Color c) {
12582 			if(c == _fillColor)
12583 				return;
12584 			_fillColor = c;
12585 			HBRUSH brush;
12586 			if(c.a == 0) {
12587 				brush = GetStockObject(HOLLOW_BRUSH);
12588 			} else {
12589 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
12590 			}
12591 			auto orig = SelectObject(hdc, brush);
12592 			if(originalBrush is null)
12593 				originalBrush = orig;
12594 
12595 			if(currentBrush !is null)
12596 				DeleteObject(currentBrush);
12597 
12598 			currentBrush = brush;
12599 
12600 			// background color is NOT set because X doesn't draw text backgrounds
12601 			//   SetBkColor(hdc, RGB(255, 255, 255));
12602 		}
12603 
12604 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
12605 			BITMAP bm;
12606 
12607 			HDC hdcMem = CreateCompatibleDC(hdc);
12608 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
12609 
12610 			GetObject(i.handle, bm.sizeof, &bm);
12611 
12612 			// or should I AlphaBlend!??!?!
12613 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
12614 
12615 			SelectObject(hdcMem, hbmOld);
12616 			DeleteDC(hdcMem);
12617 		}
12618 
12619 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12620 			BITMAP bm;
12621 
12622 			HDC hdcMem = CreateCompatibleDC(hdc);
12623 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
12624 
12625 			GetObject(s.handle, bm.sizeof, &bm);
12626 
12627 			version(CRuntime_DigitalMars) goto noalpha;
12628 
12629 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
12630 			if(s.enableAlpha) {
12631 				auto dw = w ? w : bm.bmWidth;
12632 				auto dh = h ? h : bm.bmHeight;
12633 				BLENDFUNCTION bf;
12634 				bf.BlendOp = AC_SRC_OVER;
12635 				bf.SourceConstantAlpha = 255;
12636 				bf.AlphaFormat = AC_SRC_ALPHA;
12637 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
12638 			} else {
12639 				noalpha:
12640 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
12641 			}
12642 
12643 			SelectObject(hdcMem, hbmOld);
12644 			DeleteDC(hdcMem);
12645 		}
12646 
12647 		Size textSize(scope const(char)[] text) {
12648 			bool dummyX;
12649 			if(text.length == 0) {
12650 				text = " ";
12651 				dummyX = true;
12652 			}
12653 			RECT rect;
12654 			WCharzBuffer buffer = WCharzBuffer(text);
12655 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
12656 			return Size(dummyX ? 0 : rect.right, rect.bottom);
12657 		}
12658 
12659 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
12660 			if(text.length && text[$-1] == '\n')
12661 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
12662 			if(text.length && text[$-1] == '\r')
12663 				text = text[0 .. $-1];
12664 
12665 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
12666 			if(x2 == 0 && y2 == 0) {
12667 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
12668 			} else {
12669 				RECT rect;
12670 				rect.left = x;
12671 				rect.top = y;
12672 				rect.right = x2;
12673 				rect.bottom = y2;
12674 
12675 				uint mode = DT_LEFT;
12676 				if(alignment & TextAlignment.Right)
12677 					mode = DT_RIGHT;
12678 				else if(alignment & TextAlignment.Center)
12679 					mode = DT_CENTER;
12680 
12681 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
12682 				if(alignment & TextAlignment.VerticalCenter)
12683 					mode |= DT_VCENTER | DT_SINGLELINE;
12684 
12685 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
12686 			}
12687 
12688 			/*
12689 			uint mode;
12690 
12691 			if(alignment & TextAlignment.Center)
12692 				mode = TA_CENTER;
12693 
12694 			SetTextAlign(hdc, mode);
12695 			*/
12696 		}
12697 
12698 		int fontHeight() {
12699 			TEXTMETRIC metric;
12700 			if(GetTextMetricsW(hdc, &metric)) {
12701 				return metric.tmHeight;
12702 			}
12703 
12704 			return 16; // idk just guessing here, maybe we should throw
12705 		}
12706 
12707 		void drawPixel(int x, int y) {
12708 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
12709 		}
12710 
12711 		// The basic shapes, outlined
12712 
12713 		void drawLine(int x1, int y1, int x2, int y2) {
12714 			MoveToEx(hdc, x1, y1, null);
12715 			LineTo(hdc, x2, y2);
12716 		}
12717 
12718 		void drawRectangle(int x, int y, int width, int height) {
12719 			// FIXME: with a wider pen this might not draw quite right. im not sure.
12720 			gdi.Rectangle(hdc, x, y, x + width, y + height);
12721 		}
12722 
12723 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
12724 			RoundRect(
12725 				hdc,
12726 				upperLeft.x, upperLeft.y,
12727 				lowerRight.x, lowerRight.y,
12728 				borderRadius, borderRadius
12729 			);
12730 		}
12731 
12732 		/// Arguments are the points of the bounding rectangle
12733 		void drawEllipse(int x1, int y1, int x2, int y2) {
12734 			Ellipse(hdc, x1, y1, x2, y2);
12735 		}
12736 
12737 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
12738 			//if(length > 360*64)
12739 				//length = 360*64;
12740 
12741 			if((start == 0 && length == 360*64)) {
12742 				drawEllipse(x1, y1, x1 + width, y1 + height);
12743 			} else {
12744 				import core.stdc.math;
12745 
12746 				bool clockwise = false;
12747 				if(length < 0) {
12748 					clockwise = true;
12749 					length = -length;
12750 				}
12751 
12752 				double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323;
12753 				double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323;
12754 
12755 				auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12756 				auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12757 				auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0);
12758 				auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0);
12759 
12760 				if(clockwise) {
12761 					auto t1 = c1;
12762 					auto t2 = c2;
12763 					c1 = c3;
12764 					c2 = c4;
12765 					c3 = t1;
12766 					c4 = t2;
12767 				}
12768 
12769 				//if(_activePen.color.a)
12770 					//Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12771 				//if(_fillColor.a)
12772 
12773 				Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4);
12774 			}
12775 		}
12776 
12777 		void drawPolygon(Point[] vertexes) {
12778 			POINT[] points;
12779 			points.length = vertexes.length;
12780 
12781 			foreach(i, p; vertexes) {
12782 				points[i].x = p.x;
12783 				points[i].y = p.y;
12784 			}
12785 
12786 			Polygon(hdc, points.ptr, cast(int) points.length);
12787 		}
12788 	}
12789 
12790 
12791 	// Mix this into the SimpleWindow class
12792 	mixin template NativeSimpleWindowImplementation() {
12793 		int curHidden = 0; // counter
12794 		__gshared static bool[string] knownWinClasses;
12795 		static bool altPressed = false;
12796 
12797 		HANDLE oldCursor;
12798 
12799 		void hideCursor () {
12800 			if(curHidden == 0)
12801 				oldCursor = SetCursor(null);
12802 			++curHidden;
12803 		}
12804 
12805 		void showCursor () {
12806 			--curHidden;
12807 			if(curHidden == 0) {
12808 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
12809 			}
12810 		}
12811 
12812 
12813 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
12814 
12815 		void setMinSize (int minwidth, int minheight) {
12816 			minWidth = minwidth;
12817 			minHeight = minheight;
12818 		}
12819 		void setMaxSize (int maxwidth, int maxheight) {
12820 			maxWidth = maxwidth;
12821 			maxHeight = maxheight;
12822 		}
12823 
12824 		// FIXME i'm not sure that Windows has this functionality
12825 		// though it is nonessential anyway.
12826 		void setResizeGranularity (int granx, int grany) {}
12827 
12828 		ScreenPainter getPainter(bool manualInvalidations) {
12829 			return ScreenPainter(this, hwnd, manualInvalidations);
12830 		}
12831 
12832 		HBITMAP buffer;
12833 
12834 		void setTitle(string title) {
12835 			WCharzBuffer bfr = WCharzBuffer(title);
12836 			SetWindowTextW(hwnd, bfr.ptr);
12837 		}
12838 
12839 		string getTitle() {
12840 			auto len = GetWindowTextLengthW(hwnd);
12841 			if (!len)
12842 				return null;
12843 			wchar[256] tmpBuffer;
12844 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
12845 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12846 			auto str = buffer[0 .. len2];
12847 			return makeUtf8StringFromWindowsString(str);
12848 		}
12849 
12850 		void move(int x, int y) {
12851 			RECT rect;
12852 			GetWindowRect(hwnd, &rect);
12853 			// move it while maintaining the same size...
12854 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
12855 		}
12856 
12857 		void resize(int w, int h) {
12858 			RECT rect;
12859 			GetWindowRect(hwnd, &rect);
12860 
12861 			RECT client;
12862 			GetClientRect(hwnd, &client);
12863 
12864 			rect.right = rect.right - client.right + w;
12865 			rect.bottom = rect.bottom - client.bottom + h;
12866 
12867 			// same position, new size for the client rectangle
12868 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
12869 
12870 			updateOpenglViewportIfNeeded(w, h);
12871 		}
12872 
12873 		void moveResize (int x, int y, int w, int h) {
12874 			// what's given is the client rectangle, we need to adjust
12875 
12876 			RECT rect;
12877 			rect.left = x;
12878 			rect.top = y;
12879 			rect.right = w + x;
12880 			rect.bottom = h + y;
12881 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
12882 				throw new WindowsApiException("AdjustWindowRect", GetLastError());
12883 
12884 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
12885 			updateOpenglViewportIfNeeded(w, h);
12886 			if (windowResized !is null) windowResized(w, h);
12887 		}
12888 
12889 		version(without_opengl) {} else {
12890 			HGLRC ghRC;
12891 			HDC ghDC;
12892 		}
12893 
12894 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
12895 			string cnamec;
12896 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
12897 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
12898 				cnamec = "DSimpleWindow";
12899 			} else {
12900 				cnamec = sdpyWindowClass;
12901 			}
12902 
12903 			WCharzBuffer cn = WCharzBuffer(cnamec);
12904 
12905 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
12906 
12907 			if(cnamec !in knownWinClasses) {
12908 				WNDCLASSEX wc;
12909 
12910 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
12911 				// to the object. Maybe.
12912 				wc.cbSize = wc.sizeof;
12913 				wc.cbClsExtra = 0;
12914 				wc.cbWndExtra = 0;
12915 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
12916 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
12917 				wc.hIcon = LoadIcon(hInstance, null);
12918 				wc.hInstance = hInstance;
12919 				wc.lpfnWndProc = &WndProc;
12920 				wc.lpszClassName = cn.ptr;
12921 				wc.hIconSm = null;
12922 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
12923 				if(!RegisterClassExW(&wc))
12924 					throw new WindowsApiException("RegisterClassExW", GetLastError());
12925 				knownWinClasses[cnamec] = true;
12926 			}
12927 
12928 			int style;
12929 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
12930 
12931 			// FIXME: windowType and customizationFlags
12932 			final switch(windowType) {
12933 				case WindowTypes.normal:
12934 					if(resizability == Resizability.fixedSize) {
12935 						style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION;
12936 					} else {
12937 						style = WS_OVERLAPPEDWINDOW;
12938 					}
12939 				break;
12940 				case WindowTypes.undecorated:
12941 					style = WS_POPUP | WS_SYSMENU;
12942 				break;
12943 				case WindowTypes.eventOnly:
12944 					_hidden = true;
12945 				break;
12946 				case WindowTypes.tooltip:
12947 				case WindowTypes.dnd:
12948 				case WindowTypes.comboBoxDropdown:
12949 				case WindowTypes.dropdownMenu:
12950 				case WindowTypes.popupMenu:
12951 				case WindowTypes.notification:
12952 					style = WS_POPUP;
12953 					flags |= WS_EX_NOACTIVATE;
12954 				break;
12955 				case WindowTypes.dialog:
12956 					style = WS_OVERLAPPEDWINDOW;
12957 				break;
12958 				case WindowTypes.nestedChild:
12959 					style = WS_CHILD;
12960 				break;
12961 				case WindowTypes.minimallyWrapped:
12962 					assert(0, "construct minimally wrapped through the other ctor overlad");
12963 			}
12964 
12965 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12966 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
12967 
12968 			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
12969 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
12970 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
12971 
12972 			if(!hwnd)
12973 				throw new WindowsApiException("CreateWindowEx", GetLastError());
12974 
12975 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
12976 				setOpacity(255);
12977 
12978 			SimpleWindow.nativeMapping[hwnd] = this;
12979 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
12980 
12981 			if(windowType == WindowTypes.eventOnly)
12982 				return;
12983 
12984 			HDC hdc = GetDC(hwnd);
12985 
12986 			if(!hdc)
12987 				throw new WindowsApiException("GetDC", GetLastError());
12988 
12989 			version(without_opengl) {}
12990 			else {
12991 				if(opengl == OpenGlOptions.yes) {
12992 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
12993 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
12994 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
12995 					ghDC = hdc;
12996 					PIXELFORMATDESCRIPTOR pfd;
12997 
12998 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
12999 					pfd.nVersion = 1;
13000 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
13001 					pfd.dwLayerMask = PFD_MAIN_PLANE;
13002 					pfd.iPixelType = PFD_TYPE_RGBA;
13003 					pfd.cColorBits = 24;
13004 					pfd.cDepthBits = 24;
13005 					pfd.cAccumBits = 0;
13006 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
13007 
13008 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
13009 
13010 					if (pixelformat == 0)
13011 						throw new WindowsApiException("ChoosePixelFormat", GetLastError());
13012 
13013 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
13014 						throw new WindowsApiException("SetPixelFormat", GetLastError());
13015 
13016 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
13017 						// windoze is idiotic: we have to have OpenGL context to get function addresses
13018 						// so we will create fake context to get that stupid address
13019 						auto tmpcc = wglCreateContext(ghDC);
13020 						if (tmpcc !is null) {
13021 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
13022 							wglMakeCurrent(ghDC, tmpcc);
13023 							wglInitOtherFunctions();
13024 						}
13025 					}
13026 
13027 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
13028 						int[9] contextAttribs = [
13029 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
13030 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
13031 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
13032 							// for modern context, set "forward compatibility" flag too
13033 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
13034 							0/*None*/,
13035 						];
13036 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
13037 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
13038 							// activate fallback mode
13039 							// 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;
13040 							ghRC = wglCreateContext(ghDC);
13041 						}
13042 						if (ghRC is null)
13043 							throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError());
13044 					} else {
13045 						// try to do at least something
13046 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
13047 							sdpyOpenGLContextVersion = 0;
13048 							ghRC = wglCreateContext(ghDC);
13049 						}
13050 						if (ghRC is null)
13051 							throw new WindowsApiException("wglCreateContext", GetLastError());
13052 					}
13053 				}
13054 			}
13055 
13056 			if(opengl == OpenGlOptions.no) {
13057 				buffer = CreateCompatibleBitmap(hdc, width, height);
13058 
13059 				auto hdcBmp = CreateCompatibleDC(hdc);
13060 				// make sure it's filled with a blank slate
13061 				auto oldBmp = SelectObject(hdcBmp, buffer);
13062 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
13063 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
13064 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
13065 				SelectObject(hdcBmp, oldBmp);
13066 				SelectObject(hdcBmp, oldBrush);
13067 				SelectObject(hdcBmp, oldPen);
13068 				DeleteDC(hdcBmp);
13069 
13070 				bmpWidth = width;
13071 				bmpHeight = height;
13072 
13073 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
13074 			}
13075 
13076 			// We want the window's client area to match the image size
13077 			RECT rcClient, rcWindow;
13078 			POINT ptDiff;
13079 			GetClientRect(hwnd, &rcClient);
13080 			GetWindowRect(hwnd, &rcWindow);
13081 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
13082 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
13083 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
13084 
13085 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
13086 				ShowWindow(hwnd, SW_SHOWNORMAL);
13087 			} else {
13088 				_hidden = true;
13089 			}
13090 			this._visibleForTheFirstTimeCalled = false; // hack!
13091 		}
13092 
13093 
13094 		void dispose() {
13095 			if(buffer)
13096 				DeleteObject(buffer);
13097 		}
13098 
13099 		void closeWindow() {
13100 			if(ghRC) {
13101 				wglDeleteContext(ghRC);
13102 				ghRC = null;
13103 			}
13104 			DestroyWindow(hwnd);
13105 		}
13106 
13107 		bool setOpacity(ubyte alpha) {
13108 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
13109 		}
13110 
13111 		HANDLE currentCursor;
13112 
13113 		// returns zero if it recognized the event
13114 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
13115 			MouseEvent mouse;
13116 
13117 			void mouseEvent(bool isScreen, ulong mods) {
13118 				auto x = LOWORD(lParam);
13119 				auto y = HIWORD(lParam);
13120 				if(isScreen) {
13121 					POINT p;
13122 					p.x = x;
13123 					p.y = y;
13124 					ScreenToClient(hwnd, &p);
13125 					x = cast(ushort) p.x;
13126 					y = cast(ushort) p.y;
13127 				}
13128 
13129 				if(wind.resizability == Resizability.automaticallyScaleIfPossible) {
13130 					x = cast(ushort)( x * wind._virtualWidth / wind._width );
13131 					y = cast(ushort)( y * wind._virtualHeight / wind._height );
13132 				}
13133 
13134 				mouse.x = x + offsetX;
13135 				mouse.y = y + offsetY;
13136 
13137 				wind.mdx(mouse);
13138 				mouse.modifierState = cast(int) mods;
13139 				mouse.window = wind;
13140 
13141 				if(wind.handleMouseEvent)
13142 					wind.handleMouseEvent(mouse);
13143 			}
13144 
13145 			switch(msg) {
13146 				case WM_GETMINMAXINFO:
13147 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
13148 
13149 					if(wind.minWidth > 0) {
13150 						RECT rect;
13151 						rect.left = 100;
13152 						rect.top = 100;
13153 						rect.right = wind.minWidth + 100;
13154 						rect.bottom = wind.minHeight + 100;
13155 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
13156 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
13157 
13158 						mmi.ptMinTrackSize.x = rect.right - rect.left;
13159 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
13160 					}
13161 
13162 					if(wind.maxWidth < int.max) {
13163 						RECT rect;
13164 						rect.left = 100;
13165 						rect.top = 100;
13166 						rect.right = wind.maxWidth + 100;
13167 						rect.bottom = wind.maxHeight + 100;
13168 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
13169 							throw new WindowsApiException("AdjustWindowRect", GetLastError());
13170 
13171 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
13172 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
13173 					}
13174 				break;
13175 				case WM_CHAR:
13176 					wchar c = cast(wchar) wParam;
13177 					if(wind.handleCharEvent)
13178 						wind.handleCharEvent(cast(dchar) c);
13179 				break;
13180 				  case WM_SETFOCUS:
13181 				  case WM_KILLFOCUS:
13182 					wind._focused = (msg == WM_SETFOCUS);
13183 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
13184 					if(wind.onFocusChange)
13185 						wind.onFocusChange(msg == WM_SETFOCUS);
13186 				  break;
13187 
13188 				case WM_SYSKEYDOWN:
13189 					goto case;
13190 				case WM_SYSKEYUP:
13191 					if(lParam & (1 << 29)) {
13192 						goto case;
13193 					} else {
13194 						// no window has keyboard focus
13195 						goto default;
13196 					}
13197 				case WM_KEYDOWN:
13198 				case WM_KEYUP:
13199 					KeyEvent ev;
13200 					ev.key = cast(Key) wParam;
13201 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
13202 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
13203 
13204 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
13205 
13206 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
13207 						ev.modifierState |= ModifierState.shift;
13208 					//k8: this doesn't work; thanks for nothing, windows
13209 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
13210 						ev.modifierState |= ModifierState.alt;*/
13211 					// this never seems to actually be set
13212 					// if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
13213 
13214 					if (wParam == 0x12) {
13215 						altPressed = (msg == WM_SYSKEYDOWN);
13216 					}
13217 
13218 					if(msg == WM_KEYDOWN || msg == WM_KEYUP) {
13219 						altPressed = false;
13220 					}
13221 					// sdpyPrintDebugString(altPressed ? "alt down" : " up ");
13222 
13223 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
13224 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
13225 						ev.modifierState |= ModifierState.ctrl;
13226 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
13227 						ev.modifierState |= ModifierState.windows;
13228 					if(GetKeyState(Key.NumLock))
13229 						ev.modifierState |= ModifierState.numLock;
13230 					if(GetKeyState(Key.CapsLock))
13231 						ev.modifierState |= ModifierState.capsLock;
13232 
13233 					/+
13234 					// we always want to send the character too, so let's convert it
13235 					ubyte[256] state;
13236 					wchar[16] buffer;
13237 					GetKeyboardState(state.ptr);
13238 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
13239 
13240 					foreach(dchar d; buffer) {
13241 						ev.character = d;
13242 						break;
13243 					}
13244 					+/
13245 
13246 					ev.window = wind;
13247 					if(wind.handleKeyEvent)
13248 						wind.handleKeyEvent(ev);
13249 				break;
13250 				case 0x0249 /* WM_POINTERENTER */:
13251 				case 0x024A /* WM_POINTERLEAVE */:
13252 				case 0x0246 /* WM_POINTERDOWN */:
13253 				case 0x0247 /* WM_POINTERUP */:
13254 				case 0x0245 /* WM_POINTERUPDATE */:
13255 					//import std.conv; import arsd.core; writeln("update ", LOWORD(wParam), " ", to!string(HIWORD(wParam), 2), " ", cast(short) LOWORD(lParam), "x", cast(short) HIWORD(lParam));
13256 
13257 					auto pointerId = LOWORD(wParam);
13258 					auto flags = HIWORD(wParam);
13259 					auto x = cast(short) LOWORD(lParam);
13260 					auto y = cast(short) HIWORD(lParam);
13261 
13262 					void dispatchIde(InputDeviceEvent ide) {
13263 						ide.event = msg;
13264 						ide.rootX = x;
13265 						ide.rootY = y;
13266 						ide.buttons = 0;
13267 						ide.valuators[] = double.nan;
13268 
13269 						ide.window = wind;
13270 						wind.dispatchXInputEvent(ide);
13271 					}
13272 
13273 					POINTER_INPUT_TYPE pit;
13274 					if(GetPointerType(pointerId, &pit)) {
13275 						switch(pit) {
13276 							case POINTER_INPUT_TYPE.PT_TOUCH:
13277 								POINTER_TOUCH_INFO[16] buffer;
13278 								uint count = cast(uint) buffer.length;
13279 								if(GetPointerFrameTouchInfo(pointerId, &count, buffer.ptr)) {
13280 									auto frame = buffer[0 .. count];
13281 
13282 									foreach(f; frame) {
13283 										InputDeviceEvent ide;
13284 
13285 										// check f.pointerInfo.hwndTarget to translate these correctly...
13286 										ide.windowX = f.pointerInfo.ptPixelLocation.x;
13287 										ide.windowY = f.pointerInfo.ptPixelLocation.y;
13288 										ide.deviceId = f.pointerInfo.sourceDevice;
13289 										ide.detail = pointerId;
13290 										ide.pressure = f.pressure;
13291 
13292 										dispatchIde(ide);
13293 									}
13294 								} else {
13295 									throw new WindowsApiException("GetPointerFrameTouchInfo", GetLastError());
13296 								}
13297 							break;
13298 							case POINTER_INPUT_TYPE.PT_PEN:
13299 								POINTER_PEN_INFO[16] buffer;
13300 								uint count = cast(uint) buffer.length;
13301 								if(GetPointerFramePenInfo(pointerId, &count, buffer.ptr)) {
13302 									auto frame = buffer[0 .. count];
13303 
13304 									foreach(f; frame) {
13305 										InputDeviceEvent ide;
13306 
13307 										// check f.pointerInfo.hwndTarget to translate these correctly...
13308 										ide.windowX = f.pointerInfo.ptPixelLocation.x;
13309 										ide.windowY = f.pointerInfo.ptPixelLocation.y;
13310 										ide.deviceId = f.pointerInfo.sourceDevice;
13311 										ide.detail = pointerId;
13312 										ide.pressure = f.pressure;
13313 										ide.tiltX = f.tiltX;
13314 										ide.tiltY = f.tiltY;
13315 
13316 										dispatchIde(ide);
13317 									}
13318 								} else {
13319 									throw new WindowsApiException("GetPointerFramePenInfo", GetLastError());
13320 								}
13321 							break;
13322 							case POINTER_INPUT_TYPE.PT_MOUSE:
13323 							case POINTER_INPUT_TYPE.PT_TOUCHPAD:
13324 								// generic GetPointerInfo
13325 
13326 							break;
13327 							case POINTER_INPUT_TYPE.PT_POINTER:
13328 								// should never happen according to docs
13329 							default:
13330 						}
13331 					} else {
13332 						throw new WindowsApiException("GetPointerType", GetLastError());
13333 					}
13334 
13335 					//wind.dispatchXInputEvent
13336 
13337 				break;
13338 				case 0x024C /* WM_POINTERCAPTURECHANGED */:
13339 				break;
13340 				case 0x020a /*WM_MOUSEWHEEL*/:
13341 					// send click
13342 					mouse.type = cast(MouseEventType) 1;
13343 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
13344 					mouseEvent(true, LOWORD(wParam));
13345 
13346 					// also send release
13347 					mouse.type = cast(MouseEventType) 2;
13348 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
13349 					mouseEvent(true, LOWORD(wParam));
13350 				break;
13351 				case 0x020E /* WM_MOUSEHWHEEL */:
13352 					// send click
13353 					mouse.type = cast(MouseEventType) 1;
13354 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) < 0) ? MouseButton.wheelLeft : MouseButton.wheelRight);
13355 					mouseEvent(true, LOWORD(wParam));
13356 
13357 					// also send release
13358 					mouse.type = cast(MouseEventType) 2;
13359 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) < 0) ? MouseButton.wheelLeft : MouseButton.wheelRight);
13360 					mouseEvent(true, LOWORD(wParam));
13361 				break;
13362 				case WM_MOUSEMOVE:
13363 					mouse.type = cast(MouseEventType) 0;
13364 					mouseEvent(false, wParam);
13365 				break;
13366 				case WM_LBUTTONDOWN:
13367 				case WM_LBUTTONDBLCLK:
13368 					mouse.type = cast(MouseEventType) 1;
13369 					mouse.button = MouseButton.left;
13370 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
13371 					mouseEvent(false, wParam);
13372 				break;
13373 				case WM_LBUTTONUP:
13374 					mouse.type = cast(MouseEventType) 2;
13375 					mouse.button = MouseButton.left;
13376 					mouseEvent(false, wParam);
13377 				break;
13378 				case WM_RBUTTONDOWN:
13379 				case WM_RBUTTONDBLCLK:
13380 					mouse.type = cast(MouseEventType) 1;
13381 					mouse.button = MouseButton.right;
13382 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
13383 					mouseEvent(false, wParam);
13384 				break;
13385 				case WM_RBUTTONUP:
13386 					mouse.type = cast(MouseEventType) 2;
13387 					mouse.button = MouseButton.right;
13388 					mouseEvent(false, wParam);
13389 				break;
13390 				case WM_MBUTTONDOWN:
13391 				case WM_MBUTTONDBLCLK:
13392 					mouse.type = cast(MouseEventType) 1;
13393 					mouse.button = MouseButton.middle;
13394 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
13395 					mouseEvent(false, wParam);
13396 				break;
13397 				case WM_MBUTTONUP:
13398 					mouse.type = cast(MouseEventType) 2;
13399 					mouse.button = MouseButton.middle;
13400 					mouseEvent(false, wParam);
13401 				break;
13402 				case WM_XBUTTONDOWN:
13403 				case WM_XBUTTONDBLCLK:
13404 					mouse.type = cast(MouseEventType) 1;
13405 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
13406 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
13407 					mouseEvent(false, wParam);
13408 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
13409 				case WM_XBUTTONUP:
13410 					mouse.type = cast(MouseEventType) 2;
13411 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
13412 					mouseEvent(false, wParam);
13413 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
13414 
13415 				default: return 1;
13416 			}
13417 			return 0;
13418 		}
13419 
13420 		HWND hwnd;
13421 		private int oldWidth;
13422 		private int oldHeight;
13423 		private bool inSizeMove;
13424 
13425 		/++
13426 			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.
13427 
13428 			History:
13429 				Added November 23, 2021
13430 
13431 				Not fully stable, may be moved out of the impl struct.
13432 
13433 				Default value changed to `true` on February 15, 2021
13434 		+/
13435 		bool doLiveResizing = true;
13436 
13437 		package int bmpWidth;
13438 		package int bmpHeight;
13439 
13440 		// the extern(Windows) wndproc should just forward to this
13441 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
13442 		try {
13443 			assert(hwnd is this.hwnd);
13444 
13445 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
13446 			switch(msg) {
13447 				case WM_MENUCHAR: // menu active but key not associated with a thing.
13448 					// you would ideally use this for like a search function but sdpy not that ideally designed. alas.
13449 					// The main things we can do are select, execute, close, or ignore
13450 					// the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to
13451 					// the user. This can be a bit annoying for sdpy things so instead im overriding and setting it
13452 					// to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy
13453 					// that's the lesser bad choice rn. Can always override by returning true in triggerEvents....
13454 
13455 					// returns the value in the *high order word* of the return value
13456 					// hence the << 16
13457 					return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user
13458 				case WM_SETCURSOR:
13459 					if(cast(HWND) wParam !is hwnd)
13460 						return 0; // further processing elsewhere
13461 
13462 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
13463 						SetCursor(this.curHidden > 0 ? null : currentCursor);
13464 						return 1;
13465 					} else {
13466 						return DefWindowProc(hwnd, msg, wParam, lParam);
13467 					}
13468 				//break;
13469 
13470 				case WM_CLOSE:
13471 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
13472 				break;
13473 				case WM_DESTROY:
13474 					if (this.visibilityChanged !is null && this._visible) this.visibilityChanged(false);
13475 
13476 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
13477 					SimpleWindow.nativeMapping.remove(hwnd);
13478 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
13479 
13480 					bool anyImportant = false;
13481 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
13482 						if(w.beingOpenKeepsAppOpen) {
13483 							anyImportant = true;
13484 							break;
13485 						}
13486 					if(!anyImportant) {
13487 						PostQuitMessage(0);
13488 					}
13489 				break;
13490 				case 0x02E0 /*WM_DPICHANGED*/:
13491 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
13492 
13493 					RECT* prcNewWindow = cast(RECT*)lParam;
13494 					// docs say this is the recommended position and we should honor it
13495 					SetWindowPos(hwnd,
13496 							null,
13497 							prcNewWindow.left,
13498 							prcNewWindow.top,
13499 							prcNewWindow.right - prcNewWindow.left,
13500 							prcNewWindow.bottom - prcNewWindow.top,
13501 							SWP_NOZORDER | SWP_NOACTIVATE);
13502 
13503 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
13504 					// im not sure it is completely correct
13505 					// but without it the tabs and such do look weird as things change.
13506 					if(SystemParametersInfoForDpi) {
13507 						LOGFONT lfText;
13508 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
13509 						HFONT hFontNew = CreateFontIndirect(&lfText);
13510 						if (hFontNew)
13511 						{
13512 							//DeleteObject(hFontOld);
13513 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
13514 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
13515 								return TRUE;
13516 							}
13517 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
13518 						}
13519 					}
13520 
13521 					if(this.onDpiChanged)
13522 						this.onDpiChanged();
13523 				break;
13524 				case WM_ENTERIDLE:
13525 					// when a menu is up, it stops normal event processing (modal message loop)
13526 					// but this at least gives us a chance to SOMETIMES catch up
13527 					// FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it.
13528 					SimpleWindow.processAllCustomEvents;
13529 					SimpleWindow.processAllCustomEvents;
13530 					SleepEx(0, true);
13531 					break;
13532 				case WM_SIZE:
13533 					if(wParam == 1 /* SIZE_MINIMIZED */)
13534 						break;
13535 					_width = LOWORD(lParam);
13536 					_height = HIWORD(lParam);
13537 
13538 					// I want to avoid tearing in the windows (my code is inefficient
13539 					// so this is a hack around that) so while sizing, we don't trigger,
13540 					// but we do want to trigger on events like mazimize.
13541 					if(!inSizeMove || doLiveResizing)
13542 						goto size_changed;
13543 				break;
13544 				/+
13545 				case WM_SIZING:
13546 					writeln("size");
13547 				break;
13548 				+/
13549 				// I don't like the tearing I get when redrawing on WM_SIZE
13550 				// (I know there's other ways to fix that but I don't like that behavior anyway)
13551 				// so instead it is going to redraw only at the end of a size.
13552 				case 0x0231: /* WM_ENTERSIZEMOVE */
13553 					inSizeMove = true;
13554 				break;
13555 				case 0x0232: /* WM_EXITSIZEMOVE */
13556 					inSizeMove = false;
13557 
13558 					size_changed:
13559 
13560 					// nothing relevant changed, don't bother redrawing
13561 					if(oldWidth == _width && oldHeight == _height) {
13562 						if(msg == 0x0232)
13563 							goto finalize_resize;
13564 						break;
13565 					}
13566 
13567 					// note: OpenGL windows don't use a backing bmp, so no need to change them
13568 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
13569 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
13570 						// gotta get the double buffer bmp to match the window
13571 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
13572 
13573 						// if it is auto-scaled, we keep the backing bitmap the same size all the time
13574 						if(resizability != Resizability.automaticallyScaleIfPossible)
13575 						if(_width > bmpWidth || _height > bmpHeight) {
13576 							auto hdc = GetDC(hwnd);
13577 							auto oldBuffer = buffer;
13578 							buffer = CreateCompatibleBitmap(hdc, _width, _height);
13579 
13580 							auto hdcBmp = CreateCompatibleDC(hdc);
13581 							auto oldBmp = SelectObject(hdcBmp, buffer);
13582 
13583 							auto hdcOldBmp = CreateCompatibleDC(hdc);
13584 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
13585 
13586 							/+
13587 							RECT r;
13588 							r.left = 0;
13589 							r.top = 0;
13590 							r.right = width;
13591 							r.bottom = height;
13592 							auto c = Color.green;
13593 							auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
13594 							FillRect(hdcBmp, &r, brush);
13595 							DeleteObject(brush);
13596 							+/
13597 
13598 							BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
13599 
13600 							bmpWidth = _width;
13601 							bmpHeight = _height;
13602 
13603 							SelectObject(hdcOldBmp, oldOldBmp);
13604 							DeleteDC(hdcOldBmp);
13605 
13606 							SelectObject(hdcBmp, oldBmp);
13607 							DeleteDC(hdcBmp);
13608 
13609 							ReleaseDC(hwnd, hdc);
13610 
13611 							DeleteObject(oldBuffer);
13612 						}
13613 					}
13614 
13615 					updateOpenglViewportIfNeeded(_width, _height);
13616 
13617 					if(resizability != Resizability.automaticallyScaleIfPossible)
13618 					if(windowResized !is null)
13619 						windowResized(_width, _height);
13620 
13621 					/+
13622 					if(inSizeMove) {
13623 						// SimpleWindow.processAllCustomEvents();
13624 						// SimpleWindow.processAllCustomEvents();
13625 
13626 						//RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13627 						//sdpyPrintDebugString("redraw b");
13628 					} else {
13629 					+/ {
13630 						finalize_resize:
13631 						// when it is all done, make sure everything is freshly drawn or there might be
13632 						// weird bugs left.
13633 						SimpleWindow.processAllCustomEvents();
13634 						SimpleWindow.processAllCustomEvents();
13635 
13636 						RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
13637 						// sdpyPrintDebugString("redraw");
13638 					}
13639 
13640 					oldWidth = this._width;
13641 					oldHeight = this._height;
13642 				break;
13643 				case WM_ERASEBKGND:
13644 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
13645 					if (!this._visibleForTheFirstTimeCalled) {
13646 						this._visibleForTheFirstTimeCalled = true;
13647 						if (this.visibleForTheFirstTime !is null) {
13648 							this.visibleForTheFirstTime();
13649 						}
13650 					}
13651 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
13652 					version(without_opengl) {} else {
13653 						if (openglMode == OpenGlOptions.yes) return 1;
13654 					}
13655 					// call windows default handler, so it can paint standard controls
13656 					goto default;
13657 				case WM_CTLCOLORBTN:
13658 				case WM_CTLCOLORSTATIC:
13659 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
13660 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
13661 					GetSysColorBrush(COLOR_3DFACE);
13662 				//break;
13663 				case WM_SHOWWINDOW:
13664 					auto before = this._visible;
13665 					this._visible = (wParam != 0);
13666 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
13667 						this._visibleForTheFirstTimeCalled = true;
13668 						if (this.visibleForTheFirstTime !is null) {
13669 							this.visibleForTheFirstTime();
13670 						}
13671 					}
13672 					if (this.visibilityChanged !is null && this._visible != before) this.visibilityChanged(this._visible);
13673 					break;
13674 				case WM_PAINT: {
13675 					if (!this._visibleForTheFirstTimeCalled) {
13676 						this._visibleForTheFirstTimeCalled = true;
13677 						if (this.visibleForTheFirstTime !is null) {
13678 							this.visibleForTheFirstTime();
13679 						}
13680 					}
13681 
13682 					BITMAP bm;
13683 					PAINTSTRUCT ps;
13684 
13685 					HDC hdc = BeginPaint(hwnd, &ps);
13686 
13687 					if(openglMode == OpenGlOptions.no) {
13688 
13689 						HDC hdcMem = CreateCompatibleDC(hdc);
13690 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
13691 
13692 						GetObject(buffer, bm.sizeof, &bm);
13693 
13694 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
13695 						if(resizability == Resizability.automaticallyScaleIfPossible)
13696 						StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
13697 						else
13698 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
13699 						//BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
13700 
13701 						SelectObject(hdcMem, hbmOld);
13702 						DeleteDC(hdcMem);
13703 						EndPaint(hwnd, &ps);
13704 					} else {
13705 						EndPaint(hwnd, &ps);
13706 						version(without_opengl) {} else
13707 							redrawOpenGlSceneSoon();
13708 					}
13709 				} break;
13710 				  default:
13711 					return DefWindowProc(hwnd, msg, wParam, lParam);
13712 			}
13713 			 return 0;
13714 
13715 		}
13716 		catch(Throwable t) {
13717 			sdpyPrintDebugString(t.toString);
13718 			return 0;
13719 		}
13720 		}
13721 	}
13722 
13723 	mixin template NativeImageImplementation() {
13724 		HBITMAP handle;
13725 		ubyte* rawData;
13726 
13727 	final:
13728 
13729 		Color getPixel(int x, int y) @system {
13730 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13731 			// remember, bmps are upside down
13732 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13733 
13734 			Color c;
13735 			if(enableAlpha)
13736 				c.a = rawData[offset + 3];
13737 			else
13738 				c.a = 255;
13739 			c.b = rawData[offset + 0];
13740 			c.g = rawData[offset + 1];
13741 			c.r = rawData[offset + 2];
13742 			c.unPremultiply();
13743 			return c;
13744 		}
13745 
13746 		void setPixel(int x, int y, Color c) @system {
13747 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13748 			// remember, bmps are upside down
13749 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
13750 
13751 			if(enableAlpha)
13752 				c.premultiply();
13753 
13754 			rawData[offset + 0] = c.b;
13755 			rawData[offset + 1] = c.g;
13756 			rawData[offset + 2] = c.r;
13757 			if(enableAlpha)
13758 				rawData[offset + 3] = c.a;
13759 		}
13760 
13761 		void convertToRgbaBytes(ubyte[] where) @system {
13762 			assert(where.length == this.width * this.height * 4);
13763 
13764 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
13765 			int idx = 0;
13766 			int offset = itemsPerLine * (height - 1);
13767 			// remember, bmps are upside down
13768 			for(int y = height - 1; y >= 0; y--) {
13769 				auto offsetStart = offset;
13770 				for(int x = 0; x < width; x++) {
13771 					where[idx + 0] = rawData[offset + 2]; // r
13772 					where[idx + 1] = rawData[offset + 1]; // g
13773 					where[idx + 2] = rawData[offset + 0]; // b
13774 					if(enableAlpha) {
13775 						where[idx + 3] = rawData[offset + 3]; // a
13776 						unPremultiplyRgba(where[idx .. idx + 4]);
13777 						offset++;
13778 					} else
13779 						where[idx + 3] = 255; // a
13780 					idx += 4;
13781 					offset += 3;
13782 				}
13783 
13784 				offset = offsetStart - itemsPerLine;
13785 			}
13786 		}
13787 
13788 		void setFromRgbaBytes(in ubyte[] what) @system {
13789 			assert(what.length == this.width * this.height * 4);
13790 
13791 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
13792 			int idx = 0;
13793 			int offset = itemsPerLine * (height - 1);
13794 			// remember, bmps are upside down
13795 			for(int y = height - 1; y >= 0; y--) {
13796 				auto offsetStart = offset;
13797 				for(int x = 0; x < width; x++) {
13798 					if(enableAlpha) {
13799 						auto a = what[idx + 3];
13800 
13801 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
13802 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
13803 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
13804 						rawData[offset + 3] = a; // a
13805 						//premultiplyBgra(rawData[offset .. offset + 4]);
13806 						offset++;
13807 					} else {
13808 						rawData[offset + 2] = what[idx + 0]; // r
13809 						rawData[offset + 1] = what[idx + 1]; // g
13810 						rawData[offset + 0] = what[idx + 2]; // b
13811 					}
13812 					idx += 4;
13813 					offset += 3;
13814 				}
13815 
13816 				offset = offsetStart - itemsPerLine;
13817 			}
13818 		}
13819 
13820 
13821 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
13822 			BITMAPINFO infoheader;
13823 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
13824 			infoheader.bmiHeader.biWidth = width;
13825 			infoheader.bmiHeader.biHeight = height;
13826 			infoheader.bmiHeader.biPlanes = 1;
13827 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
13828 			infoheader.bmiHeader.biCompression = BI_RGB;
13829 
13830 			handle = CreateDIBSection(
13831 				null,
13832 				&infoheader,
13833 				DIB_RGB_COLORS,
13834 				cast(void**) &rawData,
13835 				null,
13836 				0);
13837 			if(handle is null)
13838 				throw new WindowsApiException("create image failed", GetLastError());
13839 
13840 		}
13841 
13842 		void dispose() {
13843 			DeleteObject(handle);
13844 		}
13845 	}
13846 
13847 	enum KEY_ESCAPE = 27;
13848 }
13849 
13850 version(Emscripten) {
13851 	alias int delegate(void*) NativeEventHandler;
13852 	alias void* NativeWindowHandle;
13853 
13854 	mixin template NativeSimpleWindowImplementation() { }
13855 	mixin template NativeScreenPainterImplementation() { }
13856 	mixin template NativeImageImplementation() { }
13857 }
13858 
13859 version(X11) {
13860 	/// This is the default font used. You might change this before doing anything else with
13861 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
13862 	/// for cross-platform compatibility.
13863 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13864 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
13865 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
13866 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
13867 
13868 	alias int delegate(XEvent) NativeEventHandler;
13869 	alias Window NativeWindowHandle;
13870 
13871 	enum KEY_ESCAPE = 9;
13872 
13873 	mixin template NativeScreenPainterImplementation() {
13874 		Display* display;
13875 		Drawable d;
13876 		Drawable destiny;
13877 
13878 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
13879 		GC gc;
13880 
13881 		__gshared bool fontAttempted;
13882 
13883 		__gshared XFontStruct* defaultfont;
13884 		__gshared XFontSet defaultfontset;
13885 
13886 		XFontStruct* font;
13887 		XFontSet fontset;
13888 
13889 		void create(PaintingHandle window) {
13890 			this.display = XDisplayConnection.get();
13891 
13892 			Drawable buffer = None;
13893 			if(auto sw = cast(SimpleWindow) this.window) {
13894 				if(sw.useDirectDraw)
13895 					goto direct_draw;
13896 				buffer = sw.impl.buffer;
13897 				this.destiny = cast(Drawable) window;
13898 			} else {
13899 				direct_draw:
13900 				buffer = cast(Drawable) window;
13901 				this.destiny = None;
13902 			}
13903 
13904 			this.d = cast(Drawable) buffer;
13905 
13906 			auto dgc = DefaultGC(display, DefaultScreen(display));
13907 
13908 			this.gc = XCreateGC(display, d, 0, null);
13909 
13910 			XCopyGC(display, dgc, 0xffffffff, this.gc);
13911 
13912 			ensureDefaultFontLoaded();
13913 
13914 			font = defaultfont;
13915 			fontset = defaultfontset;
13916 
13917 			if(font) {
13918 				XSetFont(display, gc, font.fid);
13919 			}
13920 		}
13921 
13922 		static void ensureDefaultFontLoaded() {
13923 			if(!fontAttempted) {
13924 				auto display = XDisplayConnection.get;
13925 				auto font = XLoadQueryFont(display, xfontstr.ptr);
13926 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
13927 				if(font is null) {
13928 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
13929 					font = XLoadQueryFont(display, xfontstr.ptr);
13930 				}
13931 
13932 				char** lol;
13933 				int lol2;
13934 				char* lol3;
13935 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
13936 
13937 				fontAttempted = true;
13938 
13939 				defaultfont = font;
13940 				defaultfontset = fontset;
13941 			}
13942 		}
13943 
13944 		arsd.color.Rectangle _clipRectangle;
13945 		void setClipRectangle(int x, int y, int width, int height) {
13946 			auto old = _clipRectangle;
13947 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
13948 			if(old == _clipRectangle)
13949 				return;
13950 
13951 			if(width == 0 || height == 0) {
13952 				XSetClipMask(display, gc, None);
13953 
13954 				if(xrenderPicturePainter) {
13955 
13956 					XRectangle[1] rects;
13957 					rects[0] = XRectangle(short.min, short.min, short.max, short.max);
13958 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13959 				}
13960 
13961 				version(with_xft) {
13962 					if(xftDraw is null)
13963 						return;
13964 					XftDrawSetClip(xftDraw, null);
13965 				}
13966 			} else {
13967 				XRectangle[1] rects;
13968 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
13969 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
13970 
13971 				if(xrenderPicturePainter)
13972 					XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
13973 
13974 				version(with_xft) {
13975 					if(xftDraw is null)
13976 						return;
13977 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
13978 				}
13979 			}
13980 		}
13981 
13982 		version(with_xft) {
13983 			XftFont* xftFont;
13984 			XftDraw* xftDraw;
13985 
13986 			XftColor xftColor;
13987 
13988 			void updateXftColor() {
13989 				if(xftFont is null)
13990 					return;
13991 
13992 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
13993 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
13994 
13995 				XftColorAllocValue(
13996 					display,
13997 					DefaultVisual(display, DefaultScreen(display)),
13998 					DefaultColormap(display, 0),
13999 					&colorIn,
14000 					&xftColor
14001 				);
14002 			}
14003 		}
14004 
14005 		void enableXftDraw() {
14006 			if(xftDraw is null) {
14007 				xftDraw = XftDrawCreate(
14008 					display,
14009 					d,
14010 					DefaultVisual(display, DefaultScreen(display)),
14011 					DefaultColormap(display, 0)
14012 				);
14013 
14014 				updateXftColor();
14015 			}
14016 		}
14017 
14018 		private OperatingSystemFont _activeFont;
14019 		void setFont(OperatingSystemFont font) {
14020 			_activeFont = font;
14021 			version(with_xft) {
14022 				if(font && font.isXft && font.xftFont)
14023 					this.xftFont = font.xftFont;
14024 				else
14025 					this.xftFont = null;
14026 
14027 				if(this.xftFont) {
14028 					enableXftDraw();
14029 					return;
14030 				}
14031 			}
14032 
14033 			if(font && font.font) {
14034 				this.font = font.font;
14035 				this.fontset = font.fontset;
14036 				XSetFont(display, gc, font.font.fid);
14037 			} else {
14038 				this.font = defaultfont;
14039 				this.fontset = defaultfontset;
14040 			}
14041 
14042 		}
14043 
14044 		private Picture xrenderPicturePainter;
14045 
14046 		bool manualInvalidations;
14047 		void invalidateRect(Rectangle invalidRect) {
14048 			// FIXME if manualInvalidations
14049 		}
14050 
14051 		void dispose() {
14052 			this.rasterOp = RasterOp.normal;
14053 
14054 			if(xrenderPicturePainter) {
14055 				XRenderFreePicture(display, xrenderPicturePainter);
14056 				xrenderPicturePainter = None;
14057 			}
14058 
14059 			// FIXME: this.window.width/height is probably wrong
14060 
14061 			// src x,y     then dest x, y
14062 			if(destiny != None) {
14063 				// FIXME: if manual invalidations we can actually only copy some of the area.
14064 				// if(manualInvalidations)
14065 				XSetClipMask(display, gc, None);
14066 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
14067 			}
14068 
14069 			XFreeGC(display, gc);
14070 
14071 			version(with_xft)
14072 			if(xftDraw) {
14073 				XftDrawDestroy(xftDraw);
14074 				xftDraw = null;
14075 			}
14076 
14077 			/+
14078 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
14079 			if(font && font !is defaultfont) {
14080 				XFreeFont(display, font);
14081 				font = null;
14082 			}
14083 			if(fontset && fontset !is defaultfontset) {
14084 				XFreeFontSet(display, fontset);
14085 				fontset = null;
14086 			}
14087 			+/
14088 			XFlush(display);
14089 
14090 			if(window.paintingFinishedDg !is null)
14091 				window.paintingFinishedDg()();
14092 		}
14093 
14094 		bool backgroundIsNotTransparent = true;
14095 		bool foregroundIsNotTransparent = true;
14096 
14097 		bool _penInitialized = false;
14098 		Pen _activePen;
14099 
14100 		Color _outlineColor;
14101 		Color _fillColor;
14102 
14103 		@property void pen(Pen p) {
14104 			if(_penInitialized && p == _activePen) {
14105 				return;
14106 			}
14107 			_penInitialized = true;
14108 			_activePen = p;
14109 			_outlineColor = p.color;
14110 
14111 			int style;
14112 
14113 			byte dashLength;
14114 
14115 			final switch(p.style) {
14116 				case Pen.Style.Solid:
14117 					style = 0 /*LineSolid*/;
14118 				break;
14119 				case Pen.Style.Dashed:
14120 					style = 1 /*LineOnOffDash*/;
14121 					dashLength = 4;
14122 				break;
14123 				case Pen.Style.Dotted:
14124 					style = 1 /*LineOnOffDash*/;
14125 					dashLength = 1;
14126 				break;
14127 			}
14128 
14129 			XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0);
14130 			if(dashLength)
14131 				XSetDashes(display, gc, 0, &dashLength, 1);
14132 
14133 			if(p.color.a == 0) {
14134 				foregroundIsNotTransparent = false;
14135 				return;
14136 			}
14137 
14138 			foregroundIsNotTransparent = true;
14139 
14140 			XSetForeground(display, gc, colorToX(p.color, display));
14141 
14142 			version(with_xft)
14143 				updateXftColor();
14144 		}
14145 
14146 		RasterOp _currentRasterOp;
14147 		bool _currentRasterOpInitialized = false;
14148 		@property void rasterOp(RasterOp op) {
14149 			if(_currentRasterOpInitialized && _currentRasterOp == op)
14150 				return;
14151 			_currentRasterOp = op;
14152 			_currentRasterOpInitialized = true;
14153 			int mode;
14154 			final switch(op) {
14155 				case RasterOp.normal:
14156 					mode = GXcopy;
14157 				break;
14158 				case RasterOp.xor:
14159 					mode = GXxor;
14160 				break;
14161 			}
14162 			XSetFunction(display, gc, mode);
14163 		}
14164 
14165 
14166 		bool _fillColorInitialized = false;
14167 
14168 		@property void fillColor(Color c) {
14169 			if(_fillColorInitialized && _fillColor == c)
14170 				return; // already good, no need to waste time calling it
14171 			_fillColor = c;
14172 			_fillColorInitialized = true;
14173 			if(c.a == 0) {
14174 				backgroundIsNotTransparent = false;
14175 				return;
14176 			}
14177 
14178 			backgroundIsNotTransparent = true;
14179 
14180 			XSetBackground(display, gc, colorToX(c, display));
14181 
14182 		}
14183 
14184 		void swapColors() {
14185 			auto tmp = _fillColor;
14186 			fillColor = _outlineColor;
14187 			auto newPen = _activePen;
14188 			newPen.color = tmp;
14189 			pen(newPen);
14190 		}
14191 
14192 		uint colorToX(Color c, Display* display) {
14193 			auto visual = DefaultVisual(display, DefaultScreen(display));
14194 			import core.bitop;
14195 			uint color = 0;
14196 			{
14197 			auto startBit = bsf(visual.red_mask);
14198 			auto lastBit = bsr(visual.red_mask);
14199 			auto r = cast(uint) c.r;
14200 			r >>= 7 - (lastBit - startBit);
14201 			r <<= startBit;
14202 			color |= r;
14203 			}
14204 			{
14205 			auto startBit = bsf(visual.green_mask);
14206 			auto lastBit = bsr(visual.green_mask);
14207 			auto g = cast(uint) c.g;
14208 			g >>= 7 - (lastBit - startBit);
14209 			g <<= startBit;
14210 			color |= g;
14211 			}
14212 			{
14213 			auto startBit = bsf(visual.blue_mask);
14214 			auto lastBit = bsr(visual.blue_mask);
14215 			auto b = cast(uint) c.b;
14216 			b >>= 7 - (lastBit - startBit);
14217 			b <<= startBit;
14218 			color |= b;
14219 			}
14220 
14221 
14222 
14223 			return color;
14224 		}
14225 
14226 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
14227 			// source x, source y
14228 			if(ix >= i.width) return;
14229 			if(iy >= i.height) return;
14230 			if(ix + w > i.width) w = i.width - ix;
14231 			if(iy + h > i.height) h = i.height - iy;
14232 			if(i.usingXshm)
14233 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
14234 			else
14235 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
14236 		}
14237 
14238 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
14239 			if(s.enableAlpha) {
14240 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
14241 				if(this.xrenderPicturePainter == None) {
14242 					XRenderPictureAttributes attrs;
14243 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
14244 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
14245 
14246 					// need to initialize the clip
14247 					XRectangle[1] rects;
14248 					rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
14249 
14250 					if(_clipRectangle != Rectangle.init)
14251 						XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
14252 				}
14253 
14254 				XRenderComposite(
14255 					display,
14256 					3, // PicOpOver
14257 					s.xrenderPicture,
14258 					None,
14259 					this.xrenderPicturePainter,
14260 					ix,
14261 					iy,
14262 					0,
14263 					0,
14264 					x,
14265 					y,
14266 					w ? w : s.width,
14267 					h ? h : s.height
14268 				);
14269 			} else {
14270 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
14271 			}
14272 		}
14273 
14274 		int fontHeight() {
14275 			version(with_xft)
14276 				if(xftFont !is null)
14277 					return xftFont.height;
14278 			if(font)
14279 				return font.max_bounds.ascent + font.max_bounds.descent;
14280 			return 12; // pretty common default...
14281 		}
14282 
14283 		int textWidth(in char[] line) {
14284 			version(with_xft)
14285 			if(xftFont) {
14286 				if(line.length == 0)
14287 					return 0;
14288 				XGlyphInfo extents;
14289 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
14290 				return extents.width;
14291 			}
14292 
14293 			if(fontset) {
14294 				if(line.length == 0)
14295 					return 0;
14296 				XRectangle rect;
14297 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
14298 
14299 				return rect.width;
14300 			}
14301 
14302 			if(font)
14303 				// FIXME: unicode
14304 				return XTextWidth( font, line.ptr, cast(int) line.length);
14305 			else
14306 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
14307 		}
14308 
14309 		Size textSize(in char[] text) {
14310 			auto maxWidth = 0;
14311 			auto lineHeight = fontHeight;
14312 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
14313 			foreach(line; text.split('\n')) {
14314 				int textWidth = this.textWidth(line);
14315 				if(textWidth > maxWidth)
14316 					maxWidth = textWidth;
14317 				h += lineHeight + 4;
14318 			}
14319 			return Size(maxWidth, h);
14320 		}
14321 
14322 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
14323 			const(char)[] text;
14324 			version(with_xft)
14325 			if(xftFont) {
14326 				text = originalText;
14327 				goto loaded;
14328 			}
14329 
14330 			if(fontset)
14331 				text = originalText;
14332 			else {
14333 				text.reserve(originalText.length);
14334 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
14335 				// then strip the rest so there isn't garbage
14336 				foreach(dchar ch; originalText)
14337 					if(ch < 256)
14338 						text ~= cast(ubyte) ch;
14339 					else
14340 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
14341 			}
14342 			loaded:
14343 			if(text.length == 0)
14344 				return;
14345 
14346 			// FIXME: should we clip it to the bounding box?
14347 			int textHeight = fontHeight;
14348 
14349 			auto lines = text.split('\n');
14350 
14351 			const lineHeight = textHeight;
14352 			textHeight *= lines.length;
14353 
14354 			int cy = y;
14355 
14356 			if(alignment & TextAlignment.VerticalBottom) {
14357 				if(y2 <= 0)
14358 					return;
14359 				auto h = y2 - y;
14360 				if(h > textHeight) {
14361 					cy += h - textHeight;
14362 					cy -= lineHeight / 2;
14363 				}
14364 			} else if(alignment & TextAlignment.VerticalCenter) {
14365 				if(y2 <= 0)
14366 					return;
14367 				auto h = y2 - y;
14368 				if(textHeight < h) {
14369 					cy += (h - textHeight) / 2;
14370 					//cy -= lineHeight / 4;
14371 				}
14372 			}
14373 
14374 			foreach(line; text.split('\n')) {
14375 				int textWidth = this.textWidth(line);
14376 
14377 				int px = x, py = cy;
14378 
14379 				if(alignment & TextAlignment.Center) {
14380 					if(x2 <= 0)
14381 						return;
14382 					auto w = x2 - x;
14383 					if(w > textWidth)
14384 						px += (w - textWidth) / 2;
14385 				} else if(alignment & TextAlignment.Right) {
14386 					if(x2 <= 0)
14387 						return;
14388 					auto pos = x2 - textWidth;
14389 					if(pos > x)
14390 						px = pos;
14391 				}
14392 
14393 				version(with_xft)
14394 				if(xftFont) {
14395 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
14396 
14397 					goto carry_on;
14398 				}
14399 
14400 				if(fontset)
14401 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
14402 				else
14403 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
14404 				carry_on:
14405 				cy += lineHeight + 4;
14406 			}
14407 		}
14408 
14409 		void drawPixel(int x, int y) {
14410 			XDrawPoint(display, d, gc, x, y);
14411 		}
14412 
14413 		// The basic shapes, outlined
14414 
14415 		void drawLine(int x1, int y1, int x2, int y2) {
14416 			if(foregroundIsNotTransparent)
14417 				XDrawLine(display, d, gc, x1, y1, x2, y2);
14418 		}
14419 
14420 		void drawRectangle(int x, int y, int width, int height) {
14421 			if(backgroundIsNotTransparent) {
14422 				swapColors();
14423 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
14424 				swapColors();
14425 			}
14426 			// 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
14427 			if(foregroundIsNotTransparent)
14428 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
14429 		}
14430 
14431 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
14432 			int[4] radii = borderRadius;
14433 			auto r = Rectangle(upperLeft, lowerRight);
14434 
14435 			if(backgroundIsNotTransparent) {
14436 				swapColors();
14437 				// FIXME these overlap and thus draw the pixels multiple times
14438 				XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius);
14439 				XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
14440 				swapColors();
14441 			}
14442 
14443 			drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top);
14444 			drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1);
14445 			drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2);
14446 			drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2);
14447 
14448 			//drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height);
14449 
14450 			drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64);
14451 			drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64);
14452 			drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64);
14453 			drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64);
14454 		}
14455 
14456 
14457 		/// Arguments are the points of the bounding rectangle
14458 		void drawEllipse(int x1, int y1, int x2, int y2) {
14459 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
14460 		}
14461 
14462 		// NOTE: start and finish are in units of degrees * 64
14463 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
14464 			if(backgroundIsNotTransparent) {
14465 				swapColors();
14466 				XFillArc(display, d, gc, x1, y1, width, height, start, length);
14467 				swapColors();
14468 			}
14469 			if(foregroundIsNotTransparent) {
14470 				XDrawArc(display, d, gc, x1, y1, width, height, start, length);
14471 
14472 				// Windows draws the straight lines on the edges too so FIXME sort of
14473 			}
14474 		}
14475 
14476 		void drawPolygon(Point[] vertexes) {
14477 			XPoint[16] pointsBuffer;
14478 			XPoint[] points;
14479 			if(vertexes.length <= pointsBuffer.length)
14480 				points = pointsBuffer[0 .. vertexes.length];
14481 			else
14482 				points.length = vertexes.length;
14483 
14484 			foreach(i, p; vertexes) {
14485 				points[i].x = cast(short) p.x;
14486 				points[i].y = cast(short) p.y;
14487 			}
14488 
14489 			if(backgroundIsNotTransparent) {
14490 				swapColors();
14491 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
14492 				swapColors();
14493 			}
14494 			if(foregroundIsNotTransparent) {
14495 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
14496 			}
14497 		}
14498 	}
14499 
14500 	/* XRender { */
14501 
14502 	struct XRenderColor {
14503 		ushort red;
14504 		ushort green;
14505 		ushort blue;
14506 		ushort alpha;
14507 	}
14508 
14509 	alias Picture = XID;
14510 	alias PictFormat = XID;
14511 
14512 	struct XGlyphInfo {
14513 		ushort width;
14514 		ushort height;
14515 		short x;
14516 		short y;
14517 		short xOff;
14518 		short yOff;
14519 	}
14520 
14521 struct XRenderDirectFormat {
14522     short   red;
14523     short   redMask;
14524     short   green;
14525     short   greenMask;
14526     short   blue;
14527     short   blueMask;
14528     short   alpha;
14529     short   alphaMask;
14530 }
14531 
14532 struct XRenderPictFormat {
14533     PictFormat		id;
14534     int			type;
14535     int			depth;
14536     XRenderDirectFormat	direct;
14537     Colormap		colormap;
14538 }
14539 
14540 enum PictFormatID	=   (1 << 0);
14541 enum PictFormatType	=   (1 << 1);
14542 enum PictFormatDepth	=   (1 << 2);
14543 enum PictFormatRed	=   (1 << 3);
14544 enum PictFormatRedMask  =(1 << 4);
14545 enum PictFormatGreen	=   (1 << 5);
14546 enum PictFormatGreenMask=(1 << 6);
14547 enum PictFormatBlue	=   (1 << 7);
14548 enum PictFormatBlueMask =(1 << 8);
14549 enum PictFormatAlpha	=   (1 << 9);
14550 enum PictFormatAlphaMask=(1 << 10);
14551 enum PictFormatColormap =(1 << 11);
14552 
14553 struct XRenderPictureAttributes {
14554 	int 		repeat;
14555 	Picture		alpha_map;
14556 	int			alpha_x_origin;
14557 	int			alpha_y_origin;
14558 	int			clip_x_origin;
14559 	int			clip_y_origin;
14560 	Pixmap		clip_mask;
14561 	Bool		graphics_exposures;
14562 	int			subwindow_mode;
14563 	int			poly_edge;
14564 	int			poly_mode;
14565 	Atom		dither;
14566 	Bool		component_alpha;
14567 }
14568 
14569 alias int XFixed;
14570 
14571 struct XPointFixed {
14572     XFixed  x, y;
14573 }
14574 
14575 struct XCircle {
14576     XFixed x;
14577     XFixed y;
14578     XFixed radius;
14579 }
14580 
14581 struct XTransform {
14582     XFixed[3][3]  matrix;
14583 }
14584 
14585 struct XFilters {
14586     int	    nfilter;
14587     char    **filter;
14588     int	    nalias;
14589     short   *alias_;
14590 }
14591 
14592 struct XIndexValue {
14593     c_ulong    pixel;
14594     ushort   red, green, blue, alpha;
14595 }
14596 
14597 struct XAnimCursor {
14598     Cursor	    cursor;
14599     c_ulong   delay;
14600 }
14601 
14602 struct XLinearGradient {
14603     XPointFixed p1;
14604     XPointFixed p2;
14605 }
14606 
14607 struct XRadialGradient {
14608     XCircle inner;
14609     XCircle outer;
14610 }
14611 
14612 struct XConicalGradient {
14613     XPointFixed center;
14614     XFixed angle; /* in degrees */
14615 }
14616 
14617 enum PictStandardARGB32  = 0;
14618 enum PictStandardRGB24   = 1;
14619 enum PictStandardA8	 =  2;
14620 enum PictStandardA4	 =  3;
14621 enum PictStandardA1	 =  4;
14622 enum PictStandardNUM	 =  5;
14623 
14624 interface XRender {
14625 extern(C) @nogc:
14626 
14627 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
14628 
14629 	Status XRenderQueryVersion (Display *dpy,
14630 			int     *major_versionp,
14631 			int     *minor_versionp);
14632 
14633 	Status XRenderQueryFormats (Display *dpy);
14634 
14635 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
14636 
14637 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
14638 
14639 	XRenderPictFormat *
14640 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
14641 
14642 	XRenderPictFormat *
14643 		XRenderFindFormat (Display			*dpy,
14644 				c_ulong		mask,
14645 				const XRenderPictFormat	*templ,
14646 				int				count);
14647 	XRenderPictFormat *
14648 		XRenderFindStandardFormat (Display		*dpy,
14649 				int			format);
14650 
14651 	XIndexValue *
14652 		XRenderQueryPictIndexValues(Display			*dpy,
14653 				const XRenderPictFormat	*format,
14654 				int				*num);
14655 
14656 	Picture XRenderCreatePicture(
14657 		Display *dpy,
14658 		Drawable drawable,
14659 		const XRenderPictFormat *format,
14660 		c_ulong valuemask,
14661 		const XRenderPictureAttributes *attributes);
14662 
14663 	void XRenderChangePicture (Display				*dpy,
14664 				Picture				picture,
14665 				c_ulong			valuemask,
14666 				const XRenderPictureAttributes  *attributes);
14667 
14668 	void
14669 		XRenderSetPictureClipRectangles (Display	    *dpy,
14670 				Picture	    picture,
14671 				int		    xOrigin,
14672 				int		    yOrigin,
14673 				const XRectangle *rects,
14674 				int		    n);
14675 
14676 	void
14677 		XRenderSetPictureClipRegion (Display	    *dpy,
14678 				Picture	    picture,
14679 				Region	    r);
14680 
14681 	void
14682 		XRenderSetPictureTransform (Display	    *dpy,
14683 				Picture	    picture,
14684 				XTransform	    *transform);
14685 
14686 	void
14687 		XRenderFreePicture (Display                   *dpy,
14688 				Picture                   picture);
14689 
14690 	void
14691 		XRenderComposite (Display   *dpy,
14692 				int	    op,
14693 				Picture   src,
14694 				Picture   mask,
14695 				Picture   dst,
14696 				int	    src_x,
14697 				int	    src_y,
14698 				int	    mask_x,
14699 				int	    mask_y,
14700 				int	    dst_x,
14701 				int	    dst_y,
14702 				uint	width,
14703 				uint	height);
14704 
14705 
14706 	Picture XRenderCreateSolidFill (Display *dpy,
14707 			const XRenderColor *color);
14708 
14709 	Picture XRenderCreateLinearGradient (Display *dpy,
14710 			const XLinearGradient *gradient,
14711 			const XFixed *stops,
14712 			const XRenderColor *colors,
14713 			int nstops);
14714 
14715 	Picture XRenderCreateRadialGradient (Display *dpy,
14716 			const XRadialGradient *gradient,
14717 			const XFixed *stops,
14718 			const XRenderColor *colors,
14719 			int nstops);
14720 
14721 	Picture XRenderCreateConicalGradient (Display *dpy,
14722 			const XConicalGradient *gradient,
14723 			const XFixed *stops,
14724 			const XRenderColor *colors,
14725 			int nstops);
14726 
14727 
14728 
14729 	Cursor
14730 		XRenderCreateCursor (Display	    *dpy,
14731 				Picture	    source,
14732 				uint   x,
14733 				uint   y);
14734 
14735 	XFilters *
14736 		XRenderQueryFilters (Display *dpy, Drawable drawable);
14737 
14738 	void
14739 		XRenderSetPictureFilter (Display    *dpy,
14740 				Picture    picture,
14741 				const char *filter,
14742 				XFixed	    *params,
14743 				int	    nparams);
14744 
14745 	Cursor
14746 		XRenderCreateAnimCursor (Display	*dpy,
14747 				int		ncursor,
14748 				XAnimCursor	*cursors);
14749 }
14750 
14751 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
14752 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
14753 
14754 	/* XRender } */
14755 
14756 	/* Xrandr { */
14757 
14758 struct XRRMonitorInfo {
14759     Atom name;
14760     Bool primary;
14761     Bool automatic;
14762     int noutput;
14763     int x;
14764     int y;
14765     int width;
14766     int height;
14767     int mwidth;
14768     int mheight;
14769     /*RROutput*/ void *outputs;
14770 }
14771 
14772 struct XRRScreenChangeNotifyEvent {
14773     int type;                   /* event base */
14774     c_ulong serial;       /* # of last request processed by server */
14775     Bool send_event;            /* true if this came from a SendEvent request */
14776     Display *display;           /* Display the event was read from */
14777     Window window;              /* window which selected for this event */
14778     Window root;                /* Root window for changed screen */
14779     Time timestamp;             /* when the screen change occurred */
14780     Time config_timestamp;      /* when the last configuration change */
14781     ushort/*SizeID*/ size_index;
14782     ushort/*SubpixelOrder*/ subpixel_order;
14783     ushort/*Rotation*/ rotation;
14784     int width;
14785     int height;
14786     int mwidth;
14787     int mheight;
14788 }
14789 
14790 enum RRScreenChangeNotify = 0;
14791 
14792 enum RRScreenChangeNotifyMask = 1;
14793 
14794 __gshared int xrrEventBase = -1;
14795 
14796 
14797 interface XRandr {
14798 extern(C) @nogc:
14799 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
14800 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
14801 
14802 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
14803 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
14804 
14805 	void XRRSelectInput(Display *dpy, Window window, int mask);
14806 }
14807 
14808 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
14809 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
14810 	/* Xrandr } */
14811 
14812 	/* Xft { */
14813 
14814 	// actually freetype
14815 	alias void FT_Face;
14816 
14817 	// actually fontconfig
14818 	private alias FcBool = int;
14819 	alias void FcCharSet;
14820 	alias void FcPattern;
14821 	alias void FcResult;
14822 	enum FcEndian { FcEndianBig, FcEndianLittle }
14823 	struct FcFontSet {
14824 		int nfont;
14825 		int sfont;
14826 		FcPattern** fonts;
14827 	}
14828 
14829 	// actually XRegion
14830 	struct BOX {
14831 		short x1, x2, y1, y2;
14832 	}
14833 	struct _XRegion {
14834 		c_long size;
14835 		c_long numRects;
14836 		BOX* rects;
14837 		BOX extents;
14838 	}
14839 
14840 	alias Region = _XRegion*;
14841 
14842 	// ok actually Xft
14843 
14844 	struct XftFontInfo;
14845 
14846 	struct XftFont {
14847 		int         ascent;
14848 		int         descent;
14849 		int         height;
14850 		int         max_advance_width;
14851 		FcCharSet*  charset;
14852 		FcPattern*  pattern;
14853 	}
14854 
14855 	struct XftDraw;
14856 
14857 	struct XftColor {
14858 		c_ulong pixel;
14859 		XRenderColor color;
14860 	}
14861 
14862 	struct XftCharSpec {
14863 		dchar           ucs4;
14864 		short           x;
14865 		short           y;
14866 	}
14867 
14868 	struct XftCharFontSpec {
14869 		XftFont         *font;
14870 		dchar           ucs4;
14871 		short           x;
14872 		short           y;
14873 	}
14874 
14875 	struct XftGlyphSpec {
14876 		uint            glyph;
14877 		short           x;
14878 		short           y;
14879 	}
14880 
14881 	struct XftGlyphFontSpec {
14882 		XftFont         *font;
14883 		uint            glyph;
14884 		short           x;
14885 		short           y;
14886 	}
14887 
14888 	interface Xft {
14889 	extern(C) @nogc pure:
14890 
14891 	Bool XftColorAllocName (Display  *dpy,
14892 				const Visual   *visual,
14893 				Colormap cmap,
14894 				const char     *name,
14895 				XftColor *result);
14896 
14897 	Bool XftColorAllocValue (Display         *dpy,
14898 				Visual          *visual,
14899 				Colormap        cmap,
14900 				const XRenderColor    *color,
14901 				XftColor        *result);
14902 
14903 	void XftColorFree (Display   *dpy,
14904 				Visual    *visual,
14905 				Colormap  cmap,
14906 				XftColor  *color);
14907 
14908 	Bool XftDefaultHasRender (Display *dpy);
14909 
14910 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
14911 
14912 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
14913 
14914 	XftDraw * XftDrawCreate (Display   *dpy,
14915 		       Drawable  drawable,
14916 		       Visual    *visual,
14917 		       Colormap  colormap);
14918 
14919 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
14920 			     Pixmap   bitmap);
14921 
14922 	XftDraw * XftDrawCreateAlpha (Display *dpy,
14923 			    Pixmap  pixmap,
14924 			    int     depth);
14925 
14926 	void XftDrawChange (XftDraw  *draw,
14927 		       Drawable drawable);
14928 
14929 	Display * XftDrawDisplay (XftDraw *draw);
14930 
14931 	Drawable XftDrawDrawable (XftDraw *draw);
14932 
14933 	Colormap XftDrawColormap (XftDraw *draw);
14934 
14935 	Visual * XftDrawVisual (XftDraw *draw);
14936 
14937 	void XftDrawDestroy (XftDraw *draw);
14938 
14939 	Picture XftDrawPicture (XftDraw *draw);
14940 
14941 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
14942 
14943 	void XftDrawGlyphs (XftDraw          *draw,
14944 				const XftColor *color,
14945 				XftFont          *pub,
14946 				int              x,
14947 				int              y,
14948 				const uint  *glyphs,
14949 				int              nglyphs);
14950 
14951 	void XftDrawString8 (XftDraw             *draw,
14952 				const XftColor    *color,
14953 				XftFont             *pub,
14954 				int                 x,
14955 				int                 y,
14956 				const char     *string,
14957 				int                 len);
14958 
14959 	void XftDrawString16 (XftDraw            *draw,
14960 				const XftColor   *color,
14961 				XftFont            *pub,
14962 				int                x,
14963 				int                y,
14964 				const wchar   *string,
14965 				int                len);
14966 
14967 	void XftDrawString32 (XftDraw            *draw,
14968 				const XftColor   *color,
14969 				XftFont            *pub,
14970 				int                x,
14971 				int                y,
14972 				const dchar   *string,
14973 				int                len);
14974 
14975 	void XftDrawStringUtf8 (XftDraw          *draw,
14976 				const XftColor *color,
14977 				XftFont          *pub,
14978 				int              x,
14979 				int              y,
14980 				const char  *string,
14981 				int              len);
14982 	void XftDrawStringUtf16 (XftDraw             *draw,
14983 				const XftColor    *color,
14984 				XftFont             *pub,
14985 				int                 x,
14986 				int                 y,
14987 				const char     *string,
14988 				FcEndian            endian,
14989 				int                 len);
14990 
14991 	void XftDrawCharSpec (XftDraw                *draw,
14992 				const XftColor       *color,
14993 				XftFont                *pub,
14994 				const XftCharSpec    *chars,
14995 				int                    len);
14996 
14997 	void XftDrawCharFontSpec (XftDraw                    *draw,
14998 				const XftColor           *color,
14999 				const XftCharFontSpec    *chars,
15000 				int                        len);
15001 
15002 	void XftDrawGlyphSpec (XftDraw               *draw,
15003 				const XftColor      *color,
15004 				XftFont               *pub,
15005 				const XftGlyphSpec  *glyphs,
15006 				int                   len);
15007 
15008 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
15009 				const XftColor          *color,
15010 				const XftGlyphFontSpec  *glyphs,
15011 				int                       len);
15012 
15013 	void XftDrawRect (XftDraw            *draw,
15014 				const XftColor   *color,
15015 				int                x,
15016 				int                y,
15017 				uint       width,
15018 				uint       height);
15019 
15020 	Bool XftDrawSetClip (XftDraw     *draw,
15021 				Region      r);
15022 
15023 
15024 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
15025 				int                   xOrigin,
15026 				int                   yOrigin,
15027 				const XRectangle    *rects,
15028 				int                   n);
15029 
15030 	void XftDrawSetSubwindowMode (XftDraw    *draw,
15031 				int        mode);
15032 
15033 	void XftGlyphExtents (Display            *dpy,
15034 				XftFont            *pub,
15035 				const uint    *glyphs,
15036 				int                nglyphs,
15037 				XGlyphInfo         *extents);
15038 
15039 	void XftTextExtents8 (Display            *dpy,
15040 				XftFont            *pub,
15041 				const char    *string,
15042 				int                len,
15043 				XGlyphInfo         *extents);
15044 
15045 	void XftTextExtents16 (Display           *dpy,
15046 				XftFont           *pub,
15047 				const wchar  *string,
15048 				int               len,
15049 				XGlyphInfo        *extents);
15050 
15051 	void XftTextExtents32 (Display           *dpy,
15052 				XftFont           *pub,
15053 				const dchar  *string,
15054 				int               len,
15055 				XGlyphInfo        *extents);
15056 
15057 	void XftTextExtentsUtf8 (Display         *dpy,
15058 				XftFont         *pub,
15059 				const char *string,
15060 				int             len,
15061 				XGlyphInfo      *extents);
15062 
15063 	void XftTextExtentsUtf16 (Display            *dpy,
15064 				XftFont            *pub,
15065 				const char    *string,
15066 				FcEndian           endian,
15067 				int                len,
15068 				XGlyphInfo         *extents);
15069 
15070 	FcPattern * XftFontMatch (Display           *dpy,
15071 				int               screen,
15072 				const FcPattern *pattern,
15073 				FcResult          *result);
15074 
15075 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
15076 
15077 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
15078 
15079 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
15080 
15081 	FT_Face XftLockFace (XftFont *pub);
15082 
15083 	void XftUnlockFace (XftFont *pub);
15084 
15085 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
15086 
15087 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
15088 
15089 	dchar XftFontInfoHash (const XftFontInfo *fi);
15090 
15091 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
15092 
15093 	XftFont * XftFontOpenInfo (Display        *dpy,
15094 				FcPattern      *pattern,
15095 				XftFontInfo    *fi);
15096 
15097 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
15098 
15099 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
15100 
15101 	void XftFontClose (Display *dpy, XftFont *pub);
15102 
15103 	FcBool XftInitFtLibrary();
15104 	void XftFontLoadGlyphs (Display          *dpy,
15105 				XftFont          *pub,
15106 				FcBool           need_bitmaps,
15107 				const uint  *glyphs,
15108 				int              nglyph);
15109 
15110 	void XftFontUnloadGlyphs (Display            *dpy,
15111 				XftFont            *pub,
15112 				const uint    *glyphs,
15113 				int                nglyph);
15114 
15115 	FcBool XftFontCheckGlyph (Display  *dpy,
15116 				XftFont  *pub,
15117 				FcBool   need_bitmaps,
15118 				uint  glyph,
15119 				uint  *missing,
15120 				int      *nmissing);
15121 
15122 	FcBool XftCharExists (Display      *dpy,
15123 				XftFont      *pub,
15124 				dchar    ucs4);
15125 
15126 	uint XftCharIndex (Display       *dpy,
15127 				XftFont       *pub,
15128 				dchar      ucs4);
15129 	FcBool XftInit (const char *config);
15130 
15131 	int XftGetVersion ();
15132 
15133 	FcFontSet * XftListFonts (Display   *dpy,
15134 				int       screen,
15135 				...);
15136 
15137 	FcPattern *XftNameParse (const char *name);
15138 
15139 	void XftGlyphRender (Display         *dpy,
15140 				int             op,
15141 				Picture         src,
15142 				XftFont         *pub,
15143 				Picture         dst,
15144 				int             srcx,
15145 				int             srcy,
15146 				int             x,
15147 				int             y,
15148 				const uint *glyphs,
15149 				int             nglyphs);
15150 
15151 	void XftGlyphSpecRender (Display                 *dpy,
15152 				int                     op,
15153 				Picture                 src,
15154 				XftFont                 *pub,
15155 				Picture                 dst,
15156 				int                     srcx,
15157 				int                     srcy,
15158 				const XftGlyphSpec    *glyphs,
15159 				int                     nglyphs);
15160 
15161 	void XftCharSpecRender (Display              *dpy,
15162 				int                  op,
15163 				Picture              src,
15164 				XftFont              *pub,
15165 				Picture              dst,
15166 				int                  srcx,
15167 				int                  srcy,
15168 				const XftCharSpec  *chars,
15169 				int                  len);
15170 	void XftGlyphFontSpecRender (Display                     *dpy,
15171 				int                         op,
15172 				Picture                     src,
15173 				Picture                     dst,
15174 				int                         srcx,
15175 				int                         srcy,
15176 				const XftGlyphFontSpec    *glyphs,
15177 				int                         nglyphs);
15178 
15179 	void XftCharFontSpecRender (Display                  *dpy,
15180 				int                      op,
15181 				Picture                  src,
15182 				Picture                  dst,
15183 				int                      srcx,
15184 				int                      srcy,
15185 				const XftCharFontSpec  *chars,
15186 				int                      len);
15187 
15188 	void XftTextRender8 (Display         *dpy,
15189 				int             op,
15190 				Picture         src,
15191 				XftFont         *pub,
15192 				Picture         dst,
15193 				int             srcx,
15194 				int             srcy,
15195 				int             x,
15196 				int             y,
15197 				const char *string,
15198 				int             len);
15199 	void XftTextRender16 (Display            *dpy,
15200 				int                op,
15201 				Picture            src,
15202 				XftFont            *pub,
15203 				Picture            dst,
15204 				int                srcx,
15205 				int                srcy,
15206 				int                x,
15207 				int                y,
15208 				const wchar   *string,
15209 				int                len);
15210 
15211 	void XftTextRender16BE (Display          *dpy,
15212 				int              op,
15213 				Picture          src,
15214 				XftFont          *pub,
15215 				Picture          dst,
15216 				int              srcx,
15217 				int              srcy,
15218 				int              x,
15219 				int              y,
15220 				const char  *string,
15221 				int              len);
15222 
15223 	void XftTextRender16LE (Display          *dpy,
15224 				int              op,
15225 				Picture          src,
15226 				XftFont          *pub,
15227 				Picture          dst,
15228 				int              srcx,
15229 				int              srcy,
15230 				int              x,
15231 				int              y,
15232 				const char  *string,
15233 				int              len);
15234 
15235 	void XftTextRender32 (Display            *dpy,
15236 				int                op,
15237 				Picture            src,
15238 				XftFont            *pub,
15239 				Picture            dst,
15240 				int                srcx,
15241 				int                srcy,
15242 				int                x,
15243 				int                y,
15244 				const dchar   *string,
15245 				int                len);
15246 
15247 	void XftTextRender32BE (Display          *dpy,
15248 				int              op,
15249 				Picture          src,
15250 				XftFont          *pub,
15251 				Picture          dst,
15252 				int              srcx,
15253 				int              srcy,
15254 				int              x,
15255 				int              y,
15256 				const char  *string,
15257 				int              len);
15258 
15259 	void XftTextRender32LE (Display          *dpy,
15260 				int              op,
15261 				Picture          src,
15262 				XftFont          *pub,
15263 				Picture          dst,
15264 				int              srcx,
15265 				int              srcy,
15266 				int              x,
15267 				int              y,
15268 				const char  *string,
15269 				int              len);
15270 
15271 	void XftTextRenderUtf8 (Display          *dpy,
15272 				int              op,
15273 				Picture          src,
15274 				XftFont          *pub,
15275 				Picture          dst,
15276 				int              srcx,
15277 				int              srcy,
15278 				int              x,
15279 				int              y,
15280 				const char  *string,
15281 				int              len);
15282 
15283 	void XftTextRenderUtf16 (Display         *dpy,
15284 				int             op,
15285 				Picture         src,
15286 				XftFont         *pub,
15287 				Picture         dst,
15288 				int             srcx,
15289 				int             srcy,
15290 				int             x,
15291 				int             y,
15292 				const char *string,
15293 				FcEndian        endian,
15294 				int             len);
15295 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
15296 
15297 	}
15298 
15299 	interface FontConfig {
15300 	extern(C) @nogc pure:
15301 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
15302 		void FcFontSetDestroy(FcFontSet*);
15303 		char* FcNameUnparse(const FcPattern *);
15304 	}
15305 
15306 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
15307 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
15308 
15309 
15310 	/* Xft } */
15311 
15312 	class XDisconnectException : Exception {
15313 		bool userRequested;
15314 		this(bool userRequested = true) {
15315 			this.userRequested = userRequested;
15316 			super("X disconnected");
15317 		}
15318 	}
15319 
15320 	/++
15321 		Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
15322 
15323 		Please note that it returns
15324 	+/
15325 	XErrorEvent[] trapXErrors(scope void delegate() dg) {
15326 
15327 		static XErrorEvent[] errorBuffer;
15328 
15329 		static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
15330 			errorBuffer ~= *evt;
15331 			return 0;
15332 		}
15333 
15334 		auto savedErrorHandler = XSetErrorHandler(&handler);
15335 
15336 		try {
15337 			dg();
15338 		} finally {
15339 			XSync(XDisplayConnection.get, 0/*False*/);
15340 			XSetErrorHandler(savedErrorHandler);
15341 		}
15342 
15343 		auto bfr = errorBuffer;
15344 		errorBuffer = null;
15345 
15346 		return bfr;
15347 	}
15348 
15349 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
15350 	class XDisplayConnection {
15351 		private __gshared Display* display;
15352 		private __gshared XIM xim;
15353 		private __gshared char* displayName;
15354 
15355 		private __gshared int connectionSequence_;
15356 		private __gshared bool isLocal_;
15357 
15358 		/// use this for lazy caching when reconnection
15359 		static int connectionSequenceNumber() { return connectionSequence_; }
15360 
15361 		/++
15362 			Guesses if the connection appears to be local.
15363 
15364 			History:
15365 				Added June 3, 2021
15366 		+/
15367 		static @property bool isLocal() nothrow @trusted @nogc {
15368 			return isLocal_;
15369 		}
15370 
15371 		/// Attempts recreation of state, may require application assistance
15372 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
15373 		/// then call this, and if successful, reenter the loop.
15374 		static void discardAndRecreate(string newDisplayString = null) {
15375 			if(insideXEventLoop)
15376 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
15377 
15378 			// 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
15379 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
15380 
15381 			foreach(handle; chnenhm) {
15382 				handle.discardConnectionState();
15383 			}
15384 
15385 			discardState();
15386 
15387 			if(newDisplayString !is null)
15388 				setDisplayName(newDisplayString);
15389 
15390 			auto display = get();
15391 
15392 			foreach(handle; chnenhm) {
15393 				handle.recreateAfterDisconnect();
15394 			}
15395 		}
15396 
15397 		private __gshared EventMask rootEventMask;
15398 
15399 		/++
15400 			Requests the specified input from the root window on the connection, in addition to any other request.
15401 
15402 
15403 			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.
15404 
15405 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
15406 		+/
15407 		static void addRootInput(EventMask mask) {
15408 			auto old = rootEventMask;
15409 			rootEventMask |= mask;
15410 			get(); // to ensure display connected
15411 			if(display !is null && rootEventMask != old)
15412 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
15413 		}
15414 
15415 		static void discardState() {
15416 			freeImages();
15417 
15418 			foreach(atomPtr; interredAtoms)
15419 				*atomPtr = 0;
15420 			interredAtoms = null;
15421 			interredAtoms.assumeSafeAppend();
15422 
15423 			ScreenPainterImplementation.fontAttempted = false;
15424 			ScreenPainterImplementation.defaultfont = null;
15425 			ScreenPainterImplementation.defaultfontset = null;
15426 
15427 			Image.impl.xshmQueryCompleted = false;
15428 			Image.impl._xshmAvailable = false;
15429 
15430 			SimpleWindow.nativeMapping = null;
15431 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
15432 			// GlobalHotkeyManager
15433 
15434 			display = null;
15435 			xim = null;
15436 		}
15437 
15438 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
15439 		private static void createXIM () {
15440 			import core.stdc.locale : setlocale, LC_ALL;
15441 			import core.stdc.stdio : stderr, fprintf;
15442 			import core.stdc.stdlib : free;
15443 			import core.stdc.string : strdup;
15444 
15445 			static immutable string[3] mtry = [ "", "@im=local", "@im=" ];
15446 
15447 			auto olocale = strdup(setlocale(LC_ALL, null));
15448 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
15449 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
15450 
15451 			//fprintf(stderr, "opening IM...\n");
15452 			foreach (string s; mtry) {
15453 				XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
15454 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
15455 			}
15456 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
15457 		}
15458 
15459 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
15460 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
15461 		static struct ImgList {
15462 			size_t img; // class; hide it from GC
15463 			ImgList* next;
15464 		}
15465 
15466 		static __gshared ImgList* imglist = null;
15467 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
15468 
15469 		static void registerImage (Image img) {
15470 			if (!imglistLocked && img !is null) {
15471 				import core.stdc.stdlib : malloc;
15472 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
15473 				assert(it !is null); // do proper checks
15474 				it.img = cast(size_t)cast(void*)img;
15475 				it.next = imglist;
15476 				imglist = it;
15477 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
15478 			}
15479 		}
15480 
15481 		static void unregisterImage (Image img) {
15482 			if (!imglistLocked && img !is null) {
15483 				import core.stdc.stdlib : free;
15484 				ImgList* prev = null;
15485 				ImgList* cur = imglist;
15486 				while (cur !is null) {
15487 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
15488 					prev = cur;
15489 					cur = cur.next;
15490 				}
15491 				if (cur !is null) {
15492 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
15493 					free(cur);
15494 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
15495 				} else {
15496 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
15497 				}
15498 			}
15499 		}
15500 
15501 		static void freeImages () { // needed for discardAndRecreate
15502 			imglistLocked = true;
15503 			scope(exit) imglistLocked = false;
15504 			ImgList* cur = imglist;
15505 			ImgList* next = null;
15506 			while (cur !is null) {
15507 				import core.stdc.stdlib : free;
15508 				next = cur.next;
15509 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
15510 				(cast(Image)cast(void*)cur.img).dispose();
15511 				free(cur);
15512 				cur = next;
15513 			}
15514 			imglist = null;
15515 		}
15516 
15517 		/// can be used to override normal handling of display name
15518 		/// from environment and/or command line
15519 		static setDisplayName(string newDisplayName) {
15520 			displayName = cast(char*) (newDisplayName ~ '\0');
15521 		}
15522 
15523 		/// resets to the default display string
15524 		static resetDisplayName() {
15525 			displayName = null;
15526 		}
15527 
15528 		///
15529 		static Display* get() {
15530 			if(display is null) {
15531 				if(!librariesSuccessfullyLoaded)
15532 					throw new Exception("Unable to load X11 client libraries");
15533 				display = XOpenDisplay(displayName);
15534 
15535 				isLocal_ = false;
15536 
15537 				connectionSequence_++;
15538 				if(display is null)
15539 					throw new Exception("Unable to open X display");
15540 
15541 				auto str = display.display_name;
15542 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
15543 				// and otherwise it probably isn't
15544 				if(str is null || (str[0] != ':' && str[0] != '/'))
15545 					isLocal_ = false;
15546 				else
15547 					isLocal_ = true;
15548 
15549 				XSetErrorHandler(&adrlogger);
15550 
15551 				debug(sdpy_x_errors) {
15552 					XSynchronize(display, true);
15553 
15554 					extern(C) int wtf() {
15555 						if(errorHappened) {
15556 							asm { int 3; }
15557 							errorHappened = false;
15558 						}
15559 						return 0;
15560 					}
15561 					XSetAfterFunction(display, &wtf);
15562 				}
15563 
15564 
15565 				XSetIOErrorHandler(&x11ioerrCB);
15566 				Bool sup;
15567 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
15568 				createXIM();
15569 				version(with_eventloop) {
15570 					import arsd.eventloop;
15571 					addFileEventListeners(display.fd, &eventListener, null, null);
15572 				}
15573 			}
15574 
15575 			return display;
15576 		}
15577 
15578 		extern(C)
15579 		static int x11ioerrCB(Display* dpy) {
15580 			throw new XDisconnectException(false);
15581 		}
15582 
15583 		version(with_eventloop) {
15584 			import arsd.eventloop;
15585 			static void eventListener(OsFileHandle fd) {
15586 				//this.mtLock();
15587 				//scope(exit) this.mtUnlock();
15588 				while(XPending(display))
15589 					doXNextEvent(display);
15590 			}
15591 		}
15592 
15593 		// close connection on program exit -- we need this to properly free all images
15594 		static ~this () {
15595 			// the gui thread must clean up after itself or else Xlib might deadlock
15596 			// using this flag on any thread destruction is the easiest way i know of
15597 			// (shared static this is run by the LAST thread to exit, which may not be
15598 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
15599 			if(thisIsGuiThread)
15600 				close();
15601 		}
15602 
15603 		///
15604 		static void close() {
15605 			if(display is null)
15606 				return;
15607 
15608 			version(with_eventloop) {
15609 				import arsd.eventloop;
15610 				removeFileEventListeners(display.fd);
15611 			}
15612 
15613 			// now remove all registered images to prevent shared memory leaks
15614 			freeImages();
15615 
15616 			// tbh I don't know why it is doing this but like if this happens to run
15617 			// from the other thread there's frequent hanging inside here.
15618 			if(thisIsGuiThread)
15619 				XCloseDisplay(display);
15620 			display = null;
15621 		}
15622 	}
15623 
15624 	mixin template NativeImageImplementation() {
15625 		XImage* handle;
15626 		ubyte* rawData;
15627 
15628 		XShmSegmentInfo shminfo;
15629 		bool premultiply = true;
15630 
15631 		__gshared bool xshmQueryCompleted;
15632 		__gshared bool _xshmAvailable;
15633 		public static @property bool xshmAvailable() {
15634 			if(!xshmQueryCompleted) {
15635 				int i1, i2, i3;
15636 				xshmQueryCompleted = true;
15637 
15638 				if(!XDisplayConnection.isLocal)
15639 					_xshmAvailable = false;
15640 				else
15641 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
15642 			}
15643 			return _xshmAvailable;
15644 		}
15645 
15646 		bool usingXshm;
15647 	final:
15648 
15649 		private __gshared bool xshmfailed;
15650 
15651 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
15652 			auto display = XDisplayConnection.get();
15653 			assert(display !is null);
15654 			auto screen = DefaultScreen(display);
15655 
15656 			// it will only use shared memory for somewhat largish images,
15657 			// since otherwise we risk wasting shared memory handles on a lot of little ones
15658 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
15659 
15660 
15661 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
15662 				// the actual use still fails. For example, if the program is in a container and permission denied
15663 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
15664 				//
15665 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
15666 
15667 
15668 				// synchronize so preexisting buffers are clear
15669 				XSync(display, false);
15670 				xshmfailed = false;
15671 
15672 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
15673 
15674 
15675 				usingXshm = true;
15676 				handle = XShmCreateImage(
15677 					display,
15678 					DefaultVisual(display, screen),
15679 					enableAlpha ? 32: 24,
15680 					ImageFormat.ZPixmap,
15681 					null,
15682 					&shminfo,
15683 					width, height);
15684 				if(handle is null)
15685 					goto abortXshm1;
15686 
15687 				if(handle.bytes_per_line != 4 * width)
15688 					goto abortXshm2;
15689 
15690 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
15691 				if(shminfo.shmid < 0)
15692 					goto abortXshm3;
15693 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
15694 				if(rawData == cast(ubyte*) -1)
15695 					goto abortXshm4;
15696 				shminfo.readOnly = 0;
15697 				XShmAttach(display, &shminfo);
15698 
15699 				// and now to the final error check to ensure it actually worked.
15700 				XSync(display, false);
15701 				if(xshmfailed)
15702 					goto abortXshm5;
15703 
15704 				XSetErrorHandler(oldErrorHandler);
15705 
15706 				XDisplayConnection.registerImage(this);
15707 				// if I don't flush here there's a chance the dtor will run before the
15708 				// ctor and lead to a bad value X error. While this hurts the efficiency
15709 				// it is local anyway so prolly better to keep it simple
15710 				XFlush(display);
15711 
15712 				return;
15713 
15714 				abortXshm5:
15715 					shmdt(shminfo.shmaddr);
15716 					rawData = null;
15717 
15718 				abortXshm4:
15719 					shmctl(shminfo.shmid, IPC_RMID, null);
15720 
15721 				abortXshm3:
15722 					// nothing needed, the shmget failed so there's nothing to free
15723 
15724 				abortXshm2:
15725 					XDestroyImage(handle);
15726 					handle = null;
15727 
15728 				abortXshm1:
15729 					XSetErrorHandler(oldErrorHandler);
15730 					usingXshm = false;
15731 					handle = null;
15732 
15733 					shminfo = typeof(shminfo).init;
15734 
15735 					_xshmAvailable = false; // don't try again in the future
15736 
15737 					// writeln("fallingback");
15738 
15739 					goto fallback;
15740 
15741 			} else {
15742 				fallback:
15743 
15744 				if (forcexshm) throw new Exception("can't create XShm Image");
15745 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
15746 				import core.stdc.stdlib : malloc;
15747 				rawData = cast(ubyte*) malloc(width * height * 4);
15748 
15749 				handle = XCreateImage(
15750 					display,
15751 					DefaultVisual(display, screen),
15752 					enableAlpha ? 32 : 24, // bpp
15753 					ImageFormat.ZPixmap,
15754 					0, // offset
15755 					rawData,
15756 					width, height,
15757 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
15758 			}
15759 		}
15760 
15761 		void dispose() {
15762 			// note: this calls free(rawData) for us
15763 			if(handle) {
15764 				if (usingXshm) {
15765 					XDisplayConnection.unregisterImage(this);
15766 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
15767 				}
15768 				XDestroyImage(handle);
15769 				if(usingXshm) {
15770 					shmdt(shminfo.shmaddr);
15771 					shmctl(shminfo.shmid, IPC_RMID, null);
15772 				}
15773 				handle = null;
15774 			}
15775 		}
15776 
15777 		Color getPixel(int x, int y) @system {
15778 			auto offset = (y * width + x) * 4;
15779 			Color c;
15780 			c.a = enableAlpha ? rawData[offset + 3] : 255;
15781 			c.b = rawData[offset + 0];
15782 			c.g = rawData[offset + 1];
15783 			c.r = rawData[offset + 2];
15784 			if(enableAlpha && premultiply)
15785 				c.unPremultiply;
15786 			return c;
15787 		}
15788 
15789 		void setPixel(int x, int y, Color c) @system {
15790 			if(enableAlpha && premultiply)
15791 				c.premultiply();
15792 			auto offset = (y * width + x) * 4;
15793 			rawData[offset + 0] = c.b;
15794 			rawData[offset + 1] = c.g;
15795 			rawData[offset + 2] = c.r;
15796 			if(enableAlpha)
15797 				rawData[offset + 3] = c.a;
15798 		}
15799 
15800 		void convertToRgbaBytes(ubyte[] where) @system {
15801 			assert(where.length == this.width * this.height * 4);
15802 
15803 			// if rawData had a length....
15804 			//assert(rawData.length == where.length);
15805 			for(int idx = 0; idx < where.length; idx += 4) {
15806 				where[idx + 0] = rawData[idx + 2]; // r
15807 				where[idx + 1] = rawData[idx + 1]; // g
15808 				where[idx + 2] = rawData[idx + 0]; // b
15809 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
15810 
15811 				if(enableAlpha && premultiply)
15812 					unPremultiplyRgba(where[idx .. idx + 4]);
15813 			}
15814 		}
15815 
15816 		void setFromRgbaBytes(in ubyte[] where) @system {
15817 			assert(where.length == this.width * this.height * 4);
15818 
15819 			// if rawData had a length....
15820 			//assert(rawData.length == where.length);
15821 			for(int idx = 0; idx < where.length; idx += 4) {
15822 				rawData[idx + 2] = where[idx + 0]; // r
15823 				rawData[idx + 1] = where[idx + 1]; // g
15824 				rawData[idx + 0] = where[idx + 2]; // b
15825 				if(enableAlpha) {
15826 					rawData[idx + 3] = where[idx + 3]; // a
15827 					if(premultiply)
15828 						premultiplyBgra(rawData[idx .. idx + 4]);
15829 				}
15830 			}
15831 		}
15832 
15833 	}
15834 
15835 	mixin template NativeSimpleWindowImplementation() {
15836 		GC gc;
15837 		Window window;
15838 		Display* display;
15839 
15840 		Pixmap buffer;
15841 		int bufferw, bufferh; // size of the buffer; can be bigger than window
15842 		XIC xic; // input context
15843 		int curHidden = 0; // counter
15844 		Cursor blankCurPtr = 0;
15845 		int cursorSequenceNumber = 0;
15846 		int warpEventCount = 0; // number of mouse movement events to eat
15847 
15848 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS
15849 		X11GetSelectionHandler[Atom] getSelectionHandlers;
15850 
15851 		version(without_opengl) {} else
15852 		GLXContext glc;
15853 
15854 		private void fixFixedSize(bool forced=false) (int width, int height) {
15855 			if (forced || this.resizability == Resizability.fixedSize) {
15856 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
15857 				XSizeHints sh;
15858 				static if (!forced) {
15859 					c_long spr;
15860 					XGetWMNormalHints(display, window, &sh, &spr);
15861 					sh.flags |= PMaxSize | PMinSize;
15862 				} else {
15863 					sh.flags = PMaxSize | PMinSize;
15864 				}
15865 				sh.min_width = width;
15866 				sh.min_height = height;
15867 				sh.max_width = width;
15868 				sh.max_height = height;
15869 				XSetWMNormalHints(display, window, &sh);
15870 				//XFlush(display);
15871 			}
15872 		}
15873 
15874 		ScreenPainter getPainter(bool manualInvalidations) {
15875 			return ScreenPainter(this, window, manualInvalidations);
15876 		}
15877 
15878 		void move(int x, int y) {
15879 			XMoveWindow(display, window, x, y);
15880 		}
15881 
15882 		void resize(int w, int h) {
15883 			if (w < 1) w = 1;
15884 			if (h < 1) h = 1;
15885 			XResizeWindow(display, window, w, h);
15886 
15887 			// calling this now to avoid waiting for the server to
15888 			// acknowledge the resize; draws without returning to the
15889 			// event loop will thus actually work. the server's event
15890 			// btw might overrule this and resize it again
15891 			recordX11Resize(display, this, w, h);
15892 
15893 			updateOpenglViewportIfNeeded(w, h);
15894 		}
15895 
15896 		void moveResize (int x, int y, int w, int h) {
15897 			if (w < 1) w = 1;
15898 			if (h < 1) h = 1;
15899 			XMoveResizeWindow(display, window, x, y, w, h);
15900 			updateOpenglViewportIfNeeded(w, h);
15901 		}
15902 
15903 		void hideCursor () {
15904 			if (curHidden++ == 0) {
15905 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
15906 					static const(char)[1] cmbmp = 0;
15907 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
15908 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
15909 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
15910 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
15911 					XFreePixmap(display, pm);
15912 				}
15913 				XDefineCursor(display, window, blankCurPtr);
15914 			}
15915 		}
15916 
15917 		void showCursor () {
15918 			if (--curHidden == 0) XUndefineCursor(display, window);
15919 		}
15920 
15921 		void warpMouse (int x, int y) {
15922 			// here i will send dummy "ignore next mouse motion" event,
15923 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
15924 			// and we don't need to report it to the user (as warping is
15925 			// used when the user needs movement deltas).
15926 			//XClientMessageEvent xclient;
15927 			XEvent e;
15928 			e.xclient.type = EventType.ClientMessage;
15929 			e.xclient.window = window;
15930 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15931 			e.xclient.format = 32;
15932 			e.xclient.data.l[0] = 0;
15933 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
15934 			//{ 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]); }
15935 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15936 			// now warp pointer...
15937 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
15938 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
15939 			// ...and flush
15940 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
15941 			XFlush(display);
15942 		}
15943 
15944 		void sendDummyEvent () {
15945 			// here i will send dummy event to ping event queue
15946 			XEvent e;
15947 			e.xclient.type = EventType.ClientMessage;
15948 			e.xclient.window = window;
15949 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
15950 			e.xclient.format = 32;
15951 			e.xclient.data.l[0] = 0;
15952 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
15953 			XFlush(display);
15954 		}
15955 
15956 		void setTitle(string title) {
15957 			if (title.ptr is null) title = "";
15958 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15959 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15960 			XTextProperty windowName;
15961 			windowName.value = title.ptr;
15962 			windowName.encoding = XA_UTF8; //XA_STRING;
15963 			windowName.format = 8;
15964 			windowName.nitems = cast(uint)title.length;
15965 			XSetWMName(display, window, &windowName);
15966 			char[1024] namebuf = 0;
15967 			auto maxlen = namebuf.length-1;
15968 			if (maxlen > title.length) maxlen = title.length;
15969 			namebuf[0..maxlen] = title[0..maxlen];
15970 			XStoreName(display, window, namebuf.ptr);
15971 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
15972 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
15973 		}
15974 
15975 		string[] getTitles() {
15976 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
15977 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
15978 			XTextProperty textProp;
15979 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
15980 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
15981 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
15982 				} else
15983 					return [];
15984 			} else
15985 				return null;
15986 		}
15987 
15988 		string getTitle() {
15989 			auto titles = getTitles();
15990 			return titles.length ? titles[0] : null;
15991 		}
15992 
15993 		void setMinSize (int minwidth, int minheight) {
15994 			import core.stdc.config : c_long;
15995 			if (minwidth < 1) minwidth = 1;
15996 			if (minheight < 1) minheight = 1;
15997 			XSizeHints sh;
15998 			c_long spr;
15999 			XGetWMNormalHints(display, window, &sh, &spr);
16000 			sh.min_width = minwidth;
16001 			sh.min_height = minheight;
16002 			sh.flags |= PMinSize;
16003 			XSetWMNormalHints(display, window, &sh);
16004 			flushGui();
16005 		}
16006 
16007 		void setMaxSize (int maxwidth, int maxheight) {
16008 			import core.stdc.config : c_long;
16009 			if (maxwidth < 1) maxwidth = 1;
16010 			if (maxheight < 1) maxheight = 1;
16011 			XSizeHints sh;
16012 			c_long spr;
16013 			XGetWMNormalHints(display, window, &sh, &spr);
16014 			sh.max_width = maxwidth;
16015 			sh.max_height = maxheight;
16016 			sh.flags |= PMaxSize;
16017 			XSetWMNormalHints(display, window, &sh);
16018 			flushGui();
16019 		}
16020 
16021 		void setResizeGranularity (int granx, int grany) {
16022 			import core.stdc.config : c_long;
16023 			if (granx < 1) granx = 1;
16024 			if (grany < 1) grany = 1;
16025 			XSizeHints sh;
16026 			c_long spr;
16027 			XGetWMNormalHints(display, window, &sh, &spr);
16028 			sh.width_inc = granx;
16029 			sh.height_inc = grany;
16030 			sh.flags |= PResizeInc;
16031 			XSetWMNormalHints(display, window, &sh);
16032 			flushGui();
16033 		}
16034 
16035 		void setOpacity (uint opacity) {
16036 			arch_ulong o = opacity;
16037 			if (opacity == uint.max)
16038 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
16039 			else
16040 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
16041 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
16042 		}
16043 
16044 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted {
16045 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
16046 			display = XDisplayConnection.get();
16047 			auto screen = DefaultScreen(display);
16048 
16049 			bool overrideRedirect = false;
16050 			if(
16051 				windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu ||
16052 				windowType == WindowTypes.tooltip ||
16053 				windowType == WindowTypes.notification ||
16054 				windowType == WindowTypes.dnd ||
16055 				windowType == WindowTypes.comboBoxDropdown ||
16056 				(customizationFlags & WindowFlags.overrideRedirect)
16057 			)// || windowType == WindowTypes.nestedChild)
16058 				overrideRedirect = true;
16059 
16060 			version(without_opengl) {}
16061 			else {
16062 				if(opengl == OpenGlOptions.yes) {
16063 					GLXFBConfig fbconf = null;
16064 					XVisualInfo* vi = null;
16065 					bool useLegacy = false;
16066 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
16067 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
16068 						int[23] visualAttribs = [
16069 							GLX_X_RENDERABLE , 1/*True*/,
16070 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
16071 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
16072 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
16073 							GLX_RED_SIZE     , 8,
16074 							GLX_GREEN_SIZE   , 8,
16075 							GLX_BLUE_SIZE    , 8,
16076 							GLX_ALPHA_SIZE   , 8,
16077 							GLX_DEPTH_SIZE   , 24,
16078 							GLX_STENCIL_SIZE , 8,
16079 							GLX_DOUBLEBUFFER , 1/*True*/,
16080 							0/*None*/,
16081 						];
16082 						int fbcount;
16083 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
16084 						if (fbcount == 0) {
16085 							useLegacy = true; // try to do at least something
16086 						} else {
16087 							// pick the FB config/visual with the most samples per pixel
16088 							int bestidx = -1, bestns = -1;
16089 							foreach (int fbi; 0..fbcount) {
16090 								int sb, samples;
16091 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
16092 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
16093 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
16094 							}
16095 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
16096 							fbconf = fbc[bestidx];
16097 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
16098 							XFree(fbc);
16099 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
16100 						}
16101 					}
16102 					if (vi is null || useLegacy) {
16103 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
16104 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
16105 						useLegacy = true;
16106 					}
16107 					if (vi is null) throw new Exception("no open gl visual found");
16108 
16109 					XSetWindowAttributes swa;
16110 					auto root = RootWindow(display, screen);
16111 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
16112 
16113 					swa.override_redirect = overrideRedirect;
16114 
16115 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
16116 						0, 0, width, height,
16117 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa);
16118 
16119 					// now try to use `glXCreateContextAttribsARB()` if it's here
16120 					if (!useLegacy) {
16121 						// request fairly advanced context, even with stencil buffer!
16122 						int[9] contextAttribs = [
16123 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
16124 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
16125 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
16126 							// for modern context, set "forward compatibility" flag too
16127 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
16128 							0/*None*/,
16129 						];
16130 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
16131 						if (glc is null && sdpyOpenGLContextAllowFallback) {
16132 							sdpyOpenGLContextVersion = 0;
16133 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
16134 						}
16135 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
16136 					} else {
16137 						// fallback to old GLX call
16138 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
16139 							sdpyOpenGLContextVersion = 0;
16140 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
16141 						}
16142 					}
16143 					// sync to ensure any errors generated are processed
16144 					XSync(display, 0/*False*/);
16145 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
16146 					if(glc is null)
16147 						throw new Exception("glc");
16148 				}
16149 			}
16150 
16151 			if(opengl == OpenGlOptions.no) {
16152 
16153 				XSetWindowAttributes swa;
16154 				swa.background_pixel = WhitePixel(display, screen);
16155 				swa.border_pixel = BlackPixel(display, screen);
16156 				swa.override_redirect = overrideRedirect;
16157 				auto root = RootWindow(display, screen);
16158 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
16159 
16160 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
16161 					0, 0, width, height,
16162 						// I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit.
16163 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa);
16164 
16165 
16166 
16167 				/*
16168 				window = XCreateSimpleWindow(
16169 					display,
16170 					parent is null ? RootWindow(display, screen) : parent.impl.window,
16171 					0, 0, // x, y
16172 					width, height,
16173 					1, // border width
16174 					BlackPixel(display, screen), // border
16175 					WhitePixel(display, screen)); // background
16176 				*/
16177 
16178 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
16179 				bufferw = width;
16180 				bufferh = height;
16181 
16182 				gc = DefaultGC(display, screen);
16183 
16184 				// clear out the buffer to get us started...
16185 				XSetForeground(display, gc, WhitePixel(display, screen));
16186 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
16187 				XSetForeground(display, gc, BlackPixel(display, screen));
16188 			}
16189 
16190 			// input context
16191 			//TODO: create this only for top-level windows, and reuse that?
16192 			populateXic();
16193 
16194 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
16195 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
16196 			// window class
16197 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
16198 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
16199 				XClassHint klass;
16200 				XWMHints wh;
16201 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
16202 					wh.input = true;
16203 					wh.flags |= InputHint;
16204 				}
16205 				XSizeHints size;
16206 				klass.res_name = sdpyWindowClassStr;
16207 				klass.res_class = sdpyWindowClassStr;
16208 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
16209 			}
16210 
16211 			setTitle(title);
16212 			SimpleWindow.nativeMapping[window] = this;
16213 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
16214 
16215 			// This gives our window a close button
16216 			if (windowType != WindowTypes.eventOnly) {
16217 				Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
16218 				int useAtoms;
16219 				if(this.customizationFlags & WindowFlags.managesChildWindowFocus) {
16220 					useAtoms = 2;
16221 				} else {
16222 					useAtoms = 1;
16223 				}
16224 				assert(useAtoms <= atoms.length);
16225 				XSetWMProtocols(display, window, atoms.ptr, useAtoms);
16226 			}
16227 
16228 			// FIXME: windowType and customizationFlags
16229 			Atom[8] wsatoms; // here, due to goto
16230 			int wmsacount = 0; // here, due to goto
16231 
16232 			try
16233 			final switch(windowType) {
16234 				case WindowTypes.normal:
16235 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
16236 				break;
16237 				case WindowTypes.undecorated:
16238 					motifHideDecorations();
16239 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
16240 				break;
16241 				case WindowTypes.eventOnly:
16242 					_hidden = true;
16243 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
16244 					goto hiddenWindow;
16245 				//break;
16246 				case WindowTypes.nestedChild:
16247 					// handled in XCreateWindow calls
16248 				break;
16249 
16250 				case WindowTypes.dropdownMenu:
16251 					motifHideDecorations();
16252 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
16253 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
16254 				break;
16255 				case WindowTypes.popupMenu:
16256 					motifHideDecorations();
16257 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
16258 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
16259 				break;
16260 				case WindowTypes.notification:
16261 					motifHideDecorations();
16262 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
16263 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
16264 				break;
16265 				case WindowTypes.minimallyWrapped:
16266 					assert(0, "don't create a minimallyWrapped thing explicitly!");
16267 
16268 				case WindowTypes.dialog:
16269 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display));
16270 				break;
16271 				case WindowTypes.comboBoxDropdown:
16272 					motifHideDecorations();
16273 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display));
16274 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
16275 				break;
16276 				case WindowTypes.tooltip:
16277 					motifHideDecorations();
16278 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display));
16279 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
16280 				break;
16281 				case WindowTypes.dnd:
16282 					motifHideDecorations();
16283 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display));
16284 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
16285 				break;
16286 				/+
16287 				case WindowTypes.menu:
16288 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
16289 					motifHideDecorations();
16290 				break;
16291 				case WindowTypes.desktop:
16292 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
16293 				break;
16294 				case WindowTypes.dock:
16295 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
16296 				break;
16297 				case WindowTypes.toolbar:
16298 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
16299 				break;
16300 				case WindowTypes.menu:
16301 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
16302 				break;
16303 				case WindowTypes.utility:
16304 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
16305 				break;
16306 				case WindowTypes.splash:
16307 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
16308 				break;
16309 				case WindowTypes.notification:
16310 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
16311 				break;
16312 				+/
16313 			}
16314 			catch(Exception e) {
16315 				// XInternAtom failed, prolly a WM
16316 				// that doesn't support these things
16317 			}
16318 
16319 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
16320 			// the two following flags may be ignored by WM
16321 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
16322 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
16323 
16324 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
16325 
16326 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
16327 
16328 			// What would be ideal here is if they only were
16329 			// selected if there was actually an event handler
16330 			// for them...
16331 
16332 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
16333 
16334 			hiddenWindow:
16335 
16336 			// set the pid property for lookup later by window managers
16337 			// a standard convenience
16338 			import core.sys.posix.unistd;
16339 			arch_ulong pid = getpid();
16340 
16341 			XChangeProperty(
16342 				display,
16343 				impl.window,
16344 				GetAtom!("_NET_WM_PID", true)(display),
16345 				XA_CARDINAL,
16346 				32 /* bits */,
16347 				0 /*PropModeReplace*/,
16348 				&pid,
16349 				1);
16350 
16351 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
16352 				if(parent is null) assert(0);
16353 				// sdpyPrintDebugString("transient");
16354 				XChangeProperty(
16355 					display,
16356 					impl.window,
16357 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
16358 					XA_WINDOW,
16359 					32 /* bits */,
16360 					0 /*PropModeReplace*/,
16361 					&parent.impl.window,
16362 					1);
16363 
16364 			}
16365 
16366 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
16367 				XMapWindow(display, window);
16368 			} else {
16369 				_hidden = true;
16370 			}
16371 		}
16372 
16373 		void populateXic() {
16374 			if (XDisplayConnection.xim !is null) {
16375 				xic = XCreateIC(XDisplayConnection.xim,
16376 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
16377 						/*XNClientWindow*/"clientWindow".ptr, window,
16378 						/*XNFocusWindow*/"focusWindow".ptr, window,
16379 						null);
16380 				if (xic is null) {
16381 					import core.stdc.stdio : stderr, fprintf;
16382 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
16383 				}
16384 			}
16385 		}
16386 
16387 		void selectDefaultInput(bool forceIncludeMouseMotion) {
16388 			auto mask = EventMask.ExposureMask |
16389 				EventMask.KeyPressMask |
16390 				EventMask.KeyReleaseMask |
16391 				EventMask.PropertyChangeMask |
16392 				EventMask.FocusChangeMask |
16393 				EventMask.StructureNotifyMask |
16394 				EventMask.SubstructureNotifyMask |
16395 				EventMask.VisibilityChangeMask
16396 				| EventMask.ButtonPressMask
16397 				| EventMask.ButtonReleaseMask
16398 			;
16399 
16400 			// xshm is our shortcut for local connections
16401 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
16402 				mask |= EventMask.PointerMotionMask;
16403 			else
16404 				mask |= EventMask.ButtonMotionMask;
16405 
16406 			XSelectInput(display, window, mask);
16407 		}
16408 
16409 
16410 		void setNetWMWindowType(Atom type) {
16411 			Atom[2] atoms;
16412 
16413 			atoms[0] = type;
16414 			// generic fallback
16415 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
16416 
16417 			XChangeProperty(
16418 				display,
16419 				impl.window,
16420 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
16421 				XA_ATOM,
16422 				32 /* bits */,
16423 				0 /*PropModeReplace*/,
16424 				atoms.ptr,
16425 				cast(int) atoms.length);
16426 		}
16427 
16428 		void motifHideDecorations(bool hide = true) {
16429 			MwmHints hints;
16430 			hints.flags = MWM_HINTS_DECORATIONS;
16431 			hints.decorations = hide ? 0 : 1;
16432 
16433 			XChangeProperty(
16434 				display,
16435 				impl.window,
16436 				GetAtom!"_MOTIF_WM_HINTS"(display),
16437 				GetAtom!"_MOTIF_WM_HINTS"(display),
16438 				32 /* bits */,
16439 				0 /*PropModeReplace*/,
16440 				&hints,
16441 				hints.sizeof / 4);
16442 		}
16443 
16444 		/*k8: unused
16445 		void createOpenGlContext() {
16446 
16447 		}
16448 		*/
16449 
16450 		void closeWindow() {
16451 			// I can't close this or a child window closing will
16452 			// break events for everyone. So I'm just leaking it right
16453 			// now and that is probably perfectly fine...
16454 			version(none)
16455 			if (customEventFDRead != -1) {
16456 				import core.sys.posix.unistd : close;
16457 				auto same = customEventFDRead == customEventFDWrite;
16458 
16459 				close(customEventFDRead);
16460 				if(!same)
16461 					close(customEventFDWrite);
16462 				customEventFDRead = -1;
16463 				customEventFDWrite = -1;
16464 			}
16465 
16466 			version(without_opengl) {} else
16467 			if(glc !is null) {
16468 				glXDestroyContext(display, glc);
16469 				glc = null;
16470 			}
16471 
16472 			if(buffer)
16473 				XFreePixmap(display, buffer);
16474 			bufferw = bufferh = 0;
16475 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
16476 			XDestroyWindow(display, window);
16477 			XFlush(display);
16478 		}
16479 
16480 		void dispose() {
16481 		}
16482 
16483 		bool destroyed = false;
16484 	}
16485 
16486 	bool insideXEventLoop;
16487 }
16488 
16489 version(X11) {
16490 
16491 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
16492 
16493 	private class ResizeEvent {
16494 		int width, height;
16495 	}
16496 
16497 	void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) {
16498 		if(win.windowType == WindowTypes.minimallyWrapped)
16499 			return;
16500 
16501 		if(win.pendingResizeEvent is null) {
16502 			win.pendingResizeEvent = new ResizeEvent();
16503 			win.addEventListener((ResizeEvent re) {
16504 				recordX11Resize(XDisplayConnection.get, win, re.width, re.height);
16505 			});
16506 		}
16507 		win.pendingResizeEvent.width = width;
16508 		win.pendingResizeEvent.height = height;
16509 		if(!win.eventQueued!ResizeEvent) {
16510 			win.postEvent(win.pendingResizeEvent);
16511 		}
16512 	}
16513 
16514 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
16515 		if(win.windowType == WindowTypes.minimallyWrapped)
16516 			return;
16517 		if(win.closed)
16518 			return;
16519 
16520 		if(width != win.width || height != win.height) {
16521 
16522 		//  writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window);
16523 			win._width = width;
16524 			win._height = height;
16525 
16526 			if(win.openglMode == OpenGlOptions.no) {
16527 				// FIXME: could this be more efficient?
16528 
16529 				if (win.bufferw < width || win.bufferh < height) {
16530 					//{ 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); }
16531 					// grow the internal buffer to match the window...
16532 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
16533 					{
16534 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
16535 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
16536 						scope(exit) XFreeGC(win.display, xgc);
16537 						XSetClipMask(win.display, xgc, None);
16538 						XSetForeground(win.display, xgc, 0);
16539 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
16540 					}
16541 					XCopyArea(display,
16542 						cast(Drawable) win.buffer,
16543 						cast(Drawable) newPixmap,
16544 						win.gc, 0, 0,
16545 						win.bufferw < width ? win.bufferw : win.width,
16546 						win.bufferh < height ? win.bufferh : win.height,
16547 						0, 0);
16548 
16549 					XFreePixmap(display, win.buffer);
16550 					win.buffer = newPixmap;
16551 					win.bufferw = width;
16552 					win.bufferh = height;
16553 				}
16554 
16555 				// clear unused parts of the buffer
16556 				if (win.bufferw > width || win.bufferh > height) {
16557 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
16558 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
16559 					scope(exit) XFreeGC(win.display, xgc);
16560 					XSetClipMask(win.display, xgc, None);
16561 					XSetForeground(win.display, xgc, 0);
16562 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
16563 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
16564 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
16565 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
16566 				}
16567 
16568 			}
16569 
16570 			win.updateOpenglViewportIfNeeded(width, height);
16571 
16572 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
16573 
16574 			if(win.resizability != Resizability.automaticallyScaleIfPossible)
16575 			if(win.windowResized !is null) {
16576 				XUnlockDisplay(display);
16577 				scope(exit) XLockDisplay(display);
16578 				win.windowResized(width, height);
16579 			}
16580 		}
16581 	}
16582 
16583 
16584 	/// Platform-specific, you might use it when doing a custom event loop.
16585 	bool doXNextEvent(Display* display) {
16586 		bool done;
16587 		XEvent e;
16588 		XNextEvent(display, &e);
16589 		version(sddddd) {
16590 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16591 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
16592 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
16593 			}
16594 		}
16595 
16596 		// filter out compose events
16597 		if (XFilterEvent(&e, None)) {
16598 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
16599 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
16600 			return false;
16601 		}
16602 		// process keyboard mapping changes
16603 		if (e.type == EventType.KeymapNotify) {
16604 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
16605 			XRefreshKeyboardMapping(&e.xmapping);
16606 			return false;
16607 		}
16608 
16609 		version(with_eventloop)
16610 			import arsd.eventloop;
16611 
16612 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
16613 			// see windows impl's comments
16614 			XUnlockDisplay(display);
16615 			scope(exit) XLockDisplay(display);
16616 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
16617 			if(ret == 0)
16618 				return done;
16619 		}
16620 
16621 
16622 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
16623 			if(win.getNativeEventHandler !is null) {
16624 				XUnlockDisplay(display);
16625 				scope(exit) XLockDisplay(display);
16626 				auto ret = win.getNativeEventHandler()(e);
16627 				if(ret == 0)
16628 					return done;
16629 			}
16630 		}
16631 
16632 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
16633 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
16634 				// we get this because of the RRScreenChangeNotifyMask
16635 
16636 				// this isn't actually an ideal way to do it since it wastes time
16637 				// but meh it is simple and it works.
16638 				win.actualDpiLoadAttempted = false;
16639 				SimpleWindow.xRandrInfoLoadAttemped = false;
16640 				win.updateActualDpi(); // trigger a reload
16641 			}
16642 		}
16643 
16644 		switch(e.type) {
16645 		  case EventType.SelectionClear:
16646 		  	// writeln("SelectionClear");
16647 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
16648 				// FIXME so it is supposed to finish any in progress transfers... but idk...
16649 				// writeln("SelectionClear");
16650 			}
16651 			SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
16652 			mightShortCircuitClipboard = false;
16653 		  break;
16654 		  case EventType.SelectionRequest:
16655 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
16656 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
16657 				// printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
16658 				XUnlockDisplay(display);
16659 				scope(exit) XLockDisplay(display);
16660 				(*ssh).handleRequest(e);
16661 			}
16662 		  break;
16663 		  case EventType.PropertyNotify:
16664 			// import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
16665 
16666 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
16667 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
16668 					ssh.sendMoreIncr(&e.xproperty);
16669 			}
16670 
16671 
16672 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
16673 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16674 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
16675 					Atom target;
16676 					int format;
16677 					arch_ulong bytesafter, length;
16678 					void* value;
16679 
16680 					ubyte[] s;
16681 					Atom targetToKeep;
16682 
16683 					XGetWindowProperty(
16684 						e.xproperty.display,
16685 						e.xproperty.window,
16686 						e.xproperty.atom,
16687 						0,
16688 						100000 /* length */,
16689 						true, /* erase it to signal we got it and want more */
16690 						0 /*AnyPropertyType*/,
16691 						&target, &format, &length, &bytesafter, &value);
16692 
16693 					if(!targetToKeep)
16694 						targetToKeep = target;
16695 
16696 					auto id = (cast(ubyte*) value)[0 .. length];
16697 
16698 					handler.handleIncrData(targetToKeep, id);
16699 					if(length == 0) {
16700 						win.getSelectionHandlers.remove(e.xproperty.atom);
16701 					}
16702 
16703 					XFree(value);
16704 				}
16705 			}
16706 		  break;
16707 		  case EventType.SelectionNotify:
16708 		  	// import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom);
16709 			if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
16710 			if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
16711 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
16712 					XUnlockDisplay(display);
16713 					scope(exit) XLockDisplay(display);
16714 					handler.handleData(None, null);
16715 					win.getSelectionHandlers.remove(e.xproperty.atom);
16716 				} else {
16717 					Atom target;
16718 					int format;
16719 					arch_ulong bytesafter, length;
16720 					void* value;
16721 					XGetWindowProperty(
16722 						e.xselection.display,
16723 						e.xselection.requestor,
16724 						e.xselection.property,
16725 						0,
16726 						100000 /* length */,
16727 						//false, /* don't erase it */
16728 						true, /* do erase it lol */
16729 						0 /*AnyPropertyType*/,
16730 						&target, &format, &length, &bytesafter, &value);
16731 
16732 					// FIXME: I don't have to copy it now since it is in char[] instead of string
16733 
16734 					{
16735 						XUnlockDisplay(display);
16736 						scope(exit) XLockDisplay(display);
16737 
16738 						if(target == XA_ATOM) {
16739 							// initial request, see what they are able to work with and request the best one
16740 							// we can handle, if available
16741 
16742 							Atom[] answer = (cast(Atom*) value)[0 .. length];
16743 							Atom best = handler.findBestFormat(answer);
16744 
16745 							/+
16746 							writeln("got ", answer);
16747 							foreach(a; answer)
16748 								writeln(XGetAtomName(display, a).stringz);
16749 							writeln("best ", best);
16750 							+/
16751 
16752 							if(best != None) {
16753 								// actually request the best format
16754 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
16755 							}
16756 						} else if(target == GetAtom!"INCR"(display)) {
16757 							// incremental
16758 
16759 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
16760 
16761 							// signal the sending program that we see
16762 							// the incr and are ready to receive more.
16763 							XDeleteProperty(
16764 								e.xselection.display,
16765 								e.xselection.requestor,
16766 								e.xselection.property);
16767 						} else {
16768 							// unsupported type... maybe, forward, then we done with it
16769 							if(target != None) {
16770 								handler.handleData(target, cast(ubyte[]) value[0 .. length]);
16771 								win.getSelectionHandlers.remove(e.xproperty.atom);
16772 							}
16773 						}
16774 					}
16775 					XFree(value);
16776 					/*
16777 					XDeleteProperty(
16778 						e.xselection.display,
16779 						e.xselection.requestor,
16780 						e.xselection.property);
16781 					*/
16782 				}
16783 			}
16784 			break;
16785 		  case EventType.ConfigureNotify:
16786 			auto event = e.xconfigure;
16787 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
16788 				if(win.windowType == WindowTypes.minimallyWrapped)
16789 					break;
16790 					//version(sdddd) { writeln(" w=", event.width, "; h=", event.height); }
16791 
16792 				/+
16793 					The ICCCM says window managers must send a synthetic event when the window
16794 					is moved but NOT when it is resized. In the resize case, an event is sent
16795 					with position (0, 0) which can be wrong and break the dpi calculations.
16796 
16797 					So we only consider the synthetic events from the WM and otherwise
16798 					need to wait for some other event to get the position which... sucks.
16799 
16800 					I'd rather not have windows changing their layout on mouse motion after
16801 					switching monitors... might be forced to but for now just ignoring it.
16802 
16803 					Easiest way to switch monitors without sending a size position is by
16804 					maximize or fullscreen in a setup like mine, but on most setups those
16805 					work on the monitor it is already living on, so it should be ok most the
16806 					time.
16807 				+/
16808 				if(event.send_event) {
16809 					win.screenPositionKnown = true;
16810 					win.screenPositionX = event.x;
16811 					win.screenPositionY = event.y;
16812 					win.updateActualDpi();
16813 				}
16814 
16815 				win.updateIMEPopupLocation();
16816 				recordX11ResizeAsync(display, *win, event.width, event.height);
16817 			}
16818 		  break;
16819 		  case EventType.Expose:
16820 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
16821 				if(win.windowType == WindowTypes.minimallyWrapped)
16822 					break;
16823 				// if it is closing from a popup menu, it can get
16824 				// an Expose event right by the end and trigger a
16825 				// BadDrawable error ... we'll just check
16826 				// closed to handle that.
16827 				if((*win).closed) break;
16828 				if((*win).openglMode == OpenGlOptions.no) {
16829 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
16830 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
16831 					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);
16832 				} else {
16833 					// need to redraw the scene somehow
16834 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
16835 						XUnlockDisplay(display);
16836 						scope(exit) XLockDisplay(display);
16837 						version(without_opengl) {} else
16838 						win.redrawOpenGlSceneSoon();
16839 					}
16840 				}
16841 			}
16842 		  break;
16843 		  case EventType.FocusIn:
16844 		  case EventType.FocusOut:
16845 			mightShortCircuitClipboard = false; // if the focus has changed, good chance the clipboard cache is invalidated too, kinda hacky but always better to skip when unnecessary than use when we shouldn't have
16846 
16847 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16848 				/+
16849 
16850 				void info(string detail) {
16851 					string s;
16852 					// import std.conv;
16853 					// import std.datetime;
16854 					s ~= to!string(Clock.currTime);
16855 					s ~= " ";
16856 					s ~= e.type == EventType.FocusIn ? "in " : "out";
16857 					s ~= " ";
16858 					s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main  ";
16859 					s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed ";
16860 					s ~= detail;
16861 					s ~= " ";
16862 
16863 					sdpyPrintDebugString(s);
16864 
16865 				}
16866 
16867 				switch(e.xfocus.detail) {
16868 					case NotifyDetail.NotifyAncestor: info("Ancestor"); break;
16869 					case NotifyDetail.NotifyVirtual: info("Virtual"); break;
16870 					case NotifyDetail.NotifyInferior: info("Inferior"); break;
16871 					case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break;
16872 					case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break;
16873 					case NotifyDetail.NotifyPointer: info("pointer"); break;
16874 					case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break;
16875 					case NotifyDetail.NotifyDetailNone: info("none"); break;
16876 					default:
16877 
16878 				}
16879 				+/
16880 
16881 
16882 				if(e.xfocus.detail == NotifyDetail.NotifyPointer)
16883 					break; // just ignore these they seem irrelevant
16884 
16885 				auto old = win._focused;
16886 				win._focused = e.type == EventType.FocusIn;
16887 
16888 				// yes, we are losing the focus, but to our own child. that's actually kinda keeping it.
16889 				if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior)
16890 					win._focused = true;
16891 
16892 				if(win.demandingAttention)
16893 					demandAttention(*win, false);
16894 
16895 				win.updateIMEFocused();
16896 
16897 				if(old != win._focused && win.onFocusChange) {
16898 					XUnlockDisplay(display);
16899 					scope(exit) XLockDisplay(display);
16900 					win.onFocusChange(win._focused);
16901 				}
16902 			}
16903 		  break;
16904 		  case EventType.VisibilityNotify:
16905 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
16906 					auto before = (*win)._visible;
16907 					(*win)._visible = (e.xvisibility.state != VisibilityNotify.VisibilityFullyObscured);
16908 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
16909 						if (win.visibilityChanged !is null && before == true) {
16910 								XUnlockDisplay(display);
16911 								scope(exit) XLockDisplay(display);
16912 								win.visibilityChanged(false);
16913 							}
16914 					} else {
16915 						if (win.visibilityChanged !is null && before == false) {
16916 							XUnlockDisplay(display);
16917 							scope(exit) XLockDisplay(display);
16918 							win.visibilityChanged(true);
16919 						}
16920 					}
16921 				}
16922 				break;
16923 		  case EventType.ClientMessage:
16924 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
16925 					// "ignore next mouse motion" event, increment ignore counter for teh window
16926 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16927 						++(*win).warpEventCount;
16928 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
16929 					} else {
16930 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
16931 					}
16932 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
16933 					// user clicked the close button on the window manager
16934 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16935 						XUnlockDisplay(display);
16936 						scope(exit) XLockDisplay(display);
16937 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
16938 					}
16939 
16940 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
16941 					// writeln("HAPPENED");
16942 					// user clicked the close button on the window manager
16943 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16944 						XUnlockDisplay(display);
16945 						scope(exit) XLockDisplay(display);
16946 
16947 						auto setTo = *win;
16948 
16949 						if(win.setRequestedInputFocus !is null) {
16950 							auto s = win.setRequestedInputFocus();
16951 							if(s !is null) {
16952 								setTo = s;
16953 							}
16954 						}
16955 
16956 						assert(setTo !is null);
16957 
16958 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
16959 
16960 						// sdpyPrintDebugString("WM_TAKE_FOCUS ", setTo.impl.window);
16961 						XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]);
16962 					}
16963 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
16964 					foreach(nai; NotificationAreaIcon.activeIcons)
16965 						nai.newManager();
16966 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
16967 
16968 					bool xDragWindow = true;
16969 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
16970 						//XDefineCursor(display, xDragWindow.impl.window,
16971 							//writeln("XdndStatus ", e.xclient.data.l);
16972 					}
16973 					if(auto dh = win.dropHandler) {
16974 
16975 						static Atom[3] xFormatsBuffer;
16976 						static Atom[] xFormats;
16977 
16978 						void resetXFormats() {
16979 							xFormatsBuffer[] = 0;
16980 							xFormats = xFormatsBuffer[];
16981 						}
16982 
16983 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
16984 							// on Windows it is supposed to return the effect you actually do FIXME
16985 
16986 							auto sourceWindow =  e.xclient.data.l[0];
16987 
16988 							xFormatsBuffer[0] = e.xclient.data.l[2];
16989 							xFormatsBuffer[1] = e.xclient.data.l[3];
16990 							xFormatsBuffer[2] = e.xclient.data.l[4];
16991 
16992 							if(e.xclient.data.l[1] & 1) {
16993 								// can just grab it all but like we don't necessarily need them...
16994 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
16995 							} else {
16996 								int len;
16997 								foreach(fmt; xFormatsBuffer)
16998 									if(fmt) len++;
16999 								xFormats = xFormatsBuffer[0 .. len];
17000 							}
17001 
17002 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
17003 
17004 							dh.dragEnter(&pkg);
17005 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
17006 
17007 							auto pack = e.xclient.data.l[2];
17008 
17009 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
17010 
17011 
17012 							XClientMessageEvent xclient;
17013 
17014 							xclient.type = EventType.ClientMessage;
17015 							xclient.window = e.xclient.data.l[0];
17016 							xclient.message_type = GetAtom!"XdndStatus"(display);
17017 							xclient.format = 32;
17018 							xclient.data.l[0] = win.impl.window;
17019 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
17020 							auto r = result.consistentWithin;
17021 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
17022 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
17023 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
17024 
17025 							XSendEvent(
17026 								display,
17027 								e.xclient.data.l[0],
17028 								false,
17029 								EventMask.NoEventMask,
17030 								cast(XEvent*) &xclient
17031 							);
17032 
17033 
17034 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
17035 							//writeln("XdndLeave");
17036 							// drop cancelled.
17037 							// data.l[0] is the source window
17038 							dh.dragLeave();
17039 
17040 							resetXFormats();
17041 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
17042 							// drop happening, should fetch data, then send finished
17043 							// writeln("XdndDrop");
17044 
17045 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
17046 
17047 							dh.drop(&pkg);
17048 
17049 							resetXFormats();
17050 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
17051 							// writeln("XdndFinished");
17052 
17053 							dh.finish();
17054 						}
17055 
17056 					}
17057 				}
17058 		  break;
17059 		  case EventType.MapNotify:
17060 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
17061 					auto before = (*win)._visible;
17062 					(*win)._visible = true;
17063 					if (!(*win)._visibleForTheFirstTimeCalled) {
17064 						(*win)._visibleForTheFirstTimeCalled = true;
17065 						if ((*win).visibleForTheFirstTime !is null) {
17066 							XUnlockDisplay(display);
17067 							scope(exit) XLockDisplay(display);
17068 							(*win).visibleForTheFirstTime();
17069 						}
17070 					}
17071 					if ((*win).visibilityChanged !is null && before == false) {
17072 						XUnlockDisplay(display);
17073 						scope(exit) XLockDisplay(display);
17074 						(*win).visibilityChanged(true);
17075 					}
17076 				}
17077 		  break;
17078 		  case EventType.UnmapNotify:
17079 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
17080 					auto before = (*win)._visible;
17081 					win._visible = false;
17082 					if (win.visibilityChanged !is null && before == true) {
17083 						XUnlockDisplay(display);
17084 						scope(exit) XLockDisplay(display);
17085 						win.visibilityChanged(false);
17086 					}
17087 			}
17088 		  break;
17089 		  case EventType.DestroyNotify:
17090 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
17091 				if(win.destroyed)
17092 					break; // might get a notification both for itself and from its parent
17093 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
17094 				win._closed = true; // just in case
17095 				win.destroyed = true;
17096 				if (win.xic !is null) {
17097 					XDestroyIC(win.xic);
17098 					win.xic = null; // just in case
17099 				}
17100 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
17101 				bool anyImportant = false;
17102 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
17103 					if(w.beingOpenKeepsAppOpen) {
17104 						anyImportant = true;
17105 						break;
17106 					}
17107 				if(!anyImportant) {
17108 					EventLoop.quitApplication();
17109 					done = true;
17110 				}
17111 			}
17112 			auto window = e.xdestroywindow.window;
17113 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
17114 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
17115 
17116 			version(with_eventloop) {
17117 				if(done) exit();
17118 			}
17119 		  break;
17120 
17121 		  case EventType.MotionNotify:
17122 			MouseEvent mouse;
17123 			auto event = e.xmotion;
17124 
17125 			mouse.type = MouseEventType.motion;
17126 			mouse.x = event.x;
17127 			mouse.y = event.y;
17128 			mouse.modifierState = event.state;
17129 
17130 			mouse.timestamp = event.time;
17131 
17132 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
17133 				mouse.window = *win;
17134 				if (win.warpEventCount > 0) {
17135 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
17136 					--(*win).warpEventCount;
17137 					(*win).mdx(mouse); // so deltas will be correctly updated
17138 				} else {
17139 					win.warpEventCount = 0; // just in case
17140 					(*win).mdx(mouse);
17141 					if((*win).handleMouseEvent) {
17142 						XUnlockDisplay(display);
17143 						scope(exit) XLockDisplay(display);
17144 						(*win).handleMouseEvent(mouse);
17145 					}
17146 				}
17147 			}
17148 
17149 		  	version(with_eventloop)
17150 				send(mouse);
17151 		  break;
17152 		  case EventType.ButtonPress:
17153 		  case EventType.ButtonRelease:
17154 			MouseEvent mouse;
17155 			auto event = e.xbutton;
17156 
17157 			mouse.timestamp = event.time;
17158 
17159 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
17160 			mouse.x = event.x;
17161 			mouse.y = event.y;
17162 
17163 			static Time lastMouseDownTime = 0;
17164 			static int lastMouseDownButton = -1;
17165 
17166 			mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
17167 			if(e.type == EventType.ButtonPress) {
17168 				lastMouseDownTime = event.time;
17169 				lastMouseDownButton = event.button;
17170 			}
17171 
17172 			switch(event.button) {
17173 				case 1: mouse.button = MouseButton.left; break; // left
17174 				case 2: mouse.button = MouseButton.middle; break; // middle
17175 				case 3: mouse.button = MouseButton.right; break; // right
17176 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
17177 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
17178 				case 6: mouse.button = MouseButton.wheelLeft; break; // scroll left
17179 				case 7: mouse.button = MouseButton.wheelRight; break; // scroll right
17180 				case 8: mouse.button = MouseButton.backButton; break;
17181 				case 9: mouse.button = MouseButton.forwardButton; break;
17182 				default:
17183 			}
17184 
17185 			// FIXME: double check this
17186 			mouse.modifierState = event.state;
17187 
17188 			//mouse.modifierState = event.detail;
17189 
17190 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
17191 				mouse.window = *win;
17192 				(*win).mdx(mouse);
17193 				if((*win).handleMouseEvent) {
17194 					XUnlockDisplay(display);
17195 					scope(exit) XLockDisplay(display);
17196 					(*win).handleMouseEvent(mouse);
17197 				}
17198 			}
17199 			version(with_eventloop)
17200 				send(mouse);
17201 		  break;
17202 
17203 		  case EventType.GenericEvent:
17204 			import arsd.core;
17205 
17206 			auto cookie = &e.xcookie;
17207 			if(XGetEventData(XDisplayConnection.get, cookie)) {
17208 				scope(exit)
17209 					XFreeEventData(XDisplayConnection.get, cookie);
17210 
17211 				// should only happen if it was already loaded since otherwise we wouldn't have subscribed to the events
17212 				if(xi2.loadAttempted && cookie.extension == xi_opcode) {
17213 					// if(cookie.evtype == XIEventType.XI_Motion)
17214 
17215 					auto deviceEvent = cast(XIDeviceEvent*) cookie.data;
17216 
17217 					static void interpretXI2MaskThing(ubyte* mask, int mask_len, scope void delegate(int idx, bool value) inspector) {
17218 						foreach(idx, by; mask[0 .. mask_len]) {
17219 							foreach(bitIdx; 0 .. 8) {
17220 								inspector(cast(int) (idx * 8 + bitIdx), (by & (1 << bitIdx)) ? true : false);
17221 							}
17222 						}
17223 					}
17224 
17225 					InputDeviceEvent ide;
17226 
17227 					ide.event = cookie.evtype;
17228 					ide.deviceId = deviceEvent.deviceid;
17229 					// deviceEvent.sourceid is the physical device an event came from
17230 
17231 					// for a touchscreen, detail is the touch id
17232 					ide.detail = deviceEvent.detail;
17233 					ide.flags = deviceEvent.flags;
17234 					ide.rootX = deviceEvent.root_x;
17235 					ide.rootY = deviceEvent.root_y;
17236 					ide.windowX = deviceEvent.event_x;
17237 					ide.windowY = deviceEvent.event_y;
17238 					ide.buttons = 0;
17239 					ide.valuators[] = double.nan;
17240 
17241 					interpretXI2MaskThing(deviceEvent.buttons.mask, deviceEvent.buttons.mask_len, (idx, value) {
17242 						if(idx < 64) {
17243 							if(value)
17244 								ide.buttons |= 1 << idx;
17245 
17246 						}
17247 					});
17248 
17249 					auto values = deviceEvent.valuators.values;
17250 					interpretXI2MaskThing(deviceEvent.valuators.mask, deviceEvent.valuators.mask_len, (idx, value) {
17251 						if(value && idx < ide.valuators.length) {
17252 							ide.valuators[idx] = *values;
17253 							values++;
17254 						}
17255 					});
17256 
17257 					if(auto win = deviceEvent.event in SimpleWindow.nativeMapping) {
17258 						ide.window = *win;
17259 						XUnlockDisplay(display);
17260 						scope(exit) XLockDisplay(display);
17261 						(*win).dispatchXInputEvent(ide);
17262 					}
17263 				}
17264 			}
17265 		  break;
17266 
17267 		  case EventType.KeyPress:
17268 		  case EventType.KeyRelease:
17269 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
17270 			KeyEvent ke;
17271 			ke.pressed = e.type == EventType.KeyPress;
17272 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
17273 
17274 			auto sym = XKeycodeToKeysym(
17275 				XDisplayConnection.get(),
17276 				e.xkey.keycode,
17277 				0);
17278 
17279 			ke.key = cast(Key) sym;//e.xkey.keycode;
17280 
17281 			ke.modifierState = e.xkey.state;
17282 
17283 			// writefln("%x", sym);
17284 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
17285 			int charbuflen = 0; // return value of XwcLookupString
17286 			if (ke.pressed) {
17287 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
17288 				if (win !is null && win.xic !is null) {
17289 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
17290 					Status status;
17291 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
17292 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
17293 				} else {
17294 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
17295 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
17296 					char[16] buffer;
17297 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
17298 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
17299 				}
17300 			}
17301 
17302 			// if there's no char, subst one
17303 			if (charbuflen == 0) {
17304 				switch (sym) {
17305 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
17306 					case 0xff8d: // keypad enter
17307 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
17308 					default : // ignore
17309 				}
17310 			}
17311 
17312 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
17313 				ke.window = *win;
17314 
17315 
17316 				if(win.inputProxy)
17317 					win = &win.inputProxy;
17318 
17319 				// char events are separate since they are on Windows too
17320 				// also, xcompose can generate long char sequences
17321 				// don't send char events if Meta and/or Hyper is pressed
17322 				// TODO: ctrl+char should only send control chars; not yet
17323 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
17324 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
17325 				}
17326 
17327 				dchar[32] charsComingBuffer;
17328 				int charsComingPosition;
17329 				dchar[] charsComing = charsComingBuffer[];
17330 
17331 				if (ke.pressed && charbuflen > 0) {
17332 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
17333 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
17334 						if(charsComingPosition >= charsComing.length)
17335 							charsComing.length = charsComingPosition + 8;
17336 
17337 						charsComing[charsComingPosition++] = ch;
17338 					}
17339 
17340 					charsComing = charsComing[0 .. charsComingPosition];
17341 				} else {
17342 					charsComing = null;
17343 				}
17344 
17345 				ke.charsPossible = charsComing;
17346 
17347 				if (win.handleKeyEvent) {
17348 					XUnlockDisplay(display);
17349 					scope(exit) XLockDisplay(display);
17350 					win.handleKeyEvent(ke);
17351 				}
17352 
17353 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
17354 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
17355 					XUnlockDisplay(display);
17356 					scope(exit) XLockDisplay(display);
17357 					foreach(ch; charsComing)
17358 						win.handleCharEvent(ch);
17359 				}
17360 			}
17361 
17362 			version(with_eventloop)
17363 				send(ke);
17364 		  break;
17365 		  default:
17366 		}
17367 
17368 		return done;
17369 	}
17370 }
17371 
17372 /* *************************************** */
17373 /*      Done with simpledisplay stuff      */
17374 /* *************************************** */
17375 
17376 // Necessary C library bindings follow
17377 version(Windows) {} else
17378 version(Emscripten) {} else
17379 version(X11) {
17380 
17381 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
17382 
17383 // X11 bindings needed here
17384 /*
17385 	A little of this is from the bindings project on
17386 	D Source and some of it is copy/paste from the C
17387 	header.
17388 
17389 	The DSource listing consistently used D's long
17390 	where C used long. That's wrong - C long is 32 bit, so
17391 	it should be int in D. I changed that here.
17392 
17393 	Note:
17394 	This isn't complete, just took what I needed for myself.
17395 */
17396 
17397 import core.stdc.stddef : wchar_t;
17398 
17399 interface XLib {
17400 extern(C) nothrow @nogc {
17401 	char* XResourceManagerString(Display*);
17402 	void XrmInitialize();
17403 	XrmDatabase XrmGetStringDatabase(char* data);
17404 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
17405 
17406 	Cursor XCreateFontCursor(Display*, uint shape);
17407 	int XDefineCursor(Display* display, Window w, Cursor cursor);
17408 	int XUndefineCursor(Display* display, Window w);
17409 
17410 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
17411 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
17412 	int XFreeCursor(Display* display, Cursor cursor);
17413 
17414 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
17415 
17416 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
17417 
17418 	XVaNestedList XVaCreateNestedList(int unused, ...);
17419 
17420 	char *XKeysymToString(KeySym keysym);
17421 	KeySym XKeycodeToKeysym(
17422 		Display*		/* display */,
17423 		KeyCode		/* keycode */,
17424 		int			/* index */
17425 	);
17426 
17427 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
17428 
17429 	int XFree(void*);
17430 	int XDeleteProperty(Display *display, Window w, Atom property);
17431 
17432 	// int XSetCommand(Display*, Window, const char**, int);
17433 
17434 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements);
17435 
17436 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
17437 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
17438 		*actual_type_return, int *actual_format_return, arch_ulong
17439 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
17440 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
17441 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
17442 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
17443 
17444 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
17445 
17446 	Window XGetSelectionOwner(Display *display, Atom selection);
17447 
17448 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
17449 
17450 	char** XListFonts(Display*, const char*, int, int*);
17451 	void XFreeFontNames(char**);
17452 
17453 	Display* XOpenDisplay(const char*);
17454 	int XCloseDisplay(Display*);
17455 
17456 	int function() XSynchronize(Display*, bool);
17457 	int function() XSetAfterFunction(Display*, int function() proc);
17458 
17459 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
17460 
17461 	Bool XSupportsLocale();
17462 	char* XSetLocaleModifiers(const(char)* modifier_list);
17463 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
17464 	Status XCloseOM(XOM om);
17465 
17466 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
17467 	Status XCloseIM(XIM im);
17468 
17469 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
17470 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
17471 	Display* XDisplayOfIM(XIM im);
17472 	char* XLocaleOfIM(XIM im);
17473 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
17474 	void XDestroyIC(XIC ic);
17475 	void XSetICFocus(XIC ic);
17476 	void XUnsetICFocus(XIC ic);
17477 	//wchar_t* XwcResetIC(XIC ic);
17478 	char* XmbResetIC(XIC ic);
17479 	char* Xutf8ResetIC(XIC ic);
17480 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
17481 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
17482 	XIM XIMOfIC(XIC ic);
17483 
17484 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
17485 
17486 
17487 	XFontStruct *XLoadQueryFont(Display *display, scope const char *name);
17488 	int XFreeFont(Display *display, XFontStruct *font_struct);
17489 	int XSetFont(Display* display, GC gc, Font font);
17490 	int XTextWidth(XFontStruct*, scope const char*, int);
17491 
17492 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
17493 	int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n);
17494 
17495 	Window XCreateSimpleWindow(
17496 		Display*	/* display */,
17497 		Window		/* parent */,
17498 		int			/* x */,
17499 		int			/* y */,
17500 		uint		/* width */,
17501 		uint		/* height */,
17502 		uint		/* border_width */,
17503 		uint		/* border */,
17504 		uint		/* background */
17505 	);
17506 	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);
17507 
17508 	int XReparentWindow(Display*, Window, Window, int, int);
17509 	int XClearWindow(Display*, Window);
17510 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
17511 	int XMoveWindow(Display*, Window, int, int);
17512 	int XResizeWindow(Display *display, Window w, uint width, uint height);
17513 
17514 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
17515 
17516 	Status XMatchVisualInfo(Display  *display,  int screen, int depth, int class_, XVisualInfo *vinfo_return);
17517 
17518 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
17519 
17520 	XImage *XCreateImage(
17521 		Display*		/* display */,
17522 		Visual*		/* visual */,
17523 		uint	/* depth */,
17524 		int			/* format */,
17525 		int			/* offset */,
17526 		ubyte*		/* data */,
17527 		uint	/* width */,
17528 		uint	/* height */,
17529 		int			/* bitmap_pad */,
17530 		int			/* bytes_per_line */
17531 	);
17532 
17533 	Status XInitImage (XImage* image);
17534 
17535 	Atom XInternAtom(
17536 		Display*		/* display */,
17537 		const char*	/* atom_name */,
17538 		Bool		/* only_if_exists */
17539 	);
17540 
17541 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
17542 	char* XGetAtomName(Display*, Atom);
17543 	Status XGetAtomNames(Display*, Atom*, int count, char**);
17544 
17545 	int XPutImage(
17546 		Display*	/* display */,
17547 		Drawable	/* d */,
17548 		GC			/* gc */,
17549 		XImage*	/* image */,
17550 		int			/* src_x */,
17551 		int			/* src_y */,
17552 		int			/* dest_x */,
17553 		int			/* dest_y */,
17554 		uint		/* width */,
17555 		uint		/* height */
17556 	);
17557 
17558 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
17559 
17560 
17561 	int XDestroyWindow(
17562 		Display*	/* display */,
17563 		Window		/* w */
17564 	);
17565 
17566 	int XDestroyImage(XImage*);
17567 
17568 	int XSelectInput(
17569 		Display*	/* display */,
17570 		Window		/* w */,
17571 		EventMask	/* event_mask */
17572 	);
17573 
17574 	int XMapWindow(
17575 		Display*	/* display */,
17576 		Window		/* w */
17577 	);
17578 
17579 	Status XIconifyWindow(Display*, Window, int);
17580 	int XMapRaised(Display*, Window);
17581 	int XMapSubwindows(Display*, Window);
17582 
17583 	int XNextEvent(
17584 		Display*	/* display */,
17585 		XEvent*		/* event_return */
17586 	);
17587 
17588 	int XMaskEvent(Display*, arch_long, XEvent*);
17589 
17590 	Bool XFilterEvent(XEvent *event, Window window);
17591 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
17592 
17593 	Status XSetWMProtocols(
17594 		Display*	/* display */,
17595 		Window		/* w */,
17596 		Atom*		/* protocols */,
17597 		int			/* count */
17598 	);
17599 
17600 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
17601 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
17602 
17603 
17604 	Status XInitThreads();
17605 	void XLockDisplay (Display* display);
17606 	void XUnlockDisplay (Display* display);
17607 
17608 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
17609 
17610 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
17611 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
17612 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
17613 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
17614 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
17615 
17616 
17617 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
17618 	int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int);
17619 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
17620 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
17621 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
17622 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
17623 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
17624 	int XDrawPoint(Display*, Drawable, GC, int, int);
17625 	int XSetForeground(Display*, GC, uint);
17626 	int XSetBackground(Display*, GC, uint);
17627 
17628 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
17629 	void XFreeFontSet(Display*, XFontSet);
17630 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int);
17631 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
17632 
17633 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
17634 
17635 
17636 //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);
17637 
17638 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
17639 	int XSetFunction(Display*, GC, int);
17640 
17641 	GC XCreateGC(Display*, Drawable, uint, void*);
17642 	int XCopyGC(Display*, GC, uint, GC);
17643 	int XFreeGC(Display*, GC);
17644 
17645 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
17646 	bool XCheckMaskEvent(Display*, int, XEvent*);
17647 
17648 	int XPending(Display*);
17649 	int XEventsQueued(Display* display, int mode);
17650 
17651 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
17652 	int XFreePixmap(Display*, Pixmap);
17653 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
17654 	int XFlush(Display*);
17655 	int XBell(Display*, int);
17656 	int XSync(Display*, bool);
17657 
17658 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
17659 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
17660 
17661 	int XGrabKeyboard(Display*, Window, Bool, int, int, Time);
17662 	int XUngrabKeyboard(Display*, Time);
17663 
17664 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
17665 
17666 	KeySym XStringToKeysym(const char *string);
17667 
17668 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
17669 
17670 	Window XDefaultRootWindow(Display*);
17671 
17672 	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);
17673 
17674 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window);
17675 
17676 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
17677 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
17678 
17679 	Status XAllocColor(Display*, Colormap, XColor*);
17680 
17681 	int XWithdrawWindow(Display*, Window, int);
17682 	int XUnmapWindow(Display*, Window);
17683 	int XLowerWindow(Display*, Window);
17684 	int XRaiseWindow(Display*, Window);
17685 
17686 	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);
17687 	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);
17688 
17689 	int XGetInputFocus(Display*, Window*, int*);
17690 	int XSetInputFocus(Display*, Window, int, Time);
17691 
17692 	XErrorHandler XSetErrorHandler(XErrorHandler);
17693 
17694 	int XGetErrorText(Display*, int, char*, int);
17695 
17696 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
17697 
17698 
17699 	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);
17700 	int XUngrabPointer(Display *display, Time time);
17701 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
17702 
17703 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
17704 
17705 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
17706 	int XSetClipMask(Display*, GC, Pixmap);
17707 	int XSetClipOrigin(Display*, GC, int, int);
17708 
17709 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
17710 
17711 	void XSetWMName(Display*, Window, XTextProperty*);
17712 	Status XGetWMName(Display*, Window, XTextProperty*);
17713 	int XStoreName(Display* display, Window w, const(char)* window_name);
17714 
17715 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
17716 
17717 	Bool XGetEventData(Display* dpy, XGenericEventCookie* cookie);
17718 	void XFreeEventData(Display* dpy, XGenericEventCookie* cookie);
17719 
17720 }
17721 }
17722 
17723 interface Xext {
17724 extern(C) nothrow @nogc {
17725 	Status XShmAttach(Display*, XShmSegmentInfo*);
17726 	Status XShmDetach(Display*, XShmSegmentInfo*);
17727 	Status XShmPutImage(
17728 		Display*            /* dpy */,
17729 		Drawable            /* d */,
17730 		GC                  /* gc */,
17731 		XImage*             /* image */,
17732 		int                 /* src_x */,
17733 		int                 /* src_y */,
17734 		int                 /* dst_x */,
17735 		int                 /* dst_y */,
17736 		uint        /* src_width */,
17737 		uint        /* src_height */,
17738 		Bool                /* send_event */
17739 	);
17740 
17741 	Status XShmQueryExtension(Display*);
17742 
17743 	XImage *XShmCreateImage(
17744 		Display*            /* dpy */,
17745 		Visual*             /* visual */,
17746 		uint        /* depth */,
17747 		int                 /* format */,
17748 		char*               /* data */,
17749 		XShmSegmentInfo*    /* shminfo */,
17750 		uint        /* width */,
17751 		uint        /* height */
17752 	);
17753 
17754 	Pixmap XShmCreatePixmap(
17755 		Display*            /* dpy */,
17756 		Drawable            /* d */,
17757 		char*               /* data */,
17758 		XShmSegmentInfo*    /* shminfo */,
17759 		uint        /* width */,
17760 		uint        /* height */,
17761 		uint        /* depth */
17762 	);
17763 
17764 }
17765 }
17766 
17767 	// this requires -lXpm
17768 	//int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
17769 
17770 
17771 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
17772 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
17773 shared static this() {
17774 	xlib.loadDynamicLibrary();
17775 	xext.loadDynamicLibrary();
17776 }
17777 
17778 // See WM_POINTERDOWN and friends for Windows
17779 interface XI2 { extern(C) nothrow @nogc:
17780 	Status XIQueryVersion(Display*, int*, int*);
17781 	XIDeviceInfo* XIQueryDevice(Display*, int deviceId, int* ndev_return);
17782 	void XIFreeDeviceInfo(XIDeviceInfo* info);
17783 	int XISelectEvents(Display* dpy, Window win, XIEventMask* mask, int num_masks);
17784 
17785 	Status XISetFocus(Display* dpy, int deviceid, Window focus, Time time);
17786 	Status XIGetFocus(Display* dpy, int deviceid, Window *focus_return);
17787 
17788 	Status XIGrabDevice(
17789 	     Display*           dpy,
17790 	     int                deviceid,
17791 	     Window             grab_window,
17792 	     Time               time,
17793 	     Cursor             cursor,
17794 	     int                grab_mode,
17795 	     int                paired_device_mode,
17796 	     Bool               owner_events,
17797 	     XIEventMask        *mask
17798 	);
17799 
17800 	Status XIUngrabDevice(Display* dpy, int deviceid, Time time);
17801 
17802 	// FIXME: i might want the ability to float a device and maybe reattach it
17803 	// and there's even more in there
17804 	// see /usr/include/X11/extensions/XInput2.h and /usr/include/X11/extensions/XI2.h
17805 }
17806 __gshared bool xi2SuccessfullyLoaded = true;
17807 mixin DynamicLoad!(XI2, "Xi", 6, xi2SuccessfullyLoaded) xi2;
17808 
17809 int xi_opcode() @system {
17810 	__gshared int code;
17811 	__gshared int status;
17812 	// FIXME: disconnect and reconnect
17813 
17814 	if(code && status == 3)
17815 		return code;
17816 	if(status) {
17817 		throw new Exception("XInput failed previously");
17818 	}
17819 
17820 	if(!xi2.loadAttempted) {
17821 		xi2.loadDynamicLibrary();
17822 	}
17823 	if(!xi2SuccessfullyLoaded) {
17824 		throw new Exception("XInput2 library load failure");
17825 	}
17826 
17827 	auto dpy = XDisplayConnection.get;
17828 
17829 	int ev, err;
17830 	if(!XQueryExtension(dpy, "XInputExtension", &code, &ev, &err)) {
17831 		status = 1;
17832 		throw new Exception("XInputExtension not supported by server");
17833 	}
17834 
17835 	int major = 2;
17836 	int minor = 4;
17837 
17838 	if(XIQueryVersion(dpy, &major, &minor) != Success) {
17839 		status = 2;
17840 		throw new Exception("XInput2 not supported by server");
17841 	}
17842 
17843 	status = 3;
17844 
17845 	return code;
17846 }
17847 
17848 void XISetMask(ubyte[] mask, int event) {
17849 	mask[event >> 3] |= (1 << (event & 7));
17850 }
17851 
17852 int XIMaskLen(int event) {
17853 	return (event >> 3) + 1;
17854 }
17855 
17856 struct XIEventMask {
17857 	int deviceid;
17858 	int mask_len;
17859 	ubyte* mask_ptr;
17860 
17861 	// my extensions
17862 	ubyte[XIMaskLen(XIEventType.max)] buffer;
17863 	this(int deviceid) {
17864 		this.deviceid = deviceid;
17865 		this.mask_len = cast(int) buffer.length;
17866 		this.mask_ptr = buffer.ptr;
17867 	}
17868 
17869 	void set(XIEventType[] val...) {
17870 		foreach(item; val)
17871 			XISetMask(buffer[], item);
17872 	}
17873 }
17874 
17875 /+
17876 // this can be sent for logical devices almost randomly just as the user switches which physical device they use...
17877 struct XIDeviceChangedEvent {
17878     int           type;         /* GenericEvent */
17879     unsigned long serial;       /* # of last request processed by server */
17880     Bool          send_event;   /* true if this came from a SendEvent request */
17881     Display       *display;     /* Display the event was read from */
17882     int           extension;    /* XI extension offset */
17883     int           evtype;       /* XI_DeviceChanged */
17884     Time          time;
17885     int           deviceid;     /* id of the device that changed */
17886     int           sourceid;     /* Source for the new classes. */
17887     int           reason;       /* Reason for the change */
17888     int           num_classes;
17889     XIAnyClassInfo **classes; /* same as in XIDeviceInfo */
17890 }
17891 +/
17892 
17893 struct XIDeviceEvent {
17894     int           type;         /* GenericEvent */
17895     arch_ulong serial;       /* # of last request processed by server */
17896     Bool          send_event;   /* true if this came from a SendEvent request */
17897     Display       *display;     /* Display the event was read from */
17898     int           extension;    /* XI extension offset */
17899     XIEventType   evtype;
17900     Time          time;
17901     int           deviceid;
17902     int           sourceid;
17903     int           detail;
17904     Window        root;
17905     Window        event;
17906     Window        child;
17907     double        root_x;
17908     double        root_y;
17909     double        event_x;
17910     double        event_y;
17911     int           flags;
17912     XIButtonState       buttons;
17913     XIValuatorState     valuators;
17914     XIModifierState     mods;
17915     XIGroupState        group;
17916 }
17917 
17918 struct XIModifierState {
17919     int    base;
17920     int    latched;
17921     int    locked;
17922     int    effective;
17923 }
17924 
17925 alias XIGroupState = XIModifierState;
17926 
17927 enum XIAllDevices = 0;
17928 struct XIDeviceInfo {
17929 	int deviceid;
17930 	char* name;
17931 	int use;
17932 	int attachment;
17933 	Bool enabled;
17934 	int num_classes;
17935 	XIAnyClassInfo **classes;
17936 }
17937 // device classes
17938 enum XIDeviceClass : int {
17939 	XIKeyClass       = 0,
17940 	XIButtonClass    = 1,
17941 	XIValuatorClass  = 2,
17942 	XIScrollClass    = 3,
17943 	XITouchClass     = 8,
17944 	XIGestureClass   = 9,
17945 }
17946 
17947 enum XIDeviceType : int {
17948 	XIMasterPointer  = 1,
17949 	XIMasterKeyboard = 2,
17950 	XISlavePointer   = 3,
17951 	XISlaveKeyboard  = 4,
17952 	XIFloatingSlave  = 5,
17953 }
17954 
17955 enum XIValuatorMode : int {
17956 	XIModeRelative   = 0,
17957 	XIModeAbsolute   = 1,
17958 }
17959 
17960 enum XIScrollType : int {
17961 	XIScrollTypeVertical = 1,
17962 	XIScrollTypeHorizontal = 2,
17963 }
17964 
17965 enum XIEventType : int {
17966 	XI_DeviceChanged               = 1,
17967 	XI_KeyPress                    = 2,
17968 	XI_KeyRelease                  = 3,
17969 	XI_ButtonPress                 = 4,
17970 	XI_ButtonRelease               = 5,
17971 	XI_Motion                      = 6,
17972 	XI_Enter                       = 7,
17973 	XI_Leave                       = 8,
17974 	XI_FocusIn                     = 9,
17975 	XI_FocusOut                    = 10,
17976 	XI_HierarchyChanged            = 11,
17977 	XI_PropertyEvent               = 12,
17978 	XI_RawKeyPress                 = 13,
17979 	XI_RawKeyRelease               = 14,
17980 	XI_RawButtonPress              = 15,
17981 	XI_RawButtonRelease            = 16,
17982 	XI_RawMotion                   = 17,
17983 	XI_TouchBegin                  = 18, /* XI 2.2 */
17984 	XI_TouchUpdate                 = 19,
17985 	XI_TouchEnd                    = 20,
17986 	XI_TouchOwnership              = 21,
17987 	XI_RawTouchBegin               = 22,
17988 	XI_RawTouchUpdate              = 23,
17989 	XI_RawTouchEnd                 = 24,
17990 	XI_BarrierHit                  = 25, /* XI 2.3 */
17991 	XI_BarrierLeave                = 26,
17992 	XI_GesturePinchBegin           = 27, /* XI 2.4 */
17993 	XI_GesturePinchUpdate          = 28,
17994 	XI_GesturePinchEnd             = 29,
17995 	XI_GestureSwipeBegin           = 30,
17996 	XI_GestureSwipeUpdate          = 31,
17997 	XI_GestureSwipeEnd             = 32,
17998 }
17999 
18000 enum XIEventMaskValues : int {
18001 	XI_DeviceChangedMask           = (1 << XIEventType.XI_DeviceChanged),
18002 	XI_KeyPressMask                = (1 << XIEventType.XI_KeyPress),
18003 	XI_KeyReleaseMask              = (1 << XIEventType.XI_KeyRelease),
18004 	XI_ButtonPressMask             = (1 << XIEventType.XI_ButtonPress),
18005 	XI_ButtonReleaseMask           = (1 << XIEventType.XI_ButtonRelease),
18006 	XI_MotionMask                  = (1 << XIEventType.XI_Motion),
18007 	XI_EnterMask                   = (1 << XIEventType.XI_Enter),
18008 	XI_LeaveMask                   = (1 << XIEventType.XI_Leave),
18009 	XI_FocusInMask                 = (1 << XIEventType.XI_FocusIn),
18010 	XI_FocusOutMask                = (1 << XIEventType.XI_FocusOut),
18011 	XI_HierarchyChangedMask        = (1 << XIEventType.XI_HierarchyChanged),
18012 	XI_PropertyEventMask           = (1 << XIEventType.XI_PropertyEvent),
18013 	XI_RawKeyPressMask             = (1 << XIEventType.XI_RawKeyPress),
18014 	XI_RawKeyReleaseMask           = (1 << XIEventType.XI_RawKeyRelease),
18015 	XI_RawButtonPressMask          = (1 << XIEventType.XI_RawButtonPress),
18016 	XI_RawButtonReleaseMask        = (1 << XIEventType.XI_RawButtonRelease),
18017 	XI_RawMotionMask               = (1 << XIEventType.XI_RawMotion),
18018 	XI_TouchBeginMask              = (1 << XIEventType.XI_TouchBegin),
18019 	XI_TouchEndMask                = (1 << XIEventType.XI_TouchEnd),
18020 	XI_TouchOwnershipChangedMask   = (1 << XIEventType.XI_TouchOwnership),
18021 	XI_TouchUpdateMask             = (1 << XIEventType.XI_TouchUpdate),
18022 	XI_RawTouchBeginMask           = (1 << XIEventType.XI_RawTouchBegin),
18023 	XI_RawTouchEndMask             = (1 << XIEventType.XI_RawTouchEnd),
18024 	XI_RawTouchUpdateMask          = (1 << XIEventType.XI_RawTouchUpdate),
18025 	XI_BarrierHitMask              = (1 << XIEventType.XI_BarrierHit),
18026 	XI_BarrierLeaveMask            = (1 << XIEventType.XI_BarrierLeave),
18027 }
18028 
18029 struct XIAnyClassInfo {
18030 	XIDeviceClass type;
18031 	int sourceid;
18032 }
18033 struct XIKeyClassInfo {
18034 	XIAnyClassInfo any;
18035 	alias any this;
18036 	int num_keycodes;
18037 	int* keycodes;
18038 }
18039 struct XIButtonState {
18040 	int mask_len;
18041 	ubyte* mask;
18042 }
18043 
18044 // https://who-t.blogspot.com/2009/07/xi2-recipes-part-4.html
18045 struct XIValuatorState {
18046 	int mask_len;
18047 	ubyte* mask;
18048 	double* values;
18049 }
18050 struct XIButtonClassInfo {
18051 	XIAnyClassInfo any;
18052 	alias any this;
18053 	int num_buttons;
18054 	Atom* labels;
18055 	XIButtonState state;
18056 }
18057 struct XIValuatorClassInfo {
18058 	XIAnyClassInfo any;
18059 	alias any this;
18060 
18061 	int number;
18062 	Atom label;
18063 	double min;
18064 	double max;
18065 	double value;
18066 	int resolution;
18067 	int mode;
18068 }
18069 struct XIScrollClassInfo {
18070 	XIAnyClassInfo any;
18071 	alias any this;
18072 
18073 	int number;
18074 	int scroll_type;
18075 	double increment;
18076 	int flags;
18077 }
18078 struct XITouchClassInfo {
18079 	XIAnyClassInfo any;
18080 	alias any this;
18081 	int mode;
18082 	int num_touches;
18083 }
18084 struct XIGestureClassInfo {
18085 	XIAnyClassInfo any;
18086 	alias any this;
18087 	int num_touches;
18088 }
18089 
18090 extern(C) nothrow @nogc {
18091 
18092 alias XrmDatabase = void*;
18093 struct XrmValue {
18094 	uint size;
18095 	void* addr;
18096 }
18097 
18098 struct XVisualInfo {
18099 	Visual* visual;
18100 	VisualID visualid;
18101 	int screen;
18102 	uint depth;
18103 	int c_class;
18104 	c_ulong red_mask;
18105 	c_ulong green_mask;
18106 	c_ulong blue_mask;
18107 	int colormap_size;
18108 	int bits_per_rgb;
18109 }
18110 
18111 enum VisualNoMask=	0x0;
18112 enum VisualIDMask=	0x1;
18113 enum VisualScreenMask=0x2;
18114 enum VisualDepthMask=	0x4;
18115 enum VisualClassMask=	0x8;
18116 enum VisualRedMaskMask=0x10;
18117 enum VisualGreenMaskMask=0x20;
18118 enum VisualBlueMaskMask=0x40;
18119 enum VisualColormapSizeMask=0x80;
18120 enum VisualBitsPerRGBMask=0x100;
18121 enum VisualAllMask=	0x1FF;
18122 
18123 enum AnyKey = 0;
18124 enum AnyModifier = 1 << 15;
18125 
18126 // XIM and other crap
18127 struct _XOM {}
18128 struct _XIM {}
18129 struct _XIC {}
18130 alias XOM = _XOM*;
18131 alias XIM = _XIM*;
18132 alias XIC = _XIC*;
18133 
18134 alias XVaNestedList = void*;
18135 
18136 alias XIMStyle = arch_ulong;
18137 enum : arch_ulong {
18138 	XIMPreeditArea      = 0x0001,
18139 	XIMPreeditCallbacks = 0x0002,
18140 	XIMPreeditPosition  = 0x0004,
18141 	XIMPreeditNothing   = 0x0008,
18142 	XIMPreeditNone      = 0x0010,
18143 	XIMStatusArea       = 0x0100,
18144 	XIMStatusCallbacks  = 0x0200,
18145 	XIMStatusNothing    = 0x0400,
18146 	XIMStatusNone       = 0x0800,
18147 }
18148 
18149 
18150 /* X Shared Memory Extension functions */
18151 	//pragma(lib, "Xshm");
18152 	alias arch_ulong ShmSeg;
18153 	struct XShmSegmentInfo {
18154 		ShmSeg shmseg;
18155 		int shmid;
18156 		ubyte* shmaddr;
18157 		Bool readOnly;
18158 	}
18159 
18160 	// and the necessary OS functions
18161 	int shmget(int, size_t, int);
18162 	void* shmat(int, scope const void*, int);
18163 	int shmdt(scope const void*);
18164 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
18165 
18166 	enum IPC_PRIVATE = 0;
18167 	enum IPC_CREAT = 512;
18168 	enum IPC_RMID = 0;
18169 
18170 /* MIT-SHM end */
18171 
18172 
18173 enum MappingType:int {
18174 	MappingModifier		=0,
18175 	MappingKeyboard		=1,
18176 	MappingPointer		=2
18177 }
18178 
18179 /* ImageFormat -- PutImage, GetImage */
18180 enum ImageFormat:int {
18181 	XYBitmap	=0,	/* depth 1, XYFormat */
18182 	XYPixmap	=1,	/* depth == drawable depth */
18183 	ZPixmap	=2	/* depth == drawable depth */
18184 }
18185 
18186 enum ModifierName:int {
18187 	ShiftMapIndex	=0,
18188 	LockMapIndex	=1,
18189 	ControlMapIndex	=2,
18190 	Mod1MapIndex	=3,
18191 	Mod2MapIndex	=4,
18192 	Mod3MapIndex	=5,
18193 	Mod4MapIndex	=6,
18194 	Mod5MapIndex	=7
18195 }
18196 
18197 enum ButtonMask:int {
18198 	Button1Mask	=1<<8,
18199 	Button2Mask	=1<<9,
18200 	Button3Mask	=1<<10,
18201 	Button4Mask	=1<<11,
18202 	Button5Mask	=1<<12,
18203 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
18204 }
18205 
18206 enum KeyOrButtonMask:uint {
18207 	ShiftMask	=1<<0,
18208 	LockMask	=1<<1,
18209 	ControlMask	=1<<2,
18210 	Mod1Mask	=1<<3,
18211 	Mod2Mask	=1<<4,
18212 	Mod3Mask	=1<<5,
18213 	Mod4Mask	=1<<6,
18214 	Mod5Mask	=1<<7,
18215 	Button1Mask	=1<<8,
18216 	Button2Mask	=1<<9,
18217 	Button3Mask	=1<<10,
18218 	Button4Mask	=1<<11,
18219 	Button5Mask	=1<<12,
18220 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
18221 }
18222 
18223 enum ButtonName:int {
18224 	Button1	=1,
18225 	Button2	=2,
18226 	Button3	=3,
18227 	Button4	=4,
18228 	Button5	=5
18229 }
18230 
18231 /* Notify modes */
18232 enum NotifyModes:int
18233 {
18234 	NotifyNormal		=0,
18235 	NotifyGrab			=1,
18236 	NotifyUngrab		=2,
18237 	NotifyWhileGrabbed	=3
18238 }
18239 enum NotifyHint = 1;	/* for MotionNotify events */
18240 
18241 /* Notify detail */
18242 enum NotifyDetail:int
18243 {
18244 	NotifyAncestor			=0,
18245 	NotifyVirtual			=1,
18246 	NotifyInferior			=2,
18247 	NotifyNonlinear			=3,
18248 	NotifyNonlinearVirtual	=4,
18249 	NotifyPointer			=5,
18250 	NotifyPointerRoot		=6,
18251 	NotifyDetailNone		=7
18252 }
18253 
18254 /* Visibility notify */
18255 
18256 enum VisibilityNotify:int
18257 {
18258 VisibilityUnobscured		=0,
18259 VisibilityPartiallyObscured	=1,
18260 VisibilityFullyObscured		=2
18261 }
18262 
18263 
18264 enum WindowStackingMethod:int
18265 {
18266 	Above		=0,
18267 	Below		=1,
18268 	TopIf		=2,
18269 	BottomIf	=3,
18270 	Opposite	=4
18271 }
18272 
18273 /* Circulation request */
18274 enum CirculationRequest:int
18275 {
18276 	PlaceOnTop		=0,
18277 	PlaceOnBottom	=1
18278 }
18279 
18280 enum PropertyNotification:int
18281 {
18282 	PropertyNewValue	=0,
18283 	PropertyDelete		=1
18284 }
18285 
18286 enum ColorMapNotification:int
18287 {
18288 	ColormapUninstalled	=0,
18289 	ColormapInstalled		=1
18290 }
18291 
18292 
18293 	struct _XPrivate {}
18294 	struct _XrmHashBucketRec {}
18295 
18296 	alias void* XPointer;
18297 	alias void* XExtData;
18298 
18299 	version( X86_64 ) {
18300 		alias ulong XID;
18301 		alias ulong arch_ulong;
18302 		alias long arch_long;
18303 	} else version (AArch64) {
18304 		alias ulong XID;
18305 		alias ulong arch_ulong;
18306 		alias long arch_long;
18307 	} else {
18308 		alias uint XID;
18309 		alias uint arch_ulong;
18310 		alias int arch_long;
18311 	}
18312 
18313 	alias XID Window;
18314 	alias XID Drawable;
18315 	alias XID Pixmap;
18316 
18317 	alias arch_ulong Atom;
18318 	alias int Bool;
18319 	alias Display XDisplay;
18320 
18321 	alias int ByteOrder;
18322 	alias arch_ulong Time;
18323 	alias void ScreenFormat;
18324 
18325 	struct XImage {
18326 		int width, height;			/* size of image */
18327 		int xoffset;				/* number of pixels offset in X direction */
18328 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
18329 		void *data;					/* pointer to image data */
18330 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
18331 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
18332 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
18333 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
18334 		int depth;					/* depth of image */
18335 		int bytes_per_line;			/* accelarator to next line */
18336 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
18337 		arch_ulong red_mask;	/* bits in z arrangment */
18338 		arch_ulong green_mask;
18339 		arch_ulong blue_mask;
18340 		XPointer obdata;			/* hook for the object routines to hang on */
18341 		static struct F {				/* image manipulation routines */
18342 			XImage* function(
18343 				XDisplay* 			/* display */,
18344 				Visual*				/* visual */,
18345 				uint				/* depth */,
18346 				int					/* format */,
18347 				int					/* offset */,
18348 				ubyte*				/* data */,
18349 				uint				/* width */,
18350 				uint				/* height */,
18351 				int					/* bitmap_pad */,
18352 				int					/* bytes_per_line */) create_image;
18353 			int function(XImage *) destroy_image;
18354 			arch_ulong function(XImage *, int, int) get_pixel;
18355 			int function(XImage *, int, int, arch_ulong) put_pixel;
18356 			XImage* function(XImage *, int, int, uint, uint) sub_image;
18357 			int function(XImage *, arch_long) add_pixel;
18358 		}
18359 		F f;
18360 	}
18361 	version(X86_64) static assert(XImage.sizeof == 136);
18362 	else version(X86) static assert(XImage.sizeof == 88);
18363 
18364 struct XCharStruct {
18365 	short       lbearing;       /* origin to left edge of raster */
18366 	short       rbearing;       /* origin to right edge of raster */
18367 	short       width;          /* advance to next char's origin */
18368 	short       ascent;         /* baseline to top edge of raster */
18369 	short       descent;        /* baseline to bottom edge of raster */
18370 	ushort attributes;  /* per char flags (not predefined) */
18371 }
18372 
18373 /*
18374  * To allow arbitrary information with fonts, there are additional properties
18375  * returned.
18376  */
18377 struct XFontProp {
18378 	Atom name;
18379 	arch_ulong card32;
18380 }
18381 
18382 alias Atom Font;
18383 
18384 struct XFontStruct {
18385 	XExtData *ext_data;           /* Hook for extension to hang data */
18386 	Font fid;                     /* Font ID for this font */
18387 	uint direction;           /* Direction the font is painted */
18388 	uint min_char_or_byte2;   /* First character */
18389 	uint max_char_or_byte2;   /* Last character */
18390 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
18391 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
18392 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
18393 	uint default_char;        /* Char to print for undefined character */
18394 	int n_properties;             /* How many properties there are */
18395 	XFontProp *properties;        /* Pointer to array of additional properties*/
18396 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
18397 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
18398 	XCharStruct *per_char;        /* first_char to last_char information */
18399 	int ascent;                   /* Max extent above baseline for spacing */
18400 	int descent;                  /* Max descent below baseline for spacing */
18401 }
18402 
18403 
18404 /*
18405  * Definitions of specific events.
18406  */
18407 struct XKeyEvent
18408 {
18409 	int type;			/* of event */
18410 	arch_ulong serial;		/* # of last request processed by server */
18411 	Bool send_event;	/* true if this came from a SendEvent request */
18412 	Display *display;	/* Display the event was read from */
18413 	Window window;	        /* "event" window it is reported relative to */
18414 	Window root;	        /* root window that the event occurred on */
18415 	Window subwindow;	/* child window */
18416 	Time time;		/* milliseconds */
18417 	int x, y;		/* pointer x, y coordinates in event window */
18418 	int x_root, y_root;	/* coordinates relative to root */
18419 	KeyOrButtonMask state;	/* key or button mask */
18420 	uint keycode;	/* detail */
18421 	Bool same_screen;	/* same screen flag */
18422 }
18423 version(X86_64) static assert(XKeyEvent.sizeof == 96);
18424 alias XKeyEvent XKeyPressedEvent;
18425 alias XKeyEvent XKeyReleasedEvent;
18426 
18427 struct XButtonEvent
18428 {
18429 	int type;		/* of event */
18430 	arch_ulong serial;	/* # of last request processed by server */
18431 	Bool send_event;	/* true if this came from a SendEvent request */
18432 	Display *display;	/* Display the event was read from */
18433 	Window window;	        /* "event" window it is reported relative to */
18434 	Window root;	        /* root window that the event occurred on */
18435 	Window subwindow;	/* child window */
18436 	Time time;		/* milliseconds */
18437 	int x, y;		/* pointer x, y coordinates in event window */
18438 	int x_root, y_root;	/* coordinates relative to root */
18439 	KeyOrButtonMask state;	/* key or button mask */
18440 	uint button;	/* detail */
18441 	Bool same_screen;	/* same screen flag */
18442 }
18443 alias XButtonEvent XButtonPressedEvent;
18444 alias XButtonEvent XButtonReleasedEvent;
18445 
18446 struct XMotionEvent{
18447 	int type;		/* of event */
18448 	arch_ulong serial;	/* # of last request processed by server */
18449 	Bool send_event;	/* true if this came from a SendEvent request */
18450 	Display *display;	/* Display the event was read from */
18451 	Window window;	        /* "event" window reported relative to */
18452 	Window root;	        /* root window that the event occurred on */
18453 	Window subwindow;	/* child window */
18454 	Time time;		/* milliseconds */
18455 	int x, y;		/* pointer x, y coordinates in event window */
18456 	int x_root, y_root;	/* coordinates relative to root */
18457 	KeyOrButtonMask state;	/* key or button mask */
18458 	byte is_hint;		/* detail */
18459 	Bool same_screen;	/* same screen flag */
18460 }
18461 alias XMotionEvent XPointerMovedEvent;
18462 
18463 struct XCrossingEvent{
18464 	int type;		/* of event */
18465 	arch_ulong serial;	/* # of last request processed by server */
18466 	Bool send_event;	/* true if this came from a SendEvent request */
18467 	Display *display;	/* Display the event was read from */
18468 	Window window;	        /* "event" window reported relative to */
18469 	Window root;	        /* root window that the event occurred on */
18470 	Window subwindow;	/* child window */
18471 	Time time;		/* milliseconds */
18472 	int x, y;		/* pointer x, y coordinates in event window */
18473 	int x_root, y_root;	/* coordinates relative to root */
18474 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
18475 	NotifyDetail detail;
18476 	/*
18477 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
18478 	 * NotifyNonlinear,NotifyNonlinearVirtual
18479 	 */
18480 	Bool same_screen;	/* same screen flag */
18481 	Bool focus;		/* Boolean focus */
18482 	KeyOrButtonMask state;	/* key or button mask */
18483 }
18484 alias XCrossingEvent XEnterWindowEvent;
18485 alias XCrossingEvent XLeaveWindowEvent;
18486 
18487 struct XFocusChangeEvent{
18488 	int type;		/* FocusIn or FocusOut */
18489 	arch_ulong serial;	/* # of last request processed by server */
18490 	Bool send_event;	/* true if this came from a SendEvent request */
18491 	Display *display;	/* Display the event was read from */
18492 	Window window;		/* window of event */
18493 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
18494 				   NotifyGrab, NotifyUngrab */
18495 	NotifyDetail detail;
18496 	/*
18497 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
18498 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
18499 	 * NotifyPointerRoot, NotifyDetailNone
18500 	 */
18501 }
18502 alias XFocusChangeEvent XFocusInEvent;
18503 alias XFocusChangeEvent XFocusOutEvent;
18504 
18505 enum CWBackPixmap              = (1L<<0);
18506 enum CWBackPixel               = (1L<<1);
18507 enum CWBorderPixmap            = (1L<<2);
18508 enum CWBorderPixel             = (1L<<3);
18509 enum CWBitGravity              = (1L<<4);
18510 enum CWWinGravity              = (1L<<5);
18511 enum CWBackingStore            = (1L<<6);
18512 enum CWBackingPlanes           = (1L<<7);
18513 enum CWBackingPixel            = (1L<<8);
18514 enum CWOverrideRedirect        = (1L<<9);
18515 enum CWSaveUnder               = (1L<<10);
18516 enum CWEventMask               = (1L<<11);
18517 enum CWDontPropagate           = (1L<<12);
18518 enum CWColormap                = (1L<<13);
18519 enum CWCursor                  = (1L<<14);
18520 
18521 struct XWindowAttributes {
18522 	int x, y;			/* location of window */
18523 	int width, height;		/* width and height of window */
18524 	int border_width;		/* border width of window */
18525 	int depth;			/* depth of window */
18526 	Visual *visual;			/* the associated visual structure */
18527 	Window root;			/* root of screen containing window */
18528 	int class_;			/* InputOutput, InputOnly*/
18529 	int bit_gravity;		/* one of the bit gravity values */
18530 	int win_gravity;		/* one of the window gravity values */
18531 	int backing_store;		/* NotUseful, WhenMapped, Always */
18532 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
18533 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
18534 	Bool save_under;		/* boolean, should bits under be saved? */
18535 	Colormap colormap;		/* color map to be associated with window */
18536 	Bool map_installed;		/* boolean, is color map currently installed*/
18537 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
18538 	arch_long all_event_masks;		/* set of events all people have interest in*/
18539 	arch_long your_event_mask;		/* my event mask */
18540 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
18541 	Bool override_redirect;		/* boolean value for override-redirect */
18542 	Screen *screen;			/* back pointer to correct screen */
18543 }
18544 
18545 enum IsUnmapped = 0;
18546 enum IsUnviewable = 1;
18547 enum IsViewable = 2;
18548 
18549 struct XSetWindowAttributes {
18550 	Pixmap background_pixmap;/* background, None, or ParentRelative */
18551 	arch_ulong background_pixel;/* background pixel */
18552 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
18553 	arch_ulong border_pixel;/* border pixel value */
18554 	int bit_gravity;         /* one of bit gravity values */
18555 	int win_gravity;         /* one of the window gravity values */
18556 	int backing_store;       /* NotUseful, WhenMapped, Always */
18557 	arch_ulong backing_planes;/* planes to be preserved if possible */
18558 	arch_ulong backing_pixel;/* value to use in restoring planes */
18559 	Bool save_under;         /* should bits under be saved? (popups) */
18560 	arch_long event_mask;         /* set of events that should be saved */
18561 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
18562 	Bool override_redirect;  /* boolean value for override_redirect */
18563 	Colormap colormap;       /* color map to be associated with window */
18564 	Cursor cursor;           /* cursor to be displayed (or None) */
18565 }
18566 
18567 
18568 alias int Status;
18569 
18570 
18571 enum EventMask:int
18572 {
18573 	NoEventMask				=0,
18574 	KeyPressMask			=1<<0,
18575 	KeyReleaseMask			=1<<1,
18576 	ButtonPressMask			=1<<2,
18577 	ButtonReleaseMask		=1<<3,
18578 	EnterWindowMask			=1<<4,
18579 	LeaveWindowMask			=1<<5,
18580 	PointerMotionMask		=1<<6,
18581 	PointerMotionHintMask	=1<<7,
18582 	Button1MotionMask		=1<<8,
18583 	Button2MotionMask		=1<<9,
18584 	Button3MotionMask		=1<<10,
18585 	Button4MotionMask		=1<<11,
18586 	Button5MotionMask		=1<<12,
18587 	ButtonMotionMask		=1<<13,
18588 	KeymapStateMask		=1<<14,
18589 	ExposureMask			=1<<15,
18590 	VisibilityChangeMask	=1<<16,
18591 	StructureNotifyMask		=1<<17,
18592 	ResizeRedirectMask		=1<<18,
18593 	SubstructureNotifyMask	=1<<19,
18594 	SubstructureRedirectMask=1<<20,
18595 	FocusChangeMask			=1<<21,
18596 	PropertyChangeMask		=1<<22,
18597 	ColormapChangeMask		=1<<23,
18598 	OwnerGrabButtonMask		=1<<24
18599 }
18600 
18601 struct MwmHints {
18602 	c_ulong flags;
18603 	c_ulong functions;
18604 	c_ulong decorations;
18605 	c_long input_mode;
18606 	c_ulong status;
18607 }
18608 
18609 enum {
18610 	MWM_HINTS_FUNCTIONS = (1L << 0),
18611 	MWM_HINTS_DECORATIONS =  (1L << 1),
18612 
18613 	MWM_FUNC_ALL = (1L << 0),
18614 	MWM_FUNC_RESIZE = (1L << 1),
18615 	MWM_FUNC_MOVE = (1L << 2),
18616 	MWM_FUNC_MINIMIZE = (1L << 3),
18617 	MWM_FUNC_MAXIMIZE = (1L << 4),
18618 	MWM_FUNC_CLOSE = (1L << 5),
18619 
18620 	MWM_DECOR_ALL = (1L << 0),
18621 	MWM_DECOR_BORDER = (1L << 1),
18622 	MWM_DECOR_RESIZEH = (1L << 2),
18623 	MWM_DECOR_TITLE = (1L << 3),
18624 	MWM_DECOR_MENU = (1L << 4),
18625 	MWM_DECOR_MINIMIZE = (1L << 5),
18626 	MWM_DECOR_MAXIMIZE = (1L << 6),
18627 }
18628 
18629 import core.stdc.config : c_long, c_ulong;
18630 
18631 	/* Size hints mask bits */
18632 
18633 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
18634 	enum   USSize      = (1L << 1)          /* user specified width, height */;
18635 	enum   PPosition   = (1L << 2)          /* program specified position */;
18636 	enum   PSize       = (1L << 3)          /* program specified size */;
18637 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
18638 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
18639 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
18640 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
18641 	enum   PBaseSize   = (1L << 8);
18642 	enum   PWinGravity = (1L << 9);
18643 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
18644 	struct XSizeHints {
18645 		arch_long flags;         /* marks which fields in this structure are defined */
18646 		int x, y;           /* Obsolete */
18647 		int width, height;  /* Obsolete */
18648 		int min_width, min_height;
18649 		int max_width, max_height;
18650 		int width_inc, height_inc;
18651 		struct Aspect {
18652 			int x;       /* numerator */
18653 			int y;       /* denominator */
18654 		}
18655 
18656 		Aspect min_aspect;
18657 		Aspect max_aspect;
18658 		int base_width, base_height;
18659 		int win_gravity;
18660 		/* this structure may be extended in the future */
18661 	}
18662 
18663 
18664 
18665 enum EventType:int
18666 {
18667 	KeyPress			=2,
18668 	KeyRelease			=3,
18669 	ButtonPress			=4,
18670 	ButtonRelease		=5,
18671 	MotionNotify		=6,
18672 	EnterNotify			=7,
18673 	LeaveNotify			=8,
18674 	FocusIn				=9,
18675 	FocusOut			=10,
18676 	KeymapNotify		=11,
18677 	Expose				=12,
18678 	GraphicsExpose		=13,
18679 	NoExpose			=14,
18680 	VisibilityNotify	=15,
18681 	CreateNotify		=16,
18682 	DestroyNotify		=17,
18683 	UnmapNotify		=18,
18684 	MapNotify			=19,
18685 	MapRequest			=20,
18686 	ReparentNotify		=21,
18687 	ConfigureNotify		=22,
18688 	ConfigureRequest	=23,
18689 	GravityNotify		=24,
18690 	ResizeRequest		=25,
18691 	CirculateNotify		=26,
18692 	CirculateRequest	=27,
18693 	PropertyNotify		=28,
18694 	SelectionClear		=29,
18695 	SelectionRequest	=30,
18696 	SelectionNotify		=31,
18697 	ColormapNotify		=32,
18698 	ClientMessage		=33,
18699 	MappingNotify		=34,
18700 	GenericEvent            = 35,
18701 	LASTEvent			=36	/* must be bigger than any event # */
18702 }
18703 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
18704 struct XKeymapEvent
18705 {
18706 	int type;
18707 	arch_ulong serial;	/* # of last request processed by server */
18708 	Bool send_event;	/* true if this came from a SendEvent request */
18709 	Display *display;	/* Display the event was read from */
18710 	Window window;
18711 	byte[32] key_vector;
18712 }
18713 
18714 struct XExposeEvent
18715 {
18716 	int type;
18717 	arch_ulong serial;	/* # of last request processed by server */
18718 	Bool send_event;	/* true if this came from a SendEvent request */
18719 	Display *display;	/* Display the event was read from */
18720 	Window window;
18721 	int x, y;
18722 	int width, height;
18723 	int count;		/* if non-zero, at least this many more */
18724 }
18725 
18726 struct XGraphicsExposeEvent{
18727 	int type;
18728 	arch_ulong serial;	/* # of last request processed by server */
18729 	Bool send_event;	/* true if this came from a SendEvent request */
18730 	Display *display;	/* Display the event was read from */
18731 	Drawable drawable;
18732 	int x, y;
18733 	int width, height;
18734 	int count;		/* if non-zero, at least this many more */
18735 	int major_code;		/* core is CopyArea or CopyPlane */
18736 	int minor_code;		/* not defined in the core */
18737 }
18738 
18739 struct XNoExposeEvent{
18740 	int type;
18741 	arch_ulong serial;	/* # of last request processed by server */
18742 	Bool send_event;	/* true if this came from a SendEvent request */
18743 	Display *display;	/* Display the event was read from */
18744 	Drawable drawable;
18745 	int major_code;		/* core is CopyArea or CopyPlane */
18746 	int minor_code;		/* not defined in the core */
18747 }
18748 
18749 struct XVisibilityEvent{
18750 	int type;
18751 	arch_ulong serial;	/* # of last request processed by server */
18752 	Bool send_event;	/* true if this came from a SendEvent request */
18753 	Display *display;	/* Display the event was read from */
18754 	Window window;
18755 	VisibilityNotify state;		/* Visibility state */
18756 }
18757 
18758 struct XCreateWindowEvent{
18759 	int type;
18760 	arch_ulong serial;	/* # of last request processed by server */
18761 	Bool send_event;	/* true if this came from a SendEvent request */
18762 	Display *display;	/* Display the event was read from */
18763 	Window parent;		/* parent of the window */
18764 	Window window;		/* window id of window created */
18765 	int x, y;		/* window location */
18766 	int width, height;	/* size of window */
18767 	int border_width;	/* border width */
18768 	Bool override_redirect;	/* creation should be overridden */
18769 }
18770 
18771 struct XDestroyWindowEvent
18772 {
18773 	int type;
18774 	arch_ulong serial;		/* # of last request processed by server */
18775 	Bool send_event;	/* true if this came from a SendEvent request */
18776 	Display *display;	/* Display the event was read from */
18777 	Window event;
18778 	Window window;
18779 }
18780 
18781 struct XUnmapEvent
18782 {
18783 	int type;
18784 	arch_ulong serial;		/* # of last request processed by server */
18785 	Bool send_event;	/* true if this came from a SendEvent request */
18786 	Display *display;	/* Display the event was read from */
18787 	Window event;
18788 	Window window;
18789 	Bool from_configure;
18790 }
18791 
18792 struct XMapEvent
18793 {
18794 	int type;
18795 	arch_ulong serial;		/* # of last request processed by server */
18796 	Bool send_event;	/* true if this came from a SendEvent request */
18797 	Display *display;	/* Display the event was read from */
18798 	Window event;
18799 	Window window;
18800 	Bool override_redirect;	/* Boolean, is override set... */
18801 }
18802 
18803 struct XMapRequestEvent
18804 {
18805 	int type;
18806 	arch_ulong serial;	/* # of last request processed by server */
18807 	Bool send_event;	/* true if this came from a SendEvent request */
18808 	Display *display;	/* Display the event was read from */
18809 	Window parent;
18810 	Window window;
18811 }
18812 
18813 struct XReparentEvent
18814 {
18815 	int type;
18816 	arch_ulong serial;	/* # of last request processed by server */
18817 	Bool send_event;	/* true if this came from a SendEvent request */
18818 	Display *display;	/* Display the event was read from */
18819 	Window event;
18820 	Window window;
18821 	Window parent;
18822 	int x, y;
18823 	Bool override_redirect;
18824 }
18825 
18826 struct XConfigureEvent
18827 {
18828 	int type;
18829 	arch_ulong serial;	/* # of last request processed by server */
18830 	Bool send_event;	/* true if this came from a SendEvent request */
18831 	Display *display;	/* Display the event was read from */
18832 	Window event;
18833 	Window window;
18834 	int x, y;
18835 	int width, height;
18836 	int border_width;
18837 	Window above;
18838 	Bool override_redirect;
18839 }
18840 
18841 struct XGravityEvent
18842 {
18843 	int type;
18844 	arch_ulong serial;	/* # of last request processed by server */
18845 	Bool send_event;	/* true if this came from a SendEvent request */
18846 	Display *display;	/* Display the event was read from */
18847 	Window event;
18848 	Window window;
18849 	int x, y;
18850 }
18851 
18852 struct XResizeRequestEvent
18853 {
18854 	int type;
18855 	arch_ulong serial;	/* # of last request processed by server */
18856 	Bool send_event;	/* true if this came from a SendEvent request */
18857 	Display *display;	/* Display the event was read from */
18858 	Window window;
18859 	int width, height;
18860 }
18861 
18862 struct  XConfigureRequestEvent
18863 {
18864 	int type;
18865 	arch_ulong serial;	/* # of last request processed by server */
18866 	Bool send_event;	/* true if this came from a SendEvent request */
18867 	Display *display;	/* Display the event was read from */
18868 	Window parent;
18869 	Window window;
18870 	int x, y;
18871 	int width, height;
18872 	int border_width;
18873 	Window above;
18874 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
18875 	arch_ulong value_mask;
18876 }
18877 
18878 struct XCirculateEvent
18879 {
18880 	int type;
18881 	arch_ulong serial;	/* # of last request processed by server */
18882 	Bool send_event;	/* true if this came from a SendEvent request */
18883 	Display *display;	/* Display the event was read from */
18884 	Window event;
18885 	Window window;
18886 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
18887 }
18888 
18889 struct XCirculateRequestEvent
18890 {
18891 	int type;
18892 	arch_ulong serial;	/* # of last request processed by server */
18893 	Bool send_event;	/* true if this came from a SendEvent request */
18894 	Display *display;	/* Display the event was read from */
18895 	Window parent;
18896 	Window window;
18897 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
18898 }
18899 
18900 struct XPropertyEvent
18901 {
18902 	int type;
18903 	arch_ulong serial;	/* # of last request processed by server */
18904 	Bool send_event;	/* true if this came from a SendEvent request */
18905 	Display *display;	/* Display the event was read from */
18906 	Window window;
18907 	Atom atom;
18908 	Time time;
18909 	PropertyNotification state;		/* NewValue, Deleted */
18910 }
18911 
18912 struct XSelectionClearEvent
18913 {
18914 	int type;
18915 	arch_ulong serial;	/* # of last request processed by server */
18916 	Bool send_event;	/* true if this came from a SendEvent request */
18917 	Display *display;	/* Display the event was read from */
18918 	Window window;
18919 	Atom selection;
18920 	Time time;
18921 }
18922 
18923 struct XSelectionRequestEvent
18924 {
18925 	int type;
18926 	arch_ulong serial;	/* # of last request processed by server */
18927 	Bool send_event;	/* true if this came from a SendEvent request */
18928 	Display *display;	/* Display the event was read from */
18929 	Window owner;
18930 	Window requestor;
18931 	Atom selection;
18932 	Atom target;
18933 	Atom property;
18934 	Time time;
18935 }
18936 
18937 struct XSelectionEvent
18938 {
18939 	int type;
18940 	arch_ulong serial;	/* # of last request processed by server */
18941 	Bool send_event;	/* true if this came from a SendEvent request */
18942 	Display *display;	/* Display the event was read from */
18943 	Window requestor;
18944 	Atom selection;
18945 	Atom target;
18946 	Atom property;		/* ATOM or None */
18947 	Time time;
18948 }
18949 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
18950 
18951 struct XColormapEvent
18952 {
18953 	int type;
18954 	arch_ulong serial;	/* # of last request processed by server */
18955 	Bool send_event;	/* true if this came from a SendEvent request */
18956 	Display *display;	/* Display the event was read from */
18957 	Window window;
18958 	Colormap colormap;	/* COLORMAP or None */
18959 	Bool new_;		/* C++ */
18960 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
18961 }
18962 version(X86_64) static assert(XColormapEvent.sizeof == 56);
18963 
18964 struct XClientMessageEvent
18965 {
18966 	int type;
18967 	arch_ulong serial;	/* # of last request processed by server */
18968 	Bool send_event;	/* true if this came from a SendEvent request */
18969 	Display *display;	/* Display the event was read from */
18970 	Window window;
18971 	Atom message_type;
18972 	int format;
18973 	union Data{
18974 		byte[20] b;
18975 		short[10] s;
18976 		arch_ulong[5] l;
18977 	}
18978 	Data data;
18979 
18980 }
18981 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
18982 
18983 struct XMappingEvent
18984 {
18985 	int type;
18986 	arch_ulong serial;	/* # of last request processed by server */
18987 	Bool send_event;	/* true if this came from a SendEvent request */
18988 	Display *display;	/* Display the event was read from */
18989 	Window window;		/* unused */
18990 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
18991 				   MappingPointer */
18992 	int first_keycode;	/* first keycode */
18993 	int count;		/* defines range of change w. first_keycode*/
18994 }
18995 
18996 struct XErrorEvent
18997 {
18998 	int type;
18999 	Display *display;	/* Display the event was read from */
19000 	XID resourceid;		/* resource id */
19001 	arch_ulong serial;	/* serial number of failed request */
19002 	ubyte error_code;	/* error code of failed request */
19003 	ubyte request_code;	/* Major op-code of failed request */
19004 	ubyte minor_code;	/* Minor op-code of failed request */
19005 }
19006 
19007 struct XAnyEvent
19008 {
19009 	int type;
19010 	arch_ulong serial;	/* # of last request processed by server */
19011 	Bool send_event;	/* true if this came from a SendEvent request */
19012 	Display *display;/* Display the event was read from */
19013 	Window window;	/* window on which event was requested in event mask */
19014 }
19015 
19016 union XEvent{
19017 	int type;		/* must not be changed; first element */
19018 	XAnyEvent xany;
19019 	XKeyEvent xkey;
19020 	XButtonEvent xbutton;
19021 	XMotionEvent xmotion;
19022 	XCrossingEvent xcrossing;
19023 	XFocusChangeEvent xfocus;
19024 	XExposeEvent xexpose;
19025 	XGraphicsExposeEvent xgraphicsexpose;
19026 	XNoExposeEvent xnoexpose;
19027 	XVisibilityEvent xvisibility;
19028 	XCreateWindowEvent xcreatewindow;
19029 	XDestroyWindowEvent xdestroywindow;
19030 	XUnmapEvent xunmap;
19031 	XMapEvent xmap;
19032 	XMapRequestEvent xmaprequest;
19033 	XReparentEvent xreparent;
19034 	XConfigureEvent xconfigure;
19035 	XGravityEvent xgravity;
19036 	XResizeRequestEvent xresizerequest;
19037 	XConfigureRequestEvent xconfigurerequest;
19038 	XCirculateEvent xcirculate;
19039 	XCirculateRequestEvent xcirculaterequest;
19040 	XPropertyEvent xproperty;
19041 	XSelectionClearEvent xselectionclear;
19042 	XSelectionRequestEvent xselectionrequest;
19043 	XSelectionEvent xselection;
19044 	XColormapEvent xcolormap;
19045 	XClientMessageEvent xclient;
19046 	XMappingEvent xmapping;
19047 	XErrorEvent xerror;
19048 	XKeymapEvent xkeymap;
19049 	XGenericEvent xgeneric;
19050 	XGenericEventCookie xcookie;
19051 	arch_ulong[24] pad;
19052 }
19053 struct XGenericEvent {
19054 	int            type;
19055 	arch_ulong  serial;
19056 	Bool           send_event;
19057 	Display        *display;
19058 	int            extension;
19059 	int            evtype;
19060 }
19061 
19062 struct XGenericEventCookie {
19063 	int            type;
19064 	arch_ulong  serial;
19065 	Bool           send_event;
19066 	Display        *display;
19067 	int            extension;
19068 	int            evtype;
19069 	uint   cookie;
19070 	void           *data;
19071 }
19072 
19073 	struct Display {
19074 		XExtData *ext_data;	/* hook for extension to hang data */
19075 		_XPrivate *private1;
19076 		int fd;			/* Network socket. */
19077 		int private2;
19078 		int proto_major_version;/* major version of server's X protocol */
19079 		int proto_minor_version;/* minor version of servers X protocol */
19080 		char *vendor;		/* vendor of the server hardware */
19081 	    	XID private3;
19082 		XID private4;
19083 		XID private5;
19084 		int private6;
19085 		XID function(Display*)resource_alloc;/* allocator function */
19086 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
19087 		int bitmap_unit;	/* padding and data requirements */
19088 		int bitmap_pad;		/* padding requirements on bitmaps */
19089 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
19090 		int nformats;		/* number of pixmap formats in list */
19091 		ScreenFormat *pixmap_format;	/* pixmap format list */
19092 		int private8;
19093 		int release;		/* release of the server */
19094 		_XPrivate *private9;
19095 		_XPrivate *private10;
19096 		int qlen;		/* Length of input event queue */
19097 		arch_ulong last_request_read; /* seq number of last event read */
19098 		arch_ulong request;	/* sequence number of last request. */
19099 		XPointer private11;
19100 		XPointer private12;
19101 		XPointer private13;
19102 		XPointer private14;
19103 		uint max_request_size; /* maximum number 32 bit words in request*/
19104 		_XrmHashBucketRec *db;
19105 		int function  (Display*)private15;
19106 		char *display_name;	/* "host:display" string used on this connect*/
19107 		int default_screen;	/* default screen for operations */
19108 		int nscreens;		/* number of screens on this server*/
19109 		Screen *screens;	/* pointer to list of screens */
19110 		arch_ulong motion_buffer;	/* size of motion buffer */
19111 		arch_ulong private16;
19112 		int min_keycode;	/* minimum defined keycode */
19113 		int max_keycode;	/* maximum defined keycode */
19114 		XPointer private17;
19115 		XPointer private18;
19116 		int private19;
19117 		byte *xdefaults;	/* contents of defaults from server */
19118 		/* there is more to this structure, but it is private to Xlib */
19119 	}
19120 
19121 	// I got these numbers from a C program as a sanity test
19122 	version(X86_64) {
19123 		static assert(Display.sizeof == 296);
19124 		static assert(XPointer.sizeof == 8);
19125 		static assert(XErrorEvent.sizeof == 40);
19126 		static assert(XAnyEvent.sizeof == 40);
19127 		static assert(XMappingEvent.sizeof == 56);
19128 		static assert(XEvent.sizeof == 192);
19129     	} else version (AArch64) {
19130 		// omit check for aarch64
19131 	} else {
19132 		static assert(Display.sizeof == 176);
19133 		static assert(XPointer.sizeof == 4);
19134 		static assert(XEvent.sizeof == 96);
19135 	}
19136 
19137 struct Depth
19138 {
19139 	int depth;		/* this depth (Z) of the depth */
19140 	int nvisuals;		/* number of Visual types at this depth */
19141 	Visual *visuals;	/* list of visuals possible at this depth */
19142 }
19143 
19144 alias void* GC;
19145 alias c_ulong VisualID;
19146 alias XID Colormap;
19147 alias XID Cursor;
19148 alias XID KeySym;
19149 alias uint KeyCode;
19150 enum None = 0;
19151 }
19152 
19153 version(without_opengl) {}
19154 else {
19155 extern(C) nothrow @nogc {
19156 
19157 
19158 static if(!SdpyIsUsingIVGLBinds) {
19159 enum GLX_USE_GL=            1;       /* support GLX rendering */
19160 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
19161 enum GLX_LEVEL=             3;       /* level in plane stacking */
19162 enum GLX_RGBA=              4;       /* true if RGBA mode */
19163 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
19164 enum GLX_STEREO=            6;       /* stereo buffering supported */
19165 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
19166 enum GLX_RED_SIZE=          8;       /* number of red component bits */
19167 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
19168 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
19169 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
19170 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
19171 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
19172 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
19173 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
19174 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
19175 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
19176 
19177 
19178 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
19179 
19180 
19181 
19182 enum GL_TRUE = 1;
19183 enum GL_FALSE = 0;
19184 }
19185 
19186 alias XID GLXContextID;
19187 alias XID GLXPixmap;
19188 alias XID GLXDrawable;
19189 alias XID GLXPbuffer;
19190 alias XID GLXWindow;
19191 alias XID GLXFBConfigID;
19192 alias void* GLXContext;
19193 
19194 }
19195 }
19196 
19197 enum AllocNone = 0;
19198 
19199 extern(C) {
19200 	/* WARNING, this type not in Xlib spec */
19201 	extern(C) alias XIOErrorHandler = int function (Display* display);
19202 }
19203 
19204 extern(C) nothrow
19205 alias XErrorHandler = int function(Display*, XErrorEvent*);
19206 
19207 extern(C) nothrow @nogc {
19208 struct Screen{
19209 	XExtData *ext_data;		/* hook for extension to hang data */
19210 	Display *display;		/* back pointer to display structure */
19211 	Window root;			/* Root window id. */
19212 	int width, height;		/* width and height of screen */
19213 	int mwidth, mheight;	/* width and height of  in millimeters */
19214 	int ndepths;			/* number of depths possible */
19215 	Depth *depths;			/* list of allowable depths on the screen */
19216 	int root_depth;			/* bits per pixel */
19217 	Visual *root_visual;	/* root visual */
19218 	GC default_gc;			/* GC for the root root visual */
19219 	Colormap cmap;			/* default color map */
19220 	uint white_pixel;
19221 	uint black_pixel;		/* White and Black pixel values */
19222 	int max_maps, min_maps;	/* max and min color maps */
19223 	int backing_store;		/* Never, WhenMapped, Always */
19224 	bool save_unders;
19225 	int root_input_mask;	/* initial root input mask */
19226 }
19227 
19228 struct Visual
19229 {
19230 	XExtData *ext_data;	/* hook for extension to hang data */
19231 	VisualID visualid;	/* visual id of this visual */
19232 	int class_;			/* class of screen (monochrome, etc.) */
19233 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
19234 	int bits_per_rgb;	/* log base 2 of distinct color values */
19235 	int map_entries;	/* color map entries */
19236 }
19237 
19238 	alias Display* _XPrivDisplay;
19239 
19240 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system {
19241 		assert(dpy !is null);
19242 		return &dpy.screens[scr];
19243 	}
19244 
19245 	extern(D) Window RootWindow(Display *dpy,int scr) {
19246 		return ScreenOfDisplay(dpy,scr).root;
19247 	}
19248 
19249 	struct XWMHints {
19250 		arch_long flags;
19251 		Bool input;
19252 		int initial_state;
19253 		Pixmap icon_pixmap;
19254 		Window icon_window;
19255 		int icon_x, icon_y;
19256 		Pixmap icon_mask;
19257 		XID window_group;
19258 	}
19259 
19260 	struct XClassHint {
19261 		char* res_name;
19262 		char* res_class;
19263 	}
19264 
19265 	extern(D) int DefaultScreen(Display *dpy) {
19266 		return dpy.default_screen;
19267 	}
19268 
19269 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
19270 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
19271 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
19272 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
19273 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
19274 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
19275 
19276 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
19277 
19278 	enum int AnyPropertyType = 0;
19279 	enum int Success = 0;
19280 
19281 	enum int RevertToNone = None;
19282 	enum int PointerRoot = 1;
19283 	enum Time CurrentTime = 0;
19284 	enum int RevertToPointerRoot = PointerRoot;
19285 	enum int RevertToParent = 2;
19286 
19287 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
19288 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
19289 	}
19290 
19291 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
19292 		return ScreenOfDisplay(dpy,scr).root_visual;
19293 	}
19294 
19295 	extern(D) GC DefaultGC(Display *dpy,int scr) {
19296 		return ScreenOfDisplay(dpy,scr).default_gc;
19297 	}
19298 
19299 	extern(D) uint BlackPixel(Display *dpy,int scr) {
19300 		return ScreenOfDisplay(dpy,scr).black_pixel;
19301 	}
19302 
19303 	extern(D) uint WhitePixel(Display *dpy,int scr) {
19304 		return ScreenOfDisplay(dpy,scr).white_pixel;
19305 	}
19306 
19307 	alias void* XFontSet; // i think
19308 	struct XmbTextItem {
19309 		char* chars;
19310 		int nchars;
19311 		int delta;
19312 		XFontSet font_set;
19313 	}
19314 
19315 	struct XTextItem {
19316 		char* chars;
19317 		int nchars;
19318 		int delta;
19319 		Font font;
19320 	}
19321 
19322 	enum {
19323 		GXclear        = 0x0, /* 0 */
19324 		GXand          = 0x1, /* src AND dst */
19325 		GXandReverse   = 0x2, /* src AND NOT dst */
19326 		GXcopy         = 0x3, /* src */
19327 		GXandInverted  = 0x4, /* NOT src AND dst */
19328 		GXnoop         = 0x5, /* dst */
19329 		GXxor          = 0x6, /* src XOR dst */
19330 		GXor           = 0x7, /* src OR dst */
19331 		GXnor          = 0x8, /* NOT src AND NOT dst */
19332 		GXequiv        = 0x9, /* NOT src XOR dst */
19333 		GXinvert       = 0xa, /* NOT dst */
19334 		GXorReverse    = 0xb, /* src OR NOT dst */
19335 		GXcopyInverted = 0xc, /* NOT src */
19336 		GXorInverted   = 0xd, /* NOT src OR dst */
19337 		GXnand         = 0xe, /* NOT src OR NOT dst */
19338 		GXset          = 0xf, /* 1 */
19339 	}
19340 	enum QueueMode : int {
19341 		QueuedAlready,
19342 		QueuedAfterReading,
19343 		QueuedAfterFlush
19344 	}
19345 
19346 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
19347 
19348 	struct XPoint {
19349 		short x;
19350 		short y;
19351 	}
19352 
19353 	enum CoordMode:int {
19354 		CoordModeOrigin = 0,
19355 		CoordModePrevious = 1
19356 	}
19357 
19358 	enum PolygonShape:int {
19359 		Complex = 0,
19360 		Nonconvex = 1,
19361 		Convex = 2
19362 	}
19363 
19364 	struct XTextProperty {
19365 		const(char)* value;		/* same as Property routines */
19366 		Atom encoding;			/* prop type */
19367 		int format;				/* prop data format: 8, 16, or 32 */
19368 		arch_ulong nitems;		/* number of data items in value */
19369 	}
19370 
19371 	version( X86_64 ) {
19372 		static assert(XTextProperty.sizeof == 32);
19373 	}
19374 
19375 
19376 	struct XGCValues {
19377 		int function_;           /* logical operation */
19378 		arch_ulong plane_mask;/* plane mask */
19379 		arch_ulong foreground;/* foreground pixel */
19380 		arch_ulong background;/* background pixel */
19381 		int line_width;         /* line width */
19382 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
19383 		int cap_style;          /* CapNotLast, CapButt,
19384 					   CapRound, CapProjecting */
19385 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
19386 		int fill_style;         /* FillSolid, FillTiled,
19387 					   FillStippled, FillOpaeueStippled */
19388 		int fill_rule;          /* EvenOddRule, WindingRule */
19389 		int arc_mode;           /* ArcChord, ArcPieSlice */
19390 		Pixmap tile;            /* tile pixmap for tiling operations */
19391 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
19392 		int ts_x_origin;        /* offset for tile or stipple operations */
19393 		int ts_y_origin;
19394 		Font font;              /* default text font for text operations */
19395 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
19396 		Bool graphics_exposures;/* boolean, should exposures be generated */
19397 		int clip_x_origin;      /* origin for clipping */
19398 		int clip_y_origin;
19399 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
19400 		int dash_offset;        /* patterned/dashed line information */
19401 		char dashes;
19402 	}
19403 
19404 	struct XColor {
19405 		arch_ulong pixel;
19406 		ushort red, green, blue;
19407 		byte flags;
19408 		byte pad;
19409 	}
19410 
19411 	struct XRectangle {
19412 		short x;
19413 		short y;
19414 		ushort width;
19415 		ushort height;
19416 	}
19417 
19418 	enum ClipByChildren = 0;
19419 	enum IncludeInferiors = 1;
19420 
19421 	enum Atom XA_PRIMARY = 1;
19422 	enum Atom XA_SECONDARY = 2;
19423 	enum Atom XA_STRING = 31;
19424 	enum Atom XA_CARDINAL = 6;
19425 	enum Atom XA_WM_NAME = 39;
19426 	enum Atom XA_ATOM = 4;
19427 	enum Atom XA_WINDOW = 33;
19428 	enum Atom XA_WM_HINTS = 35;
19429 	enum int PropModeAppend = 2;
19430 	enum int PropModeReplace = 0;
19431 	enum int PropModePrepend = 1;
19432 
19433 	enum int CopyFromParent = 0;
19434 	enum int InputOutput = 1;
19435 
19436 	// XWMHints
19437 	enum InputHint = 1 << 0;
19438 	enum StateHint = 1 << 1;
19439 	enum IconPixmapHint = (1L << 2);
19440 	enum IconWindowHint = (1L << 3);
19441 	enum IconPositionHint = (1L << 4);
19442 	enum IconMaskHint = (1L << 5);
19443 	enum WindowGroupHint = (1L << 6);
19444 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
19445 	enum XUrgencyHint = (1L << 8);
19446 
19447 	// GC Components
19448 	enum GCFunction           =   (1L<<0);
19449 	enum GCPlaneMask         =    (1L<<1);
19450 	enum GCForeground       =     (1L<<2);
19451 	enum GCBackground      =      (1L<<3);
19452 	enum GCLineWidth      =       (1L<<4);
19453 	enum GCLineStyle     =        (1L<<5);
19454 	enum GCCapStyle     =         (1L<<6);
19455 	enum GCJoinStyle   =          (1L<<7);
19456 	enum GCFillStyle  =           (1L<<8);
19457 	enum GCFillRule  =            (1L<<9);
19458 	enum GCTile     =             (1L<<10);
19459 	enum GCStipple           =    (1L<<11);
19460 	enum GCTileStipXOrigin  =     (1L<<12);
19461 	enum GCTileStipYOrigin =      (1L<<13);
19462 	enum GCFont               =   (1L<<14);
19463 	enum GCSubwindowMode     =    (1L<<15);
19464 	enum GCGraphicsExposures=     (1L<<16);
19465 	enum GCClipXOrigin     =      (1L<<17);
19466 	enum GCClipYOrigin    =       (1L<<18);
19467 	enum GCClipMask      =        (1L<<19);
19468 	enum GCDashOffset   =         (1L<<20);
19469 	enum GCDashList    =          (1L<<21);
19470 	enum GCArcMode    =           (1L<<22);
19471 	enum GCLastBit   =            22;
19472 
19473 
19474 	enum int WithdrawnState = 0;
19475 	enum int NormalState = 1;
19476 	enum int IconicState = 3;
19477 
19478 }
19479 } else version (OSXCocoa) {
19480 
19481 /+
19482 	DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do.
19483 +/
19484 
19485 	import core.attribute;
19486 
19487 	private __gshared AppDelegate globalAppDelegate;
19488 
19489 	extern(Objective-C)
19490 	class AppDelegate : NSObject, NSApplicationDelegate {
19491 		override static AppDelegate alloc() @selector("alloc");
19492 
19493 
19494 		void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") {
19495 			SimpleWindow.processAllCustomEvents();
19496 		}
19497 
19498 		override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
19499 			immutable style = NSWindowStyleMask.resizable |
19500 				NSWindowStyleMask.closable |
19501 				NSWindowStyleMask.miniaturizable |
19502 				NSWindowStyleMask.titled;
19503 
19504 			NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
19505 
19506 			{
19507 				auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
19508 				auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
19509 				mainMenu.setSubmenu(menu, item);
19510 
19511 				auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow);
19512 				newItem.target = NSApp;
19513 				auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow);
19514 				newItem2.target = NSApp;
19515 			}
19516 
19517 			{
19518 				auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
19519 				auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
19520 				mainMenu.setSubmenu(menu, item);
19521 
19522 				auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow);
19523 				menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow);
19524 			}
19525 
19526 
19527 			NSApp.menu = mainMenu;
19528 
19529 
19530 			// auto controller = ViewController.alloc.init;
19531 
19532 			// auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true);
19533 
19534 			/+
19535 			this.window = window;
19536 			this.controller = controller;
19537 			+/
19538 		}
19539 
19540 		override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
19541 			NSApplication.shared_.activateIgnoringOtherApps(false);
19542 
19543 			sdpyPrintDebugString("before");
19544 			NSApp.stop(cast(void*) NSApp); // stop NSApp.run and let arsd.core event loop take over...
19545 			sdpyPrintDebugString("after");
19546 		}
19547 		override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
19548 			return true;
19549 		}
19550 	}
19551 
19552 	extern(Objective-C)
19553 	class SDWindowDelegate : NSObject, NSWindowDelegate {
19554 		override static SDWindowDelegate alloc() @selector("alloc");
19555 		override SDWindowDelegate init() @selector("init");
19556 
19557 		SimpleWindow simpleWindow;
19558 
19559 		override void windowWillClose(NSNotification notification) @selector("windowWillClose:") {
19560 			auto window = cast(void*) notification.object;
19561 
19562 			// FIXME: do i need to release it?
19563 			if(auto swp = window in SimpleWindow.nativeMapping) {
19564 				auto sw = *swp;
19565 
19566 				sw._closed = true;
19567 
19568 				if (sw.visibilityChanged !is null && sw._visible) sw.visibilityChanged(false);
19569 
19570 				if (sw.onDestroyed !is null) try { sw.onDestroyed(); } catch (Exception e) {} // sorry
19571 				SimpleWindow.nativeMapping.remove(window);
19572 				// FIXME: this makes a ref to typeinfo apparently
19573 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
19574 
19575 				bool anyImportant = false;
19576 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
19577 					if(w.beingOpenKeepsAppOpen) {
19578 						anyImportant = true;
19579 						break;
19580 					}
19581 				if(!anyImportant) {
19582 					EventLoop.quitApplication();
19583 				}
19584 
19585 			}
19586 		}
19587 
19588 		override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") {
19589 			if(simpleWindow.windowResized) {
19590 				// FIXME: automaticallyScaleIfPossible behaviors
19591 
19592 				simpleWindow._width = cast(int) frameSize.width;
19593 				simpleWindow._height = cast(int) frameSize.height;
19594 
19595 				simpleWindow.view.setFrameSize(frameSize);
19596 
19597 				/+
19598 				auto size = simpleWindow.view.frame.size;
19599 				writeln(cast(int) size.width, "x", cast(int) size.height);
19600 				+/
19601 
19602 				simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height);
19603 
19604 				simpleWindow.windowResized(simpleWindow._width, simpleWindow._height);
19605 
19606 				// simpleWindow.view.setNeedsDisplay(true);
19607 			}
19608 
19609 			return frameSize;
19610 		}
19611 
19612 		/+
19613 		override void windowDidResize(NSNotification notification) @selector("windowDidResize:") {
19614 			if(simpleWindow.windowResized) {
19615 				auto window = simpleWindow.window;
19616 				auto rect = window.contentRectForFrameRect(window.frame);
19617 				import std.stdio; writeln(window.frame.size);
19618 				simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height);
19619 			}
19620 		}
19621 		+/
19622 	}
19623 
19624 	extern(Objective-C)
19625 	class SDGraphicsView : NSView {
19626 		SimpleWindow simpleWindow;
19627 
19628 		override static SDGraphicsView alloc() @selector("alloc");
19629 		override SDGraphicsView init() @selector("init");/* {
19630 			super.init();
19631 			return this;
19632 		}*/
19633 
19634 		override void drawRect(NSRect rect) @selector("drawRect:") {
19635 			auto curCtx = NSGraphicsContext.currentContext.graphicsPort;
19636 			auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
19637 			auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext));
19638 			CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
19639 			CGImageRelease(cgImage);
19640 		}
19641 
19642 		extern(D)
19643 		private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) {
19644 			MouseEvent me;
19645 			me.type = type;
19646 
19647 			auto pos = event.locationInWindow;
19648 
19649 			me.x = cast(int) pos.x;
19650 
19651 			// FIXME: 1-based things here might need fixup
19652 			me.y = cast(int) (simpleWindow.height - pos.y);
19653 
19654 			me.dx = 0; // FIXME
19655 			me.dy = 0; // FIXME
19656 
19657 			me.button = button;
19658 			me.modifierState = cast(uint) event.modifierFlags;
19659 			me.window = simpleWindow;
19660 
19661 			me.doubleClick = false;
19662 
19663 			if(simpleWindow && simpleWindow.handleMouseEvent)
19664 				simpleWindow.handleMouseEvent(me);
19665 		}
19666 
19667 		override void mouseDown(NSEvent event) @selector("mouseDown:") {
19668 			// writeln(event.pressedMouseButtons);
19669 
19670 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
19671 		}
19672 		override void mouseDragged(NSEvent event) @selector("mouseDragged:") {
19673 			mouseHelper(event, MouseEventType.motion, MouseButton.left);
19674 		}
19675 		override void mouseUp(NSEvent event) @selector("mouseUp:") {
19676 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left);
19677 		}
19678 		override void mouseMoved(NSEvent event) @selector("mouseMoved:") {
19679 			mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly
19680 		}
19681 		/+
19682 			// FIXME
19683 		override void mouseEntered(NSEvent event) @selector("mouseEntered:") {
19684 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
19685 		}
19686 		override void mouseExited(NSEvent event) @selector("mouseExited:") {
19687 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left);
19688 		}
19689 		+/
19690 
19691 		override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") {
19692 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right);
19693 		}
19694 		override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") {
19695 			mouseHelper(event, MouseEventType.motion, MouseButton.right);
19696 		}
19697 		override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") {
19698 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right);
19699 		}
19700 
19701 		override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") {
19702 			mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle);
19703 		}
19704 		override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") {
19705 			mouseHelper(event, MouseEventType.motion, MouseButton.middle);
19706 		}
19707 		override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") {
19708 			mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle);
19709 		}
19710 
19711 		override void scrollWheel(NSEvent event) @selector("scrollWheel:") {
19712 			// import std.stdio; writeln(event.deltaY);
19713 		}
19714 
19715 		override void keyDown(NSEvent event) @selector("keyDown:") {
19716 			// the event may have multiple characters, and we send them all at once.
19717 			if (simpleWindow.handleCharEvent) {
19718 				auto chars = DeifiedNSString(event.characters);
19719 				foreach (dchar dc; chars.str)
19720 					simpleWindow.handleCharEvent(dc);
19721 			}
19722 
19723 			keyHelper(event, true);
19724 		}
19725 
19726 		override void keyUp(NSEvent event) @selector("keyUp:") {
19727 			keyHelper(event, false);
19728 		}
19729 
19730 		extern(D)
19731 		private void keyHelper(NSEvent event, bool pressed) {
19732 			if(simpleWindow.handleKeyEvent) {
19733 				KeyEvent ev;
19734 				ev.key = cast(Key) event.keyCode;//  (event.specialKey ? event.specialKey : event.keyCode);
19735 				ev.pressed = pressed;
19736 				ev.hardwareCode = cast(ubyte) event.keyCode;
19737 				ev.modifierState = cast(uint) event.modifierFlags;
19738 				ev.window = simpleWindow;
19739 
19740 				simpleWindow.handleKeyEvent(ev);
19741 			}
19742 		}
19743 
19744 		override bool isFlipped() @selector("isFlipped") {
19745 			return true;
19746 		}
19747 		override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
19748 			return true;
19749 		}
19750 
19751 		void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") {
19752 			if(simpleWindow && simpleWindow.handlePulse)
19753 				simpleWindow.handlePulse();
19754 			/+
19755 			setNeedsDisplay = true;
19756 			+/
19757 		}
19758 	}
19759 
19760 private:
19761 	alias const(void)* CFStringRef;
19762 	alias const(void)* CFAllocatorRef;
19763 	alias const(void)* CFTypeRef;
19764 	alias const(void)* CGColorSpaceRef;
19765 	alias const(void)* CGImageRef;
19766 	alias ulong CGBitmapInfo;
19767 	alias NSGraphicsContext CGContextRef; // actually CGContextRef should be a subclass...
19768 
19769 	alias NSPoint CGPoint;
19770 	alias NSSize CGSize;
19771 	alias NSRect CGRect;
19772 
19773 	struct CGAffineTransform {
19774 		double a, b, c, d, tx, ty;
19775 	}
19776 
19777 	enum NSApplicationActivationPolicyRegular = 0;
19778 	enum NSBackingStoreBuffered = 2;
19779 	enum kCFStringEncodingUTF8 = 0x08000100;
19780 
19781 	enum : size_t {
19782 		NSBorderlessWindowMask = 0,
19783 		NSTitledWindowMask = 1 << 0,
19784 		NSClosableWindowMask = 1 << 1,
19785 		NSMiniaturizableWindowMask = 1 << 2,
19786 		NSResizableWindowMask = 1 << 3,
19787 		NSTexturedBackgroundWindowMask = 1 << 8
19788 	}
19789 
19790 	enum : ulong {
19791 		kCGImageAlphaNone,
19792 		kCGImageAlphaPremultipliedLast,
19793 		kCGImageAlphaPremultipliedFirst,
19794 		kCGImageAlphaLast,
19795 		kCGImageAlphaFirst,
19796 		kCGImageAlphaNoneSkipLast,
19797 		kCGImageAlphaNoneSkipFirst
19798 	}
19799 	enum : ulong {
19800 		kCGBitmapAlphaInfoMask = 0x1F,
19801 		kCGBitmapFloatComponents = (1 << 8),
19802 		kCGBitmapByteOrderMask = 0x7000,
19803 		kCGBitmapByteOrderDefault = (0 << 12),
19804 		kCGBitmapByteOrder16Little = (1 << 12),
19805 		kCGBitmapByteOrder32Little = (2 << 12),
19806 		kCGBitmapByteOrder16Big = (3 << 12),
19807 		kCGBitmapByteOrder32Big = (4 << 12)
19808 	}
19809 	enum CGPathDrawingMode {
19810 		kCGPathFill,
19811 		kCGPathEOFill,
19812 		kCGPathStroke,
19813 		kCGPathFillStroke,
19814 		kCGPathEOFillStroke
19815 	}
19816 	enum objc_AssociationPolicy : size_t {
19817 		OBJC_ASSOCIATION_ASSIGN = 0,
19818 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
19819 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
19820 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
19821 		OBJC_ASSOCIATION_COPY = 0x303 //01403
19822 	}
19823 
19824 	extern(C) {
19825 		CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo);
19826 		void CGContextRelease(CGContextRef c);
19827 		ubyte* CGBitmapContextGetData(CGContextRef c);
19828 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
19829 		size_t CGBitmapContextGetWidth(CGContextRef c);
19830 		size_t CGBitmapContextGetHeight(CGContextRef c);
19831 
19832 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
19833 		void CGColorSpaceRelease(CGColorSpaceRef cs);
19834 
19835 		alias void* CGFontRef;
19836 		alias CTFontRef = NSFont;
19837 		CGFontRef CTFontCopyGraphicsFont(CTFontRef font, void  /*CTFontDescriptorRef*/ * attributes);
19838 
19839 
19840 		void CGContextSetFont(CGContextRef c, CGFontRef font);
19841 		void CGContextSetFontSize(CGContextRef c, CGFloat size);
19842 
19843 		void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha);
19844 		void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
19845 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
19846 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length);
19847 		void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count);
19848 		void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
19849 
19850 		void CGContextBeginPath(CGContextRef c);
19851 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
19852 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
19853 		void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
19854 		void CGContextAddRect(CGContextRef c, CGRect rect);
19855 		void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count);
19856 		void CGContextSaveGState(CGContextRef c);
19857 		void CGContextRestoreGState(CGContextRef c);
19858 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding);
19859 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
19860 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
19861 
19862 		void CGImageRelease(CGImageRef image);
19863 	}
19864 } else static assert(0, "Unsupported operating system");
19865 
19866 
19867 version(OSXCocoa) {
19868 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
19869 	//
19870 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
19871 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
19872 	//
19873 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
19874 	// Probably won't even fully compile right now
19875 
19876 	private enum double PI = 3.14159265358979323;
19877 
19878 	alias NSWindow NativeWindowHandle;
19879 	alias void delegate(NSid) NativeEventHandler;
19880 
19881 	enum KEY_ESCAPE = 27;
19882 
19883 	mixin template NativeImageImplementation() {
19884 		CGContextRef context;
19885 		ubyte* rawData;
19886 
19887 		final:
19888 
19889 		void convertToRgbaBytes(ubyte[] where) @system {
19890 			assert(where.length == this.width * this.height * 4);
19891 
19892 			// if rawData had a length....
19893 			//assert(rawData.length == where.length);
19894 			for(long idx = 0; idx < where.length; idx += 4) {
19895 				auto alpha = rawData[idx + 3];
19896 				if(alpha == 255) {
19897 					where[idx + 0] = rawData[idx + 0]; // r
19898 					where[idx + 1] = rawData[idx + 1]; // g
19899 					where[idx + 2] = rawData[idx + 2]; // b
19900 					where[idx + 3] = rawData[idx + 3]; // a
19901 				} else {
19902 					where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
19903 					where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
19904 					where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
19905 					where[idx + 3] = rawData[idx + 3]; // a
19906 
19907 				}
19908 			}
19909 		}
19910 
19911 		void setFromRgbaBytes(in ubyte[] where) @system {
19912 			// FIXME: this is probably wrong
19913 			assert(where.length == this.width * this.height * 4);
19914 
19915 			// if rawData had a length....
19916 			//assert(rawData.length == where.length);
19917 			for(long idx = 0; idx < where.length; idx += 4) {
19918 				auto alpha = where[idx + 3];
19919 				if(alpha == 255) {
19920 					rawData[idx + 0] = where[idx + 0]; // r
19921 					rawData[idx + 1] = where[idx + 1]; // g
19922 					rawData[idx + 2] = where[idx + 2]; // b
19923 					rawData[idx + 3] = where[idx + 3]; // a
19924 				} else if(alpha == 0) {
19925 					rawData[idx + 0] = 0;
19926 					rawData[idx + 1] = 0;
19927 					rawData[idx + 2] = 0;
19928 					rawData[idx + 3] = 0;
19929 				} else {
19930 					rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
19931 					rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
19932 					rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
19933 					rawData[idx + 3] = where[idx + 3]; // a
19934 				}
19935 			}
19936 		}
19937 
19938 
19939 		void createImage(int width, int height, bool forcexshm=false, bool ignored = false) {
19940 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
19941 			context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
19942 			CGColorSpaceRelease(colorSpace);
19943 			rawData = CGBitmapContextGetData(context);
19944 		}
19945 		void dispose() {
19946 			CGContextRelease(context);
19947 		}
19948 
19949 		void setPixel(int x, int y, Color c) @system {
19950 			auto offset = (y * width + x) * 4;
19951 			if (c.a == 255) {
19952 				rawData[offset + 0] = c.r;
19953 				rawData[offset + 1] = c.g;
19954 				rawData[offset + 2] = c.b;
19955 				rawData[offset + 3] = c.a;
19956 			} else {
19957 				rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
19958 				rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
19959 				rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
19960 				rawData[offset + 3] = c.a;
19961 			}
19962 		}
19963 	}
19964 
19965 	mixin template NativeScreenPainterImplementation() {
19966 		CGContextRef context;
19967 		ubyte[4] _outlineComponents;
19968 		NSView view;
19969 
19970 		Pen _activePen;
19971 		Color _fillColor;
19972 		Rectangle _clipRectangle;
19973 		OperatingSystemFont _font;
19974 
19975 		OperatingSystemFont getFont() {
19976 			if(_font is null) {
19977 				static OperatingSystemFont _defaultFont;
19978 				if(_defaultFont is null) {
19979 					_defaultFont = new OperatingSystemFont();
19980 					_defaultFont.loadDefault();
19981 				}
19982 				_font = _defaultFont;
19983 			}
19984 
19985 			return _font;
19986 		}
19987 
19988 		void create(PaintingHandle window) {
19989 			// this.destiny = window;
19990 			if(auto sw = cast(SimpleWindow) this.window) {
19991 				context = sw.drawingContext;
19992 				view = sw.view;
19993 			} else {
19994 				throw new NotYetImplementedException();
19995 			}
19996 		}
19997 
19998 		void dispose() {
19999 			view.setNeedsDisplay(true);
20000 		}
20001 
20002 		bool manualInvalidations;
20003 		void invalidateRect(Rectangle invalidRect) { }
20004 
20005 		// NotYetImplementedException
20006 		void rasterOp(RasterOp op) {
20007 		}
20008 		void setClipRectangle(int, int, int, int) {
20009 		}
20010 		Size textSize(in char[] txt) {
20011 			auto font = getFont();
20012 			return Size(castFnumToCnum(font.stringWidth(txt)), castFnumToCnum(font.height()));
20013 		}
20014 
20015 		void setFont(OperatingSystemFont font) {
20016 			_font = font;
20017 			// font.font.setInContext(context);
20018 			if(font) {
20019 				// FIXME: should i free this thing?
20020 				/+
20021 				auto f = CTFontCopyGraphicsFont(font.font, null);
20022 				if(font.font is null)
20023 					sdpyPrintDebugString("input is null");
20024 				if(f is null)
20025 					sdpyPrintDebugString("f is null");
20026 				CGContextSetFont(context, f);
20027 				+/
20028 				// CGContextSetFontSize(context, font.size);
20029 
20030 				// FIXME kinda hacky
20031 				CGContextSelectFont(context, (font.loadedInfo.name ~ "\0").ptr, font.loadedInfo.size, 1);
20032 			} else {} // FIMXE
20033 		}
20034 		int fontHeight() {
20035 			auto font = getFont();
20036 			return castFnumToCnum(font.height);
20037 		}
20038 
20039 		// end
20040 
20041 		void pen(Pen pen) {
20042 			_activePen = pen;
20043 			auto color = pen.color; // FIXME
20044 			double alphaComponent = color.a/255.0f;
20045 			CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
20046 
20047 			double[2] patternBuffer;
20048 			double[] pattern;
20049 			final switch(pen.style) {
20050 				case Pen.Style.Solid:
20051 					pattern = null;
20052 				break;
20053 				case Pen.Style.Dashed:
20054 					patternBuffer[0] = 4;
20055 					patternBuffer[1] = 1;
20056 					pattern = patternBuffer[];
20057 				break;
20058 				case Pen.Style.Dotted:
20059 					patternBuffer[0] = 1;
20060 					patternBuffer[1] = 1;
20061 					pattern = patternBuffer[];
20062 				break;
20063 			}
20064 
20065 			CGContextSetLineDash(context, 0, pattern.ptr, pattern.length);
20066 
20067 			if (color.a != 255) {
20068 				_outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
20069 				_outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
20070 				_outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
20071 				_outlineComponents[3] = color.a;
20072 			} else {
20073 				_outlineComponents[0] = color.r;
20074 				_outlineComponents[1] = color.g;
20075 				_outlineComponents[2] = color.b;
20076 				_outlineComponents[3] = color.a;
20077 			}
20078 		}
20079 
20080 		@property void fillColor(Color color) {
20081 			CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
20082 		}
20083 
20084 		void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
20085 		// NotYetImplementedException for upper left/width/height
20086 			auto cgImage = CGBitmapContextCreateImage(image.context);
20087 			auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context));
20088 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
20089 			CGImageRelease(cgImage);
20090 		}
20091 
20092 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
20093 		// FIXME: is this efficient?
20094 			auto cgImage = CGBitmapContextCreateImage(s.handle);
20095 			auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle));
20096 			CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
20097 			CGImageRelease(cgImage);
20098 		}
20099 
20100 
20101 		void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
20102 		// FIXME: alignment
20103 			if (_outlineComponents[3] != 0) {
20104 				CGContextSaveGState(context);
20105 				auto invAlpha = 1.0f/_outlineComponents[3];
20106 				CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
20107 												  _outlineComponents[1]*invAlpha,
20108 												  _outlineComponents[2]*invAlpha,
20109 												  _outlineComponents[3]/255.0f);
20110 
20111 
20112 
20113 				// FIXME: should we clip it to the bounding box?
20114 				int textHeight = fontHeight;
20115 
20116 				auto lines = text.split('\n');
20117 
20118 				const lineHeight = textHeight;
20119 				textHeight *= lines.length;
20120 
20121 				int cy = y;
20122 
20123 				if(alignment & TextAlignment.VerticalBottom) {
20124 					if(y2 <= 0)
20125 						return;
20126 					auto h = y2 - y;
20127 					if(h > textHeight) {
20128 						cy += h - textHeight;
20129 						cy -= lineHeight / 2;
20130 					}
20131 				} else if(alignment & TextAlignment.VerticalCenter) {
20132 					if(y2 <= 0)
20133 						return;
20134 					auto h = y2 - y;
20135 					if(textHeight < h) {
20136 						cy += (h - textHeight) / 2;
20137 						//cy -= lineHeight / 4;
20138 					}
20139 				}
20140 
20141 				foreach(line; text.split('\n')) {
20142 					int textWidth = this.textSize(line).width;
20143 
20144 					int px = x, py = cy;
20145 
20146 					if(alignment & TextAlignment.Center) {
20147 						if(x2 <= 0)
20148 							return;
20149 						auto w = x2 - x;
20150 						if(w > textWidth)
20151 							px += (w - textWidth) / 2;
20152 					} else if(alignment & TextAlignment.Right) {
20153 						if(x2 <= 0)
20154 							return;
20155 						auto pos = x2 - textWidth;
20156 						if(pos > x)
20157 							px = pos;
20158 					}
20159 
20160 					CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length);
20161 
20162 					carry_on:
20163 					cy += lineHeight + 4;
20164 				}
20165 
20166 // auto cfstr = cast(NSid)createCFString(text);
20167 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
20168 // NSPoint(x, y), null);
20169 // CFRelease(cfstr);
20170 				CGContextRestoreGState(context);
20171 			}
20172 		}
20173 
20174 		void drawPixel(int x, int y) {
20175 			auto rawData = CGBitmapContextGetData(context);
20176 			auto width = CGBitmapContextGetWidth(context);
20177 			auto height = CGBitmapContextGetHeight(context);
20178 			auto offset = ((height - y - 1) * width + x) * 4;
20179 			rawData[offset .. offset+4] = _outlineComponents;
20180 		}
20181 
20182 		void drawLine(int x1, int y1, int x2, int y2) {
20183 			CGPoint[2] linePoints;
20184 			linePoints[0] = CGPoint(x1, y1);
20185 			linePoints[1] = CGPoint(x2, y2);
20186 			CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
20187 		}
20188 
20189 		void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) {
20190 			drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded
20191 		}
20192 
20193 		void drawRectangle(int x, int y, int width, int height) {
20194 			CGContextBeginPath(context);
20195 			// trying to align with actual pixels...
20196 			auto rect = CGRect(CGPoint(x + 0.5, y + 0.5), CGSize(width - 1, height - 1));
20197 			CGContextAddRect(context, rect);
20198 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
20199 		}
20200 
20201 		void drawEllipse(int x1, int y1, int x2, int y2) {
20202 			CGContextBeginPath(context);
20203 			auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
20204 			CGContextAddEllipseInRect(context, rect);
20205 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
20206 		}
20207 
20208 		void drawArc(int x1, int y1, int width, int height, int start, int length) {
20209 			// @@@BUG@@@ Does not support elliptic arc (width != height).
20210 			CGContextBeginPath(context);
20211 			int clockwise = 0;
20212 			if(length < 0) {
20213 				clockwise = 1;
20214 				length = -length;
20215 			}
20216 			CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
20217 							start*PI/(180*64), (start+length)*PI/(180*64), clockwise);
20218 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
20219 		}
20220 
20221 		void drawPolygon(Point[] intPoints) {
20222 			CGContextBeginPath(context);
20223 			CGPoint[16] pointsBuffer;
20224 			CGPoint[] points;
20225 			if(intPoints.length <= pointsBuffer.length)
20226 				points = pointsBuffer[0 .. intPoints.length];
20227 			else
20228 				points = new CGPoint[](intPoints.length);
20229 
20230 			foreach(idx, pt; intPoints)
20231 				points[idx] = CGPoint(pt.x, pt.y);
20232 
20233 			CGContextAddLines(context, points.ptr, points.length);
20234 			CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
20235 		}
20236 	}
20237 
20238 	private bool appInitialized = false;
20239 	void initializeApp() {
20240 		if(appInitialized)
20241 			return;
20242 		synchronized {
20243 			if(appInitialized)
20244 				return;
20245 
20246 			auto app = NSApp(); // ensure the is initialized
20247 
20248 			auto dg = AppDelegate.alloc;
20249 			globalAppDelegate = dg;
20250 			NSApp.delegate_ = dg;
20251 
20252 			NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
20253 
20254 			appInitialized = true;
20255 		}
20256 	}
20257 
20258 	mixin template NativeSimpleWindowImplementation() {
20259 		void setTitle(string title) {
20260 			window.title = MacString(title).borrow;
20261 		}
20262 
20263 		void moveResize (int x, int y, int w, int h) {
20264 			//auto f = window.frame;
20265 			// FIXME: finish
20266 			sdpyPrintDebugString("moveResize not implemented");
20267 		}
20268 
20269 		void resize(int w, int h) {
20270 			// FIXME: finish
20271 			sdpyPrintDebugString("resize not implemented");
20272 		}
20273 
20274 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
20275 			initializeApp();
20276 
20277 			auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
20278 
20279 			window = NSWindow.alloc.initWithContentRect(
20280 				contentRect,
20281 				NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled,
20282 				NSBackingStoreType.buffered,
20283 				true
20284 			);
20285 
20286 			SimpleWindow.nativeMapping[cast(void*) window] = this;
20287 
20288 			window.title = MacString(title).borrow;
20289 
20290 			auto dg = SDWindowDelegate.alloc.init;
20291 			dg.simpleWindow = this;
20292 			window.delegate_ = dg;
20293 
20294 			auto view = SDGraphicsView.alloc.init;
20295 			assert(view !is null);
20296 			window.contentView = view;
20297 			this.view = view;
20298 			view.simpleWindow = this;
20299 
20300 			window.center();
20301 
20302 			window.makeKeyAndOrderFront(null);
20303 
20304 			// no need to make a bitmap on mac since everything is double buffered already
20305 
20306 			// create area to draw on.
20307 			createNewDrawingContext(width, height);
20308 
20309 			window.setBackgroundColor(NSColor.whiteColor);
20310 
20311 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
20312 				// show it
20313 				view.setNeedsDisplay(true);
20314 			} else {
20315 
20316 				view.setNeedsDisplay(true);
20317 				// hide it
20318 				//window.setIsVisible = false;
20319 			}
20320 		}
20321 
20322 		void createNewDrawingContext(int width, int height) {
20323 			// FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay
20324 			if(this.drawingContext)
20325 				CGContextRelease(this.drawingContext);
20326 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
20327 			this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big);
20328 			CGColorSpaceRelease(colorSpace);
20329 			CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
20330 			auto matrix = CGContextGetTextMatrix(drawingContext);
20331 			matrix.c = -matrix.c;
20332 			matrix.d = -matrix.d;
20333 			CGContextSetTextMatrix(drawingContext, matrix);
20334 
20335 		}
20336 
20337 		void dispose() {
20338 			closeWindow();
20339 			// window.release(); // closing the window does this automatically i think
20340 		}
20341 		void closeWindow() {
20342 			if(window)
20343 				window.close();
20344 		}
20345 
20346 		ScreenPainter getPainter(bool manualInvalidations) {
20347 			return ScreenPainter(this, this.window, manualInvalidations);
20348 		}
20349 
20350 		NSWindow window;
20351 		NSView view;
20352 		CGContextRef drawingContext;
20353 	}
20354 }
20355 
20356 version(without_opengl) {} else
20357 extern(System) nothrow @nogc {
20358 	//enum uint GL_VERSION = 0x1F02;
20359 	//const(char)* glGetString (/*GLenum*/uint);
20360 	version(X11) {
20361 	static if (!SdpyIsUsingIVGLBinds) {
20362 
20363 		enum GLX_X_RENDERABLE = 0x8012;
20364 		enum GLX_DRAWABLE_TYPE = 0x8010;
20365 		enum GLX_RENDER_TYPE = 0x8011;
20366 		enum GLX_X_VISUAL_TYPE = 0x22;
20367 		enum GLX_TRUE_COLOR = 0x8002;
20368 		enum GLX_WINDOW_BIT = 0x00000001;
20369 		enum GLX_RGBA_BIT = 0x00000001;
20370 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
20371 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
20372 		enum GLX_SAMPLES = 0x186a1;
20373 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
20374 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
20375 	}
20376 
20377 		// GLX_EXT_swap_control
20378 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
20379 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
20380 
20381 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
20382 		extern(System) {
20383 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
20384 		}
20385 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
20386 
20387 		// this made public so we don't have to get it again and again
20388 		public bool glXCreateContextAttribsARB_present () @system {
20389 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
20390 				// get it
20391 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
20392 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
20393 			}
20394 			return (glXCreateContextAttribsARBFn !is null);
20395 		}
20396 
20397 		// this made public so we don't have to get it again and again
20398 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system {
20399 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
20400 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
20401 		}
20402 
20403 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
20404 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
20405 
20406 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
20407 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
20408 			if (_glx_swapInterval_fn is null) {
20409 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
20410 				if (_glx_swapInterval_fn is null) {
20411 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
20412 					return;
20413 				}
20414 				version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); }
20415 			}
20416 
20417 			if(glXSwapIntervalMESA is null) {
20418 				// it seems to require both to actually take effect on many computers
20419 				// idk why
20420 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
20421 				if(glXSwapIntervalMESA is null)
20422 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
20423 			}
20424 
20425 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
20426 				glXSwapIntervalMESA(wait ? 1 : 0);
20427 
20428 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
20429 		}
20430 	} else version(Windows) {
20431 	static if (!SdpyIsUsingIVGLBinds) {
20432 	enum GL_TRUE = 1;
20433 	enum GL_FALSE = 0;
20434 
20435 	public void* glbindGetProcAddress (const(char)* name) {
20436 		void* res = wglGetProcAddress(name);
20437 		if (res is null) {
20438 			/+
20439 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
20440 			import core.sys.windows.windef, core.sys.windows.winbase;
20441 			__gshared HINSTANCE dll = null;
20442 			if (dll is null) {
20443 				dll = LoadLibraryA("opengl32.dll");
20444 				if (dll is null) return null; // <32, but idc
20445 			}
20446 			res = GetProcAddress(dll, name);
20447 			+/
20448 			res = GetProcAddress(gl.libHandle, name);
20449 		}
20450 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
20451 		return res;
20452 	}
20453 	}
20454 
20455 
20456  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
20457 	void wglSetVSync(bool wait) {
20458 		if(wglSwapIntervalEXT is null) {
20459 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
20460 			if(wglSwapIntervalEXT is null)
20461 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
20462 		}
20463 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
20464 			return;
20465 
20466 		wglSwapIntervalEXT(wait ? 1 : 0);
20467 	}
20468 
20469 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
20470 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
20471 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
20472 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
20473 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
20474 
20475 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
20476 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
20477 
20478 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
20479 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
20480 
20481 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
20482 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
20483 
20484 		void wglInitOtherFunctions () {
20485 			if (wglCreateContextAttribsARB is null) {
20486 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
20487 			}
20488 		}
20489 	}
20490 
20491 	static if (!SdpyIsUsingIVGLBinds) {
20492 
20493 	interface GL {
20494 		extern(System) @nogc nothrow:
20495 
20496 		void glGetIntegerv(int, void*);
20497 		void glMatrixMode(int);
20498 		void glPushMatrix();
20499 		void glLoadIdentity();
20500 		void glOrtho(double, double, double, double, double, double);
20501 		void glFrustum(double, double, double, double, double, double);
20502 
20503 		void glPopMatrix();
20504 		void glEnable(int);
20505 		void glDisable(int);
20506 		void glClear(int);
20507 		void glBegin(int);
20508 		void glVertex2f(float, float);
20509 		void glVertex3f(float, float, float);
20510 		void glEnd();
20511 		void glColor3b(byte, byte, byte);
20512 		void glColor3ub(ubyte, ubyte, ubyte);
20513 		void glColor4b(byte, byte, byte, byte);
20514 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
20515 		void glColor3i(int, int, int);
20516 		void glColor3ui(uint, uint, uint);
20517 		void glColor4i(int, int, int, int);
20518 		void glColor4ui(uint, uint, uint, uint);
20519 		void glColor3f(float, float, float);
20520 		void glColor4f(float, float, float, float);
20521 		void glTranslatef(float, float, float);
20522 		void glScalef(float, float, float);
20523 		version(X11) {
20524 			void glSecondaryColor3b(byte, byte, byte);
20525 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
20526 			void glSecondaryColor3i(int, int, int);
20527 			void glSecondaryColor3ui(uint, uint, uint);
20528 			void glSecondaryColor3f(float, float, float);
20529 		}
20530 
20531 		void glDrawElements(int, int, int, void*);
20532 
20533 		void glRotatef(float, float, float, float);
20534 
20535 		uint glGetError();
20536 
20537 		void glDeleteTextures(int, uint*);
20538 
20539 
20540 		void glRasterPos2i(int, int);
20541 		void glDrawPixels(int, int, uint, uint, void*);
20542 		void glClearColor(float, float, float, float);
20543 
20544 
20545 		void glPixelStorei(uint, int);
20546 
20547 		void glGenTextures(uint, uint*);
20548 		void glBindTexture(int, int);
20549 		void glTexParameteri(uint, uint, int);
20550 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
20551 		void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*);
20552 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
20553 			/*GLsizei*/int width, /*GLsizei*/int height,
20554 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
20555 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
20556 
20557 		void glLineWidth(int);
20558 
20559 
20560 		void glTexCoord2f(float, float);
20561 		void glVertex2i(int, int);
20562 		void glBlendFunc (int, int);
20563 		void glDepthFunc (int);
20564 		void glViewport(int, int, int, int);
20565 
20566 		void glClearDepth(double);
20567 
20568 		void glReadBuffer(uint);
20569 		void glReadPixels(int, int, int, int, int, int, void*);
20570 
20571 		void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
20572 
20573 		void glFlush();
20574 		void glFinish();
20575 
20576 		version(Windows) {
20577 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
20578 			HGLRC wglCreateContext(HDC);
20579 			HGLRC wglCreateLayerContext(HDC, int);
20580 			BOOL wglDeleteContext(HGLRC);
20581 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
20582 			HGLRC wglGetCurrentContext();
20583 			HDC wglGetCurrentDC();
20584 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
20585 			PROC wglGetProcAddress(LPCSTR);
20586 			BOOL wglMakeCurrent(HDC, HGLRC);
20587 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
20588 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
20589 			BOOL wglShareLists(HGLRC, HGLRC);
20590 			BOOL wglSwapLayerBuffers(HDC, UINT);
20591 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
20592 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
20593 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
20594 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
20595 		}
20596 
20597 	}
20598 
20599 	interface GL3 {
20600 		extern(System) @nogc nothrow:
20601 
20602 		void glGenVertexArrays(GLsizei, GLuint*);
20603 		void glBindVertexArray(GLuint);
20604 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
20605 		void glGenerateMipmap(GLenum);
20606 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
20607 		void glStencilMask(GLuint);
20608 		void glStencilFunc(GLenum, GLint, GLuint);
20609 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
20610 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
20611 		GLuint glCreateProgram();
20612 		GLuint glCreateShader(GLenum);
20613 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
20614 		void glCompileShader(GLuint);
20615 		void glGetShaderiv(GLuint, GLenum, GLint*);
20616 		void glAttachShader(GLuint, GLuint);
20617 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
20618 		void glLinkProgram(GLuint);
20619 		void glGetProgramiv(GLuint, GLenum, GLint*);
20620 		void glDeleteProgram(GLuint);
20621 		void glDeleteShader(GLuint);
20622 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
20623 		void glGenBuffers(GLsizei, GLuint*);
20624 
20625 		void glUniform1f(GLint location, GLfloat v0);
20626 		void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
20627 		void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
20628 		void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
20629 		void glUniform1i(GLint location, GLint v0);
20630 		void glUniform2i(GLint location, GLint v0, GLint v1);
20631 		void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
20632 		void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
20633 		void glUniform1ui(GLint location, GLuint v0);
20634 		void glUniform2ui(GLint location, GLuint v0, GLuint v1);
20635 		void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
20636 		void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
20637 		void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
20638 		void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
20639 		void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
20640 		void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
20641 		void glUniform1iv(GLint location, GLsizei count, const GLint *value);
20642 		void glUniform2iv(GLint location, GLsizei count, const GLint *value);
20643 		void glUniform3iv(GLint location, GLsizei count, const GLint *value);
20644 		void glUniform4iv(GLint location, GLsizei count, const GLint *value);
20645 		void glUniform1uiv(GLint location, GLsizei count, const GLuint *value);
20646 		void glUniform2uiv(GLint location, GLsizei count, const GLuint *value);
20647 		void glUniform3uiv(GLint location, GLsizei count, const GLuint *value);
20648 		void glUniform4uiv(GLint location, GLsizei count, const GLuint *value);
20649 		void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
20650 		void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
20651 		void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
20652 		void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
20653 		void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
20654 		void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
20655 		void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
20656 		void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
20657 		void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
20658 
20659 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
20660 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
20661 		void glDrawArrays(GLenum, GLint, GLsizei);
20662 		void glStencilOp(GLenum, GLenum, GLenum);
20663 		void glUseProgram(GLuint);
20664 		void glCullFace(GLenum);
20665 		void glFrontFace(GLenum);
20666 		void glActiveTexture(GLenum);
20667 		void glBindBuffer(GLenum, GLuint);
20668 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
20669 		void glEnableVertexAttribArray(GLuint);
20670 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
20671 		void glUniform1i(GLint, GLint);
20672 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
20673 		void glDisableVertexAttribArray(GLuint);
20674 		void glDeleteBuffers(GLsizei, const(GLuint)*);
20675 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
20676 		void glLogicOp (GLenum opcode);
20677 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
20678 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
20679 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
20680 		GLenum glCheckFramebufferStatus (GLenum target);
20681 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
20682 	}
20683 
20684 	interface GL4 {
20685 		extern(System) @nogc nothrow:
20686 
20687 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
20688 			/*GLsizei*/int width, /*GLsizei*/int height,
20689 			uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels);
20690 	}
20691 
20692 	interface GLU {
20693 		extern(System) @nogc nothrow:
20694 
20695 		void gluLookAt(double, double, double, double, double, double, double, double, double);
20696 		void gluPerspective(double, double, double, double);
20697 
20698 		char* gluErrorString(uint);
20699 	}
20700 
20701 
20702 	enum GL_RED = 0x1903;
20703 	enum GL_ALPHA = 0x1906;
20704 
20705 	enum uint GL_FRONT = 0x0404;
20706 
20707 	enum uint GL_BLEND = 0x0be2;
20708 	enum uint GL_LEQUAL = 0x0203;
20709 
20710 
20711 	enum uint GL_RGB = 0x1907;
20712 	enum uint GL_BGRA = 0x80e1;
20713 	enum uint GL_RGBA = 0x1908;
20714 	enum uint GL_RGBA8 = 0x8058;
20715 	enum uint GL_TEXTURE_2D =   0x0DE1;
20716 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
20717 	enum uint GL_NEAREST = 0x2600;
20718 	enum uint GL_LINEAR = 0x2601;
20719 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
20720 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
20721 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
20722 	enum uint GL_REPEAT = 0x2901;
20723 	enum uint GL_CLAMP = 0x2900;
20724 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
20725 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
20726 	enum uint GL_DECAL = 0x2101;
20727 	enum uint GL_MODULATE = 0x2100;
20728 	enum uint GL_TEXTURE_ENV = 0x2300;
20729 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
20730 	enum uint GL_REPLACE = 0x1E01;
20731 	enum uint GL_LIGHTING = 0x0B50;
20732 	enum uint GL_DITHER = 0x0BD0;
20733 
20734 	enum uint GL_NO_ERROR = 0;
20735 
20736 
20737 
20738 	enum int GL_VIEWPORT = 0x0BA2;
20739 	enum int GL_MODELVIEW = 0x1700;
20740 	enum int GL_TEXTURE = 0x1702;
20741 	enum int GL_PROJECTION = 0x1701;
20742 	enum int GL_DEPTH_TEST = 0x0B71;
20743 
20744 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
20745 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
20746 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
20747 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
20748 
20749 	enum int GL_POINTS = 0x0000;
20750 	enum int GL_LINES =  0x0001;
20751 	enum int GL_LINE_LOOP = 0x0002;
20752 	enum int GL_LINE_STRIP = 0x0003;
20753 	enum int GL_TRIANGLES = 0x0004;
20754 	enum int GL_TRIANGLE_STRIP = 5;
20755 	enum int GL_TRIANGLE_FAN = 6;
20756 	enum int GL_QUADS = 7;
20757 	enum int GL_QUAD_STRIP = 8;
20758 	enum int GL_POLYGON = 9;
20759 
20760 	alias GLvoid = void;
20761 	alias GLboolean = ubyte;
20762 	alias GLint = int;
20763 	alias GLuint = uint;
20764 	alias GLenum = uint;
20765 	alias GLchar = char;
20766 	alias GLsizei = int;
20767 	alias GLfloat = float;
20768 	alias GLintptr = size_t;
20769 	alias GLsizeiptr = ptrdiff_t;
20770 
20771 
20772 	enum uint GL_INVALID_ENUM = 0x0500;
20773 
20774 	enum uint GL_ZERO = 0;
20775 	enum uint GL_ONE = 1;
20776 
20777 	enum uint GL_BYTE = 0x1400;
20778 	enum uint GL_UNSIGNED_BYTE = 0x1401;
20779 	enum uint GL_SHORT = 0x1402;
20780 	enum uint GL_UNSIGNED_SHORT = 0x1403;
20781 	enum uint GL_INT = 0x1404;
20782 	enum uint GL_UNSIGNED_INT = 0x1405;
20783 	enum uint GL_FLOAT = 0x1406;
20784 	enum uint GL_2_BYTES = 0x1407;
20785 	enum uint GL_3_BYTES = 0x1408;
20786 	enum uint GL_4_BYTES = 0x1409;
20787 	enum uint GL_DOUBLE = 0x140A;
20788 
20789 	enum uint GL_STREAM_DRAW = 0x88E0;
20790 
20791 	enum uint GL_CCW = 0x0901;
20792 
20793 	enum uint GL_STENCIL_TEST = 0x0B90;
20794 	enum uint GL_SCISSOR_TEST = 0x0C11;
20795 
20796 	enum uint GL_EQUAL = 0x0202;
20797 	enum uint GL_NOTEQUAL = 0x0205;
20798 
20799 	enum uint GL_ALWAYS = 0x0207;
20800 	enum uint GL_KEEP = 0x1E00;
20801 
20802 	enum uint GL_INCR = 0x1E02;
20803 
20804 	enum uint GL_INCR_WRAP = 0x8507;
20805 	enum uint GL_DECR_WRAP = 0x8508;
20806 
20807 	enum uint GL_CULL_FACE = 0x0B44;
20808 	enum uint GL_BACK = 0x0405;
20809 
20810 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
20811 	enum uint GL_VERTEX_SHADER = 0x8B31;
20812 
20813 	enum uint GL_COMPILE_STATUS = 0x8B81;
20814 	enum uint GL_LINK_STATUS = 0x8B82;
20815 
20816 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
20817 
20818 	enum uint GL_STATIC_DRAW = 0x88E4;
20819 
20820 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
20821 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
20822 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
20823 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
20824 
20825 	enum uint GL_GENERATE_MIPMAP = 0x8191;
20826 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
20827 
20828 	enum uint GL_TEXTURE0 = 0x84C0U;
20829 	enum uint GL_TEXTURE1 = 0x84C1U;
20830 
20831 	enum uint GL_ARRAY_BUFFER = 0x8892;
20832 
20833 	enum uint GL_SRC_COLOR = 0x0300;
20834 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
20835 	enum uint GL_SRC_ALPHA = 0x0302;
20836 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
20837 	enum uint GL_DST_ALPHA = 0x0304;
20838 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
20839 	enum uint GL_DST_COLOR = 0x0306;
20840 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
20841 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
20842 
20843 	enum uint GL_INVERT = 0x150AU;
20844 
20845 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
20846 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
20847 
20848 	enum uint GL_FRAMEBUFFER = 0x8D40U;
20849 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
20850 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
20851 
20852 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
20853 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
20854 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
20855 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
20856 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
20857 
20858 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
20859 	enum uint GL_CLEAR = 0x1500U;
20860 	enum uint GL_COPY = 0x1503U;
20861 	enum uint GL_XOR = 0x1506U;
20862 
20863 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
20864 
20865 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
20866 
20867 	}
20868 }
20869 
20870 /++
20871 	History:
20872 		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.
20873 +/
20874 __gshared bool gluSuccessfullyLoaded = true;
20875 
20876 version(without_opengl) {} else {
20877 static if(!SdpyIsUsingIVGLBinds) {
20878 	version(Windows) {
20879 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
20880 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
20881 	} else {
20882 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
20883 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
20884 	}
20885 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
20886 
20887 
20888 	shared static this() {
20889 		gl.loadDynamicLibrary();
20890 
20891 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
20892 		// unless those functions are actually used
20893 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
20894 		glu.loadDynamicLibrary();
20895 	}
20896 }
20897 }
20898 
20899 /++
20900 	Convenience method for converting D arrays to opengl buffer data
20901 
20902 	I would LOVE to overload it with the original glBufferData, but D won't
20903 	let me since glBufferData is a function pointer :(
20904 
20905 	Added: August 25, 2020 (version 8.5)
20906 +/
20907 version(without_opengl) {} else
20908 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
20909 	glBufferData(target, data.length, data.ptr, usage);
20910 }
20911 
20912 /++
20913 	History:
20914 		Added September 1, 2024
20915 +/
20916 version(without_opengl) {} else
20917 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) {
20918 	glBufferSubData(target, offset, data.length, data.ptr);
20919 }
20920 
20921 /++
20922 	Convenience class for using opengl shaders.
20923 
20924 	Ensure that you've loaded opengl 3+ and set your active
20925 	context before trying to use this.
20926 
20927 	Added: August 25, 2020 (version 8.5)
20928 +/
20929 version(without_opengl) {} else
20930 final class OpenGlShader {
20931 	private int shaderProgram_;
20932 	private @property void shaderProgram(int a) {
20933 		shaderProgram_ = a;
20934 	}
20935 	/// Get the program ID for use in OpenGL functions.
20936 	public @property int shaderProgram() {
20937 		return shaderProgram_;
20938 	}
20939 
20940 	/++
20941 
20942 	+/
20943 	static struct Source {
20944 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
20945 		string code; ///
20946 	}
20947 
20948 	/++
20949 		Helper method to just compile some shader code and check for errors
20950 		while you do glCreateShader, etc. on the outside yourself.
20951 
20952 		This just does `glShaderSource` and `glCompileShader` for the given code.
20953 
20954 		If you the OpenGlShader class constructor, you never need to call this yourself.
20955 	+/
20956 	static void compile(int sid, Source code) {
20957 		const(char)*[1] buffer;
20958 		int[1] lengthBuffer;
20959 
20960 		buffer[0] = code.code.ptr;
20961 		lengthBuffer[0] = cast(int) code.code.length;
20962 
20963 		glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr);
20964 		glCompileShader(sid);
20965 
20966 		int success;
20967 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
20968 		if(!success) {
20969 			char[512] info;
20970 			int len;
20971 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
20972 
20973 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
20974 		}
20975 	}
20976 
20977 	/++
20978 		Calls `glLinkProgram` and throws if error a occurs.
20979 
20980 		If you the OpenGlShader class constructor, you never need to call this yourself.
20981 	+/
20982 	static void link(int shaderProgram) {
20983 		glLinkProgram(shaderProgram);
20984 		int success;
20985 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
20986 		if(!success) {
20987 			char[512] info;
20988 			int len;
20989 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
20990 
20991 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
20992 		}
20993 	}
20994 
20995 	/++
20996 		Constructs the shader object by calling `glCreateProgram`, then
20997 		compiling each given [Source], and finally, linking them together.
20998 
20999 		Throws: on compile or link failure.
21000 	+/
21001 	this(Source[] codes...) {
21002 		shaderProgram = glCreateProgram();
21003 
21004 		int[16] shadersBufferStack;
21005 
21006 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ?
21007 			shadersBufferStack[0 .. codes.length] :
21008 			new int[](codes.length);
21009 
21010 		foreach(idx, code; codes) {
21011 			shadersBuffer[idx] = glCreateShader(code.type);
21012 
21013 			compile(shadersBuffer[idx], code);
21014 
21015 			glAttachShader(shaderProgram, shadersBuffer[idx]);
21016 		}
21017 
21018 		link(shaderProgram);
21019 
21020 		foreach(s; shadersBuffer)
21021 			glDeleteShader(s);
21022 	}
21023 
21024 	/// Calls `glUseProgram(this.shaderProgram)`
21025 	void use() {
21026 		glUseProgram(this.shaderProgram);
21027 	}
21028 
21029 	/// Deletes the program.
21030 	void delete_() {
21031 		glDeleteProgram(shaderProgram);
21032 		shaderProgram = 0;
21033 	}
21034 
21035 	/++
21036 		[OpenGlShader.uniforms].name gives you one of these.
21037 
21038 		You can get the id out of it or just assign
21039 	+/
21040 	static struct Uniform {
21041 		/// the id passed to glUniform*
21042 		int id;
21043 
21044 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
21045 		void opAssign(float x, float y, float z, float w) {
21046 			if(id != -1)
21047 			glUniform4f(id, x, y, z, w);
21048 		}
21049 
21050 		void opAssign(float x) {
21051 			if(id != -1)
21052 			glUniform1f(id, x);
21053 		}
21054 
21055 		void opAssign(float x, float y) {
21056 			if(id != -1)
21057 			glUniform2f(id, x, y);
21058 		}
21059 
21060 		void opAssign(T)(T t) {
21061 			t.glUniform(id);
21062 		}
21063 	}
21064 
21065 	static struct UniformsHelper {
21066 		OpenGlShader _shader;
21067 
21068 		@property Uniform opDispatch(string name)() {
21069 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
21070 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
21071 			//if(i == -1)
21072 				//throw new Exception("Could not find uniform " ~ name);
21073 			return Uniform(i);
21074 		}
21075 
21076 		@property void opDispatch(string name, T)(T t) {
21077 			Uniform f = this.opDispatch!name;
21078 			t.glUniform(f);
21079 		}
21080 	}
21081 
21082 	/++
21083 		Gives access to the uniforms through dot access.
21084 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
21085 	+/
21086 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
21087 }
21088 
21089 version(without_opengl) {} else {
21090 /++
21091 	A static container of experimental types and value constructors for opengl 3+ shaders.
21092 
21093 
21094 	You can declare variables like:
21095 
21096 	```
21097 	OGL.vec3f something;
21098 	```
21099 
21100 	But generally it would be used with [OpenGlShader]'s uniform helpers like
21101 
21102 	```
21103 	shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific
21104 	```
21105 
21106 	This is still extremely experimental, not very useful at this point, and thus subject to change at random.
21107 
21108 
21109 	History:
21110 		Added December 7, 2021. Not yet stable.
21111 +/
21112 final class OGL {
21113 	static:
21114 
21115 	private template typeFromSpecifier(string specifier) {
21116 		static if(specifier == "f")
21117 			alias typeFromSpecifier = GLfloat;
21118 		else static if(specifier == "i")
21119 			alias typeFromSpecifier = GLint;
21120 		else static if(specifier == "ui")
21121 			alias typeFromSpecifier = GLuint;
21122 		else static assert(0, "I don't know this ogl type suffix " ~ specifier);
21123 	}
21124 
21125 	private template CommonType(T...) {
21126 		static if(T.length == 1)
21127 			alias CommonType = T[0];
21128 		else static if(is(typeof(true ? T[0].init : T[1].init) C))
21129 			alias CommonType = CommonType!(C, T[2 .. $]);
21130 	}
21131 
21132 	private template typesToSpecifier(T...) {
21133 		static if(is(CommonType!T == float))
21134 			enum typesToSpecifier = "f";
21135 		else static if(is(CommonType!T == int))
21136 			enum typesToSpecifier = "i";
21137 		else static if(is(CommonType!T == uint))
21138 			enum typesToSpecifier = "ui";
21139 		else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof);
21140 	}
21141 
21142 	private template genNames(size_t dim, size_t dim2 = 0) {
21143 		string helper() {
21144 			string s;
21145 			if(dim2) {
21146 				static if(__VERSION__ < 2102)
21147 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug
21148 				else
21149 					s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;";
21150 			} else {
21151 				if(dim > 0) s ~= "type x = 0;";
21152 				if(dim > 1) s ~= "type y = 0;";
21153 				if(dim > 2) s ~= "type z = 0;";
21154 				if(dim > 3) s ~= "type w = 0;";
21155 			}
21156 
21157 			s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }";
21158 			if(dim2)
21159 				s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }";
21160 
21161 			return s;
21162 		}
21163 
21164 		enum genNames = helper();
21165 	}
21166 
21167 	// there's vec, arrays of vec, mat, and arrays of mat
21168 	template opDispatch(string name)
21169 		if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat"))
21170 	{
21171 		static if(name[4] == 'x') {
21172 			enum dimX = cast(int) (name[3] - '0');
21173 			static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]);
21174 
21175 			enum dimY = cast(int) (name[5] - '0');
21176 			static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]);
21177 
21178 			enum isArray = name[$ - 1] == 'v';
21179 			enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $];
21180 			alias type = typeFromSpecifier!typeSpecifier;
21181 		} else {
21182 			enum dim = cast(int) (name[3] - '0');
21183 			static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]);
21184 			enum isArray = name[$ - 1] == 'v';
21185 			enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $];
21186 			alias type = typeFromSpecifier!typeSpecifier;
21187 		}
21188 
21189 		align(1)
21190 		struct opDispatch {
21191 			align(1):
21192 			static if(name[4] == 'x')
21193 				mixin(genNames!(dimX, dimY));
21194 			else
21195 				mixin(genNames!dim);
21196 
21197 			private void glUniform(OpenGlShader.Uniform assignTo) {
21198 				glUniform(assignTo.id);
21199 			}
21200 			private void glUniform(int assignTo) {
21201 				static if(name[4] == 'x') {
21202 					static if(name[3] == name[5]) {
21203 						// import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY);
21204 						mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]);
21205 					} else {
21206 						mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr);
21207 					}
21208 				} else
21209 					mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof);
21210 			}
21211 		}
21212 	}
21213 
21214 	auto vec(T...)(T members) {
21215 		return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members);
21216 	}
21217 }
21218 
21219 void checkGlError() {
21220 	auto error = glGetError();
21221 	int[] errors;
21222 	string[] errorStrings;
21223 	while(error != GL_NO_ERROR) {
21224 		errors ~= error;
21225 		switch(error) {
21226 			case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break;
21227 			case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break;
21228 			case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break;
21229 			case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break;
21230 			case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break;
21231 			case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break;
21232 			default: errorStrings ~= "idk";
21233 		}
21234 		error = glGetError();
21235 	}
21236 	if(errors.length)
21237 		throw ArsdException!"glGetError"(errors, errorStrings);
21238 }
21239 
21240 /++
21241 	A matrix for simple uses that easily integrates with [OpenGlShader].
21242 
21243 	Might not be useful to you since it only as some simple functions and
21244 	probably isn't that fast.
21245 
21246 	Note it uses an inline static array for its storage, so copying it
21247 	may be expensive.
21248 +/
21249 struct BasicMatrix(int columns, int rows, T = float) {
21250 	static import core.stdc.math;
21251 	static if(is(T == float)) {
21252 		alias cos = core.stdc.math.cosf;
21253 		alias sin = core.stdc.math.sinf;
21254 	} else {
21255 		alias cos = core.stdc.math.cos;
21256 		alias sin = core.stdc.math.sin;
21257 	}
21258 
21259 	T[columns * rows] data = 0.0;
21260 
21261 	/++
21262 
21263 	+/
21264 	this(T[columns * rows] data) {
21265 		this.data = data;
21266 	}
21267 
21268 	/++
21269 		Basic operations that operate *in place*.
21270 	+/
21271 	static if(columns == 4 && rows == 4)
21272 	void translate(T x, T y, T z) {
21273 		BasicMatrix m = [
21274 			1, 0, 0, x,
21275 			0, 1, 0, y,
21276 			0, 0, 1, z,
21277 			0, 0, 0, 1
21278 		];
21279 
21280 		this *= m;
21281 	}
21282 
21283 	/// ditto
21284 	static if(columns == 4 && rows == 4)
21285 	void scale(T x, T y, T z) {
21286 		BasicMatrix m = [
21287 			x, 0, 0, 0,
21288 			0, y, 0, 0,
21289 			0, 0, z, 0,
21290 			0, 0, 0, 1
21291 		];
21292 
21293 		this *= m;
21294 	}
21295 
21296 	/// ditto
21297 	static if(columns == 4 && rows == 4)
21298 	void rotateX(T theta) {
21299 		BasicMatrix m = [
21300 			1,          0,           0, 0,
21301 			0, cos(theta), -sin(theta), 0,
21302 			0, sin(theta),  cos(theta), 0,
21303 			0,          0,           0, 1
21304 		];
21305 
21306 		this *= m;
21307 	}
21308 
21309 	/// ditto
21310 	static if(columns == 4 && rows == 4)
21311 	void rotateY(T theta) {
21312 		BasicMatrix m = [
21313 			 cos(theta), 0,  sin(theta), 0,
21314 			          0, 1,           0, 0,
21315 			-sin(theta), 0,  cos(theta), 0,
21316 			          0, 0,           0, 1
21317 		];
21318 
21319 		this *= m;
21320 	}
21321 
21322 	/// ditto
21323 	static if(columns == 4 && rows == 4)
21324 	void rotateZ(T theta) {
21325 		BasicMatrix m = [
21326 			cos(theta), -sin(theta), 0, 0,
21327 			sin(theta),  cos(theta), 0, 0,
21328 			         0,           0, 1, 0,
21329 				 0,           0, 0, 1
21330 		];
21331 
21332 		this *= m;
21333 	}
21334 
21335 	/++
21336 
21337 	+/
21338 	static if(columns == rows)
21339 	static BasicMatrix identity() {
21340 		BasicMatrix m;
21341 		foreach(i; 0 .. columns)
21342 			m.data[0 + i + i * columns] = 1.0;
21343 		return m;
21344 	}
21345 
21346 	static if(columns == rows)
21347 	void loadIdentity() {
21348 		this = identity();
21349 	}
21350 
21351 	static if(columns == 4 && rows == 4)
21352 	static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) {
21353 		return BasicMatrix([
21354 			2/(r-l),       0,        0, -(r+l)/(r-l),
21355 			      0, 2/(t-b),        0, -(t+b)/(t-b),
21356 			      0,       0, -2/(f-n), -(f+n)/(f-n),
21357 			      0,       0,        0,            1
21358 		]);
21359 	}
21360 
21361 	static if(columns == 4 && rows == 4)
21362 	void loadOrtho(T l, T r, T b, T t, T n, T f) {
21363 		this = ortho(l, r, b, t, n, f);
21364 	}
21365 
21366 	void opOpAssign(string op : "+")(const BasicMatrix rhs) {
21367 		this.data[] += rhs.data;
21368 	}
21369 	void opOpAssign(string op : "-")(const BasicMatrix rhs) {
21370 		this.data[] -= rhs.data;
21371 	}
21372 	void opOpAssign(string op : "*")(const T rhs) {
21373 		this.data[] *= rhs;
21374 	}
21375 	void opOpAssign(string op : "/")(const T rhs) {
21376 		this.data[] /= rhs;
21377 	}
21378 	void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) {
21379 		static assert(columns == rhsRows);
21380 		auto multiplySize = columns;
21381 
21382 		auto tmp = this.data; // copy cuz it is a value type
21383 
21384 		int idx = 0;
21385 		foreach(r; 0 .. rows)
21386 		foreach(c; 0 .. columns) {
21387 			T sum = 0.0;
21388 
21389 			foreach(i; 0 .. multiplySize)
21390 				sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c];
21391 
21392 			tmp[idx++] = sum;
21393 		}
21394 
21395 		this.data = tmp;
21396 	}
21397 }
21398 
21399 unittest {
21400 	auto m = BasicMatrix!(2, 2)([
21401 		1, 2,
21402 		3, 4
21403 	]);
21404 
21405 	auto m2 = BasicMatrix!(2, 2)([
21406 		5, 6,
21407 		7, 8
21408 	]);
21409 
21410 	//import std.conv;
21411 	m *= m2;
21412 	assert(m.data == [
21413 		19, 22,
21414 		43, 50
21415 	]);//, to!string(m.data));
21416 }
21417 
21418 
21419 
21420 class GlObjectBase {
21421 	protected uint _vao;
21422 	protected uint _elementsCount;
21423 
21424 	protected uint element_buffer;
21425 
21426 	void gen() {
21427 		glGenVertexArrays(1, &_vao);
21428 	}
21429 
21430 	void bind() {
21431 		glBindVertexArray(_vao);
21432 	}
21433 
21434 	void dispose() {
21435 		glDeleteVertexArrays(1, &_vao);
21436 	}
21437 
21438 	void draw() {
21439 		bind();
21440 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
21441 		glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null);
21442 	}
21443 }
21444 
21445 /++
21446 
21447 +/
21448 class GlObject(T) : GlObjectBase {
21449 	protected uint VBO;
21450 
21451 	this(T[] arr, uint[] indices) {
21452 		gen();
21453 		bind();
21454 
21455 		glGenBuffers(1, &VBO);
21456 		glGenBuffers(1, &element_buffer);
21457 
21458 		glBindBuffer(GL_ARRAY_BUFFER, VBO);
21459 		glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW);
21460 
21461 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
21462 		glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
21463 		_elementsCount = cast(int) indices.length;
21464 
21465 		foreach(int idx, memberName; __traits(allMembers, T)) {
21466 			static if(memberName != "__ctor") {
21467 			static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) {
21468 				glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof);
21469 				glEnableVertexAttribArray(idx);
21470 			} else static assert(0); }
21471 		}
21472 	}
21473 
21474 	static string generateShaderDefinitions() {
21475 		string code;
21476 
21477 		foreach(idx, memberName; __traits(allMembers, T)) {
21478 			// never use stringof ladies and gents it has a LU thing at the end of it
21479 			static if(memberName != "__ctor")
21480 			code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n";
21481 		}
21482 
21483 		return code;
21484 	}
21485 }
21486 
21487 private string typeToGl(T)() {
21488 	static if(is(T == float[4]))
21489 		return "vec4";
21490 	else static if(is(T == float[3]))
21491 		return "vec3";
21492 	else static if(is(T == float[2]))
21493 		return "vec2";
21494 	else static assert(0, T.stringof);
21495 }
21496 
21497 
21498 }
21499 
21500 version(Emscripten) {
21501 
21502 } else version(linux) {
21503 	version(with_eventloop) {} else {
21504 		private int epollFd = -1;
21505 		void prepareEventLoop() {
21506 			if(epollFd != -1)
21507 				return; // already initialized, no need to do it again
21508 			import ep = core.sys.linux.epoll;
21509 
21510 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
21511 			if(epollFd == -1)
21512 				throw new Exception("epoll create failure");
21513 		}
21514 	}
21515 } else version(Posix) {
21516 	void prepareEventLoop() {}
21517 }
21518 
21519 version(X11) {
21520 	import core.stdc.locale : LC_ALL; // rdmd fix
21521 	__gshared bool sdx_isUTF8Locale;
21522 
21523 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
21524 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
21525 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
21526 	// anal magic is here. I (Ketmar) hope you like it.
21527 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
21528 	// always return correct unicode symbols. The detection is here 'cause user can change locale
21529 	// later.
21530 
21531 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
21532 	shared static this () @system {
21533 		if(!librariesSuccessfullyLoaded)
21534 			return;
21535 
21536 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
21537 
21538 		// this doesn't hurt; it may add some locking, but the speed is still
21539 		// allows doing 60 FPS videogames; also, ignore the result, as most
21540 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
21541 		// never seen this failing).
21542 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
21543 
21544 		setlocale(LC_ALL, "");
21545 		// check if out locale is UTF-8
21546 		auto lct = setlocale(LC_CTYPE, null);
21547 		if (lct is null) {
21548 			sdx_isUTF8Locale = false;
21549 		} else {
21550 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
21551 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
21552 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
21553 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
21554 				{
21555 					sdx_isUTF8Locale = true;
21556 					break;
21557 				}
21558 			}
21559 		}
21560 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
21561 	}
21562 }
21563 
21564 /++
21565 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21566 
21567 	History:
21568 		Added February 19, 2021
21569 +/
21570 /// Group: drag_and_drop
21571 interface DropHandler {
21572 	/++
21573 		Called when the drag enters the handler's area.
21574 	+/
21575 	DragAndDropAction dragEnter(DropPackage*);
21576 	/++
21577 		Called when the drag leaves the handler's area or is
21578 		cancelled. You should free your resources when this is called.
21579 	+/
21580 	void dragLeave();
21581 	/++
21582 		Called continually as the drag moves over the handler's area.
21583 
21584 		Returns: feedback to the dragger
21585 	+/
21586 	DropParameters dragOver(Point pt);
21587 	/++
21588 		The user dropped the data and you should process it now. You can
21589 		access the data through the given [DropPackage].
21590 	+/
21591 	void drop(scope DropPackage*);
21592 	/++
21593 		Called when the drop is complete. You should free whatever temporary
21594 		resources you were using. It is often reasonable to simply forward
21595 		this call to [dragLeave].
21596 	+/
21597 	void finish();
21598 
21599 	/++
21600 		Parameters returned by [DropHandler.drop].
21601 	+/
21602 	static struct DropParameters {
21603 		/++
21604 			Acceptable action over this area.
21605 		+/
21606 		DragAndDropAction action;
21607 		/++
21608 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
21609 
21610 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
21611 		+/
21612 		Rectangle consistentWithin;
21613 	}
21614 }
21615 
21616 /++
21617 	History:
21618 		Added February 19, 2021
21619 +/
21620 /// Group: drag_and_drop
21621 enum DragAndDropAction {
21622 	none = 0,
21623 	copy,
21624 	move,
21625 	link,
21626 	ask,
21627 	custom
21628 }
21629 
21630 /++
21631 	An opaque structure representing dropped data. It contains
21632 	private, platform-specific data that your `drop` function
21633 	should simply forward.
21634 
21635 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21636 
21637 	History:
21638 		Added February 19, 2021
21639 +/
21640 /// Group: drag_and_drop
21641 struct DropPackage {
21642 	/++
21643 		Lists the available formats as magic numbers. You should compare these
21644 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
21645 		understand the passed data.
21646 	+/
21647 	DraggableData.FormatId[] availableFormats() {
21648 		version(X11) {
21649 			return xFormats;
21650 		} else version(Windows) {
21651 			if(pDataObj is null)
21652 				return null;
21653 
21654 			typeof(return) ret;
21655 
21656 			IEnumFORMATETC ef;
21657 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
21658 				FORMATETC fmt;
21659 				ULONG fetched;
21660 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
21661 					if(fetched == 0)
21662 						break;
21663 
21664 					if(fmt.lindex != -1)
21665 						continue;
21666 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
21667 						continue;
21668 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
21669 						continue;
21670 
21671 					ret ~= fmt.cfFormat;
21672 				}
21673 			}
21674 
21675 			return ret;
21676 		} else throw new NotYetImplementedException();
21677 	}
21678 
21679 	/++
21680 		Gets data from the drop and optionally accepts it.
21681 
21682 		Returns:
21683 			void because the data is fed asynchronously through the `dg` parameter.
21684 
21685 		Params:
21686 			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.
21687 
21688 			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.
21689 
21690 			Calling `getData` again after accepting a drop is not permitted.
21691 
21692 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
21693 
21694 			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.
21695 
21696 		Throws:
21697 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
21698 
21699 		History:
21700 			Included in first release of [DropPackage].
21701 	+/
21702 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
21703 		version(X11) {
21704 
21705 			auto display = XDisplayConnection.get();
21706 			auto selectionAtom = GetAtom!"XdndSelection"(display);
21707 			auto best = format;
21708 
21709 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
21710 
21711 				XDisplay* display;
21712 				Atom selectionAtom;
21713 				DraggableData.FormatId best;
21714 				DraggableData.FormatId format;
21715 				void delegate(scope ubyte[] data) dg;
21716 				DragAndDropAction acceptedAction;
21717 				Window sourceWindow;
21718 				SimpleWindow win;
21719 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
21720 					this.display = display;
21721 					this.win = win;
21722 					this.sourceWindow = sourceWindow;
21723 					this.format = format;
21724 					this.selectionAtom = selectionAtom;
21725 					this.best = best;
21726 					this.dg = dg;
21727 					this.acceptedAction = acceptedAction;
21728 				}
21729 
21730 
21731 				mixin X11GetSelectionHandler_Basics;
21732 
21733 				void handleData(Atom target, in ubyte[] data) {
21734 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
21735 
21736 					dg(cast(ubyte[]) data);
21737 
21738 					if(acceptedAction != DragAndDropAction.none) {
21739 						auto display = XDisplayConnection.get;
21740 
21741 						XClientMessageEvent xclient;
21742 
21743 						xclient.type = EventType.ClientMessage;
21744 						xclient.window = sourceWindow;
21745 						xclient.message_type = GetAtom!"XdndFinished"(display);
21746 						xclient.format = 32;
21747 						xclient.data.l[0] = win.impl.window;
21748 						xclient.data.l[1] = 1; // drop successful
21749 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
21750 
21751 						XSendEvent(
21752 							display,
21753 							sourceWindow,
21754 							false,
21755 							EventMask.NoEventMask,
21756 							cast(XEvent*) &xclient
21757 						);
21758 
21759 						XFlush(display);
21760 					}
21761 				}
21762 
21763 				Atom findBestFormat(Atom[] answer) {
21764 					Atom best = None;
21765 					foreach(option; answer) {
21766 						if(option == format) {
21767 							best = option;
21768 							break;
21769 						}
21770 						/*
21771 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
21772 							best = option;
21773 							break;
21774 						} else if(option == XA_STRING) {
21775 							best = option;
21776 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
21777 							best = option;
21778 						}
21779 						*/
21780 					}
21781 					return best;
21782 				}
21783 			}
21784 
21785 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
21786 
21787 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
21788 
21789 		} else version(Windows) {
21790 
21791 			// clean up like DragLeave
21792 			// pass effect back up
21793 
21794 			FORMATETC t;
21795 			assert(format >= 0 && format <= ushort.max);
21796 			t.cfFormat = cast(ushort) format;
21797 			t.lindex = -1;
21798 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
21799 			t.tymed = TYMED.TYMED_HGLOBAL;
21800 
21801 			STGMEDIUM m;
21802 
21803 			if(pDataObj.GetData(&t, &m) != S_OK) {
21804 				// fail
21805 			} else {
21806 				// succeed, take the data and clean up
21807 
21808 				// FIXME: ensure it is legit HGLOBAL
21809 				auto handle = m.hGlobal;
21810 
21811 				if(handle) {
21812 					auto sz = GlobalSize(handle);
21813 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
21814 						scope(exit) GlobalUnlock(handle);
21815 						scope(exit) GlobalFree(handle);
21816 
21817 						auto data = ptr[0 .. sz];
21818 
21819 						dg(data);
21820 					}
21821 				}
21822 			}
21823 		}
21824 	}
21825 
21826 	private:
21827 
21828 	version(X11) {
21829 		SimpleWindow win;
21830 		Window sourceWindow;
21831 		Time dataTimestamp;
21832 
21833 		Atom[] xFormats;
21834 	}
21835 	version(Windows) {
21836 		IDataObject pDataObj;
21837 	}
21838 }
21839 
21840 /++
21841 	A generic helper base class for making a drop handler with a preference list of custom types.
21842 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
21843 	droppers too.
21844 
21845 	It assumes the whole window it used, but you can subclass to change that.
21846 
21847 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21848 
21849 	History:
21850 		Added February 19, 2021
21851 +/
21852 /// Group: drag_and_drop
21853 class GenericDropHandlerBase : DropHandler {
21854 	// no fancy state here so no need to do anything here
21855 	void finish() { }
21856 	void dragLeave() { }
21857 
21858 	private DragAndDropAction acceptedAction;
21859 	private DraggableData.FormatId acceptedFormat;
21860 	private void delegate(scope ubyte[]) acceptedHandler;
21861 
21862 	struct FormatHandler {
21863 		DraggableData.FormatId format;
21864 		void delegate(scope ubyte[]) handler;
21865 	}
21866 
21867 	protected abstract FormatHandler[] formatHandlers();
21868 
21869 	DragAndDropAction dragEnter(DropPackage* pkg) {
21870 		debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
21871 		foreach(fmt; formatHandlers())
21872 		foreach(f; pkg.availableFormats())
21873 			if(f == fmt.format) {
21874 				acceptedFormat = f;
21875 				acceptedHandler = fmt.handler;
21876 				return acceptedAction = DragAndDropAction.copy;
21877 			}
21878 		return acceptedAction = DragAndDropAction.none;
21879 	}
21880 	DropParameters dragOver(Point pt) {
21881 		return DropParameters(acceptedAction);
21882 	}
21883 
21884 	void drop(scope DropPackage* dropPackage) {
21885 		if(!acceptedFormat || acceptedHandler is null) {
21886 			debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
21887 			return; // prolly shouldn't happen anyway...
21888 		}
21889 
21890 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
21891 	}
21892 }
21893 
21894 /++
21895 	A simple handler for making your window accept drops of plain text.
21896 
21897 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21898 
21899 	History:
21900 		Added February 22, 2021
21901 +/
21902 /// Group: drag_and_drop
21903 class TextDropHandler : GenericDropHandlerBase {
21904 	private void delegate(in char[] text) dg;
21905 
21906 	/++
21907 
21908 	+/
21909 	this(void delegate(in char[] text) dg) {
21910 		this.dg = dg;
21911 	}
21912 
21913 	protected override FormatHandler[] formatHandlers() {
21914 		version(X11)
21915 			return [
21916 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
21917 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
21918 			];
21919 		else version(Windows)
21920 			return [
21921 				FormatHandler(CF_UNICODETEXT, &translator),
21922 			];
21923 		else throw new NotYetImplementedException();
21924 	}
21925 
21926 	private void translator(scope ubyte[] data) {
21927 		version(X11)
21928 			dg(cast(char[]) data);
21929 		else version(Windows)
21930 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
21931 	}
21932 }
21933 
21934 /++
21935 	A simple handler for making your window accept drops of files, issued to you as file names.
21936 
21937 	$(PITFALL This is not yet stable and may break in future versions without notice.)
21938 
21939 	History:
21940 		Added February 22, 2021
21941 +/
21942 /// Group: drag_and_drop
21943 
21944 class FilesDropHandler : GenericDropHandlerBase {
21945 	private void delegate(in char[][]) dg;
21946 
21947 	/++
21948 
21949 	+/
21950 	this(void delegate(in char[][] fileNames) dg) {
21951 		this.dg = dg;
21952 	}
21953 
21954 	protected override FormatHandler[] formatHandlers() {
21955 		version(X11)
21956 			return [
21957 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
21958 			];
21959 		else version(Windows)
21960 			return [
21961 				FormatHandler(CF_HDROP, &translator),
21962 			];
21963 		else throw new NotYetImplementedException();
21964 	}
21965 
21966 	private void translator(scope ubyte[] data) @system {
21967 		version(X11) {
21968 			char[] listString = cast(char[]) data;
21969 			char[][16] buffer;
21970 			int count;
21971 			char[][] result = buffer[];
21972 
21973 			void commit(char[] s) {
21974 				if(count == result.length)
21975 					result.length += 16;
21976 				if(s.length > 7 && s[0 ..7] == "file://")
21977 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
21978 				result[count++] = s;
21979 			}
21980 
21981 			size_t last;
21982 			foreach(idx, char c; listString) {
21983 				if(c == '\n') {
21984 					commit(listString[last .. idx - 1]); // a \r
21985 					last = idx + 1; // a \n
21986 				}
21987 			}
21988 
21989 			if(last < listString.length) {
21990 				commit(listString[last .. $]);
21991 			}
21992 
21993 			// FIXME: they are uris now, should I translate it to local file names?
21994 			// of course the host name is supposed to be there cuz of X rokking...
21995 
21996 			dg(result[0 .. count]);
21997 		} else version(Windows) {
21998 
21999 			static struct DROPFILES {
22000 				DWORD pFiles;
22001 				POINT pt;
22002 				BOOL  fNC;
22003 				BOOL  fWide;
22004 			}
22005 
22006 
22007 			const(char)[][16] buffer;
22008 			int count;
22009 			const(char)[][] result = buffer[];
22010 			size_t last;
22011 
22012 			void commitA(in char[] stuff) {
22013 				if(count == result.length)
22014 					result.length += 16;
22015 				result[count++] = stuff;
22016 			}
22017 
22018 			void commitW(in wchar[] stuff) {
22019 				commitA(makeUtf8StringFromWindowsString(stuff));
22020 			}
22021 
22022 			void magic(T)(T chars) {
22023 				size_t idx;
22024 				while(chars[idx]) {
22025 					last = idx;
22026 					while(chars[idx]) {
22027 						idx++;
22028 					}
22029 					static if(is(T == char*))
22030 						commitA(chars[last .. idx]);
22031 					else
22032 						commitW(chars[last .. idx]);
22033 					idx++;
22034 				}
22035 			}
22036 
22037 			auto df = cast(DROPFILES*) data.ptr;
22038 			if(df.fWide) {
22039 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
22040 				magic(chars);
22041 			} else {
22042 				char* chars = cast(char*) (data.ptr + df.pFiles);
22043 				magic(chars);
22044 			}
22045 			dg(result[0 .. count]);
22046 		}
22047 		else throw new NotYetImplementedException();
22048 	}
22049 }
22050 
22051 /++
22052 	Interface to describe data being dragged. See also [draggable] helper function.
22053 
22054 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22055 
22056 	History:
22057 		Added February 19, 2021
22058 +/
22059 interface DraggableData {
22060 	version(X11)
22061 		alias FormatId = Atom;
22062 	else
22063 		alias FormatId = uint;
22064 	/++
22065 		Gets the platform-specific FormatId associated with the given named format.
22066 
22067 		This may be a MIME type, but may also be other various strings defined by the
22068 		programs you want to interoperate with.
22069 
22070 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
22071 		and convert it to some particular type for you.
22072 	+/
22073 	static FormatId getFormatId(string name)() {
22074 		version(X11)
22075 			return GetAtom!name(XDisplayConnection.get);
22076 		else version(Windows) {
22077 			static UINT cache;
22078 			if(!cache)
22079 				cache = RegisterClipboardFormatA(name);
22080 			return cache;
22081 		} else
22082 			throw new NotYetImplementedException();
22083 	}
22084 
22085 	/++
22086 		Looks up a string to represent the name for the given format, if there is one.
22087 
22088 		You should avoid using this function because it is slow. It is provided more for
22089 		debugging than for primary use.
22090 	+/
22091 	static string getFormatName(FormatId format) {
22092 		version(X11) {
22093 			if(format == 0)
22094 				return "None";
22095 			else
22096 				return getAtomName(format, XDisplayConnection.get);
22097 		} else version(Windows) {
22098 			switch(format) {
22099 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
22100 				case CF_DIBV5: return "CF_DIBV5";
22101 				case CF_RIFF: return "CF_RIFF";
22102 				case CF_WAVE: return "CF_WAVE";
22103 				case CF_HDROP: return "CF_HDROP";
22104 				default:
22105 					char[1024] name;
22106 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
22107 					return name[0 .. count].idup;
22108 			}
22109 		} else throw new NotYetImplementedException();
22110 	}
22111 
22112 	FormatId[] availableFormats();
22113 	// Return the slice of data you filled, empty slice if done.
22114 	// this is to support the incremental thing
22115 	ubyte[] getData(FormatId format, return scope ubyte[] data);
22116 
22117 	size_t dataLength(FormatId format);
22118 }
22119 
22120 /++
22121 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22122 
22123 	History:
22124 		Added February 19, 2021
22125 +/
22126 DraggableData draggable(string s) {
22127 	version(X11)
22128 	return new class X11SetSelectionHandler_Text, DraggableData {
22129 		this() {
22130 			super(s);
22131 		}
22132 
22133 		override FormatId[] availableFormats() {
22134 			return X11SetSelectionHandler_Text.availableFormats();
22135 		}
22136 
22137 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
22138 			return X11SetSelectionHandler_Text.getData(format, data);
22139 		}
22140 
22141 		size_t dataLength(FormatId format) {
22142 			return s.length;
22143 		}
22144 	};
22145 	else version(Windows)
22146 	return new class DraggableData {
22147 		FormatId[] availableFormats() {
22148 			return [CF_UNICODETEXT];
22149 		}
22150 
22151 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
22152 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
22153 		}
22154 
22155 		size_t dataLength(FormatId format) {
22156 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
22157 		}
22158 	};
22159 	else
22160 	throw new NotYetImplementedException();
22161 }
22162 
22163 /++
22164 	$(PITFALL This is not yet stable and may break in future versions without notice.)
22165 
22166 	History:
22167 		Added February 19, 2021
22168 +/
22169 /// Group: drag_and_drop
22170 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
22171 in {
22172 	assert(window !is null);
22173 	assert(handler !is null);
22174 }
22175 do
22176 {
22177 	version(X11) {
22178 		auto sh = cast(X11SetSelectionHandler) handler;
22179 		if(sh is null) {
22180 			// gotta make my own adapter.
22181 			sh = new class X11SetSelectionHandler {
22182 				mixin X11SetSelectionHandler_Basics;
22183 
22184 				Atom[] availableFormats() { return handler.availableFormats(); }
22185 				ubyte[] getData(Atom format, return scope ubyte[] data) {
22186 					return handler.getData(format, data);
22187 				}
22188 
22189 				// since the drop selection is only ever used once it isn't important
22190 				// to reset it.
22191 				void done() {}
22192 			};
22193 		}
22194 		return doDragDropX11(window, sh, action);
22195 	} else version(Windows) {
22196 		return doDragDropWindows(window, handler, action);
22197 	} else throw new NotYetImplementedException();
22198 }
22199 
22200 version(Windows)
22201 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
22202 	IDataObject obj = new class IDataObject {
22203 		ULONG refCount;
22204 		ULONG AddRef() {
22205 			return ++refCount;
22206 		}
22207 		ULONG Release() {
22208 			return --refCount;
22209 		}
22210 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22211 			if (IID_IUnknown == *riid) {
22212 				*ppv = cast(void*) cast(IUnknown) this;
22213 			}
22214 			else if (IID_IDataObject == *riid) {
22215 				*ppv = cast(void*) cast(IDataObject) this;
22216 			}
22217 			else {
22218 				*ppv = null;
22219 				return E_NOINTERFACE;
22220 			}
22221 
22222 			AddRef();
22223 			return NOERROR;
22224 		}
22225 
22226 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
22227 			//  writeln("Advise");
22228 			return E_NOTIMPL;
22229 		}
22230 		HRESULT DUnadvise(DWORD dwConnection) {
22231 			return E_NOTIMPL;
22232 		}
22233 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
22234 			//  writeln("EnumDAdvise");
22235 			return OLE_E_ADVISENOTSUPPORTED;
22236 		}
22237 		// tell what formats it supports
22238 
22239 		FORMATETC[] types;
22240 		this() {
22241 			FORMATETC t;
22242 			foreach(ty; handler.availableFormats()) {
22243 				assert(ty <= ushort.max && ty >= 0);
22244 				t.cfFormat = cast(ushort) ty;
22245 				t.lindex = -1;
22246 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
22247 				t.tymed = TYMED.TYMED_HGLOBAL;
22248 			}
22249 			types ~= t;
22250 		}
22251 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
22252 			if(dwDirection == DATADIR.DATADIR_GET) {
22253 				*ppenumFormatEtc = new class IEnumFORMATETC {
22254 					ULONG refCount;
22255 					ULONG AddRef() {
22256 						return ++refCount;
22257 					}
22258 					ULONG Release() {
22259 						return --refCount;
22260 					}
22261 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22262 						if (IID_IUnknown == *riid) {
22263 							*ppv = cast(void*) cast(IUnknown) this;
22264 						}
22265 						else if (IID_IEnumFORMATETC == *riid) {
22266 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
22267 						}
22268 						else {
22269 							*ppv = null;
22270 							return E_NOINTERFACE;
22271 						}
22272 
22273 						AddRef();
22274 						return NOERROR;
22275 					}
22276 
22277 
22278 					int pos;
22279 					this() {
22280 						pos = 0;
22281 					}
22282 
22283 					HRESULT Clone(IEnumFORMATETC* ppenum) {
22284 						// writeln("clone");
22285 						return E_NOTIMPL; // FIXME
22286 					}
22287 
22288 					// Caller is responsible for freeing memory
22289 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
22290 						// fetched may be null if celt is one
22291 						if(celt != 1)
22292 							return E_NOTIMPL; // FIXME
22293 
22294 						if(celt + pos > types.length)
22295 							return S_FALSE;
22296 
22297 						*rgelt = types[pos++];
22298 
22299 						if(pceltFetched !is null)
22300 							*pceltFetched = 1;
22301 
22302 						// writeln("ok celt ", celt);
22303 						return S_OK;
22304 					}
22305 
22306 					HRESULT Reset() {
22307 						pos = 0;
22308 						return S_OK;
22309 					}
22310 
22311 					HRESULT Skip(ULONG celt) {
22312 						if(celt + pos <= types.length) {
22313 							pos += celt;
22314 							return S_OK;
22315 						}
22316 						return S_FALSE;
22317 					}
22318 				};
22319 
22320 				return S_OK;
22321 			} else
22322 				return E_NOTIMPL;
22323 		}
22324 		// given a format, return the format you'd prefer to use cuz it is identical
22325 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
22326 			// FIXME: prolly could be better but meh
22327 			// writeln("gcf: ", *pformatectIn);
22328 			*pformatetcOut = *pformatectIn;
22329 			return S_OK;
22330 		}
22331 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22332 			foreach(ty; types) {
22333 				if(ty == *pformatetcIn) {
22334 					auto format = ty.cfFormat;
22335 					// writeln("A: ", *pformatetcIn, "\nB: ", ty);
22336 					STGMEDIUM medium;
22337 					medium.tymed = TYMED.TYMED_HGLOBAL;
22338 
22339 					auto sz = handler.dataLength(format);
22340 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
22341 					if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError());
22342 					if(auto data = cast(wchar*) GlobalLock(handle)) {
22343 						auto slice = data[0 .. sz];
22344 						scope(exit)
22345 							GlobalUnlock(handle);
22346 
22347 						handler.getData(format, cast(ubyte[]) slice[]);
22348 					}
22349 
22350 
22351 					medium.hGlobal = handle; // FIXME
22352 					*pmedium = medium;
22353 					return S_OK;
22354 				}
22355 			}
22356 			return DV_E_FORMATETC;
22357 		}
22358 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
22359 			// writeln("GDH: ", *pformatetcIn);
22360 			return E_NOTIMPL; // FIXME
22361 		}
22362 		HRESULT QueryGetData(FORMATETC* pformatetc) {
22363 			auto search = *pformatetc;
22364 			search.tymed &= TYMED.TYMED_HGLOBAL;
22365 			foreach(ty; types)
22366 				if(ty == search) {
22367 					// writeln("QueryGetData ", search, " ", types[0]);
22368 					return S_OK;
22369 				}
22370 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
22371 				//writeln("QueryGetData FALSE ", search, " ", types[0]);
22372 			}
22373 			return S_FALSE;
22374 		}
22375 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
22376 			//  writeln("SetData: ");
22377 			return E_NOTIMPL;
22378 		}
22379 	};
22380 
22381 
22382 	IDropSource src = new class IDropSource {
22383 		ULONG refCount;
22384 		ULONG AddRef() {
22385 			return ++refCount;
22386 		}
22387 		ULONG Release() {
22388 			return --refCount;
22389 		}
22390 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22391 			if (IID_IUnknown == *riid) {
22392 				*ppv = cast(void*) cast(IUnknown) this;
22393 			}
22394 			else if (IID_IDropSource == *riid) {
22395 				*ppv = cast(void*) cast(IDropSource) this;
22396 			}
22397 			else {
22398 				*ppv = null;
22399 				return E_NOINTERFACE;
22400 			}
22401 
22402 			AddRef();
22403 			return NOERROR;
22404 		}
22405 
22406 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
22407 			if(fEscapePressed)
22408 				return DRAGDROP_S_CANCEL;
22409 			if(!(grfKeyState & MK_LBUTTON))
22410 				return DRAGDROP_S_DROP;
22411 			return S_OK;
22412 		}
22413 
22414 		int GiveFeedback(uint dwEffect) {
22415 			return DRAGDROP_S_USEDEFAULTCURSORS;
22416 		}
22417 	};
22418 
22419 	DWORD effect;
22420 
22421 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
22422 
22423 	DROPEFFECT de = win32DragAndDropAction(action);
22424 
22425 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
22426 	// but still prolly a FIXME
22427 
22428 	auto ret = DoDragDrop(obj, src, de, &effect);
22429 	/+
22430 	if(ret == DRAGDROP_S_DROP)
22431 		writeln("drop ", effect);
22432 	else if(ret == DRAGDROP_S_CANCEL)
22433 		writeln("cancel");
22434 	else if(ret == S_OK)
22435 		writeln("ok");
22436 	else writeln(ret);
22437 	+/
22438 
22439 	return ret;
22440 }
22441 
22442 version(Windows)
22443 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
22444 	DROPEFFECT de;
22445 
22446 	with(DragAndDropAction)
22447 	with(DROPEFFECT)
22448 	final switch(action) {
22449 		case none: de = DROPEFFECT_NONE; break;
22450 		case copy: de = DROPEFFECT_COPY; break;
22451 		case move: de = DROPEFFECT_MOVE; break;
22452 		case link: de = DROPEFFECT_LINK; break;
22453 		case ask: throw new Exception("ask not implemented yet");
22454 		case custom: throw new Exception("custom not implemented yet");
22455 	}
22456 
22457 	return de;
22458 }
22459 
22460 
22461 /++
22462 	History:
22463 		Added February 19, 2021
22464 +/
22465 /// Group: drag_and_drop
22466 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
22467 	version(X11) {
22468 		auto display = XDisplayConnection.get;
22469 
22470 		Atom atom = 5; // right???
22471 
22472 		XChangeProperty(
22473 			display,
22474 			window.impl.window,
22475 			GetAtom!"XdndAware"(display),
22476 			XA_ATOM,
22477 			32 /* bits */,
22478 			PropModeReplace,
22479 			&atom,
22480 			1);
22481 
22482 		window.dropHandler = handler;
22483 	} else version(Windows) {
22484 
22485 		initDnd();
22486 
22487 		auto dropTarget = new class (handler) IDropTarget {
22488 			DropHandler handler;
22489 			this(DropHandler handler) {
22490 				this.handler = handler;
22491 			}
22492 			ULONG refCount;
22493 			ULONG AddRef() {
22494 				return ++refCount;
22495 			}
22496 			ULONG Release() {
22497 				return --refCount;
22498 			}
22499 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
22500 				if (IID_IUnknown == *riid) {
22501 					*ppv = cast(void*) cast(IUnknown) this;
22502 				}
22503 				else if (IID_IDropTarget == *riid) {
22504 					*ppv = cast(void*) cast(IDropTarget) this;
22505 				}
22506 				else {
22507 					*ppv = null;
22508 					return E_NOINTERFACE;
22509 				}
22510 
22511 				AddRef();
22512 				return NOERROR;
22513 			}
22514 
22515 
22516 			// ///////////////////
22517 
22518 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22519 				DropPackage dropPackage = DropPackage(pDataObj);
22520 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
22521 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
22522 			}
22523 
22524 			HRESULT DragLeave() {
22525 				handler.dragLeave();
22526 				// release the IDataObject if needed
22527 				return S_OK;
22528 			}
22529 
22530 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22531 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
22532 
22533 				*pdwEffect = win32DragAndDropAction(res.action);
22534 				// same as DragEnter basically
22535 				return S_OK;
22536 			}
22537 
22538 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
22539 				DropPackage pkg = DropPackage(pDataObj);
22540 				handler.drop(&pkg);
22541 
22542 				return S_OK;
22543 			}
22544 		};
22545 		// Windows can hold on to the handler and try to call it
22546 		// during which time the GC can't see it. so important to
22547 		// manually manage this. At some point i'll FIXME and make
22548 		// all my com instances manually managed since they supposed
22549 		// to respect the refcount.
22550 		import core.memory;
22551 		GC.addRoot(cast(void*) dropTarget);
22552 
22553 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
22554 			throw new WindowsApiException("RegisterDragDrop", GetLastError());
22555 
22556 		window.dropHandler = handler;
22557 	} else throw new NotYetImplementedException();
22558 }
22559 
22560 
22561 
22562 static if(UsingSimpledisplayX11) {
22563 
22564 enum _NET_WM_STATE_ADD = 1;
22565 enum _NET_WM_STATE_REMOVE = 0;
22566 enum _NET_WM_STATE_TOGGLE = 2;
22567 
22568 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
22569 void demandAttention(SimpleWindow window, bool needs = true) {
22570 	demandAttention(window.impl.window, needs);
22571 }
22572 
22573 /// ditto
22574 void demandAttention(Window window, bool needs = true) {
22575 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
22576 }
22577 
22578 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
22579 	auto display = XDisplayConnection.get();
22580 	if(atom == None)
22581 		return; // non-failure error
22582 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
22583 
22584 	XClientMessageEvent xclient;
22585 
22586 	xclient.type = EventType.ClientMessage;
22587 	xclient.window = window;
22588 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
22589 	xclient.format = 32;
22590 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
22591 	xclient.data.l[1] = atom;
22592 	xclient.data.l[2] = atom2;
22593 	xclient.data.l[3] = 1;
22594 	// [3] == source. 0 == unknown, 1 == app, 2 == else
22595 
22596 	XSendEvent(
22597 		display,
22598 		RootWindow(display, DefaultScreen(display)),
22599 		false,
22600 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
22601 		cast(XEvent*) &xclient
22602 	);
22603 
22604 	/+
22605 	XChangeProperty(
22606 		display,
22607 		window.impl.window,
22608 		GetAtom!"_NET_WM_STATE"(display),
22609 		XA_ATOM,
22610 		32 /* bits */,
22611 		PropModeAppend,
22612 		&atom,
22613 		1);
22614 	+/
22615 }
22616 
22617 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
22618 	Atom actionAtom;
22619 	with(DragAndDropAction)
22620 	final switch(action) {
22621 		case none: actionAtom = None; break;
22622 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
22623 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
22624 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
22625 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
22626 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
22627 	}
22628 
22629 	return actionAtom;
22630 }
22631 
22632 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
22633 	// FIXME: I need to show user feedback somehow.
22634 	auto display = XDisplayConnection.get;
22635 
22636 	auto actionAtom = dndActionAtom(display, action);
22637 	assert(actionAtom, "Don't use action none to accept a drop");
22638 
22639 	setX11Selection!"XdndSelection"(window, handler, null);
22640 
22641 	auto oldKeyHandler = window.handleKeyEvent;
22642 	scope(exit) window.handleKeyEvent = oldKeyHandler;
22643 
22644 	auto oldCharHandler = window.handleCharEvent;
22645 	scope(exit) window.handleCharEvent = oldCharHandler;
22646 
22647 	auto oldMouseHandler = window.handleMouseEvent;
22648 	scope(exit) window.handleMouseEvent = oldMouseHandler;
22649 
22650 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
22651 
22652 	import core.sys.posix.sys.time;
22653 	timeval tv;
22654 	gettimeofday(&tv, null);
22655 
22656 	Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 );
22657 
22658 	Time lastMouseTimestamp;
22659 
22660 	bool dnding = true;
22661 	Window lastIn = None;
22662 
22663 	void leave() {
22664 		if(lastIn == None)
22665 			return;
22666 
22667 		XEvent ev;
22668 		ev.xclient.type = EventType.ClientMessage;
22669 		ev.xclient.window = lastIn;
22670 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
22671 		ev.xclient.format = 32;
22672 		ev.xclient.data.l[0] = window.impl.window;
22673 
22674 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22675 		XFlush(display);
22676 
22677 		lastIn = None;
22678 	}
22679 
22680 	void enter(Window w) {
22681 		assert(lastIn == None);
22682 
22683 		lastIn = w;
22684 
22685 		XEvent ev;
22686 		ev.xclient.type = EventType.ClientMessage;
22687 		ev.xclient.window = lastIn;
22688 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
22689 		ev.xclient.format = 32;
22690 		ev.xclient.data.l[0] = window.impl.window;
22691 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
22692 
22693 		auto types = handler.availableFormats();
22694 		assert(types.length > 0);
22695 
22696 		ev.xclient.data.l[2] = types[0];
22697 		if(types.length > 1)
22698 			ev.xclient.data.l[3] = types[1];
22699 		if(types.length > 2)
22700 			ev.xclient.data.l[4] = types[2];
22701 
22702 		// FIXME: other types?!?!? and make sure we skip TARGETS
22703 
22704 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22705 		XFlush(display);
22706 	}
22707 
22708 	void position(int rootX, int rootY) {
22709 		assert(lastIn != None);
22710 
22711 		XEvent ev;
22712 		ev.xclient.type = EventType.ClientMessage;
22713 		ev.xclient.window = lastIn;
22714 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
22715 		ev.xclient.format = 32;
22716 		ev.xclient.data.l[0] = window.impl.window;
22717 		ev.xclient.data.l[1] = 0; // reserved
22718 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
22719 		ev.xclient.data.l[3] = dataTimestamp;
22720 		ev.xclient.data.l[4] = actionAtom;
22721 
22722 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22723 		XFlush(display);
22724 
22725 	}
22726 
22727 	void drop() {
22728 		XEvent ev;
22729 		ev.xclient.type = EventType.ClientMessage;
22730 		ev.xclient.window = lastIn;
22731 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
22732 		ev.xclient.format = 32;
22733 		ev.xclient.data.l[0] = window.impl.window;
22734 		ev.xclient.data.l[1] = 0; // reserved
22735 		ev.xclient.data.l[2] = dataTimestamp;
22736 
22737 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
22738 		XFlush(display);
22739 
22740 		lastIn = None;
22741 		dnding = false;
22742 	}
22743 
22744 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
22745 	// but idk if i should...
22746 
22747 	window.setEventHandlers(
22748 		delegate(KeyEvent ev) {
22749 			if(ev.pressed == true && ev.key == Key.Escape) {
22750 				// cancel
22751 				dnding = false;
22752 			}
22753 		},
22754 		delegate(MouseEvent ev) {
22755 			if(ev.timestamp < lastMouseTimestamp)
22756 				return;
22757 
22758 			lastMouseTimestamp = ev.timestamp;
22759 
22760 			if(ev.type == MouseEventType.motion) {
22761 				auto display = XDisplayConnection.get;
22762 				auto root = RootWindow(display, DefaultScreen(display));
22763 
22764 				Window topWindow;
22765 				int rootX, rootY;
22766 
22767 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
22768 
22769 				if(topWindow == None)
22770 					return;
22771 
22772 				top:
22773 				if(auto result = topWindow in eligibility) {
22774 					auto dropWindow = *result;
22775 					if(dropWindow == None) {
22776 						leave();
22777 						return;
22778 					}
22779 
22780 					if(dropWindow != lastIn) {
22781 						leave();
22782 						enter(dropWindow);
22783 						position(rootX, rootY);
22784 					} else {
22785 						position(rootX, rootY);
22786 					}
22787 				} else {
22788 					// determine eligibility
22789 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
22790 					if(data.length == 1) {
22791 						// in case there is no WM or it isn't reparenting
22792 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
22793 					} else {
22794 
22795 						Window tryScanChildren(Window search, int maxRecurse) {
22796 							// could be reparenting window manager, so gotta check the next few children too
22797 							Window child;
22798 							int x;
22799 							int y;
22800 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
22801 
22802 							if(child == None)
22803 								return None;
22804 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
22805 							if(data.length == 1) {
22806 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
22807 							} else {
22808 								if(maxRecurse)
22809 									return tryScanChildren(child, maxRecurse - 1);
22810 								else
22811 									return None;
22812 							}
22813 
22814 						}
22815 
22816 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
22817 						auto topResult = tryScanChildren(topWindow, 3);
22818 						// it is easy to have a false negative due to the mouse going over a WM
22819 						// child window like the close button if separate from the frame... so I
22820 						// can't really cache negatives, :(
22821 						if(topResult != None) {
22822 							eligibility[topWindow] = topResult;
22823 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
22824 						}
22825 					}
22826 
22827 				}
22828 
22829 			} else if(ev.type == MouseEventType.buttonReleased) {
22830 				drop();
22831 				dnding = false;
22832 			}
22833 		}
22834 	);
22835 
22836 	window.grabInput();
22837 	scope(exit)
22838 		window.releaseInputGrab();
22839 
22840 
22841 	EventLoop.get.run(() => dnding);
22842 
22843 	return 0;
22844 }
22845 
22846 /// X-specific
22847 TrueColorImage getWindowNetWmIcon(Window window) {
22848 	try {
22849 		auto display = XDisplayConnection.get;
22850 
22851 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
22852 
22853 		if (data.length > arch_ulong.sizeof * 2) {
22854 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
22855 			// these are an array of rgba images that we have to convert into pixmaps ourself
22856 
22857 			int width = cast(int) meta[0];
22858 			int height = cast(int) meta[1];
22859 
22860 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
22861 
22862 			static if(arch_ulong.sizeof == 4) {
22863 				bytes = bytes[0 .. width * height * 4];
22864 				alias imageData = bytes;
22865 			} else static if(arch_ulong.sizeof == 8) {
22866 				bytes = bytes[0 .. width * height * 8];
22867 				auto imageData = new ubyte[](4 * width * height);
22868 			} else static assert(0);
22869 
22870 
22871 
22872 			// this returns ARGB. Remember it is little-endian so
22873 			//                                         we have BGRA
22874 			// our thing uses RGBA, which in little endian, is ABGR
22875 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
22876 				auto r = bytes[idx + 2];
22877 				auto g = bytes[idx + 1];
22878 				auto b = bytes[idx + 0];
22879 				auto a = bytes[idx + 3];
22880 
22881 				imageData[idx2 + 0] = r;
22882 				imageData[idx2 + 1] = g;
22883 				imageData[idx2 + 2] = b;
22884 				imageData[idx2 + 3] = a;
22885 			}
22886 
22887 			return new TrueColorImage(width, height, imageData);
22888 		}
22889 
22890 		return null;
22891 	} catch(Exception e) {
22892 		return null;
22893 	}
22894 }
22895 
22896 } /* UsingSimpledisplayX11 */
22897 
22898 
22899 void loadBinNameToWindowClassName () {
22900 	import core.stdc.stdlib : realloc;
22901 	version(linux) {
22902 		// args[0] MAY be empty, so we'll just use this
22903 		import core.sys.posix.unistd : readlink;
22904 		char[1024] ebuf = void; // 1KB should be enough for everyone!
22905 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
22906 		if (len < 1) return;
22907 	} else /*version(Windows)*/ {
22908 		import core.runtime : Runtime;
22909 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
22910 		auto ebuf = Runtime.args[0];
22911 		auto len = ebuf.length;
22912 	}
22913 	auto pos = len;
22914 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
22915 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
22916 	if (sdpyWindowClassStr is null) return; // oops
22917 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
22918 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
22919 }
22920 
22921 /++
22922 	An interface representing a font that is drawn with custom facilities.
22923 
22924 	You might want [OperatingSystemFont] instead, which represents
22925 	a font loaded and drawn by functions native to the operating system.
22926 
22927 	WARNING: I might still change this.
22928 +/
22929 interface DrawableFont : MeasurableFont {
22930 	/++
22931 		Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string.
22932 
22933 		Implementations must use the painter's fillColor to draw a rectangle behind the string,
22934 		then use the outlineColor to draw the string. It might alpha composite if there's a transparent
22935 		fill color, but that's up to the implementation.
22936 	+/
22937 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
22938 
22939 	/++
22940 		Requests that the given string is added to the image cache. You should only do this rarely, but
22941 		if you have a string that you know will be used over and over again, adding it to a cache can
22942 		improve things (assuming the implementation actually has a cache; it is also valid for an implementation
22943 		to implement this as a do-nothing method).
22944 	+/
22945 	void cacheString(SimpleWindow window, Color foreground, Color background, string text);
22946 }
22947 
22948 /++
22949 	Loads a true type font using [arsd.ttf] that can be drawn as images on windows
22950 	through a [ScreenPainter]. That module must be compiled in if you choose to use this function.
22951 
22952 	You should also consider [OperatingSystemFont], which loads and draws a font with
22953 	facilities native to the user's operating system. You might also consider
22954 	[arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind
22955 	of game, as they have their own ways to draw text too.
22956 
22957 	Be warned: this can be slow, especially on remote connections to the X server, since
22958 	it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface
22959 	offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to
22960 	experiment in your specific case.
22961 
22962 	Please note that the return type of [DrawableFont] also includes an implementation of
22963 	[MeasurableFont].
22964 +/
22965 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
22966 	import arsd.ttf;
22967 	static class ArsdTtfFont : DrawableFont {
22968 		TtfFont font;
22969 		int size;
22970 		this(in ubyte[] data, int size) {
22971 			font = TtfFont(data);
22972 			this.size = size;
22973 
22974 
22975 			auto scale = stbtt_ScaleForPixelHeight(&font.font, size);
22976 			int ascent_, descent_, line_gap;
22977 			stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap);
22978 
22979 			int advance, lsb;
22980 			stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb);
22981 			xWidth = cast(int) (advance * scale);
22982 			stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb);
22983 			MWidth = cast(int) (advance * scale);
22984 		}
22985 
22986 		private int ascent_;
22987 		private int descent_;
22988 		private int xWidth;
22989 		private int MWidth;
22990 
22991 		bool isMonospace() {
22992 			return xWidth == MWidth;
22993 		}
22994 		int averageWidth() {
22995 			return xWidth;
22996 		}
22997 		int height() {
22998 			return size;
22999 		}
23000 		int ascent() {
23001 			return ascent_;
23002 		}
23003 		int descent() {
23004 			return descent_;
23005 		}
23006 
23007 		int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
23008 			int width, height;
23009 			font.getStringSize(s, size, width, height);
23010 			return width;
23011 		}
23012 
23013 
23014 
23015 		Sprite[string] cache;
23016 
23017 		void cacheString(SimpleWindow window, Color foreground, Color background, string text) {
23018 			auto sprite = new Sprite(window, stringToImage(foreground, background, text));
23019 			cache[text] = sprite;
23020 		}
23021 
23022 		Image stringToImage(Color fg, Color bg, in char[] text) {
23023 			int width, height;
23024 			auto data = font.renderString(text, size, width, height);
23025 			auto image = new TrueColorImage(width, height);
23026 			int pos = 0;
23027 			foreach(y; 0 .. height)
23028 			foreach(x; 0 .. width) {
23029 				fg.a = data[0];
23030 				bg.a = 255;
23031 				auto color = alphaBlend(fg, bg);
23032 				image.imageData.bytes[pos++] = color.r;
23033 				image.imageData.bytes[pos++] = color.g;
23034 				image.imageData.bytes[pos++] = color.b;
23035 				image.imageData.bytes[pos++] = data[0];
23036 				data = data[1 .. $];
23037 			}
23038 			assert(data.length == 0);
23039 
23040 			return Image.fromMemoryImage(image);
23041 		}
23042 
23043 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
23044 			Sprite sprite = (text in cache) ? *(text in cache) : null;
23045 
23046 			auto fg = painter.impl._outlineColor;
23047 			auto bg = painter.impl._fillColor;
23048 
23049 			if(sprite !is null) {
23050 				auto w = cast(SimpleWindow) painter.window;
23051 				assert(w !is null);
23052 
23053 				sprite.drawAt(painter, upperLeft);
23054 			} else {
23055 				painter.drawImage(upperLeft, stringToImage(fg, bg, text));
23056 			}
23057 		}
23058 	}
23059 
23060 	return new ArsdTtfFont(data, size);
23061 }
23062 
23063 class NotYetImplementedException : Exception {
23064 	this(string file = __FILE__, size_t line = __LINE__) {
23065 		super("Not yet implemented", file, line);
23066 	}
23067 }
23068 
23069 ///
23070 __gshared bool librariesSuccessfullyLoaded = true;
23071 ///
23072 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
23073 
23074 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
23075 	// mixin(staticForeachReplacement!Iface);
23076 	static foreach(name; __traits(derivedMembers, Iface))
23077 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23078 
23079 	void loadDynamicLibrary() @nogc {
23080 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23081 	}
23082 
23083 	void loadDynamicLibraryForReal() {
23084 		foreach(name; __traits(derivedMembers, Iface)) {
23085 			mixin("alias tmp = " ~ name ~ ";");
23086 			tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
23087 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
23088 		}
23089 	}
23090 }
23091 
23092 /+
23093 private const(char)[] staticForeachReplacement(Iface)() pure {
23094 /*
23095 	// just this for gdc 9....
23096 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
23097 
23098 	static foreach(name; __traits(derivedMembers, Iface))
23099 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23100 */
23101 
23102 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
23103 	size_t pos;
23104 
23105 	void append(in char[] what) {
23106 		if(pos + what.length > code.length)
23107 			code.length = (code.length * 3) / 2;
23108 		code[pos .. pos + what.length] = what[];
23109 		pos += what.length;
23110 	}
23111 
23112 	foreach(name; __traits(derivedMembers, Iface)) {
23113 		append(`__gshared typeof(&__traits(getMember, Iface, "`);
23114 		append(name);
23115 		append(`")) `);
23116 		append(name);
23117 		append(";");
23118 	}
23119 
23120 	return code[0 .. pos];
23121 }
23122 +/
23123 
23124 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
23125 	//mixin(staticForeachReplacement!Iface);
23126 	static foreach(name; __traits(derivedMembers, Iface))
23127 		mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
23128 
23129 	private __gshared void* libHandle;
23130 	private __gshared bool attempted;
23131 
23132 	void loadDynamicLibrary() @nogc {
23133 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
23134 	}
23135 
23136 	bool loadAttempted() {
23137 		return attempted;
23138 	}
23139 	bool loadSuccessful() {
23140 		return libHandle !is null;
23141 	}
23142 
23143 	void loadDynamicLibraryForReal() {
23144 		attempted = true;
23145 		version(Posix) {
23146 			import core.sys.posix.dlfcn;
23147 			version(OSX) {
23148 				version(X11)
23149 					libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
23150 				else
23151 					libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
23152 			} else {
23153 				version(apitrace) {
23154 					if(library == "GL" || library == "GLX") {
23155 						libHandle = dlopen("glxtrace.so", RTLD_NOW);
23156 						if(libHandle is null) {
23157 							assert(false, "Failed to load `glxtrace.so`.");
23158 						}
23159 					}
23160 					else {
23161 						libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23162 					}
23163 				}
23164 				else {
23165 					libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
23166 				}
23167 				if(libHandle is null) {
23168 					libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
23169 				}
23170 			}
23171 
23172 			static void* loadsym(void* l, const char* name) {
23173 				import core.stdc.stdlib;
23174 				if(l is null)
23175 					return &abort;
23176 				return dlsym(l, name);
23177 			}
23178 		} else version(Windows) {
23179 			import core.sys.windows.winbase;
23180 			libHandle = LoadLibrary(library ~ ".dll");
23181 			static void* loadsym(void* l, const char* name) {
23182 				import core.stdc.stdlib;
23183 				if(l is null)
23184 					return &abort;
23185 				return GetProcAddress(l, name);
23186 			}
23187 		}
23188 		if(libHandle is null) {
23189 			success = false;
23190 			//throw new Exception("load failure of library " ~ library);
23191 		}
23192 		foreach(name; __traits(derivedMembers, Iface)) {
23193 			mixin("alias tmp = " ~ name ~ ";");
23194 			tmp = cast(typeof(tmp)) loadsym(libHandle, name);
23195 			if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
23196 		}
23197 	}
23198 
23199 	void unloadDynamicLibrary() {
23200 		version(Posix) {
23201 			import core.sys.posix.dlfcn;
23202 			dlclose(libHandle);
23203 		} else version(Windows) {
23204 			import core.sys.windows.winbase;
23205 			FreeLibrary(libHandle);
23206 		}
23207 		foreach(name; __traits(derivedMembers, Iface))
23208 			mixin(name ~ " = null;");
23209 	}
23210 }
23211 
23212 // version(X11)
23213 /++
23214 	Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].
23215 
23216 	$(WARNING
23217 		This function is exempted from stability guarantees.
23218 	)
23219 +/
23220 float customScalingFactorForMonitor(int monitorNumber) @system {
23221 	import core.stdc.stdlib;
23222 	auto val = getenv("ARSD_SCALING_FACTOR");
23223 
23224 	// FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given
23225 	if(val is null)
23226 		return 1.0;
23227 
23228 	char[16] buffer = 0;
23229 	int pos;
23230 
23231 	const(char)* at = val;
23232 
23233 	foreach(item; 0 .. monitorNumber + 1) {
23234 		if(*at == 0)
23235 			break; // reuse the last number when we at the end of the string
23236 		pos = 0;
23237 		while(pos + 1 < buffer.length && *at && *at != ';') {
23238 			buffer[pos++] = *at;
23239 			at++;
23240 		}
23241 		if(*at)
23242 			at++; // skip the semicolon
23243 		buffer[pos] = 0;
23244 	}
23245 
23246 	//sdpyPrintDebugString(buffer[0 .. pos]);
23247 
23248 	import core.stdc.math;
23249 	auto f = atof(buffer.ptr);
23250 
23251 	if(f <= 0.0 || isnan(f) || isinf(f))
23252 		return 1.0;
23253 
23254 	return f;
23255 }
23256 
23257 void guiAbortProcess(string msg) {
23258 	import core.stdc.stdlib;
23259 	version(Windows) {
23260 		WCharzBuffer t = WCharzBuffer(msg);
23261 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
23262 	} else {
23263 		import core.stdc.stdio;
23264 		fwrite(msg.ptr, 1, msg.length, stderr);
23265 		msg = "\n";
23266 		fwrite(msg.ptr, 1, msg.length, stderr);
23267 		fflush(stderr);
23268 	}
23269 
23270 	abort();
23271 }
23272 
23273 private int minInternal(int a, int b) {
23274 	return (a < b) ? a : b;
23275 }
23276 
23277 private alias scriptable = arsd_jsvar_compatible;
23278 
23279 
23280 version(Windows) {
23281 	enum POINTER_FLAGS : DWORD {
23282 		LOL
23283 	}
23284 
23285 	enum POINTER_INPUT_TYPE {
23286 		PT_POINTER = 1,
23287 		PT_TOUCH = 2,
23288 		PT_PEN = 3,
23289 		PT_MOUSE = 4,
23290 		PT_TOUCHPAD = 5,
23291 	}
23292 
23293 	enum POINTER_BUTTON_CHANGE_TYPE {
23294 		POINTER_CHANGE_NONE,
23295 		POINTER_CHANGE_FIRSTBUTTON_DOWN,
23296 		POINTER_CHANGE_FIRSTBUTTON_UP,
23297 		POINTER_CHANGE_SECONDBUTTON_DOWN,
23298 		POINTER_CHANGE_SECONDBUTTON_UP,
23299 		POINTER_CHANGE_THIRDBUTTON_DOWN,
23300 		POINTER_CHANGE_THIRDBUTTON_UP,
23301 		POINTER_CHANGE_FOURTHBUTTON_DOWN,
23302 		POINTER_CHANGE_FOURTHBUTTON_UP,
23303 		POINTER_CHANGE_FIFTHBUTTON_DOWN,
23304 		POINTER_CHANGE_FIFTHBUTTON_UP
23305 	}
23306 
23307 	struct POINTER_INFO {
23308 		POINTER_INPUT_TYPE         pointerType;
23309 		UINT32                     pointerId;
23310 		UINT32                     frameId;
23311 		POINTER_FLAGS              pointerFlags;
23312 		HANDLE                     sourceDevice;
23313 		HWND                       hwndTarget;
23314 		POINT                      ptPixelLocation;
23315 		POINT                      ptHimetricLocation;
23316 		POINT                      ptPixelLocationRaw;
23317 		POINT                      ptHimetricLocationRaw;
23318 		DWORD                      dwTime;
23319 		UINT32                     historyCount;
23320 		INT32                      InputData;
23321 		DWORD                      dwKeyStates;
23322 		UINT64                     PerformanceCount;
23323 		POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
23324 	}
23325 
23326 	enum PEN_FLAGS : DWORD {
23327 		PEN_FLAG_NONE = 0,
23328 		PEN_FLAG_BARREL = 1,
23329 		PEN_FLAG_INVERTED = 2,
23330 		PEN_FLAG_ERASER = 4
23331 	}
23332 	enum PEN_MASK : DWORD {
23333 		PEN_MASK_NONE = 0,
23334 		PEN_MASK_PRESSURE = 1,
23335 		PEN_MASK_ROTATION = 2,
23336 		PEN_MASK_TILT_X = 4,
23337 		PEN_MASK_TILT_Y = 8,
23338 	}
23339 	struct POINTER_PEN_INFO {
23340 		POINTER_INFO pointerInfo;
23341 		PEN_FLAGS    penFlags;
23342 		PEN_MASK     penMask;
23343 		UINT32       pressure;
23344 		UINT32       rotation;
23345 		INT32        tiltX;
23346 		INT32        tiltY;
23347 	}
23348 
23349 	enum TOUCH_FLAGS : DWORD {
23350 		none
23351 	}
23352 
23353 	enum TOUCH_MASK : DWORD {
23354 		TOUCH_MASK_NONE = 0,
23355 		TOUCH_MASK_CONTACTAREA = 1,
23356 		TOUCH_MASK_ORIENTATION = 2,
23357 		TOUCH_MASK_PRESSURE = 4,
23358 	}
23359 
23360 	struct POINTER_TOUCH_INFO {
23361 		POINTER_INFO pointerInfo;
23362 		TOUCH_FLAGS  touchFlags;
23363 		TOUCH_MASK   touchMask;
23364 		RECT         rcContact;
23365 		RECT         rcContactRaw;
23366 		UINT32       orientation;
23367 		UINT32       pressure;
23368 	}
23369 
23370 	enum POINTER_DEVICE_TYPE : DWORD {
23371 		POINTER_DEVICE_TYPE_INTEGRATED_PEN = 0x00000001,
23372 		POINTER_DEVICE_TYPE_EXTERNAL_PEN = 0x00000002,
23373 		POINTER_DEVICE_TYPE_TOUCH = 0x00000003,
23374 		POINTER_DEVICE_TYPE_TOUCH_PAD = 0x00000004,
23375 	}
23376 
23377 	struct POINTER_DEVICE_INFO {
23378 		DWORD               displayOrientation;
23379 		HANDLE              device;
23380 		POINTER_DEVICE_TYPE pointerDeviceType;
23381 		HMONITOR            monitor;
23382 		ULONG               startingCursorId;
23383 		USHORT              maxActiveContacts;
23384 		WCHAR[520]          productString;
23385 	}
23386 
23387 	extern(Windows) {
23388 		BOOL GetPointerType(UINT32 pointerId, POINTER_INPUT_TYPE* type);
23389 		BOOL GetPointerInfo(UINT32 pointerId, POINTER_INFO* info);
23390 		BOOL GetPointerFramePenInfo(UINT32 pointerId, UINT32* pointerCount, POINTER_PEN_INFO *penInfo);
23391 		BOOL GetPointerFrameTouchInfo(UINT32 pointerId, UINT32* pointerCount, POINTER_TOUCH_INFO *touchInfo);
23392 		BOOL GetPointerDevices(UINT32* deviceCount, POINTER_DEVICE_INFO* pointerDevices);
23393 	}
23394 }