1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 // https://freedesktop.org/wiki/Specifications/XDND/
4 
5 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
6 
7 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
8 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
9 
10 
11 // on Mac with X11: -L-L/usr/X11/lib 
12 
13 /+
14 
15 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
16 
17 	Progress bar in taskbar
18 		- i can probably just set a property on the window...
19 		  it sets that prop to an integer 0 .. 100. Taskbar
20 		  deletes it or window deletes it when it is handled.
21 		- prolly display it as a nice little line at the bottom.
22 
23 
24 from gtk:
25 
26 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
27 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
28 
29 >+  if (cardinal > 0)
30 >+  {
31 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
32 >+                     xid,
33 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
34 >+                     XA_CARDINAL, 32,
35 >+                     PropModeReplace,
36 >+                     (guchar *) &cardinal, 1);
37 >+  }
38 >+  else
39 >+  {
40 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
41 >+                     xid,
42 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
43 >+  }
44 
45 from Windows:
46 
47 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
48 
49 interface
50 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 
51 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 
52 listen for msg, return TRUE
53 interface->SetProgressState(hwnd, TBPF_NORMAL); 
54 interface->SetProgressValue(hwnd, 40, 100); 
55 
56 
57 	My new notification system.
58 		- use a unix socket? or a x property? or a udp port?
59 		- could of course also get on the dbus train but ugh.
60 		- it could also reply with the info as a string for easy remote examination.
61 
62 +/
63 
64 /*
65 	Event Loop would be nices:
66 
67 	* add on idle - runs when nothing else happens
68 		* which can specify how long to yield for
69 	* send messages without a recipient window
70 	* setTimeout
71 	* setInterval
72 */
73 
74 /*
75 	Classic games I want to add:
76 		* my tetris clone
77 		* pac man
78 */
79 
80 /*
81 	Text layout needs a lot of work. Plain drawText is useful but too
82 	limited. It will need some kind of text context thing which it will
83 	update and you can pass it on and get more details out of it.
84 
85 	It will need a bounding box, a current cursor location that is updated
86 	as drawing continues, and various changable facts (which can also be
87 	changed on the painter i guess) like font, color, size, background,
88 	etc.
89 
90 	We can also fetch the caret location from it somehow.
91 
92 	Should prolly be an overload of drawText
93 
94 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
95 
96 		WS_EX_NOACTIVATE
97 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
98 		full screen windows. Can just set the atom on X. Windows will be harder.
99 
100 		moving windows. resizing windows.
101 
102 		hide cursor, capture cursor, change cursor.
103 
104 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
105 	sure the pieces are there to do its job easily and make other jobs possible.
106 */
107 
108 /++
109 	simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality,
110 	including creating windows, drawing on them, working with the clipboard,
111 	timers, OpenGL, and more. However, it does NOT provide high level GUI
112 	widgets. See my minigui.d, an extension to this module, for that
113 	functionality.
114 
115 	simpledisplay provides cross-platform wrapping for Windows and Linux
116 	(and perhaps other OSes that use X11), but also does not prevent you
117 	from using the underlying facilities if you need them. It has a goal
118 	of working efficiently over a remote X link (at least as far as Xlib
119 	reasonably allows.)
120 
121 	simpledisplay depends on [arsd.color|color.d], which should be available from the
122 	same place where you got this file. Other than that, however, it has
123 	very few dependencies and ones that don't come with the OS and/or the
124 	compiler are all opt-in.
125 
126 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
127 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
128 
129 	simpledisplay is basically stable. I plan to refactor the internals,
130 	and may add new features and fix bugs, but It do not expect to
131 	significantly change the API. It has been stable a few years already now.
132 
133 	Installation_instructions:
134 
135 	`simpledisplay.d` does not have any dependencies outside the
136 	operating system and `color.d`, so it should just work most the
137 	time, but there are a few caveats on some systems:
138 
139 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
140 	console to be automatically allocated.
141 
142 	Please note when compiling on Win64, you need to explicitly list
143 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
144 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
145 
146 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
147 	note the "w".
148 
149 	I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
150 	but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
151 	See [EnableWindowsSubsystem] for more information.
152 
153 	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.
154 
155 	On Ubuntu, you might need to install X11 development libraries to
156 	successfully link.
157 
158 	$(CONSOLE
159 		$ sudo apt-get install libglc-dev
160 		$ sudo apt-get install libx11-dev
161 	)
162 
163 
164 	Jump_list:
165 
166 	Don't worry, you don't have to read this whole documentation file!
167 
168 	Check out the [#event-example] and [#Pong-example] to get started quickly.
169 
170 	The main classes you may want to create are [SimpleWindow], [Timer],
171 	[Image], and [Sprite].
172 
173 	The main functions you'll want are [setClipboardText] and [getClipboardText].
174 
175 	There are also platform-specific functions available such as [XDisplayConnection]
176 	and [GetAtom] for X11, among others.
177 
178 	See the examples and topics list below to learn more.
179 
180 	$(WARNING
181 		There should only be one GUI thread per application,
182 		and all windows should be created in it and your
183 		event loop should run there.
184 
185 		To do otherwise is undefined behavior and has no
186 		cross platform guarantees.
187 	)
188 
189 	$(H2 About this documentation)
190 
191 	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.
192 
193 	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!
194 
195 	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.
196 
197 	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.
198 
199 	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.
200 
201 	At points, I will talk about implementation details in the documentation. These are sometimes
202 	subject to change, but nevertheless useful to understand what is really going on. You can learn
203 	more about some of the referenced things by searching the web for info about using them from C.
204 	You can always look at the source of simpledisplay.d too for the most authoritative source on
205 	its specific implementation. If you disagree with how I did something, please contact me so we
206 	can discuss it!
207 
208 	$(H2 Using with fibers)
209 
210 	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).
211 
212 	$(H2 Topics)
213 
214 	$(H3 $(ID topic-windows) Windows)
215 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
216 		window on the user's screen.
217 
218 		You may create multiple windows, if the underlying platform supports it. You may check
219 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
220 		SimpleWindow's constructor at runtime to handle those cases.
221 
222 		A single running event loop will handle as many windows as needed.
223 
224 	$(H3 $(ID topic-event-loops) Event loops)
225 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
226 
227 		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:
228 
229 		---
230 		// dmd example.d simpledisplay.d color.d
231 		import arsd.simpledisplay;
232 		void main() {
233 			auto window = new SimpleWindow(200, 200);
234 			window.eventLoop(0,
235 			  delegate (dchar) { /* got a character key press */ }
236 			);
237 		}
238 		---
239 
240 		$(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.)
241 
242 		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.
243 
244 		On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
245 
246 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
247 
248 		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.
249 
250 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
251 		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.
252 
253 		See the [NotificationAreaIcon] class.
254 
255 	$(H3 $(ID topic-input-handling) Input handling)
256 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
257 
258 		See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
259 
260 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
261 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
262 
263 		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:
264 
265 		---
266 		// dmd example.d simpledisplay.d color.d
267 		import arsd.simpledisplay;
268 		void main() {
269 			auto window = new SimpleWindow(200, 200);
270 			{ // introduce sub-scope
271 				auto painter = window.draw(); // begin drawing
272 				/* draw here */
273 				painter.outlineColor = Color.red;
274 				painter.fillColor = Color.black;
275 				painter.drawRectangle(Point(0, 0), 200, 200);
276 			} // end scope, calling `painter`'s destructor, drawing to the screen.
277 			window.eventLoop(0); // handle events
278 		}
279 		---
280 
281 		Painting is done based on two color properties, a pen and a brush.
282 
283 		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.
284 
285 		FIXME Add example of 2d opengl drawing here.
286 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
287 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
288 
289 		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.
290 
291 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
292 
293 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
294 
295 		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].
296 
297 		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.
298 
299 		This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
300 
301 		---
302 		import arsd.simpledisplay;
303 
304 		void main() {
305 			auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
306 
307 			float otherColor = 0.0;
308 			float colorDelta = 0.05;
309 
310 			window.redrawOpenGlScene = delegate() {
311 				glLoadIdentity();
312 				glBegin(GL_QUADS);
313 
314 				glColor3f(1.0, otherColor, 0);
315 				glVertex3f(-0.8, -0.8, 0);
316 
317 				glColor3f(1.0, otherColor, 1.0);
318 				glVertex3f(0.8, -0.8, 0);
319 
320 				glColor3f(0, 1.0, otherColor);
321 				glVertex3f(0.8, 0.8, 0);
322 
323 				glColor3f(otherColor, 0, 1.0);
324 				glVertex3f(-0.8, 0.8, 0);
325 
326 				glEnd();
327 			};
328 
329 			window.eventLoop(50, () {
330 				otherColor += colorDelta;
331 				if(otherColor > 1.0) {
332 					otherColor = 1.0;
333 					colorDelta = -0.05;
334 				}
335 				if(otherColor < 0) {
336 					otherColor = 0;
337 					colorDelta = 0.05;
338 				}
339 				// at the end of the timer, we have to request a redraw
340 				// or we won't see the changes.
341 				window.redrawOpenGlSceneSoon();
342 			});
343 		}
344 		---
345 
346 		My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
347 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
348 		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.
349 
350 		This example program shows how you can set up a shader to draw a rectangle:
351 
352 		---
353 		module opengl3test;
354 		import arsd.simpledisplay;
355 
356 		// based on https://learnopengl.com/Getting-started/Hello-Triangle
357 
358 		void main() {
359 			// First thing we do, before creating the window, is declare what version we want.
360 			setOpenGLContextVersion(3, 3);
361 			// turning off legacy compat is required to use version 3.3 and newer
362 			openGLContextCompatible = false;
363 
364 			uint VAO;
365 			OpenGlShader shader;
366 
367 			// then we can create the window.
368 			auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
369 
370 			// additional setup needs to be done when it is visible, simpledisplay offers a property
371 			// for exactly that:
372 			window.visibleForTheFirstTime = delegate() {
373 				// now with the window loaded, we can start loading the modern opengl functions.
374 
375 				// you MUST set the context first.
376 				window.setAsCurrentOpenGlContext;
377 				// then load the remainder of the library
378 				gl3.loadDynamicLibrary();
379 
380 				// now you can create the shaders, etc.
381 				shader = new OpenGlShader(
382 					OpenGlShader.Source(GL_VERTEX_SHADER, `
383 						#version 330 core
384 						layout (location = 0) in vec3 aPos;
385 						void main() {
386 							gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
387 						}
388 					`),
389 					OpenGlShader.Source(GL_FRAGMENT_SHADER, `
390 						#version 330 core
391 						out vec4 FragColor;
392 						uniform vec4 mycolor;
393 						void main() {
394 							FragColor = mycolor;
395 						}
396 					`),
397 				);
398 
399 				// and do whatever other setup you want.
400 
401 				float[] vertices = [
402 					0.5f,  0.5f, 0.0f,  // top right
403 					0.5f, -0.5f, 0.0f,  // bottom right
404 					-0.5f, -0.5f, 0.0f,  // bottom left
405 					-0.5f,  0.5f, 0.0f   // top left 
406 				];
407 				uint[] indices = [  // note that we start from 0!
408 					0, 1, 3,  // first Triangle
409 					1, 2, 3   // second Triangle
410 				];
411 				uint VBO, EBO;
412 				glGenVertexArrays(1, &VAO);
413 				// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
414 				glBindVertexArray(VAO);
415 
416 				glGenBuffers(1, &VBO);
417 				glGenBuffers(1, &EBO);
418 
419 				glBindBuffer(GL_ARRAY_BUFFER, VBO);
420 				glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
421 
422 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
423 				glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
424 
425 				glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
426 				glEnableVertexAttribArray(0);
427 
428 				// the library will set the initial viewport and trigger our first draw,
429 				// so these next two lines are NOT needed. they are just here as comments
430 				// to show what would happen next.
431 
432 				// glViewport(0, 0, window.width, window.height);
433 				// window.redrawOpenGlSceneNow();
434 			};
435 
436 			// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
437 			// it is our render method.
438 			window.redrawOpenGlScene = delegate() {
439 				glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
440 				glClear(GL_COLOR_BUFFER_BIT);
441 
442 				glUseProgram(shader.shaderProgram);
443 
444 				// the shader helper class has methods to set uniforms too
445 				shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
446 
447 				glBindVertexArray(VAO);
448 				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
449 			};
450 
451 			window.eventLoop(0);
452 		}
453 		---
454 
455 	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.
456 
457 
458 	$(H3 $(ID topic-images) Displaying images)
459 		You can also load PNG images using [arsd.png].
460 
461 		---
462 		// dmd example.d simpledisplay.d color.d png.d
463 		import arsd.simpledisplay;
464 		import arsd.png;
465 
466 		void main() {
467 			auto image = Image.fromMemoryImage(readPng("image.png"));
468 			displayImage(image);
469 		}
470 		---
471 
472 		Compile with `dmd example.d simpledisplay.d png.d`.
473 
474 		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.
475 
476 	$(H3 $(ID topic-sprites) Sprites)
477 		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.
478 
479 		[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
480 
481 	$(H3 $(ID topic-clipboard) Clipboard)
482 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
483 
484 		It also has helpers for handling X-specific events.
485 
486 	$(H3 $(ID topic-dnd) Drag and Drop)
487 		See [enableDragAndDrop] and [draggable].
488 
489 	$(H3 $(ID topic-timers) Timers)
490 		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].
491 
492 		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.
493 
494 		---
495 			import arsd.simpledisplay;
496 
497 			void main() {
498 				auto window = new SimpleWindow(400, 400);
499 				// every 100 ms, it will draw a random line
500 				// on the window.
501 				window.eventLoop(100, {
502 					auto painter = window.draw();
503 
504 					import std.random;
505 					// random color
506 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
507 					// random line
508 					painter.drawLine(
509 						Point(uniform(0, window.width), uniform(0, window.height)),
510 						Point(uniform(0, window.width), uniform(0, window.height)));
511 
512 				});
513 			}
514 		---
515 
516 		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.
517 
518 		The pulse timer and instances of the [Timer] class may be combined at will.
519 
520 		---
521 			import arsd.simpledisplay;
522 
523 			void main() {
524 				auto window = new SimpleWindow(400, 400);
525 				auto timer = new Timer(1000, delegate {
526 					auto painter = window.draw();
527 					painter.clear();
528 				});
529 
530 				window.eventLoop(0);
531 			}
532 		---
533 
534 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
535 
536 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
537 		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.
538 
539 		See also: `xwindows.d` from my github.
540 
541 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
542 		`handleNativeEvent` and `handleNativeGlobalEvent`.
543 
544 	$(H3 $(ID topic-integration) Integration with other libraries)
545 		Integration with a third-party event loop is possible.
546 
547 		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.
548 
549 	$(H3 $(ID topic-guis) GUI widgets)
550 		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!
551 
552 		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.
553 
554 		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.)
555 
556 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
557 
558 	$(H2 Platform-specific tips and tricks)
559 
560 	Windows_tips:
561 
562 	You can add icons or manifest files to your exe using a resource file.
563 
564 	To create a Windows .ico file, use the gimp or something. I'll write a helper
565 	program later.
566 
567 	Create `yourapp.rc`:
568 
569 	```rc
570 		1 ICON filename.ico
571 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
572 	```
573 
574 	And `yourapp.exe.manifest`:
575 
576 	```xml
577 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
578 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
579 		<assemblyIdentity
580 		    version="1.0.0.0"
581 		    processorArchitecture="*"
582 		    name="CompanyName.ProductName.YourApplication"
583 		    type="win32"
584 		/>
585 		<description>Your application description here.</description>
586 		<dependency>
587 		    <dependentAssembly>
588 			<assemblyIdentity
589 			    type="win32"
590 			    name="Microsoft.Windows.Common-Controls"
591 			    version="6.0.0.0"
592 			    processorArchitecture="*"
593 			    publicKeyToken="6595b64144ccf1df"
594 			    language="*"
595 			/>
596 		    </dependentAssembly>
597 		</dependency>
598 		<application xmlns="urn:schemas-microsoft-com:asm.v3">
599 			<windowsSettings>
600 				<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
601 				<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
602 				<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
603 				<!-- to render crisply in DPI-unaware contexts --> 
604 				<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
605 			</windowsSettings>
606 		</application>
607 		</assembly>
608 	```
609 
610 	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`.
611 
612 	Doing this lets you opt into various new things since Windows XP.
613 
614 	See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
615 
616 	$(H2 Tips)
617 
618 	$(H3 Name conflicts)
619 
620 	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:
621 
622 	---
623 	static import sdpy = arsd.simpledisplay;
624 	import arsd.simpledisplay : SimpleWindow;
625 
626 	void main() {
627 		auto window = new SimpleWindow();
628 		sdpy.EventLoop.get.run();
629 	}
630 	---
631 
632 	$(H2 $(ID developer-notes) Developer notes)
633 
634 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
635 	implementation though.
636 
637 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
638 	suck. If I was rewriting it, I wouldn't do it that way again.
639 
640 	This file must not have any more required dependencies. If you need bindings, add
641 	them right to this file. Once it gets into druntime and is there for a while, remove
642 	bindings from here to avoid conflicts (or put them in an appropriate version block
643 	so it continues to just work on old dmd), but wait a couple releases before making the
644 	transition so this module remains usable with older versions of dmd.
645 
646 	You may have optional dependencies if needed by putting them in version blocks or
647 	template functions. You may also extend the module with other modules with UFCS without
648 	actually editing this - that is nice to do if you can.
649 
650 	Try to make functions work the same way across operating systems. I typically make
651 	it thinly wrap Windows, then emulate that on Linux.
652 
653 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
654 	Phobos! So try to avoid it.
655 
656 	See more comments throughout the source.
657 
658 	I realize this file is fairly large, but over half that is just bindings at the bottom
659 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
660 	to understand. I suggest you jump around the source by looking for a particular
661 	declaration you're interested in, like `class SimpleWindow` using your editor's search
662 	function, then look at one piece at a time.
663 
664 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
665 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can
666 	ping me, adam_d_ruppe, and I'll usually see it if I'm around.
667 
668 	I live in the eastern United States, so I will most likely not be around at night in
669 	that US east timezone.
670 
671 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
672 
673 	Building documentation: use my adrdox generator, `dub run adrdox`.
674 
675 	Examples:
676 
677 	$(DIV $(ID Event-example))
678 	$(H3 $(ID event-example) Event example)
679 	This program creates a window and draws events inside them as they
680 	happen, scrolling the text in the window as needed. Run this program
681 	and experiment to get a feel for where basic input events take place
682 	in the library.
683 
684 	---
685 	// dmd example.d simpledisplay.d color.d
686 	import arsd.simpledisplay;
687 	import std.conv;
688 
689 	void main() {
690 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
691 
692 		int y = 0;
693 
694 		void addLine(string text) {
695 			auto painter = window.draw();
696 
697 			if(y + painter.fontHeight >= window.height) {
698 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
699 				y -= painter.fontHeight;
700 			}
701 
702 			painter.outlineColor = Color.red;
703 			painter.fillColor = Color.black;
704 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
705 
706 			painter.outlineColor = Color.white;
707 
708 			painter.drawText(Point(10, y), text);
709 
710 			y += painter.fontHeight;
711 		}
712 
713 		window.eventLoop(1000,
714 		  () {
715 			addLine("Timer went off!");
716 		  },
717 		  (KeyEvent event) {
718 			addLine(to!string(event));
719 		  },
720 		  (MouseEvent event) {
721 			addLine(to!string(event));
722 		  },
723 		  (dchar ch) {
724 			addLine(to!string(ch));
725 		  }
726 		);
727 	}
728 	---
729 
730 	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.
731 
732 	$(COMMENT
733 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
734 
735 	---
736 
737 	---
738 	)
739 
740 
741 +/
742 module arsd.simpledisplay;
743 
744 // FIXME: tetris demo
745 // FIXME: space invaders demo
746 // FIXME: asteroids demo
747 
748 /++ $(ID Pong-example)
749 	$(H3 Pong)
750 
751 	This program creates a little Pong-like game. Player one is controlled
752 	with the keyboard.  Player two is controlled with the mouse. It demos
753 	the pulse timer, event handling, and some basic drawing.
754 +/
755 version(demos)
756 unittest {
757 	// dmd example.d simpledisplay.d color.d
758 	import arsd.simpledisplay;
759 
760 	enum paddleMovementSpeed = 8;
761 	enum paddleHeight = 48;
762 
763 	void main() {
764 		auto window = new SimpleWindow(600, 400, "Pong game!");
765 
766 		int playerOnePosition, playerTwoPosition;
767 		int playerOneMovement, playerTwoMovement;
768 		int playerOneScore, playerTwoScore;
769 
770 		int ballX, ballY;
771 		int ballDx, ballDy;
772 
773 		void serve() {
774 			import std.random;
775 
776 			ballX = window.width / 2;
777 			ballY = window.height / 2;
778 			ballDx = uniform(-4, 4) * 3;
779 			ballDy = uniform(-4, 4) * 3;
780 			if(ballDx == 0)
781 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
782 		}
783 
784 		serve();
785 
786 		window.eventLoop(50, // set a 50 ms timer pulls
787 			// This runs once per timer pulse
788 			delegate () {
789 				auto painter = window.draw();
790 
791 				painter.clear();
792 
793 				// Update everyone's motion
794 				playerOnePosition += playerOneMovement;
795 				playerTwoPosition += playerTwoMovement;
796 
797 				ballX += ballDx;
798 				ballY += ballDy;
799 
800 				// Bounce off the top and bottom edges of the window
801 				if(ballY + 7 >= window.height)
802 					ballDy = -ballDy;
803 				if(ballY - 8 <= 0)
804 					ballDy = -ballDy;
805 
806 				// Bounce off the paddle, if it is in position
807 				if(ballX - 8 <= 16) {
808 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
809 						ballDx = -ballDx + 1; // add some speed to keep it interesting
810 						ballDy += playerOneMovement; // and y movement based on your controls too
811 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
812 					} else {
813 						// Missed it
814 						playerTwoScore ++;
815 						serve();
816 					}
817 				}
818 
819 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
820 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
821 						ballDx = -ballDx - 1;
822 						ballDy += playerTwoMovement;
823 						ballX = window.width - 24;
824 					} else {
825 						// Missed it
826 						playerOneScore ++;
827 						serve();
828 					}
829 				}
830 
831 				// Draw the paddles
832 				painter.outlineColor = Color.black;
833 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
834 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
835 
836 				// Draw the ball
837 				painter.fillColor = Color.red;
838 				painter.outlineColor = Color.yellow;
839 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
840 
841 				// Draw the score
842 				painter.outlineColor = Color.blue;
843 				import std.conv;
844 				painter.drawText(Point(64, 4), to!string(playerOneScore));
845 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
846 
847 			},
848 			delegate (KeyEvent event) {
849 				// Player 1's controls are the arrow keys on the keyboard
850 				if(event.key == Key.Down)
851 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
852 				if(event.key == Key.Up)
853 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
854 
855 			},
856 			delegate (MouseEvent event) {
857 				// Player 2's controls are mouse movement while the left button is held down
858 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
859 					if(event.dy > 0)
860 						playerTwoMovement = paddleMovementSpeed;
861 					else if(event.dy < 0)
862 						playerTwoMovement = -paddleMovementSpeed;
863 				} else {
864 					playerTwoMovement = 0;
865 				}
866 			}
867 		);
868 	}
869 }
870 
871 /++ $(H3 $(ID example-minesweeper) Minesweeper)
872 
873 	This minesweeper demo shows how we can implement another classic
874 	game with simpledisplay and shows some mouse input and basic output
875 	code.
876 +/
877 version(demos)
878 unittest {
879 	import arsd.simpledisplay;
880 
881 	enum GameSquare {
882 		mine = 0,
883 		clear,
884 		m1, m2, m3, m4, m5, m6, m7, m8
885 	}
886 
887 	enum UserSquare {
888 		unknown,
889 		revealed,
890 		flagged,
891 		questioned
892 	}
893 
894 	enum GameState {
895 		inProgress,
896 		lose,
897 		win
898 	}
899 
900 	GameSquare[] board;
901 	UserSquare[] userState;
902 	GameState gameState;
903 	int boardWidth;
904 	int boardHeight;
905 
906 	bool isMine(int x, int y) {
907 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
908 			return false;
909 		return board[y * boardWidth + x] == GameSquare.mine;
910 	}
911 
912 	GameState reveal(int x, int y) {
913 		if(board[y * boardWidth + x] == GameSquare.clear) {
914 			floodFill(userState, boardWidth, boardHeight,
915 				UserSquare.unknown, UserSquare.revealed,
916 				x, y,
917 				(x, y) {
918 					if(board[y * boardWidth + x] == GameSquare.clear)
919 						return true;
920 					else {
921 						userState[y * boardWidth + x] = UserSquare.revealed;
922 						return false;
923 					}
924 				});
925 		} else {
926 			userState[y * boardWidth + x] = UserSquare.revealed;
927 			if(isMine(x, y))
928 				return GameState.lose;
929 		}
930 
931 		foreach(state; userState) {
932 			if(state == UserSquare.unknown || state == UserSquare.questioned)
933 				return GameState.inProgress;
934 		}
935 
936 		return GameState.win;
937 	}
938 
939 	void initializeBoard(int width, int height, int numberOfMines) {
940 		boardWidth = width;
941 		boardHeight = height;
942 		board.length = width * height;
943 
944 		userState.length = width * height;
945 		userState[] = UserSquare.unknown; 
946 
947 		import std.algorithm, std.random, std.range;
948 
949 		board[] = GameSquare.clear;
950 
951 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
952 			board[minePosition] = GameSquare.mine;
953 
954 		int x;
955 		int y;
956 		foreach(idx, ref square; board) {
957 			if(square == GameSquare.clear) {
958 				int danger = 0;
959 				danger += isMine(x-1, y-1)?1:0;
960 				danger += isMine(x-1, y)?1:0;
961 				danger += isMine(x-1, y+1)?1:0;
962 				danger += isMine(x, y-1)?1:0;
963 				danger += isMine(x, y+1)?1:0;
964 				danger += isMine(x+1, y-1)?1:0;
965 				danger += isMine(x+1, y)?1:0;
966 				danger += isMine(x+1, y+1)?1:0;
967 
968 				square = cast(GameSquare) (danger + 1);
969 			}
970 
971 			x++;
972 			if(x == width) {
973 				x = 0;
974 				y++;
975 			}
976 		}
977 	}
978 
979 	void redraw(SimpleWindow window) {
980 		import std.conv;
981 
982 		auto painter = window.draw();
983 
984 		painter.clear();
985 
986 		final switch(gameState) with(GameState) {
987 			case inProgress:
988 				break;
989 			case win:
990 				painter.fillColor = Color.green;
991 				painter.drawRectangle(Point(0, 0), window.width, window.height);
992 				return;
993 			case lose:
994 				painter.fillColor = Color.red;
995 				painter.drawRectangle(Point(0, 0), window.width, window.height);
996 				return;
997 		}
998 
999 		int x = 0;
1000 		int y = 0;
1001 
1002 		foreach(idx, square; board) {
1003 			auto state = userState[idx];
1004 
1005 			final switch(state) with(UserSquare) {
1006 				case unknown:
1007 					painter.outlineColor = Color.black;
1008 					painter.fillColor = Color(128,128,128);
1009 
1010 					painter.drawRectangle(
1011 						Point(x * 20, y * 20),
1012 						20, 20
1013 					);
1014 				break;
1015 				case revealed:
1016 					if(square == GameSquare.clear) {
1017 						painter.outlineColor = Color.white;
1018 						painter.fillColor = Color.white;
1019 
1020 						painter.drawRectangle(
1021 							Point(x * 20, y * 20),
1022 							20, 20
1023 						);
1024 					} else {
1025 						painter.outlineColor = Color.black;
1026 						painter.fillColor = Color.white;
1027 
1028 						painter.drawText(
1029 							Point(x * 20, y * 20),
1030 							to!string(square)[1..2],
1031 							Point(x * 20 + 20, y * 20 + 20),
1032 							TextAlignment.Center | TextAlignment.VerticalCenter);
1033 					}
1034 				break;
1035 				case flagged:
1036 					painter.outlineColor = Color.black;
1037 					painter.fillColor = Color.red;
1038 					painter.drawRectangle(
1039 						Point(x * 20, y * 20),
1040 						20, 20
1041 					);
1042 				break;
1043 				case questioned:
1044 					painter.outlineColor = Color.black;
1045 					painter.fillColor = Color.yellow;
1046 					painter.drawRectangle(
1047 						Point(x * 20, y * 20),
1048 						20, 20
1049 					);
1050 				break;
1051 			}
1052 
1053 			x++;
1054 			if(x == boardWidth) {
1055 				x = 0;
1056 				y++;
1057 			}
1058 		}
1059 
1060 	}
1061 
1062 	void main() {
1063 		auto window = new SimpleWindow(200, 200);
1064 
1065 		initializeBoard(10, 10, 10);
1066 
1067 		redraw(window);
1068 		window.eventLoop(0,
1069 			delegate (MouseEvent me) {
1070 				if(me.type != MouseEventType.buttonPressed)
1071 					return;
1072 				auto x = me.x / 20;
1073 				auto y = me.y / 20;
1074 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
1075 					if(me.button == MouseButton.left) {
1076 						gameState = reveal(x, y);
1077 					} else {
1078 						userState[y*boardWidth+x] = UserSquare.flagged;
1079 					}
1080 					redraw(window);
1081 				}
1082 			}
1083 		);
1084 	}
1085 }
1086 
1087 /*
1088 version(OSX) {
1089 	version=without_opengl;
1090 	version=allow_unimplemented_features;
1091 	version=OSXCocoa;
1092 	pragma(linkerDirective, "-framework Cocoa");
1093 }
1094 */
1095 
1096 version(without_opengl) {
1097 	enum SdpyIsUsingIVGLBinds = false;
1098 } else /*version(Posix)*/ {
1099 	static if (__traits(compiles, (){import iv.glbinds;})) {
1100 		enum SdpyIsUsingIVGLBinds = true;
1101 		public import iv.glbinds;
1102 		//pragma(msg, "SDPY: using iv.glbinds");
1103 	} else {
1104 		enum SdpyIsUsingIVGLBinds = false;
1105 	}
1106 //} else {
1107 //	enum SdpyIsUsingIVGLBinds = false;
1108 }
1109 
1110 
1111 version(Windows) {
1112 	//import core.sys.windows.windows;
1113 	import core.sys.windows.winnls;
1114 	import core.sys.windows.windef;
1115 	import core.sys.windows.basetyps;
1116 	import core.sys.windows.winbase;
1117 	import core.sys.windows.winuser;
1118 	import core.sys.windows.shellapi;
1119 	import core.sys.windows.wingdi;
1120 	static import gdi = core.sys.windows.wingdi; // so i
1121 
1122 	pragma(lib, "gdi32");
1123 	pragma(lib, "user32");
1124 
1125 	// for AlphaBlend... a breaking change....
1126 	version(CRuntime_DigitalMars) { } else
1127 		pragma(lib, "msimg32");
1128 } else version (linux) {
1129 	//k8: this is hack for rdmd. sorry.
1130 	static import core.sys.linux.epoll;
1131 	static import core.sys.linux.timerfd;
1132 }
1133 
1134 
1135 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1136 
1137 // http://wiki.dlang.org/Simpledisplay.d
1138 
1139 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1140 
1141 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1142 // but can i control the scroll lock led
1143 
1144 
1145 // Note: if you are using Image on X, you might want to do:
1146 /*
1147 	static if(UsingSimpledisplayX11) {
1148 		if(!Image.impl.xshmAvailable) {
1149 			// the images will use the slower XPutImage, you might
1150 			// want to consider an alternative method to get better speed
1151 		}
1152 	}
1153 
1154 	If the shared memory extension is available though, simpledisplay uses it
1155 	for a significant speed boost whenever you draw large Images.
1156 */
1157 
1158 // 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.
1159 
1160 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1161 
1162 /*
1163 	Biggest FIXME:
1164 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1165 
1166 		clean up opengl contexts when their windows close
1167 
1168 		fix resizing the bitmaps/pixmaps
1169 */
1170 
1171 // BTW on Windows:
1172 // -L/SUBSYSTEM:WINDOWS:5.0
1173 // to dmd will make a nice windows binary w/o a console if you want that.
1174 
1175 /*
1176 	Stuff to add:
1177 
1178 	use multibyte functions everywhere we can
1179 
1180 	OpenGL windows
1181 	more event stuff
1182 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1183 
1184 
1185 	resizeEvent
1186 		and make the windows non-resizable by default,
1187 		or perhaps stretched (if I can find something in X like StretchBlt)
1188 
1189 	take a screenshot function!
1190 
1191 	Pens and brushes?
1192 	Maybe a global event loop?
1193 
1194 	Mouse deltas
1195 	Key items
1196 */
1197 
1198 /*
1199 From MSDN:
1200 
1201 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1202 
1203 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.
1204 
1205 */
1206 
1207 version(linux) {
1208 	version = X11;
1209 	version(without_libnotify) {
1210 		// we cool
1211 	}
1212 	else
1213 		version = libnotify;
1214 }
1215 
1216 version(libnotify) {
1217 	pragma(lib, "dl");
1218 	import core.sys.posix.dlfcn;
1219 
1220 	void delegate()[int] libnotify_action_delegates;
1221 	int libnotify_action_delegates_count;
1222 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1223 		auto idx = cast(int) user_data;
1224 		if(auto dgptr = idx in libnotify_action_delegates) {
1225 			(*dgptr)();
1226 			libnotify_action_delegates.remove(idx);
1227 		}
1228 	}
1229 
1230 	struct C_DynamicLibrary {
1231 		void* handle;
1232 		this(string name) {
1233 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1234 			if(handle is null)
1235 				throw new Exception("dlopen");
1236 		}
1237 
1238 		void close() {
1239 			dlclose(handle);
1240 		}
1241 
1242 		~this() {
1243 			// close
1244 		}
1245 
1246 		// FIXME: this looks up by name every time.... 
1247 		template call(string func, Ret, Args...) {
1248 			extern(C) Ret function(Args) fptr;
1249 			typeof(fptr) call() {
1250 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1251 				return fptr;
1252 			}
1253 		}
1254 	}
1255 
1256 	C_DynamicLibrary* libnotify;
1257 }
1258 
1259 version(OSX) {
1260 	version(OSXCocoa) {}
1261 	else { version = X11; }
1262 }
1263 	//version = OSXCocoa; // this was written by KennyTM
1264 version(FreeBSD)
1265 	version = X11;
1266 version(Solaris)
1267 	version = X11;
1268 
1269 version(X11) {
1270 	version(without_xft) {}
1271 	else version=with_xft;
1272 }
1273 
1274 void featureNotImplemented()() {
1275 	version(allow_unimplemented_features)
1276 		throw new NotYetImplementedException();
1277 	else
1278 		static assert(0);
1279 }
1280 
1281 // these are so the static asserts don't trigger unless you want to
1282 // add support to it for an OS
1283 version(Windows)
1284 	version = with_timer;
1285 version(linux)
1286 	version = with_timer;
1287 
1288 version(with_timer)
1289 	enum bool SimpledisplayTimerAvailable = true;
1290 else
1291 	enum bool SimpledisplayTimerAvailable = false;
1292 
1293 /// 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.
1294 version(Windows)
1295 	enum bool UsingSimpledisplayWindows = true;
1296 else
1297 	enum bool UsingSimpledisplayWindows = false;
1298 
1299 /// 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.
1300 version(X11)
1301 	enum bool UsingSimpledisplayX11 = true;
1302 else
1303 	enum bool UsingSimpledisplayX11 = false;
1304 
1305 /// 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.
1306 version(OSXCocoa)
1307 	enum bool UsingSimpledisplayCocoa = true;
1308 else
1309 	enum bool UsingSimpledisplayCocoa = false;
1310 
1311 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1312 version(Windows)
1313 	enum multipleWindowsSupported = true;
1314 else version(X11)
1315 	enum multipleWindowsSupported = true;
1316 else version(OSXCocoa)
1317 	enum multipleWindowsSupported = true;
1318 else
1319 	static assert(0);
1320 
1321 version(without_opengl)
1322 	enum bool OpenGlEnabled = false;
1323 else
1324 	enum bool OpenGlEnabled = true;
1325 
1326 /++
1327 	Adds the necessary pragmas to your application to use the Windows gui subsystem.
1328 	If you mix this in above your `main` function, you no longer need to use the linker
1329 	flags explicitly. It does the necessary version blocks for various compilers and runtimes.
1330 
1331 	It does nothing if not compiling for Windows, so you need not version it out yourself.
1332 
1333 	Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and
1334 	stderr writeln. It will fail and throw an exception.
1335 
1336 	This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`.
1337 
1338 	History:
1339 		Added November 24, 2021 (dub v10.4)
1340 +/
1341 mixin template EnableWindowsSubsystem() {
1342 	version(Windows)
1343 	version(CRuntime_Microsoft) {
1344 		pragma(linkerDirective, "/subsystem:windows");
1345 		version(LDC)
1346 			pragma(linkerDirective, "/entry:wmainCRTStartup");
1347 		else
1348 			pragma(linkerDirective, "/entry:mainCRTStartup");
1349 	}
1350 }
1351 
1352 
1353 /++
1354 	After selecting a type from [WindowTypes], you may further customize
1355 	its behavior by setting one or more of these flags.
1356 
1357 
1358 	The different window types have different meanings of `normal`. If the
1359 	window type already is a good match for what you want to do, you should
1360 	just use [WindowFlags.normal], the default, which will do the right thing
1361 	for your users.
1362 
1363 	The window flags will not always be honored by the operating system
1364 	and window managers; they are hints, not commands.
1365 +/
1366 enum WindowFlags : int {
1367 	normal = 0, ///
1368 	skipTaskbar = 1, ///
1369 	alwaysOnTop = 2, ///
1370 	alwaysOnBottom = 4, ///
1371 	cannotBeActivated = 8, ///
1372 	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.
1373 	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.
1374 	/++
1375 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1376 		it is still a top-level window. This should NOT be set separately for most window types.
1377 
1378 		A transient window will not keep the application open if its main window closes.
1379 
1380 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1381 
1382 
1383 		From the ICCM:
1384 
1385 		$(BLOCKQUOTE
1386 			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. 
1387 
1388 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1389 		)
1390 
1391 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1392 
1393 		History:
1394 			Added February 23, 2021 but not yet stabilized.
1395 	+/
1396 	transient = 64,
1397 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1398 }
1399 
1400 /++
1401 	When creating a window, you can pass a type to SimpleWindow's constructor,
1402 	then further customize the window by changing `WindowFlags`.
1403 
1404 
1405 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1406 	use. The others are there to build a foundation for a higher level GUI toolkit,
1407 	but are themselves not as high level as you might think from their names.
1408 
1409 	This list is based on the EMWH spec for X11.
1410 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1411 +/
1412 enum WindowTypes : int {
1413 	/// An ordinary application window.
1414 	normal,
1415 	/// 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.
1416 	undecorated,
1417 	/// 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.
1418 	eventOnly,
1419 	/// A drop down menu, such as from a menu bar
1420 	dropdownMenu,
1421 	/// A popup menu, such as from a right click
1422 	popupMenu,
1423 	/// A popup bubble notification
1424 	notification,
1425 	/*
1426 	menu, /// a tearable menu bar
1427 	splashScreen, /// a loading splash screen for your application
1428 	tooltip, /// A tiny window showing temporary help text or something.
1429 	comboBoxDropdown,
1430 	dialog,
1431 	toolbar
1432 	*/
1433 	/// a child nested inside the parent. You must pass a parent window to the ctor
1434 	nestedChild,
1435 }
1436 
1437 
1438 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1439 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1440 private __gshared char* sdpyWindowClassStr = null;
1441 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1442 
1443 /**
1444 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1445 	You may want to change context version if you want to use advanced shaders or
1446 	other modern OpenGL techinques. This setting doesn't affect already created
1447 	windows. You may use version 2.1 as your default, which should be supported
1448 	by any box since 2006, so seems to be a reasonable choice.
1449 
1450 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1451 	old context creation code without any version specified. This is the safest
1452 	way to init OpenGL, but it may not give you access to advanced features.
1453 
1454 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1455 */
1456 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1457 
1458 /**
1459 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1460 	pipeline functions, and without "compatible" mode you won't be able to use
1461 	your old non-shader-based code with such contexts. By default SimpleDisplay
1462 	creates compatible context, so you can gradually upgrade your OpenGL code if
1463 	you want to (or leave it as is, as it should "just work").
1464 */
1465 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1466 
1467 /**
1468 	Set to `true` to allow creating OpenGL context with lower version than requested
1469 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1470 	`openGLContextFallbackActivated()` will return `true`.
1471 	*/
1472 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1473 
1474 /**
1475 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1476 	*/
1477 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1478 
1479 
1480 /**
1481 	Set window class name for all following `new SimpleWindow()` calls.
1482 
1483 	WARNING! For Windows, you should set your class name before creating any
1484 	window, and NEVER change it after that!
1485 */
1486 void sdpyWindowClass (const(char)[] v) {
1487 	import core.stdc.stdlib : realloc;
1488 	if (v.length == 0) v = "SimpleDisplayWindow";
1489 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1490 	if (sdpyWindowClassStr is null) return; // oops
1491 	sdpyWindowClassStr[0..v.length+1] = 0;
1492 	sdpyWindowClassStr[0..v.length] = v[];
1493 }
1494 
1495 /**
1496 	Get current window class name.
1497 */
1498 string sdpyWindowClass () {
1499 	if (sdpyWindowClassStr is null) return null;
1500 	foreach (immutable idx; 0..size_t.max-1) {
1501 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1502 	}
1503 	return null;
1504 }
1505 
1506 /++
1507 	Returns the 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.
1508 
1509 	If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0.
1510 +/
1511 float[2] getDpi() {
1512 	float[2] dpi;
1513 	version(Windows) {
1514 		HDC screen = GetDC(null);
1515 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1516 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1517 	} else version(X11) {
1518 		auto display = XDisplayConnection.get;
1519 		auto screen = DefaultScreen(display);
1520 
1521 		void fallback() {
1522 			// 25.4 millimeters in an inch...
1523 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1524 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1525 		}
1526 
1527 		char* resourceString = XResourceManagerString(display);
1528 		XrmInitialize();
1529 
1530 		auto db = XrmGetStringDatabase(resourceString);
1531 
1532 		if (resourceString) {
1533 			XrmValue value;
1534 			char* type;
1535 			if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1536 				if (value.addr) {
1537 					import core.stdc.stdlib;
1538 					dpi[0] = atof(cast(char*) value.addr);
1539 					dpi[1] = dpi[0];
1540 				} else {
1541 					fallback();
1542 				}
1543 			} else {
1544 				fallback();
1545 			}
1546 		} else {
1547 			fallback();
1548 		}
1549 	}
1550 
1551 	return dpi;
1552 }
1553 
1554 /++
1555 	Implementation used by [SimpleWindow.takeScreenshot].
1556 
1557 	Params:
1558 		handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen.
1559 		width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target.
1560 		height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target.
1561 		x = the x-offset of the image to capture, from the left.
1562 		y = the y-offset of the image to capture, from the top.
1563 
1564 	History:
1565 		Added on March 14, 2021
1566 
1567 		Documented public on September 23, 2021 with full support for null params (dub 10.3)
1568 		
1569 +/
1570 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) {
1571 	TrueColorImage got;
1572 	version(X11) {
1573 		auto display = XDisplayConnection.get;
1574 		if(handle == 0)
1575 			handle = RootWindow(display, DefaultScreen(display));
1576 
1577 		if(width == 0 || height == 0) {
1578 			Window root;
1579 			int xpos, ypos;
1580 			uint widthret, heightret, borderret, depthret;
1581 			XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret);
1582 
1583 			if(width == 0)
1584 				width = widthret;
1585 			if(height == 0)
1586 				height = heightret;
1587 		}
1588 
1589 		auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1590 
1591 		// https://github.com/adamdruppe/arsd/issues/98
1592 
1593 		auto i = new Image(image);
1594 		got = i.toTrueColorImage();
1595 
1596 		XDestroyImage(image);
1597 	} else version(Windows) {
1598 		auto hdc = GetDC(handle);
1599 		scope(exit) ReleaseDC(handle, hdc);
1600 
1601 		if(width == 0 || height == 0) {
1602 			BITMAP bmHeader;
1603 			auto bm  = GetCurrentObject(hdc, OBJ_BITMAP);
1604 			GetObject(bm, BITMAP.sizeof, &bmHeader);
1605 			if(width == 0)
1606 				width = bmHeader.bmWidth;
1607 			if(height == 0)
1608 				height = bmHeader.bmHeight;
1609 		}
1610 
1611 		auto i = new Image(width, height);
1612 		HDC hdcMem = CreateCompatibleDC(hdc);
1613 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1614 		BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY);
1615 		SelectObject(hdcMem, hbmOld);
1616 		DeleteDC(hdcMem);
1617 
1618 		got = i.toTrueColorImage();
1619 	} else featureNotImplemented();
1620 
1621 	return got;
1622 }
1623 
1624 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE);
1625 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow;
1626 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi;
1627 
1628 version(Windows)
1629 shared static this() {
1630 	auto lib = LoadLibrary("User32.dll");
1631 	if(lib is null)
1632 		return;
1633 	scope(exit)
1634 		FreeLibrary(lib);
1635 
1636 	SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext");
1637 
1638 	if(SetProcessDpiAwarenessContext is null)
1639 		return;
1640 
1641 	enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4;
1642 	if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
1643 		//writeln(GetLastError());
1644 	}
1645 
1646 	GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow");
1647 	SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi");
1648 }
1649 
1650 /++
1651 	The flagship window class.
1652 
1653 
1654 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1655 	out of more advanced or complex features of the underlying windowing system.
1656 
1657 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1658 	and get a suitable window to work with.
1659 
1660 	From there, you can opt into additional features, like custom resizability and OpenGL support
1661 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1662 	and customization flags with the final two constructor arguments.
1663 
1664 	If none of that works for you, you can also create a window using native function calls, then
1665 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1666 	though, if you do this, managing the window is still your own responsibility! Notably, you
1667 	will need to destroy it yourself.
1668 +/
1669 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1670 
1671 	/++
1672 		Copies the window's current state into a [TrueColorImage].
1673 
1674 		Be warned: this can be a very slow operation
1675 
1676 		History:
1677 			Actually implemented on March 14, 2021
1678 	+/
1679 	TrueColorImage takeScreenshot() {
1680 		version(Windows)
1681 			return trueColorImageFromNativeHandle(impl.hwnd, width, height);
1682 		else version(OSXCocoa)
1683 			throw new NotYetImplementedException();
1684 		else
1685 			return trueColorImageFromNativeHandle(impl.window, width, height);
1686 	}
1687 
1688 	/++
1689 		Returns the actual physical DPI for the window on its current display monitor. If the window
1690 		straddles monitors, it will return the value of one or the other in a platform-defined manner.
1691 
1692 		Please note this function may return zero if it doesn't know the answer!
1693 
1694 
1695 		On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10),
1696 		or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up).
1697 
1698 		On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems,
1699 		this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the
1700 		window primarily resides on by checking the center point of the window against the monitor map.
1701 
1702 		Returns:
1703 			0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It
1704 			assumes the X and Y dpi are the same.
1705 
1706 		History:
1707 			Added November 26, 2021 (dub v10.4)
1708 
1709 		Bugs:
1710 			Probably plenty. I haven't done a lot of tests on this. I know it doesn't
1711 
1712 		See_Also:
1713 			[getDpi] gives the value provided for the default monitor. Not necessarily the same
1714 			as this since the window many be on a different monitor, but it is a reasonable fallback
1715 			to use if `actualDpi` returns 0.
1716 
1717 			[onDpiChanged] is changed when `actualDpi` has changed.
1718 	+/
1719 	int actualDpi() {
1720 		if(!actualDpiLoadAttempted) {
1721 			// FIXME: do the actual monitor we are on
1722 			// and on X this is a good chance to load the monitor map.
1723 			version(Windows) {
1724 				if(GetDpiForWindow)
1725 					actualDpi_ = GetDpiForWindow(impl.hwnd);
1726 			} else version(X11) {
1727 				if(!xRandrInfoLoadAttemped) {
1728 					xRandrInfoLoadAttemped = true;
1729 					if(!XRandrLibrary.attempted) {
1730 						XRandrLibrary.loadDynamicLibrary();
1731 					}
1732 
1733 					if(XRandrLibrary.loadSuccessful) {
1734 						auto display = XDisplayConnection.get;
1735 						int scratch;
1736 						int major, minor;
1737 						if(!XRRQueryExtension(display, &xrrEventBase, &scratch))
1738 							goto fallback;
1739 
1740 						XRRQueryVersion(display, &major, &minor);
1741 						if(major <= 1 && minor < 5)
1742 							goto fallback;
1743 
1744 						int count;
1745 						XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count);
1746 						if(monitors is null)
1747 							goto fallback;
1748 						scope(exit) XRRFreeMonitors(monitors);
1749 
1750 						MonitorInfo.info = MonitorInfo.info[0 .. 0];
1751 						MonitorInfo.info.assumeSafeAppend();
1752 						foreach(monitor; monitors[0 .. count]) {
1753 							if(monitor.mwidth == 0 || monitor.mheight == 0)
1754 							// unknown physical size, just guess 96 to avoid divide by zero
1755 							MonitorInfo.info ~= MonitorInfo(
1756 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1757 								Size(monitor.mwidth, monitor.mheight),
1758 								96
1759 							);
1760 							else
1761 							// and actual thing
1762 							MonitorInfo.info ~= MonitorInfo(
1763 								Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)),
1764 								Size(monitor.mwidth, monitor.mheight),
1765 								minInternal(
1766 									// millimeter to int then rounding up.
1767 									cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5),
1768 									cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5)
1769 								)
1770 							);
1771 						}
1772 						//import std.stdio; writeln("Here", MonitorInfo.info);
1773 					}
1774 				}
1775 
1776 				if(XRandrLibrary.loadSuccessful) {
1777 					updateActualDpi(true);
1778 					//import std.stdio; writeln("updated");
1779 
1780 					if(!requestedInput) {
1781 						// this is what requests live updates should the configuration change
1782 						// each time you select input, it sends an initial event, so very important
1783 						// to not get into a loop of selecting input, getting event, updating data,
1784 						// and reselecting input...
1785 						requestedInput = true;
1786 						XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask);
1787 						//import std.stdio; writeln("requested input");
1788 					}
1789 				} else {
1790 					fallback:
1791 					// make sure we disable events that aren't coming
1792 					xrrEventBase = -1;
1793 					// best guess...
1794 					actualDpi_ = cast(int) getDpi()[0];
1795 				}
1796 			}
1797 			actualDpiLoadAttempted = true;
1798 		}
1799 		return actualDpi_;
1800 	}
1801 
1802 	private int actualDpi_;
1803 	private bool actualDpiLoadAttempted;
1804 
1805 	version(X11) private {
1806 		bool requestedInput;
1807 		static bool xRandrInfoLoadAttemped;
1808 		struct MonitorInfo {
1809 			Rectangle position;
1810 			Size size;
1811 			int dpi;
1812 
1813 			static MonitorInfo[] info;
1814 		}
1815 		int screenPositionX;
1816 		int screenPositionY;
1817 		void updateActualDpi(bool loadingNow = false) {
1818 			if(!loadingNow && !actualDpiLoadAttempted)
1819 				actualDpi(); // just to make it do the load 
1820 			foreach(idx, m; MonitorInfo.info) {
1821 				if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) {
1822 					bool changed = actualDpi_ && actualDpi_ != m.dpi;
1823 					actualDpi_ = m.dpi;
1824 					//import std.stdio; writeln("monitor ", idx);
1825 					if(changed && onDpiChanged)
1826 						onDpiChanged();
1827 					break;
1828 				}
1829 			}
1830 		}
1831 	}
1832 
1833 	/++
1834 		Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors
1835 		or if the window is moved to a new remote connection or a monitor is hot-swapped.
1836 
1837 		History:
1838 			Added November 26, 2021 (dub v10.4)
1839 
1840 		See_Also:
1841 			[actualDpi]
1842 	+/
1843 	void delegate() onDpiChanged;
1844 
1845 	version(X11) {
1846 		void recreateAfterDisconnect() {
1847 			if(!stateDiscarded) return;
1848 
1849 			if(_parent !is null && _parent.stateDiscarded)
1850 				_parent.recreateAfterDisconnect();
1851 
1852 			bool wasHidden = hidden;
1853 
1854 			activeScreenPainter = null; // should already be done but just to confirm
1855 
1856 			actualDpi_ = 0;
1857 			actualDpiLoadAttempted = false;
1858 			xRandrInfoLoadAttemped = false;
1859 
1860 			impl.createWindow(_width, _height, _title, openglMode, _parent);
1861 
1862 			if(auto dh = dropHandler) {
1863 				dropHandler = null;
1864 				enableDragAndDrop(this, dh);
1865 			}
1866 
1867 			if(recreateAdditionalConnectionState)
1868 				recreateAdditionalConnectionState();
1869 
1870 			hidden = wasHidden;
1871 			stateDiscarded = false;
1872 		}
1873 
1874 		bool stateDiscarded;
1875 		void discardConnectionState() {
1876 			if(XDisplayConnection.display)
1877 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
1878 			if(discardAdditionalConnectionState)
1879 				discardAdditionalConnectionState();
1880 			stateDiscarded = true;
1881 		}
1882 
1883 		void delegate() discardAdditionalConnectionState;
1884 		void delegate() recreateAdditionalConnectionState;
1885 
1886 	}
1887 
1888 	private DropHandler dropHandler;
1889 
1890 	SimpleWindow _parent;
1891 	bool beingOpenKeepsAppOpen = true;
1892 	/++
1893 		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.
1894 
1895 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
1896 
1897 		Params:
1898 
1899 		width = the width of the window's client area, in pixels
1900 		height = the height of the window's client area, in pixels
1901 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
1902 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
1903 		resizable = [Resizability] has three options:
1904 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
1905 			$(P `fixedSize` will not allow the user to resize the window.)
1906 			$(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.)
1907 		windowType = The type of window you want to make.
1908 		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.
1909 		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".
1910 	+/
1911 	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) {
1912 		claimGuiThread();
1913 		version(sdpy_thread_checks) assert(thisIsGuiThread);
1914 		this._width = width;
1915 		this._height = height;
1916 		this.openglMode = opengl;
1917 		this.resizability = resizable;
1918 		this.windowType = windowType;
1919 		this.customizationFlags = customizationFlags;
1920 		this._title = (title is null ? "D Application" : title);
1921 		this._parent = parent;
1922 		impl.createWindow(width, height, this._title, opengl, parent);
1923 
1924 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
1925 			beingOpenKeepsAppOpen = false;
1926 	}
1927 
1928 	/// ditto
1929 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
1930 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
1931 	}
1932 
1933 	/// Same as above, except using the `Size` struct instead of separate width and height.
1934 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
1935 		this(size.width, size.height, title, opengl, resizable);
1936 	}
1937 
1938 	/// ditto
1939 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
1940 		this(size, title, opengl, resizable);
1941 	}
1942 
1943 
1944 	/++
1945 		Creates a window based on the given [Image]. It's client area
1946 		width and height is equal to the image. (A window's client area
1947 		is the drawable space inside; it excludes the title bar, etc.)
1948 
1949 		Windows based on images will not be resizable and do not use OpenGL.
1950 
1951 		It will draw the image in upon creation, but this will be overwritten
1952 		upon any draws, including the initial window visible event.
1953 
1954 		You probably do not want to use this and it may be removed from
1955 		the library eventually, or I might change it to be a "permanent"
1956 		background image; one that is automatically drawn on it before any
1957 		other drawing event. idk.
1958 	+/
1959 	this(Image image, string title = null) {
1960 		this(image.width, image.height, title);
1961 		this.image = image;
1962 	}
1963 
1964 	/++
1965 		Wraps a native window handle with very little additional processing - notably no destruction
1966 		this is incomplete so don't use it for much right now. The purpose of this is to make native
1967 		windows created through the low level API (so you can use platform-specific options and
1968 		other details SimpleWindow does not expose) available to the event loop wrappers.
1969 	+/
1970 	this(NativeWindowHandle nativeWindow) {
1971 		version(Windows)
1972 			impl.hwnd = nativeWindow;
1973 		else version(X11) {
1974 			impl.window = nativeWindow;
1975 			if(nativeWindow)
1976 				display = XDisplayConnection.get(); // get initial display to not segfault
1977 		} else version(OSXCocoa)
1978 			throw new NotYetImplementedException();
1979 		else featureNotImplemented();
1980 		// FIXME: set the size correctly
1981 		_width = 1;
1982 		_height = 1;
1983 		if(nativeWindow)
1984 			nativeMapping[nativeWindow] = this;
1985 
1986 		beingOpenKeepsAppOpen = false;
1987 
1988 		if(nativeWindow)
1989 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
1990 		_suppressDestruction = true; // so it doesn't try to close
1991 	}
1992 
1993 	/// Experimental, do not use yet
1994 	/++
1995 		Grabs exclusive input from the user until you release it with
1996 		[releaseInputGrab].
1997 
1998 
1999 		Note: it is extremely rude to do this without good reason.
2000 		Reasons may include doing some kind of mouse drag operation
2001 		or popping up a temporary menu that should get events and will
2002 		be dismissed at ease by the user clicking away.
2003 
2004 		Params:
2005 			keyboard = do you want to grab keyboard input?
2006 			mouse = grab mouse input?
2007 			confine = confine the mouse cursor to inside this window?
2008 
2009 		History:
2010 			Prior to March 11, 2021, grabbing the keyboard would always also
2011 			set the X input focus. Now, it only focuses if it is a non-transient
2012 			window and otherwise manages the input direction internally.
2013 
2014 			This means spurious focus/blur events will no longer be sent and the
2015 			application will not steal focus from other applications (which the
2016 			window manager may have rejected anyway).
2017 	+/
2018 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
2019 		static if(UsingSimpledisplayX11) {
2020 			XSync(XDisplayConnection.get, 0);
2021 			if(keyboard) {
2022 				if(isTransient && _parent) {
2023 					/*
2024 					FIXME:
2025 						setting the keyboard focus is not actually that helpful, what I more likely want
2026 						is the events from the parent window to be sent over here if we're transient.
2027 					*/
2028 
2029 					_parent.inputProxy = this;
2030 				} else {
2031 					XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
2032 				}
2033 			}
2034 			if(mouse) {
2035 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 
2036 				EventMask.PointerMotionMask // FIXME: not efficient
2037 				| EventMask.ButtonPressMask
2038 				| EventMask.ButtonReleaseMask
2039 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
2040 				)
2041 			{
2042 				XSync(XDisplayConnection.get, 0);
2043 				import core.stdc.stdio;
2044 				printf("Grab input failed %d\n", res);
2045 				//throw new Exception("Grab input failed");
2046 			} else {
2047 				// cool
2048 			}
2049 			}
2050 
2051 		} else version(Windows) {
2052 			// FIXME: keyboard?
2053 			SetCapture(impl.hwnd);
2054 			if(confine) {
2055 				RECT rcClip;
2056 				//RECT rcOldClip;
2057 				//GetClipCursor(&rcOldClip); 
2058 				GetWindowRect(hwnd, &rcClip); 
2059 				ClipCursor(&rcClip); 
2060 			}
2061 		} else version(OSXCocoa) {
2062 			throw new NotYetImplementedException();
2063 		} else static assert(0);
2064 	}
2065 
2066 	/++
2067 		Returns the native window.
2068 
2069 		History:
2070 			Added November 5, 2021 (dub v10.4). Prior to that, you'd have
2071 			to access it through the `impl` member (which is semi-supported
2072 			but platform specific and here it is simple enough to offer an accessor).
2073 
2074 		Bugs:
2075 			Not implemented outside Windows or X11.
2076 	+/
2077 	NativeWindowHandle nativeWindowHandle() {
2078 		version(X11)
2079 			return impl.window;
2080 		else version(Windows)
2081 			return impl.hwnd;
2082 		else
2083 			throw new NotYetImplementedException();
2084 	}
2085 
2086 	private bool isTransient() {
2087 		with(WindowTypes)
2088 		final switch(windowType) {
2089 			case normal, undecorated, eventOnly:
2090 			case nestedChild:
2091 				return (customizationFlags & WindowFlags.transient) ? true : false;
2092 			case dropdownMenu, popupMenu, notification:
2093 				return true;
2094 		}
2095 	}
2096 
2097 	private SimpleWindow inputProxy;
2098 
2099 	/++
2100 		Releases the grab acquired by [grabInput].
2101 	+/
2102 	void releaseInputGrab() {
2103 		static if(UsingSimpledisplayX11) {
2104 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
2105 			if(_parent)
2106 				_parent.inputProxy = null;
2107 		} else version(Windows) {
2108 			ReleaseCapture();
2109 			ClipCursor(null); 
2110 		} else version(OSXCocoa) {
2111 			throw new NotYetImplementedException();
2112 		} else static assert(0);
2113 	}
2114 
2115 	/++
2116 		Sets the input focus to this window.
2117 
2118 		You shouldn't call this very often - please let the user control the input focus.
2119 	+/
2120 	void focus() {
2121 		static if(UsingSimpledisplayX11) {
2122 			XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
2123 		} else version(Windows) {
2124 			SetFocus(this.impl.hwnd);
2125 		} else version(OSXCocoa) {
2126 			throw new NotYetImplementedException();
2127 		} else static assert(0);
2128 	}
2129 
2130 	/++
2131 		Requests attention from the user for this window.
2132 
2133 
2134 		The typical result of this function is to change the color
2135 		of the taskbar icon, though it may be tweaked on specific
2136 		platforms.
2137 
2138 		It is meant to unobtrusively tell the user that something
2139 		relevant to them happened in the background and they should
2140 		check the window when they get a chance. Upon receiving the
2141 		keyboard focus, the window will automatically return to its
2142 		natural state.
2143 
2144 		If the window already has the keyboard focus, this function
2145 		may do nothing, because the user is presumed to already be
2146 		giving the window attention.
2147 
2148 		Implementation_note:
2149 
2150 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
2151 		atom on X11 and the FlashWindow function on Windows.
2152 	+/
2153 	void requestAttention() {
2154 		if(_focused)
2155 			return;
2156 
2157 		version(Windows) {
2158 			FLASHWINFO info;
2159 			info.cbSize = info.sizeof;
2160 			info.hwnd = impl.hwnd;
2161 			info.dwFlags = FLASHW_TRAY;
2162 			info.uCount = 1;
2163 
2164 			FlashWindowEx(&info);
2165 
2166 		} else version(X11) {
2167 			demandingAttention = true;
2168 			demandAttention(this, true);
2169 		} else version(OSXCocoa) {
2170 			throw new NotYetImplementedException();
2171 		} else static assert(0);
2172 	}
2173 
2174 	private bool _focused;
2175 
2176 	version(X11) private bool demandingAttention;
2177 
2178 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
2179 	/// You'll have to call `close()` manually if you set this delegate.
2180 	void delegate () closeQuery;
2181 
2182 	/// This will be called when window visibility was changed.
2183 	void delegate (bool becomesVisible) visibilityChanged;
2184 
2185 	/// This will be called when window becomes visible for the first time.
2186 	/// You can do OpenGL initialization here. Note that in X11 you can't call
2187 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
2188 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
2189 	private bool _visibleForTheFirstTimeCalled;
2190 	void delegate () visibleForTheFirstTime;
2191 
2192 	/// Returns true if the window has been closed.
2193 	final @property bool closed() { return _closed; }
2194 
2195 	/// Returns true if the window is focused.
2196 	final @property bool focused() { return _focused; }
2197 
2198 	private bool _visible;
2199 	/// Returns true if the window is visible (mapped).
2200 	final @property bool visible() { return _visible; }
2201 
2202 	/// Closes the window. If there are no more open windows, the event loop will terminate.
2203 	void close() {
2204 		if (!_closed) {
2205 			runInGuiThread( {
2206 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
2207 				if (onClosing !is null) onClosing();
2208 				impl.closeWindow();
2209 				_closed = true;
2210 			} );
2211 		}
2212 	}
2213 
2214 	/++
2215 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
2216 
2217 		History:
2218 			Overload added on March 7, 2021.
2219 	+/
2220 	void close() shared {
2221 		(cast() this).close();
2222 	}
2223 
2224 	/++
2225 
2226 	+/
2227 	void maximize() {
2228 		version(Windows)
2229 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
2230 		else version(X11) {
2231 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
2232 
2233 			// also note _NET_WM_STATE_FULLSCREEN
2234 		}
2235 
2236 	}
2237 
2238 	private bool _fullscreen;
2239 
2240 	/// not fully implemented but planned for a future release
2241 	void fullscreen(bool yes) {
2242 		version(X11)
2243 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
2244 
2245 		_fullscreen = yes;
2246 
2247 	}
2248 
2249 	bool fullscreen() {
2250 		return _fullscreen;
2251 	}
2252 
2253 	/++
2254 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
2255 
2256 	+/
2257 	void minimize() {
2258 		version(Windows)
2259 			ShowWindow(impl.hwnd, SW_MINIMIZE);
2260 		//else version(X11)
2261 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
2262 	}
2263 
2264 	/// Alias for `hidden = false`
2265 	void show() {
2266 		hidden = false;
2267 	}
2268 
2269 	/// Alias for `hidden = true`
2270 	void hide() {
2271 		hidden = true;
2272 	}
2273 
2274 	/// Hide cursor when it enters the window.
2275 	void hideCursor() {
2276 		version(OSXCocoa) throw new NotYetImplementedException(); else
2277 		if (!_closed) impl.hideCursor();
2278 	}
2279 
2280 	/// Don't hide cursor when it enters the window.
2281 	void showCursor() {
2282 		version(OSXCocoa) throw new NotYetImplementedException(); else
2283 		if (!_closed) impl.showCursor();
2284 	}
2285 
2286 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
2287 	 *
2288 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
2289 	 * control. Try to think for other approaches before using this function.
2290 	 *
2291 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
2292 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
2293 	 *       receive "mouse moved here" event.
2294 	 */
2295 	bool warpMouse (int x, int y) {
2296 		version(X11) {
2297 			if (!_closed) { impl.warpMouse(x, y); return true; }
2298 		} else version(Windows) {
2299 			if (!_closed) {
2300 				POINT point;
2301 				point.x = x;
2302 				point.y = y;
2303 				if(ClientToScreen(impl.hwnd, &point)) {
2304 					SetCursorPos(point.x, point.y);
2305 					return true;
2306 				}
2307 			}
2308 		}
2309 		return false;
2310 	}
2311 
2312 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
2313 	void sendDummyEvent () {
2314 		version(X11) {
2315 			if (!_closed) { impl.sendDummyEvent(); }
2316 		}
2317 	}
2318 
2319 	/// Set window minimal size.
2320 	void setMinSize (int minwidth, int minheight) {
2321 		version(OSXCocoa) throw new NotYetImplementedException(); else
2322 		if (!_closed) impl.setMinSize(minwidth, minheight);
2323 	}
2324 
2325 	/// Set window maximal size.
2326 	void setMaxSize (int maxwidth, int maxheight) {
2327 		version(OSXCocoa) throw new NotYetImplementedException(); else
2328 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
2329 	}
2330 
2331 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
2332 	/// Currently only supported on X11.
2333 	void setResizeGranularity (int granx, int grany) {
2334 		version(OSXCocoa) throw new NotYetImplementedException(); else
2335 		if (!_closed) impl.setResizeGranularity(granx, grany);
2336 	}
2337 
2338 	/// Move window.
2339 	void move(int x, int y) {
2340 		version(OSXCocoa) throw new NotYetImplementedException(); else
2341 		if (!_closed) impl.move(x, y);
2342 	}
2343 
2344 	/// ditto
2345 	void move(Point p) {
2346 		version(OSXCocoa) throw new NotYetImplementedException(); else
2347 		if (!_closed) impl.move(p.x, p.y);
2348 	}
2349 
2350 	/++
2351 		Resize window.
2352 
2353 		Note that the width and height of the window are NOT instantly
2354 		updated - it waits for the window manager to approve the resize
2355 		request, which means you must return to the event loop before the
2356 		width and height are actually changed.
2357 	+/
2358 	void resize(int w, int h) {
2359 		if(!_closed && _fullscreen) fullscreen = false;
2360 		version(OSXCocoa) throw new NotYetImplementedException(); else
2361 		if (!_closed) impl.resize(w, h);
2362 	}
2363 
2364 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
2365 	void moveResize (int x, int y, int w, int h) {
2366 		if(!_closed && _fullscreen) fullscreen = false;
2367 		version(OSXCocoa) throw new NotYetImplementedException(); else
2368 		if (!_closed) impl.moveResize(x, y, w, h);
2369 	}
2370 
2371 	private bool _hidden;
2372 
2373 	/// Returns true if the window is hidden.
2374 	final @property bool hidden() {
2375 		return _hidden;
2376 	}
2377 
2378 	/// Shows or hides the window based on the bool argument.
2379 	final @property void hidden(bool b) {
2380 		_hidden = b;
2381 		version(Windows) {
2382 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2383 		} else version(X11) {
2384 			if(b)
2385 				//XUnmapWindow(impl.display, impl.window);
2386 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2387 			else
2388 				XMapWindow(impl.display, impl.window);
2389 		} else version(OSXCocoa) {
2390 			throw new NotYetImplementedException();
2391 		} else static assert(0);
2392 	}
2393 
2394 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2395 	void opacity(double opacity) @property
2396 	in {
2397 		assert(opacity >= 0 && opacity <= 1);
2398 	} do {
2399 		version (Windows) {
2400 			impl.setOpacity(cast(ubyte)(255 * opacity));
2401 		} else version (X11) {
2402 			impl.setOpacity(cast(uint)(uint.max * opacity));
2403 		} else throw new NotYetImplementedException();
2404 	}
2405 
2406 	/++
2407 		Sets your event handlers, without entering the event loop. Useful if you
2408 		have multiple windows - set the handlers on each window, then only do eventLoop on your main window.
2409 	+/
2410 	void setEventHandlers(T...)(T eventHandlers) {
2411 		// FIXME: add more events
2412 		foreach(handler; eventHandlers) {
2413 			static if(__traits(compiles, handleKeyEvent = handler)) {
2414 				handleKeyEvent = handler;
2415 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2416 				handleCharEvent = handler;
2417 			} else static if(__traits(compiles, handlePulse = handler)) {
2418 				handlePulse = handler;
2419 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2420 				handleMouseEvent = handler;
2421 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2422 		}
2423 	}
2424 
2425 	/// The event loop automatically returns when the window is closed
2426 	/// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2427 	/// pulse timer is created. The event loop will block until an event
2428 	/// arrives or the pulse timer goes off.
2429 	final int eventLoop(T...)(
2430 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2431 		T eventHandlers) /// delegate list like std.concurrency.receive
2432 	{
2433 		setEventHandlers(eventHandlers);
2434 
2435 		version(with_eventloop) {
2436 			// delegates event loop to my other module
2437 			version(X11)
2438 				XFlush(display);
2439 
2440 			import arsd.eventloop;
2441 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2442 			scope(exit) clearInterval(handle);
2443 
2444 			loop();
2445 			return 0;
2446 		} else version(OSXCocoa) {
2447 			// FIXME
2448 			if (handlePulse !is null && pulseTimeout != 0) {
2449 				timer = scheduledTimer(pulseTimeout*1e-3,
2450 					view, sel_registerName("simpledisplay_pulse"),
2451 					null, true);
2452 			}
2453 
2454             		setNeedsDisplay(view, true);
2455             		run(NSApp);
2456             		return 0;
2457         	} else {
2458 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2459 			return el.run();
2460 		}
2461 	}
2462 
2463 	/++
2464 		This lets you draw on the window (or its backing buffer) using basic
2465 		2D primitives.
2466 
2467 		Be sure to call this in a limited scope because your changes will not
2468 		actually appear on the window until ScreenPainter's destructor runs.
2469 
2470 		Returns: an instance of [ScreenPainter], which has the drawing methods
2471 		on it to draw on this window.
2472 	+/
2473 	ScreenPainter draw() {
2474 		return impl.getPainter();
2475 	}
2476 
2477 	// This is here to implement the interface we use for various native handlers.
2478 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2479 
2480 	// maps native window handles to SimpleWindow instances, if there are any
2481 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2482 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2483 
2484 	/// Width of the window's drawable client area, in pixels.
2485 	@scriptable
2486 	final @property int width() const pure nothrow @safe @nogc { return _width; }
2487 
2488 	/// Height of the window's drawable client area, in pixels.
2489 	@scriptable
2490 	final @property int height() const pure nothrow @safe @nogc { return _height; }
2491 
2492 	private int _width;
2493 	private int _height;
2494 
2495 	// HACK: making the best of some copy constructor woes with refcounting
2496 	private ScreenPainterImplementation* activeScreenPainter_;
2497 
2498 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2499 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2500 
2501 	private OpenGlOptions openglMode;
2502 	private Resizability resizability;
2503 	private WindowTypes windowType;
2504 	private int customizationFlags;
2505 
2506 	/// `true` if OpenGL was initialized for this window.
2507 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2508 		version(without_opengl)
2509 			return false;
2510 		else
2511 			return (openglMode == OpenGlOptions.yes);
2512 	}
2513 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2514 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2515 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2516 
2517 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2518 	/// to call this, as it's not recommended to share window between threads.
2519 	void mtLock () {
2520 		version(X11) {
2521 			XLockDisplay(this.display);
2522 		}
2523 	}
2524 
2525 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2526 	/// to call this, as it's not recommended to share window between threads.
2527 	void mtUnlock () {
2528 		version(X11) {
2529 			XUnlockDisplay(this.display);
2530 		}
2531 	}
2532 
2533 	/// Emit a beep to get user's attention.
2534 	void beep () {
2535 		version(X11) {
2536 			XBell(this.display, 100);
2537 		} else version(Windows) {
2538 			MessageBeep(0xFFFFFFFF);
2539 		}
2540 	}
2541 
2542 
2543 
2544 	version(without_opengl) {} else {
2545 
2546 		/// 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`.
2547 		void delegate() redrawOpenGlScene;
2548 
2549 		/// This will allow you to change OpenGL vsync state.
2550 		final @property void vsync (bool wait) {
2551 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2552 		  version(X11) {
2553 		    setAsCurrentOpenGlContext();
2554 		    glxSetVSync(display, impl.window, wait);
2555 		  } else version(Windows) {
2556 		    setAsCurrentOpenGlContext();
2557                     wglSetVSync(wait);
2558 		  }
2559 		}
2560 
2561 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
2562 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
2563 		/// enough without waiting 'em to finish their frame bussiness.
2564 		bool useGLFinish = true;
2565 
2566 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
2567 		/// 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.
2568 		void redrawOpenGlSceneNow() {
2569 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
2570 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2571 			if(redrawOpenGlScene is null)
2572 				return;
2573 
2574 			this.mtLock();
2575 			scope(exit) this.mtUnlock();
2576 
2577 			this.setAsCurrentOpenGlContext();
2578 
2579 			redrawOpenGlScene();
2580 
2581 			this.swapOpenGlBuffers();
2582 			// 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.
2583 			if (useGLFinish) glFinish();
2584 		}
2585 
2586 		private bool redrawOpenGlSceneSoonSet = false;
2587 		private static class RedrawOpenGlSceneEvent {
2588 			SimpleWindow w;
2589 			this(SimpleWindow w) { this.w = w; }
2590 		}
2591 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
2592 		/++
2593 			Queues an opengl redraw as soon as the other pending events are cleared.
2594 		+/
2595 		void redrawOpenGlSceneSoon() {
2596 			if(!redrawOpenGlSceneSoonSet) {
2597 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
2598 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
2599 				redrawOpenGlSceneSoonSet = true;
2600 			}
2601 			this.postEvent(redrawOpenGlSceneEvent, true);
2602 		}
2603 
2604 
2605 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2606 		void setAsCurrentOpenGlContext() {
2607 			assert(openglMode == OpenGlOptions.yes);
2608 			version(X11) {
2609 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
2610 					throw new Exception("glXMakeCurrent");
2611 			} else version(Windows) {
2612 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2613 				if (!wglMakeCurrent(ghDC, ghRC))
2614 					throw new Exception("wglMakeCurrent"); // let windows users suffer too
2615 			}
2616 		}
2617 
2618 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2619 		/// This doesn't throw, returning success flag instead.
2620 		bool setAsCurrentOpenGlContextNT() nothrow {
2621 			assert(openglMode == OpenGlOptions.yes);
2622 			version(X11) {
2623 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
2624 			} else version(Windows) {
2625 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2626 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
2627 			}
2628 		}
2629 
2630 		/// 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.
2631 		/// This doesn't throw, returning success flag instead.
2632 		bool releaseCurrentOpenGlContext() nothrow {
2633 			assert(openglMode == OpenGlOptions.yes);
2634 			version(X11) {
2635 				return (glXMakeCurrent(display, 0, null) != 0);
2636 			} else version(Windows) {
2637 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2638 				return wglMakeCurrent(ghDC, null) ? true : false;
2639 			}
2640 		}
2641 
2642 		/++
2643 			simpledisplay always uses double buffering, usually automatically. This
2644 			manually swaps the OpenGL buffers.
2645 
2646 
2647 			You should not need to call this yourself because simpledisplay will do it
2648 			for you after calling your `redrawOpenGlScene`.
2649 
2650 			Remember that this may throw an exception, which you can catch in a multithreaded
2651 			application to keep your thread from dying from an unhandled exception.
2652 		+/
2653 		void swapOpenGlBuffers() {
2654 			assert(openglMode == OpenGlOptions.yes);
2655 			version(X11) {
2656 				if (!this._visible) return; // no need to do this if window is invisible
2657 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2658 				glXSwapBuffers(display, impl.window);
2659 			} else version(Windows) {
2660 				SwapBuffers(ghDC);
2661 			}
2662 		}
2663 	}
2664 
2665 	/++
2666 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
2667 
2668 
2669 		---
2670 			auto window = new SimpleWindow(100, 100, "First title");
2671 			window.title = "A new title";
2672 		---
2673 
2674 		You may call this function at any time.
2675 	+/
2676 	@property void title(string title) {
2677 		_title = title;
2678 		version(OSXCocoa) throw new NotYetImplementedException(); else
2679 		impl.setTitle(title);
2680 	}
2681 
2682 	private string _title;
2683 
2684 	/// Gets the title
2685 	@property string title() {
2686 		if(_title is null)
2687 			_title = getRealTitle();
2688 		return _title;
2689 	}
2690 
2691 	/++
2692 		Get the title as set by the window manager.
2693 		May not match what you attempted to set.
2694 	+/
2695 	string getRealTitle() {
2696 		static if(is(typeof(impl.getTitle())))
2697 			return impl.getTitle();
2698 		else
2699 			return null;
2700 	}
2701 
2702 	// don't use this generally it is not yet really released
2703 	version(X11)
2704 	@property Image secret_icon() {
2705 		return secret_icon_inner;
2706 	}
2707 	private Image secret_icon_inner;
2708 
2709 
2710 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user.
2711 	@property void icon(MemoryImage icon) {
2712 		auto tci = icon.getAsTrueColorImage();
2713 		version(Windows) {
2714 			winIcon = new WindowsIcon(icon);
2715 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
2716 		} else version(X11) {
2717 			secret_icon_inner = Image.fromMemoryImage(icon);
2718 			// FIXME: ensure this is correct
2719 			auto display = XDisplayConnection.get;
2720 			arch_ulong[] buffer;
2721 			buffer ~= icon.width;
2722 			buffer ~= icon.height;
2723 			foreach(c; tci.imageData.colors) {
2724 				arch_ulong b;
2725 				b |= c.a << 24;
2726 				b |= c.r << 16;
2727 				b |= c.g << 8;
2728 				b |= c.b;
2729 				buffer ~= b;
2730 			}
2731 
2732 			XChangeProperty(
2733 				display,
2734 				impl.window,
2735 				GetAtom!("_NET_WM_ICON", true)(display),
2736 				GetAtom!"CARDINAL"(display),
2737 				32 /* bits */,
2738 				0 /*PropModeReplace*/,
2739 				buffer.ptr,
2740 				cast(int) buffer.length);
2741 		} else version(OSXCocoa) {
2742 			throw new NotYetImplementedException();
2743 		} else static assert(0);
2744 	}
2745 
2746 	version(Windows)
2747 		private WindowsIcon winIcon;
2748 
2749 	bool _suppressDestruction;
2750 
2751 	~this() {
2752 		if(_suppressDestruction)
2753 			return;
2754 		impl.dispose();
2755 	}
2756 
2757 	private bool _closed;
2758 
2759 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
2760 	/*
2761 	ScreenPainter drawTransiently() {
2762 		return impl.getPainter();
2763 	}
2764 	*/
2765 
2766 	/// Draws an image on the window. This is meant to provide quick look
2767 	/// of a static image generated elsewhere.
2768 	@property void image(Image i) {
2769 	/+
2770 		version(Windows) {
2771 			BITMAP bm;
2772 			HDC hdc = GetDC(hwnd);
2773 			HDC hdcMem = CreateCompatibleDC(hdc);
2774 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
2775 
2776 			GetObject(i.handle, bm.sizeof, &bm);
2777 
2778 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
2779 
2780 			SelectObject(hdcMem, hbmOld);
2781 			DeleteDC(hdcMem);
2782 			ReleaseDC(hwnd, hdc);
2783 
2784 			/*
2785 			RECT r;
2786 			r.right = i.width;
2787 			r.bottom = i.height;
2788 			InvalidateRect(hwnd, &r, false);
2789 			*/
2790 		} else
2791 		version(X11) {
2792 			if(!destroyed) {
2793 				if(i.usingXshm)
2794 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
2795 				else
2796 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
2797 			}
2798 		} else
2799 		version(OSXCocoa) {
2800 			draw().drawImage(Point(0, 0), i);
2801 			setNeedsDisplay(view, true);
2802 		} else static assert(0);
2803 	+/
2804 		auto painter = this.draw;
2805 		painter.drawImage(Point(0, 0), i);
2806 	}
2807 
2808 	/++
2809 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
2810 
2811 		---
2812 		window.cursor = GenericCursor.Help;
2813 		// now the window mouse cursor is set to a generic help
2814 		---
2815 
2816 	+/
2817 	@property void cursor(MouseCursor cursor) {
2818 		version(OSXCocoa)
2819 			featureNotImplemented();
2820 		else
2821 		if(this.impl.curHidden <= 0) {
2822 			static if(UsingSimpledisplayX11) {
2823 				auto ch = cursor.cursorHandle;
2824 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
2825 			} else version(Windows) {
2826 				auto ch = cursor.cursorHandle;
2827 				impl.currentCursor = ch;
2828 				SetCursor(ch); // redraw without waiting for mouse movement to update
2829 			} else featureNotImplemented();
2830 		}
2831 
2832 	}
2833 
2834 	/// What follows are the event handlers. These are set automatically
2835 	/// by the eventLoop function, but are still public so you can change
2836 	/// them later. wasPressed == true means key down. false == key up.
2837 
2838 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
2839 	void delegate(KeyEvent ke) handleKeyEvent;
2840 
2841 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
2842 	void delegate(dchar c) handleCharEvent;
2843 
2844 	/// Handles a timer pulse. Settable through setEventHandlers.
2845 	void delegate() handlePulse;
2846 
2847 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
2848 	void delegate(bool) onFocusChange;
2849 
2850 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
2851 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
2852 	void delegate() onClosing;
2853 
2854 	/** Called when we received destroy notification. At this stage we cannot do much with our window
2855 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
2856 	 * last minute cleanup. */
2857 	void delegate() onDestroyed;
2858 
2859 	static if (UsingSimpledisplayX11)
2860 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
2861 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
2862 	 * You will probably never need to setup this handler, it is for very low-level stuff.
2863 	 *
2864 	 * WARNING! Xlib is multithread-locked when this handles is called! */
2865 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
2866 
2867 	//version(Windows)
2868 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
2869 
2870 	private {
2871 		int lastMouseX = int.min;
2872 		int lastMouseY = int.min;
2873 		void mdx(ref MouseEvent ev) {
2874 			if(lastMouseX == int.min || lastMouseY == int.min) {
2875 				ev.dx = 0;
2876 				ev.dy = 0;
2877 			} else {
2878 				ev.dx = ev.x - lastMouseX;
2879 				ev.dy = ev.y - lastMouseY;
2880 			}
2881 
2882 			lastMouseX = ev.x;
2883 			lastMouseY = ev.y;
2884 		}
2885 	}
2886 
2887 	/// Mouse event handler. Settable through setEventHandlers.
2888 	void delegate(MouseEvent) handleMouseEvent;
2889 
2890 	/// use to redraw child widgets if you use system apis to add stuff
2891 	void delegate() paintingFinished;
2892 
2893 	void delegate() paintingFinishedDg() {
2894 		return paintingFinished;
2895 	}
2896 
2897 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
2898 	/// for this to ever happen.
2899 	void delegate(int width, int height) windowResized;
2900 
2901 	/++
2902 		Platform specific - handle any native message this window gets.
2903 
2904 		Note: this is called *in addition to* other event handlers, unless you either:
2905 
2906 		1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded.
2907 
2908 		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.
2909 
2910 		On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`.
2911 
2912 		On X, it takes the form of `int delegate(XEvent)`.
2913 
2914 		History:
2915 			In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead.
2916 
2917 			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.
2918 	+/
2919 	NativeEventHandler handleNativeEvent_;
2920 
2921 	@property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe {
2922 		return handleNativeEvent_;
2923 	}
2924 	@property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe {
2925 		handleNativeEvent_ = neh;
2926 	}
2927 
2928 	version(Windows)
2929 	// compatibility shim with the old deprecated way
2930 	// in this one, if you return 0, it means you must return. otherwise the ret value is ignored.
2931 	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) {
2932 		handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) {
2933 			auto ret = dg(h, m, w, l);
2934 			if(ret == 0)
2935 				r = 1;
2936 			return ret;
2937 		};
2938 	}
2939 
2940 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
2941 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
2942 	/// this instead and it will work the same way.
2943 	__gshared NativeEventHandler handleNativeGlobalEvent;
2944 
2945 //  private:
2946 	/// The native implementation is available, but you shouldn't use it unless you are
2947 	/// familiar with the underlying operating system, don't mind depending on it, and
2948 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
2949 	/// do what you need to do with handleNativeEvent instead.
2950 	///
2951 	/// This is likely to eventually change to be just a struct holding platform-specific
2952 	/// handles instead of a template mixin at some point because I'm not happy with the
2953 	/// code duplication here (ironically).
2954 	mixin NativeSimpleWindowImplementation!() impl;
2955 
2956 	/**
2957 		This is in-process one-way (from anything to window) event sending mechanics.
2958 		It is thread-safe, so it can be used in multi-threaded applications to send,
2959 		for example, "wake up and repaint" events when thread completed some operation.
2960 		This will allow to avoid using timer pulse to check events with synchronization,
2961 		'cause event handler will be called in UI thread. You can stop guessing which
2962 		pulse frequency will be enough for your app.
2963 		Note that events handlers may be called in arbitrary order, i.e. last registered
2964 		handler can be called first, and vice versa.
2965 	*/
2966 public:
2967 	/** Is our custom event queue empty? Can be used in simple cases to prevent
2968 	 * "spamming" window with events it can't cope with.
2969 	 * It is safe to call this from non-UI threads.
2970 	 */
2971 	@property bool eventQueueEmpty() () {
2972 		synchronized(this) {
2973 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
2974 		}
2975 		return true;
2976 	}
2977 
2978 	/** Does our custom event queue contains at least one with the given type?
2979 	 * Can be used in simple cases to prevent "spamming" window with events
2980 	 * it can't cope with.
2981 	 * It is safe to call this from non-UI threads.
2982 	 */
2983 	@property bool eventQueued(ET:Object) () {
2984 		synchronized(this) {
2985 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
2986 				if (!o.doProcess) {
2987 					if (cast(ET)(o.evt)) return true;
2988 				}
2989 			}
2990 		}
2991 		return false;
2992 	}
2993 
2994 	/++
2995 		Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds.
2996 
2997 		History:
2998 			Added May 12, 2021
2999 	+/
3000 	void delegate(Exception e) nothrow eventUncaughtException;
3001 
3002 	/** Add listener for custom event. Can be used like this:
3003 	 *
3004 	 * ---------------------
3005 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
3006 	 *   ...
3007 	 *   win.removeEventListener(eid);
3008 	 * ---------------------
3009 	 *
3010 	 * Returns: 0 on failure (should never happen, so ignore it)
3011 	 *
3012 	 * $(WARNING Don't use this method in object destructors!)
3013 	 *
3014 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
3015 	 *           'cause if event handler id counter will overflow, you won't be able
3016 	 *           to register any more events.)
3017 	 */
3018 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
3019 		if (dg is null) return 0; // ignore empty handlers
3020 		synchronized(this) {
3021 			//FIXME: abort on overflow?
3022 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
3023 			EventHandlerEntry e;
3024 			e.dg = delegate (Object o) {
3025 				if (auto co = cast(ET)o) {
3026 					try {
3027 						dg(co);
3028 					} catch (Exception e) {
3029 						// sorry!
3030 						if(eventUncaughtException)
3031 							eventUncaughtException(e);
3032 					}
3033 					return true;
3034 				}
3035 				return false;
3036 			};
3037 			e.id = lastUsedHandlerId;
3038 			auto optr = eventHandlers.ptr;
3039 			eventHandlers ~= e;
3040 			if (eventHandlers.ptr !is optr) {
3041 				import core.memory : GC;
3042 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
3043 			}
3044 			return lastUsedHandlerId;
3045 		}
3046 	}
3047 
3048 	/// Remove event listener. It is safe to pass invalid event id here.
3049 	/// $(WARNING Don't use this method in object destructors!)
3050 	void removeEventListener() (uint id) {
3051 		if (id == 0 || id > lastUsedHandlerId) return;
3052 		synchronized(this) {
3053 			foreach (immutable idx; 0..eventHandlers.length) {
3054 				if (eventHandlers[idx].id == id) {
3055 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
3056 					eventHandlers[$-1].dg = null;
3057 					eventHandlers.length -= 1;
3058 					eventHandlers.assumeSafeAppend;
3059 					return;
3060 				}
3061 			}
3062 		}
3063 	}
3064 
3065 	/// Post event to queue. It is safe to call this from non-UI threads.
3066 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
3067 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3068 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3069 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
3070 		if (this.closed) return false; // closed windows can't handle events
3071 
3072 		// remove all events of type `ET`
3073 		void removeAllET () {
3074 			uint eidx = 0, ec = eventQueueUsed;
3075 			auto eptr = eventQueue.ptr;
3076 			while (eidx < ec) {
3077 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
3078 				if (cast(ET)eptr.evt !is null) {
3079 					// i found her!
3080 					if (inCustomEventProcessor) {
3081 						// if we're in custom event processing loop, processor will clear it for us
3082 						eptr.evt = null;
3083 						++eidx;
3084 						++eptr;
3085 					} else {
3086 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3087 						ec = --eventQueueUsed;
3088 						// clear last event (it is already copied)
3089 						eventQueue.ptr[ec].evt = null;
3090 					}
3091 				} else {
3092 					++eidx;
3093 					++eptr;
3094 				}
3095 			}
3096 		}
3097 
3098 		if (evt is null) {
3099 			if (replace) { synchronized(this) removeAllET(); }
3100 			// ignore empty events, they can't be handled anyway
3101 			return false;
3102 		}
3103 
3104 		// add events even if no event FD/event object created yet
3105 		synchronized(this) {
3106 			if (replace) removeAllET();
3107 			if (eventQueueUsed == uint.max) return false; // just in case
3108 			if (eventQueueUsed < eventQueue.length) {
3109 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
3110 			} else {
3111 				if (eventQueue.capacity == eventQueue.length) {
3112 					// need to reallocate; do a trick to ensure that old array is cleared
3113 					auto oarr = eventQueue;
3114 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3115 					// just in case, do yet another check
3116 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
3117 					import core.memory : GC;
3118 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
3119 				} else {
3120 					auto optr = eventQueue.ptr;
3121 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
3122 					assert(eventQueue.ptr is optr);
3123 				}
3124 				++eventQueueUsed;
3125 				assert(eventQueueUsed == eventQueue.length);
3126 			}
3127 			if (!eventWakeUp()) {
3128 				// can't wake up event processor, so there is no reason to keep the event
3129 				assert(eventQueueUsed > 0);
3130 				eventQueue[--eventQueueUsed].evt = null;
3131 				return false;
3132 			}
3133 			return true;
3134 		}
3135 	}
3136 
3137 	/// Post event to queue. It is safe to call this from non-UI threads.
3138 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
3139 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
3140 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
3141 		return postTimeout!ET(evt, 0, replace);
3142 	}
3143 
3144 private:
3145 	private import core.time : MonoTime;
3146 
3147 	version(Posix) {
3148 		__gshared int customEventFDRead = -1;
3149 		__gshared int customEventFDWrite = -1;
3150 		__gshared int customSignalFD = -1;
3151 	} else version(Windows) {
3152 		__gshared HANDLE customEventH = null;
3153 	}
3154 
3155 	// wake up event processor
3156 	static bool eventWakeUp () {
3157 		version(X11) {
3158 			import core.sys.posix.unistd : write;
3159 			ulong n = 1;
3160 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
3161 			return true;
3162 		} else version(Windows) {
3163 			if (customEventH !is null) SetEvent(customEventH);
3164 			return true;
3165 		} else {
3166 			// not implemented for other OSes
3167 			return false;
3168 		}
3169 	}
3170 
3171 	static struct QueuedEvent {
3172 		Object evt;
3173 		bool timed = false;
3174 		MonoTime hittime = MonoTime.zero;
3175 		bool doProcess = false; // process event at the current iteration (internal flag)
3176 
3177 		this (Object aevt, uint toutmsecs) {
3178 			evt = aevt;
3179 			if (toutmsecs > 0) {
3180 				import core.time : msecs;
3181 				timed = true;
3182 				hittime = MonoTime.currTime+toutmsecs.msecs;
3183 			}
3184 		}
3185 	}
3186 
3187 	alias CustomEventHandler = bool delegate (Object o) nothrow;
3188 	static struct EventHandlerEntry {
3189 		CustomEventHandler dg;
3190 		uint id;
3191 	}
3192 
3193 	uint lastUsedHandlerId;
3194 	EventHandlerEntry[] eventHandlers;
3195 	QueuedEvent[] eventQueue = null;
3196 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
3197 	bool inCustomEventProcessor = false; // required to properly remove events
3198 
3199 	// process queued events and call custom event handlers
3200 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
3201 	void processCustomEvents () {
3202 		bool hasSomethingToDo = false;
3203 		uint ecount;
3204 		bool ocep;
3205 		synchronized(this) {
3206 			ocep = inCustomEventProcessor;
3207 			inCustomEventProcessor = true;
3208 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
3209 			auto ctt = MonoTime.currTime;
3210 			bool hasEmpty = false;
3211 			// mark events to process (this is required for `eventQueued()`)
3212 			foreach (ref qe; eventQueue[0..ecount]) {
3213 				if (qe.evt is null) { hasEmpty = true; continue; }
3214 				if (qe.timed) {
3215 					qe.doProcess = (qe.hittime <= ctt);
3216 				} else {
3217 					qe.doProcess = true;
3218 				}
3219 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
3220 			}
3221 			if (!hasSomethingToDo) {
3222 				// remove empty events
3223 				if (hasEmpty) {
3224 					uint eidx = 0, ec = eventQueueUsed;
3225 					auto eptr = eventQueue.ptr;
3226 					while (eidx < ec) {
3227 						if (eptr.evt is null) {
3228 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3229 							ec = --eventQueueUsed;
3230 							eventQueue.ptr[ec].evt = null; // make GC life easier
3231 						} else {
3232 							++eidx;
3233 							++eptr;
3234 						}
3235 					}
3236 				}
3237 				inCustomEventProcessor = ocep;
3238 				return;
3239 			}
3240 		}
3241 		// process marked events
3242 		uint efree = 0; // non-processed events will be put at this index
3243 		EventHandlerEntry[] eh;
3244 		Object evt;
3245 		foreach (immutable eidx; 0..ecount) {
3246 			synchronized(this) {
3247 				if (!eventQueue[eidx].doProcess) {
3248 					// skip this event
3249 					assert(efree <= eidx);
3250 					if (efree != eidx) {
3251 						// copy this event to queue start
3252 						eventQueue[efree] = eventQueue[eidx];
3253 						eventQueue[eidx].evt = null; // just in case
3254 					}
3255 					++efree;
3256 					continue;
3257 				}
3258 				evt = eventQueue[eidx].evt;
3259 				eventQueue[eidx].evt = null; // in case event handler will hit GC
3260 				if (evt is null) continue; // just in case
3261 				// try all handlers; this can be slow, but meh...
3262 				eh = eventHandlers;
3263 			}
3264 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
3265 			evt = null;
3266 			eh = null;
3267 		}
3268 		synchronized(this) {
3269 			// move all unprocessed events to queue top; efree holds first "free index"
3270 			foreach (immutable eidx; ecount..eventQueueUsed) {
3271 				assert(efree <= eidx);
3272 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
3273 				++efree;
3274 			}
3275 			eventQueueUsed = efree;
3276 			// wake up event processor on next event loop iteration if we have more queued events
3277 			// also, remove empty events
3278 			bool awaken = false;
3279 			uint eidx = 0, ec = eventQueueUsed;
3280 			auto eptr = eventQueue.ptr;
3281 			while (eidx < ec) {
3282 				if (eptr.evt is null) {
3283 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
3284 					ec = --eventQueueUsed;
3285 					eventQueue.ptr[ec].evt = null; // make GC life easier
3286 				} else {
3287 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
3288 					++eidx;
3289 					++eptr;
3290 				}
3291 			}
3292 			inCustomEventProcessor = ocep;
3293 		}
3294 	}
3295 
3296 	// for all windows in nativeMapping
3297 	static void processAllCustomEvents () {
3298 
3299 		justCommunication.processCustomEvents();
3300 
3301 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3302 			if (sw is null || sw.closed) continue;
3303 			sw.processCustomEvents();
3304 		}
3305 
3306 		runPendingRunInGuiThreadDelegates();
3307 	}
3308 
3309 	// 0: infinite (i.e. no scheduled events in queue)
3310 	uint eventQueueTimeoutMSecs () {
3311 		synchronized(this) {
3312 			if (eventQueueUsed == 0) return 0;
3313 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3314 			uint res = int.max;
3315 			auto ctt = MonoTime.currTime;
3316 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
3317 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
3318 				if (qe.doProcess) continue; // just in case
3319 				if (!qe.timed) return 1; // minimal
3320 				if (qe.hittime <= ctt) return 1; // minimal
3321 				auto tms = (qe.hittime-ctt).total!"msecs";
3322 				if (tms < 1) tms = 1; // safety net
3323 				if (tms >= int.max) tms = int.max-1; // and another safety net
3324 				if (res > tms) res = cast(uint)tms;
3325 			}
3326 			return (res >= int.max ? 0 : res);
3327 		}
3328 	}
3329 
3330 	// for all windows in nativeMapping
3331 	static uint eventAllQueueTimeoutMSecs () {
3332 		uint res = uint.max;
3333 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
3334 			if (sw is null || sw.closed) continue;
3335 			uint to = sw.eventQueueTimeoutMSecs();
3336 			if (to && to < res) {
3337 				res = to;
3338 				if (to == 1) break; // can't have less than this
3339 			}
3340 		}
3341 		return (res >= int.max ? 0 : res);
3342 	}
3343 }
3344 
3345 /++
3346 	Magic pseudo-window for just posting events to a global queue.
3347 
3348 	Not entirely supported, I might delete it at any time.
3349 
3350 	Added Nov 5, 2021.
3351 +/
3352 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init);
3353 
3354 /* Drag and drop support { */
3355 version(X11) {
3356 
3357 } else version(Windows) {
3358 	import core.sys.windows.uuid;
3359 	import core.sys.windows.ole2;
3360 	import core.sys.windows.oleidl;
3361 	import core.sys.windows.objidl;
3362 	import core.sys.windows.wtypes;
3363 
3364 	pragma(lib, "ole32");
3365 	void initDnd() {
3366 		auto err = OleInitialize(null);
3367 		if(err != S_OK && err != S_FALSE)
3368 			throw new Exception("init");//err);
3369 	}
3370 }
3371 /* } End drag and drop support */
3372 
3373 
3374 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
3375 /// See [GenericCursor].
3376 class MouseCursor {
3377 	int osId;
3378 	bool isStockCursor;
3379 	private this(int osId) {
3380 		this.osId = osId;
3381 		this.isStockCursor = true;
3382 	}
3383 
3384 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
3385 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
3386 
3387 	version(Windows) {
3388 		HCURSOR cursor_;
3389 		HCURSOR cursorHandle() {
3390 			if(cursor_ is null)
3391 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
3392 			return cursor_;
3393 		}
3394 
3395 	} else static if(UsingSimpledisplayX11) {
3396 		Cursor cursor_ = None;
3397 		int xDisplaySequence;
3398 
3399 		Cursor cursorHandle() {
3400 			if(this.osId == None)
3401 				return None;
3402 
3403 			// we need to reload if we on a new X connection
3404 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
3405 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
3406 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
3407 			}
3408 			return cursor_;
3409 		}
3410 	}
3411 }
3412 
3413 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
3414 // https://tronche.com/gui/x/xlib/appendix/b/
3415 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
3416 /// 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.
3417 enum GenericCursorType {
3418 	Default, /// The default arrow pointer.
3419 	Wait, /// A cursor indicating something is loading and the user must wait.
3420 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3421 	Help, /// A cursor indicating the user can get help about the pointer location.
3422 	Cross, /// A crosshair.
3423 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3424 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll.
3425 	UpArrow, /// An arrow pointing straight up.
3426 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3427 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3428 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator).
3429 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator).
3430 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator).
3431 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator).
3432 
3433 }
3434 
3435 /*
3436 	X_plus == css cell == Windows ?
3437 */
3438 
3439 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3440 static struct GenericCursor {
3441 	static:
3442 	///
3443 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3444 		static MouseCursor mc;
3445 
3446 		auto type = __traits(getMember, GenericCursorType, str);
3447 
3448 		if(mc is null) {
3449 
3450 			version(Windows) {
3451 				int osId;
3452 				final switch(type) {
3453 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3454 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3455 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3456 					case GenericCursorType.Help: osId = IDC_HELP; break;
3457 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3458 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3459 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3460 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3461 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3462 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3463 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3464 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3465 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3466 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3467 				}
3468 			} else static if(UsingSimpledisplayX11) {
3469 				int osId;
3470 				final switch(type) {
3471 					case GenericCursorType.Default: osId = None; break;
3472 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3473 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3474 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3475 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3476 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3477 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3478 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3479 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3480 
3481 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3482 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3483 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3484 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3485 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3486 				}
3487 
3488 			} else featureNotImplemented();
3489 
3490 			mc = new MouseCursor(osId);
3491 		}
3492 		return mc;
3493 	}
3494 }
3495 
3496 
3497 /++
3498 	If you want to get more control over the event loop, you can use this.
3499 
3500 	Typically though, you can just call [SimpleWindow.eventLoop].
3501 +/
3502 struct EventLoop {
3503 	@disable this();
3504 
3505 	/// Gets a reference to an existing event loop
3506 	static EventLoop get() {
3507 		return EventLoop(0, null);
3508 	}
3509 
3510 	__gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
3511 
3512 	/// Construct an application-global event loop for yourself
3513 	/// See_Also: [SimpleWindow.setEventHandlers]
3514 	this(long pulseTimeout, void delegate() handlePulse) {
3515 		synchronized(monitor) {
3516 			if(impl is null) {
3517 				claimGuiThread();
3518 				version(sdpy_thread_checks) assert(thisIsGuiThread);
3519 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
3520 			} else {
3521 				if(pulseTimeout) {
3522 					impl.pulseTimeout = pulseTimeout;
3523 					impl.handlePulse = handlePulse;
3524 				}
3525 			}
3526 			impl.refcount++;
3527 		}
3528 	}
3529 
3530 	~this() {
3531 		if(impl is null)
3532 			return;
3533 		impl.refcount--;
3534 		if(impl.refcount == 0) {
3535 			impl.dispose();
3536 			if(thisIsGuiThread)
3537 				guiThreadFinalize();
3538 		}
3539 
3540 	}
3541 
3542 	this(this) {
3543 		if(impl is null)
3544 			return;
3545 		impl.refcount++;
3546 	}
3547 
3548 	/// Runs the event loop until the whileCondition, if present, returns false
3549 	int run(bool delegate() whileCondition = null) {
3550 		assert(impl !is null);
3551 		impl.notExited = true;
3552 		return impl.run(whileCondition);
3553 	}
3554 
3555 	/// Exits the event loop
3556 	void exit() {
3557 		assert(impl !is null);
3558 		impl.notExited = false;
3559 	}
3560 
3561 	version(linux)
3562 	ref void delegate(int) signalHandler() {
3563 		assert(impl !is null);
3564 		return impl.signalHandler;
3565 	}
3566 
3567 	__gshared static EventLoopImpl* impl;
3568 }
3569 
3570 version(linux)
3571 	void delegate(int, int) globalHupHandler;
3572 
3573 version(Posix)
3574 	void makeNonBlocking(int fd) {
3575 		import fcntl = core.sys.posix.fcntl;
3576 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
3577 		if(flags == -1)
3578 			throw new Exception("fcntl get");
3579 		flags |= fcntl.O_NONBLOCK;
3580 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
3581 		if(s == -1)
3582 			throw new Exception("fcntl set");
3583 	}
3584 
3585 struct EventLoopImpl {
3586 	int refcount;
3587 
3588 	bool notExited = true;
3589 
3590 	version(linux) {
3591 		static import ep = core.sys.linux.epoll;
3592 		static import unix = core.sys.posix.unistd;
3593 		static import err = core.stdc.errno;
3594 		import core.sys.linux.timerfd;
3595 
3596 		void delegate(int) signalHandler;
3597 	}
3598 
3599 	version(X11) {
3600 		int pulseFd = -1;
3601 		version(linux) ep.epoll_event[16] events = void;
3602 	} else version(Windows) {
3603 		Timer pulser;
3604 		HANDLE[] handles;
3605 	}
3606 
3607 
3608 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3609 	/// to call this, as it's not recommended to share window between threads.
3610 	void mtLock () {
3611 		version(X11) {
3612 			XLockDisplay(this.display);
3613 		}
3614 	}
3615 
3616 	version(X11)
3617 	auto display() { return XDisplayConnection.get; }
3618 
3619 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3620 	/// to call this, as it's not recommended to share window between threads.
3621 	void mtUnlock () {
3622 		version(X11) {
3623 			XUnlockDisplay(this.display);
3624 		}
3625 	}
3626 
3627 	version(with_eventloop)
3628 	void initialize(long pulseTimeout) {}
3629 	else
3630 	void initialize(long pulseTimeout) {
3631 		version(Windows) {
3632 			if(pulseTimeout && handlePulse !is null)
3633 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
3634 
3635 			if (customEventH is null) {
3636 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
3637 				if (customEventH !is null) {
3638 					handles ~= customEventH;
3639 				} else {
3640 					// this is something that should not be; better be safe than sorry
3641 					throw new Exception("can't create eventfd for custom event processing");
3642 				}
3643 			}
3644 
3645 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
3646 		}
3647 
3648 		version(linux) {
3649 			prepareEventLoop();
3650 			{
3651 				auto display = XDisplayConnection.get;
3652 				// adding Xlib file
3653 				ep.epoll_event ev = void;
3654 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3655 				ev.events = ep.EPOLLIN;
3656 				ev.data.fd = display.fd;
3657 				//import std.conv;
3658 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
3659 					throw new Exception("add x fd");// ~ to!string(epollFd));
3660 				displayFd = display.fd;
3661 			}
3662 
3663 			if(pulseTimeout && handlePulse !is null) {
3664 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
3665 				if(pulseFd == -1)
3666 					throw new Exception("pulse timer create failed");
3667 
3668 				itimerspec value;
3669 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
3670 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3671 
3672 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
3673 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3674 
3675 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
3676 					throw new Exception("couldn't make pulse timer");
3677 
3678 				ep.epoll_event ev = void;
3679 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3680 				ev.events = ep.EPOLLIN;
3681 				ev.data.fd = pulseFd;
3682 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
3683 			}
3684 
3685 			// eventfd for custom events
3686 			if (customEventFDWrite == -1) {
3687 				customEventFDWrite = eventfd(0, 0);
3688 				customEventFDRead = customEventFDWrite;
3689 				if (customEventFDRead >= 0) {
3690 					ep.epoll_event ev = void;
3691 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3692 					ev.events = ep.EPOLLIN;
3693 					ev.data.fd = customEventFDRead;
3694 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
3695 				} else {
3696 					// this is something that should not be; better be safe than sorry
3697 					throw new Exception("can't create eventfd for custom event processing");
3698 				}
3699 			}
3700 
3701 			if (customSignalFD == -1) {
3702 				import core.sys.linux.sys.signalfd;
3703 
3704 				sigset_t sigset;
3705 				auto err = sigemptyset(&sigset);
3706 				assert(!err);
3707 				err = sigaddset(&sigset, SIGINT);
3708 				assert(!err);
3709 				err = sigaddset(&sigset, SIGHUP);
3710 				assert(!err);
3711 				err = sigprocmask(SIG_BLOCK, &sigset, null);
3712 				assert(!err);
3713 
3714 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
3715 				assert(customSignalFD != -1);
3716 
3717 				ep.epoll_event ev = void;
3718 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3719 				ev.events = ep.EPOLLIN;
3720 				ev.data.fd = customSignalFD;
3721 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
3722 			}
3723 		} else version(Posix) {
3724 			prepareEventLoop();
3725 			if (customEventFDRead == -1) {
3726 				int[2] bfr;
3727 				import core.sys.posix.unistd;
3728 				auto ret = pipe(bfr);
3729 				if(ret == -1) throw new Exception("pipe");
3730 				customEventFDRead = bfr[0];
3731 				customEventFDWrite = bfr[1];
3732 			}
3733 
3734 		}
3735 
3736 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
3737 
3738 		version(linux) {
3739 			this.mtLock();
3740 			scope(exit) this.mtUnlock();
3741 			XPending(display); // no, really
3742 		}
3743 
3744 		disposed = false;
3745 	}
3746 
3747 	bool disposed = true;
3748 	version(X11)
3749 		int displayFd = -1;
3750 
3751 	version(with_eventloop)
3752 	void dispose() {}
3753 	else
3754 	void dispose() {
3755 		disposed = true;
3756 		version(X11) {
3757 			if(pulseFd != -1) {
3758 				import unix = core.sys.posix.unistd;
3759 				unix.close(pulseFd);
3760 				pulseFd = -1;
3761 			}
3762 
3763 				version(linux)
3764 				if(displayFd != -1) {
3765 					// 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
3766 					ep.epoll_event ev = void;
3767 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3768 					ev.events = ep.EPOLLIN;
3769 					ev.data.fd = displayFd;
3770 					//import std.conv;
3771 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
3772 					displayFd = -1;
3773 				}
3774 
3775 		} else version(Windows) {
3776 			if(pulser !is null) {
3777 				pulser.destroy();
3778 				pulser = null;
3779 			}
3780 			if (customEventH !is null) {
3781 				CloseHandle(customEventH);
3782 				customEventH = null;
3783 			}
3784 		}
3785 	}
3786 
3787 	this(long pulseTimeout, void delegate() handlePulse) {
3788 		this.pulseTimeout = pulseTimeout;
3789 		this.handlePulse = handlePulse;
3790 		initialize(pulseTimeout);
3791 	}
3792 
3793 	private long pulseTimeout;
3794 	void delegate() handlePulse;
3795 
3796 	~this() {
3797 		dispose();
3798 	}
3799 
3800 	version(Posix)
3801 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
3802 	version(Posix)
3803 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
3804 	version(linux)
3805 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
3806 	version(Windows)
3807 	ref auto customEventH() { return SimpleWindow.customEventH; }
3808 
3809 	version(with_eventloop) {
3810 		int loopHelper(bool delegate() whileCondition) {
3811 			// FIXME: whileCondition
3812 			import arsd.eventloop;
3813 			loop();
3814 			return 0;
3815 		}
3816 	} else
3817 	int loopHelper(bool delegate() whileCondition) {
3818 		version(X11) {
3819 			bool done = false;
3820 
3821 			XFlush(display);
3822 			insideXEventLoop = true;
3823 			scope(exit) insideXEventLoop = false;
3824 
3825 			version(linux) {
3826 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
3827 					bool forceXPending = false;
3828 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
3829 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
3830 					{
3831 						this.mtLock();
3832 						scope(exit) this.mtUnlock();
3833 						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
3834 					}
3835 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
3836 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
3837 					if(nfds == -1) {
3838 						if(err.errno == err.EINTR) {
3839 							//if(forceXPending) goto xpending;
3840 							continue; // interrupted by signal, just try again
3841 						}
3842 						throw new Exception("epoll wait failure");
3843 					}
3844 
3845 					SimpleWindow.processAllCustomEvents(); // anyway
3846 					//version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
3847 					foreach(idx; 0 .. nfds) {
3848 						if(done) break;
3849 						auto fd = events[idx].data.fd;
3850 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
3851 						auto flags = events[idx].events;
3852 						if(flags & ep.EPOLLIN) {
3853 							if (fd == customSignalFD) {
3854 								version(linux) {
3855 									import core.sys.linux.sys.signalfd;
3856 									import core.sys.posix.unistd : read;
3857 									signalfd_siginfo info;
3858 									read(customSignalFD, &info, info.sizeof);
3859 
3860 									auto sig = info.ssi_signo;
3861 
3862 									if(EventLoop.get.signalHandler !is null) {
3863 										EventLoop.get.signalHandler()(sig);
3864 									} else {
3865 										EventLoop.get.exit();
3866 									}
3867 								}
3868 							} else if(fd == display.fd) {
3869 								version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
3870 								this.mtLock();
3871 								scope(exit) this.mtUnlock();
3872 								while(!done && XPending(display)) {
3873 									done = doXNextEvent(this.display);
3874 								}
3875 								forceXPending = false;
3876 							} else if(fd == pulseFd) {
3877 								long expirationCount;
3878 								// 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...
3879 
3880 								handlePulse();
3881 
3882 								// read just to clear the buffer so poll doesn't trigger again
3883 								// BTW I read AFTER the pulse because if the pulse handler takes
3884 								// a lot of time to execute, we don't want the app to get stuck
3885 								// in a loop of timer hits without a chance to do anything else
3886 								//
3887 								// IOW handlePulse happens at most once per pulse interval.
3888 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
3889 								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
3890 							} else if (fd == customEventFDRead) {
3891 								// we have some custom events; process 'em
3892 								import core.sys.posix.unistd : read;
3893 								ulong n;
3894 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
3895 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
3896 								//SimpleWindow.processAllCustomEvents();
3897 							} else {
3898 								// some other timer
3899 								version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); }
3900 
3901 								if(Timer* t = fd in Timer.mapping)
3902 									(*t).trigger();
3903 
3904 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3905 									(*pfr).ready(flags);
3906 
3907 								// or i might add support for other FDs too
3908 								// but for now it is just timer
3909 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
3910 							}
3911 						}
3912 						if(flags & ep.EPOLLHUP) {
3913 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3914 								(*pfr).hup(flags);
3915 							if(globalHupHandler)
3916 								globalHupHandler(fd, flags);
3917 						}
3918 						/+
3919 						} else {
3920 							// not interested in OUT, we are just reading here.
3921 							//
3922 							// error or hup might also be reported
3923 							// but it shouldn't here since we are only
3924 							// using a few types of FD and Xlib will report
3925 							// if it dies.
3926 							// so instead of thoughtfully handling it, I'll
3927 							// just throw. for now at least
3928 
3929 							throw new Exception("epoll did something else");
3930 						}
3931 						+/
3932 					}
3933 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
3934 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
3935 					xpending:
3936 					if (!done && forceXPending) {
3937 						this.mtLock();
3938 						scope(exit) this.mtUnlock();
3939 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
3940 						while(!done && XPending(display)) {
3941 							done = doXNextEvent(this.display);
3942 						}
3943 					}
3944 				}
3945 			} else {
3946 				// Generic fallback: yes to simple pulse support,
3947 				// but NO timer support!
3948 
3949 				// FIXME: we could probably support the POSIX timer_create
3950 				// signal-based option, but I'm in no rush to write it since
3951 				// I prefer the fd-based functions.
3952 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
3953 
3954 					import core.sys.posix.poll;
3955 
3956 					pollfd[] pfds;
3957 					pollfd[32] pfdsBuffer;
3958 					auto len = PosixFdReader.mapping.length + 2;
3959 					// FIXME: i should just reuse the buffer
3960 					if(len < pfdsBuffer.length)
3961 						pfds = pfdsBuffer[0 .. len];
3962 					else
3963 						pfds = new pollfd[](len);
3964 
3965 					pfds[0].fd = display.fd;
3966 					pfds[0].events = POLLIN;
3967 					pfds[0].revents = 0;
3968 
3969 					int slot = 1;
3970 
3971 					if(customEventFDRead != -1) {
3972 						pfds[slot].fd = customEventFDRead;
3973 						pfds[slot].events = POLLIN;
3974 						pfds[slot].revents = 0;
3975 
3976 						slot++;
3977 					}
3978 
3979 					foreach(fd, obj; PosixFdReader.mapping) {
3980 						if(!obj.enabled) continue;
3981 						pfds[slot].fd = fd;
3982 						pfds[slot].events = POLLIN;
3983 						pfds[slot].revents = 0;
3984 
3985 						slot++;
3986 					}
3987 
3988 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
3989 					if(ret == -1) throw new Exception("poll");
3990 
3991 					if(ret == 0) {
3992 						// FIXME it may not necessarily time out if events keep coming
3993 						if(handlePulse !is null)
3994 							handlePulse();
3995 					} else {
3996 						foreach(s; 0 .. slot) {
3997 							if(pfds[s].revents == 0) continue;
3998 
3999 							if(pfds[s].fd == display.fd) {
4000 								while(!done && XPending(display)) {
4001 									this.mtLock();
4002 									scope(exit) this.mtUnlock();
4003 									done = doXNextEvent(this.display);
4004 								}
4005 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
4006 
4007 								import core.sys.posix.unistd : read;
4008 								ulong n;
4009 								read(customEventFDRead, &n, n.sizeof);
4010 								SimpleWindow.processAllCustomEvents();
4011 							} else {
4012 								auto obj = PosixFdReader.mapping[pfds[s].fd];
4013 								if(pfds[s].revents & POLLNVAL) {
4014 									obj.dispose();
4015 								} else {
4016 									obj.ready(pfds[s].revents);
4017 								}
4018 							}
4019 
4020 							ret--;
4021 							if(ret == 0) break;
4022 						}
4023 					}
4024 				}
4025 			}
4026 		}
4027 		
4028 		version(Windows) {
4029 			int ret = -1;
4030 			MSG message;
4031 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
4032 				eventLoopRound++;
4033 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
4034 				auto waitResult = MsgWaitForMultipleObjectsEx(
4035 					cast(int) handles.length, handles.ptr,
4036 					(wto == 0 ? INFINITE : wto), /* timeout */
4037 					0x04FF, /* QS_ALLINPUT */
4038 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
4039 
4040 				SimpleWindow.processAllCustomEvents(); // anyway
4041 				enum WAIT_OBJECT_0 = 0;
4042 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
4043 					auto h = handles[waitResult - WAIT_OBJECT_0];
4044 					if(auto e = h in WindowsHandleReader.mapping) {
4045 						(*e).ready();
4046 					}
4047 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
4048 					// message ready
4049 					int count;
4050 					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
4051 						ret = GetMessage(&message, null, 0, 0);
4052 						if(ret == -1)
4053 							throw new Exception("GetMessage failed");
4054 						TranslateMessage(&message);
4055 						DispatchMessage(&message);
4056 
4057 						count++;
4058 						if(count > 10)
4059 							break; // take the opportunity to catch up on other events
4060 
4061 						if(ret == 0) // WM_QUIT
4062 							break;
4063 					}
4064 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
4065 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
4066 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
4067 					// timeout, should never happen since we aren't using it
4068 				} else if(waitResult == 0xFFFFFFFF) {
4069 						// failed
4070 						throw new Exception("MsgWaitForMultipleObjectsEx failed");
4071 				} else {
4072 					// idk....
4073 				}
4074 			}
4075 
4076 			// return message.wParam;
4077 			return 0;
4078 		} else {
4079 			return 0;
4080 		}
4081 	}
4082 
4083 	int run(bool delegate() whileCondition = null) {
4084 		if(disposed)
4085 			initialize(this.pulseTimeout);
4086 
4087 		version(X11) {
4088 			try {
4089 				return loopHelper(whileCondition);
4090 			} catch(XDisconnectException e) {
4091 				if(e.userRequested) {
4092 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
4093 						item.discardConnectionState();
4094 					XCloseDisplay(XDisplayConnection.display);
4095 				}
4096 
4097 				XDisplayConnection.display = null;
4098 
4099 				this.dispose();
4100 
4101 				throw e;
4102 			}
4103 		} else {
4104 			return loopHelper(whileCondition);
4105 		}
4106 	}
4107 }
4108 
4109 
4110 /++
4111 	Provides an icon on the system notification area (also known as the system tray).
4112 
4113 
4114 	If a notification area is not available with the NotificationIcon object is created,
4115 	it will silently succeed and simply attempt to create one when an area becomes available.
4116 
4117 
4118 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
4119 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
4120 	use the older version.
4121 +/
4122 version(OSXCocoa) {} else // NotYetImplementedException
4123 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
4124 
4125 	version(X11) {
4126 		void recreateAfterDisconnect() {
4127 			stateDiscarded = false;
4128 			clippixmap = None;
4129 			throw new Exception("NOT IMPLEMENTED");
4130 		}
4131 
4132 		bool stateDiscarded;
4133 		void discardConnectionState() {
4134 			stateDiscarded = true;
4135 		}
4136 	}
4137 
4138 
4139 	version(X11) {
4140 		Image img;
4141 
4142 		NativeEventHandler getNativeEventHandler() {
4143 			return delegate int(XEvent e) {
4144 				switch(e.type) {
4145 					case EventType.Expose:
4146 					//case EventType.VisibilityNotify:
4147 						redraw();
4148 					break;
4149 					case EventType.ClientMessage:
4150 						version(sddddd) {
4151 						import std.stdio;
4152 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
4153 						writeln("\t", e.xclient.format);
4154 						writeln("\t", e.xclient.data.l);
4155 						}
4156 					break;
4157 					case EventType.ButtonPress:
4158 						auto event = e.xbutton;
4159 						if (onClick !is null || onClickEx !is null) {
4160 							MouseButton mb = cast(MouseButton)0;
4161 							switch (event.button) {
4162 								case 1: mb = MouseButton.left; break; // left
4163 								case 2: mb = MouseButton.middle; break; // middle
4164 								case 3: mb = MouseButton.right; break; // right
4165 								case 4: mb = MouseButton.wheelUp; break; // scroll up
4166 								case 5: mb = MouseButton.wheelDown; break; // scroll down
4167 								case 6: break; // idk
4168 								case 7: break; // idk
4169 								case 8: mb = MouseButton.backButton; break;
4170 								case 9: mb = MouseButton.forwardButton; break;
4171 								default:
4172 							}
4173 							if (mb) {
4174 								try { onClick()(mb); } catch (Exception) {}
4175 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
4176 							}
4177 						}
4178 					break;
4179 					case EventType.EnterNotify:
4180 						if (onEnter !is null) {
4181 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
4182 						}
4183 						break;
4184 					case EventType.LeaveNotify:
4185 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
4186 						break;
4187 					case EventType.DestroyNotify:
4188 						active = false;
4189 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
4190 					break;
4191 					case EventType.ConfigureNotify:
4192 						auto event = e.xconfigure;
4193 						this.width = event.width;
4194 						this.height = event.height;
4195 						//import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y);
4196 						redraw();
4197 					break;
4198 					default: return 1;
4199 				}
4200 				return 1;
4201 			};
4202 		}
4203 
4204 		/* private */ void hideBalloon() {
4205 			balloon.close();
4206 			version(with_timer)
4207 				timer.destroy();
4208 			balloon = null;
4209 			version(with_timer)
4210 				timer = null;
4211 		}
4212 
4213 		void redraw() {
4214 			if (!active) return;
4215 
4216 			auto display = XDisplayConnection.get;
4217 			auto gc = DefaultGC(display, DefaultScreen(display));
4218 			XClearWindow(display, nativeHandle);
4219 
4220 			XSetClipMask(display, gc, clippixmap);
4221 
4222 			XSetForeground(display, gc,
4223 				cast(uint) 0 << 16 |
4224 				cast(uint) 0 << 8 |
4225 				cast(uint) 0);
4226 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
4227 
4228 			if (img is null) {
4229 				XSetForeground(display, gc,
4230 					cast(uint) 0 << 16 |
4231 					cast(uint) 127 << 8 |
4232 					cast(uint) 0);
4233 				XFillArc(display, nativeHandle,
4234 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
4235 			} else {
4236 				int dx = 0;
4237 				int dy = 0;
4238 				if(width > img.width)
4239 					dx = (width - img.width) / 2;
4240 				if(height > img.height)
4241 					dy = (height - img.height) / 2;
4242 				XSetClipOrigin(display, gc, dx, dy);
4243 
4244 				if (img.usingXshm)
4245 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
4246 				else
4247 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
4248 			}
4249 			XSetClipMask(display, gc, None);
4250 			flushGui();
4251 		}
4252 
4253 		static Window getTrayOwner() {
4254 			auto display = XDisplayConnection.get;
4255 			auto i = cast(int) DefaultScreen(display);
4256 			if(i < 10 && i >= 0) {
4257 				static Atom atom;
4258 				if(atom == None)
4259 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
4260 				return XGetSelectionOwner(display, atom);
4261 			}
4262 			return None;
4263 		}
4264 
4265 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
4266 			auto to = getTrayOwner();
4267 			auto display = XDisplayConnection.get;
4268 			XEvent ev;
4269 			ev.xclient.type = EventType.ClientMessage;
4270 			ev.xclient.window = to;
4271 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
4272 			ev.xclient.format = 32;
4273 			ev.xclient.data.l[0] = CurrentTime;
4274 			ev.xclient.data.l[1] = message;
4275 			ev.xclient.data.l[2] = d1;
4276 			ev.xclient.data.l[3] = d2;
4277 			ev.xclient.data.l[4] = d3;
4278 
4279 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
4280 		}
4281 
4282 		private static NotificationAreaIcon[] activeIcons;
4283 
4284 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
4285 		private void newManager() {
4286 			close();
4287 			createXWin();
4288 
4289 			if(this.clippixmap)
4290 				XFreePixmap(XDisplayConnection.get, clippixmap);
4291 			if(this.originalMemoryImage)
4292 				this.icon = this.originalMemoryImage;
4293 			else if(this.img)
4294 				this.icon = this.img;
4295 		}
4296 
4297 		private void createXWin () {
4298 			// create window
4299 			auto display = XDisplayConnection.get;
4300 
4301 			// to check for MANAGER on root window to catch new/changed tray owners
4302 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
4303 			// so if a thing does appear, we can handle it
4304 			foreach(ai; activeIcons)
4305 				if(ai is this)
4306 					goto alreadythere;
4307 			activeIcons ~= this;
4308 			alreadythere:
4309 
4310 			// and check for an existing tray
4311 			auto trayOwner = getTrayOwner();
4312 			if(trayOwner == None)
4313 				return;
4314 				//throw new Exception("No notification area found");
4315 
4316 			Visual* v = cast(Visual*) CopyFromParent;
4317 			/+
4318 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
4319 			if(visualProp !is null) {
4320 				c_ulong[] info = cast(c_ulong[]) visualProp;
4321 				if(info.length == 1) {
4322 					auto vid = info[0];
4323 					int returned;
4324 					XVisualInfo t;
4325 					t.visualid = vid;
4326 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
4327 					if(got !is null) {
4328 						if(returned == 1) {
4329 							v = got.visual;
4330 							import std.stdio;
4331 							writeln("using special visual ", *got);
4332 						}
4333 						XFree(got);
4334 					}
4335 				}
4336 			}
4337 			+/
4338 
4339 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
4340 			assert(nativeWindow);
4341 
4342 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
4343 
4344 			nativeHandle = nativeWindow;
4345 
4346 			///+
4347 			arch_ulong[2] info;
4348 			info[0] = 0;
4349 			info[1] = 1;
4350 
4351 			string title = this.name is null ? "simpledisplay.d program" : this.name;
4352 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
4353 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
4354 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
4355 
4356 			XChangeProperty(
4357 				display,
4358 				nativeWindow,
4359 				GetAtom!("_XEMBED_INFO", true)(display),
4360 				GetAtom!("_XEMBED_INFO", true)(display),
4361 				32 /* bits */,
4362 				0 /*PropModeReplace*/,
4363 				info.ptr,
4364 				2);
4365 
4366 			import core.sys.posix.unistd;
4367 			arch_ulong pid = getpid();
4368 
4369 			XChangeProperty(
4370 				display,
4371 				nativeWindow,
4372 				GetAtom!("_NET_WM_PID", true)(display),
4373 				XA_CARDINAL,
4374 				32 /* bits */,
4375 				0 /*PropModeReplace*/,
4376 				&pid,
4377 				1);
4378 
4379 			updateNetWmIcon();
4380 
4381 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
4382 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
4383 				XClassHint klass;
4384 				XWMHints wh;
4385 				XSizeHints size;
4386 				klass.res_name = sdpyWindowClassStr;
4387 				klass.res_class = sdpyWindowClassStr;
4388 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
4389 			}
4390 
4391 				// believe it or not, THIS is what xfce needed for the 9999 issue
4392 				XSizeHints sh;
4393 					c_long spr;
4394 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
4395 					sh.flags |= PMaxSize | PMinSize;
4396 				// FIXME maybe nicer resizing
4397 				sh.min_width = 16;
4398 				sh.min_height = 16;
4399 				sh.max_width = 16;
4400 				sh.max_height = 16;
4401 				XSetWMNormalHints(display, nativeWindow, &sh);
4402 
4403 
4404 			//+/
4405 
4406 
4407 			XSelectInput(display, nativeWindow,
4408 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
4409 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
4410 
4411 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
4412 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
4413 			active = true;
4414 		}
4415 
4416 		void updateNetWmIcon() {
4417 			if(img is null) return;
4418 			auto display = XDisplayConnection.get;
4419 			// FIXME: ensure this is correct
4420 			arch_ulong[] buffer;
4421 			auto imgMi = img.toTrueColorImage;
4422 			buffer ~= imgMi.width;
4423 			buffer ~= imgMi.height;
4424 			foreach(c; imgMi.imageData.colors) {
4425 				arch_ulong b;
4426 				b |= c.a << 24;
4427 				b |= c.r << 16;
4428 				b |= c.g << 8;
4429 				b |= c.b;
4430 				buffer ~= b;
4431 			}
4432 
4433 			XChangeProperty(
4434 				display,
4435 				nativeHandle,
4436 				GetAtom!"_NET_WM_ICON"(display),
4437 				GetAtom!"CARDINAL"(display),
4438 				32 /* bits */,
4439 				0 /*PropModeReplace*/,
4440 				buffer.ptr,
4441 				cast(int) buffer.length);
4442 		}
4443 
4444 
4445 
4446 		private SimpleWindow balloon;
4447 		version(with_timer)
4448 		private Timer timer;
4449 
4450 		private Window nativeHandle;
4451 		private Pixmap clippixmap = None;
4452 		private int width = 16;
4453 		private int height = 16;
4454 		private bool active = false;
4455 
4456 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
4457 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
4458 		void delegate () onLeave; /// X11 only.
4459 
4460 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
4461 
4462 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
4463 		void getWindowRect (out int x, out int y, out int width, out int height) {
4464 			if (!active) { width = 1; height = 1; return; } // 1: just in case
4465 			Window dummyw;
4466 			auto dpy = XDisplayConnection.get;
4467 			//XWindowAttributes xwa;
4468 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
4469 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
4470 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
4471 			width = this.width;
4472 			height = this.height;
4473 		}
4474 	}
4475 
4476 	/+
4477 		What I actually want from this:
4478 
4479 		* set / change: icon, tooltip
4480 		* handle: mouse click, right click
4481 		* show: notification bubble.
4482 	+/
4483 
4484 	version(Windows) {
4485 		WindowsIcon win32Icon;
4486 		HWND hwnd;
4487 
4488 		NOTIFYICONDATAW data;
4489 
4490 		NativeEventHandler getNativeEventHandler() {
4491 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
4492 				if(msg == WM_USER) {
4493 					auto event = LOWORD(lParam);
4494 					auto iconId = HIWORD(lParam);
4495 					//auto x = GET_X_LPARAM(wParam);
4496 					//auto y = GET_Y_LPARAM(wParam);
4497 					switch(event) {
4498 						case WM_LBUTTONDOWN:
4499 							onClick()(MouseButton.left);
4500 						break;
4501 						case WM_RBUTTONDOWN:
4502 							onClick()(MouseButton.right);
4503 						break;
4504 						case WM_MBUTTONDOWN:
4505 							onClick()(MouseButton.middle);
4506 						break;
4507 						case WM_MOUSEMOVE:
4508 							// sent, we could use it.
4509 						break;
4510 						case WM_MOUSEWHEEL:
4511 							// NOT SENT
4512 						break;
4513 						//case NIN_KEYSELECT:
4514 						//case NIN_SELECT:
4515 						//break;
4516 						default: {}
4517 					}
4518 				}
4519 				return 0;
4520 			};
4521 		}
4522 
4523 		enum NIF_SHOWTIP = 0x00000080;
4524 
4525 		private static struct NOTIFYICONDATAW {
4526 			DWORD cbSize;
4527 			HWND  hWnd;
4528 			UINT  uID;
4529 			UINT  uFlags;
4530 			UINT  uCallbackMessage;
4531 			HICON hIcon;
4532 			WCHAR[128] szTip;
4533 			DWORD dwState;
4534 			DWORD dwStateMask;
4535 			WCHAR[256] szInfo;
4536 			union {
4537 				UINT uTimeout;
4538 				UINT uVersion;
4539 			}
4540 			WCHAR[64] szInfoTitle;
4541 			DWORD dwInfoFlags;
4542 			GUID  guidItem;
4543 			HICON hBalloonIcon;
4544 		}
4545 
4546 	}
4547 
4548 	/++
4549 		Note that on Windows, only left, right, and middle buttons are sent.
4550 		Mouse wheel buttons are NOT set, so don't rely on those events if your
4551 		program is meant to be used on Windows too.
4552 	+/
4553 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
4554 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
4555 		// but on X, we need an Image, so its canonical ctor is there. They should
4556 		// forward to each other though.
4557 		version(X11) {
4558 			this.name = name;
4559 			this.onClick = onClick;
4560 			createXWin();
4561 			this.icon = icon;
4562 		} else version(Windows) {
4563 			this.onClick = onClick;
4564 			this.win32Icon = new WindowsIcon(icon);
4565 
4566 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
4567 
4568 			static bool registered = false;
4569 			if(!registered) {
4570 				WNDCLASSEX wc;
4571 				wc.cbSize = wc.sizeof;
4572 				wc.hInstance = hInstance;
4573 				wc.lpfnWndProc = &WndProc;
4574 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
4575 				if(!RegisterClassExW(&wc))
4576 					throw new WindowsApiException("RegisterClass");
4577 				registered = true;
4578 			}
4579 
4580 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
4581 			if(hwnd is null)
4582 				throw new Exception("CreateWindow");
4583 
4584 			data.cbSize = data.sizeof;
4585 			data.hWnd = hwnd;
4586 			data.uID = cast(uint) cast(void*) this;
4587 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
4588 				// NIF_INFO means show balloon
4589 			data.uCallbackMessage = WM_USER;
4590 			data.hIcon = this.win32Icon.hIcon;
4591 			data.szTip = ""; // FIXME
4592 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4593 			data.dwStateMask = NIS_HIDDEN; // windows vista
4594 
4595 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
4596 
4597 
4598 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
4599 
4600 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
4601 		} else version(OSXCocoa) {
4602 			throw new NotYetImplementedException();
4603 		} else static assert(0);
4604 	}
4605 
4606 	/// ditto
4607 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
4608 		version(X11) {
4609 			this.onClick = onClick;
4610 			this.name = name;
4611 			createXWin();
4612 			this.icon = icon;
4613 		} else version(Windows) {
4614 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
4615 		} else version(OSXCocoa) {
4616 			throw new NotYetImplementedException();
4617 		} else static assert(0);
4618 	}
4619 
4620 	version(X11) {
4621 		/++
4622 			X-specific extension (for now at least)
4623 		+/
4624 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4625 			this.onClickEx = onClickEx;
4626 			createXWin();
4627 			if (icon !is null) this.icon = icon;
4628 		}
4629 
4630 		/// ditto
4631 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4632 			this.onClickEx = onClickEx;
4633 			createXWin();
4634 			this.icon = icon;
4635 		}
4636 	}
4637 
4638 	private void delegate (MouseButton button) onClick_;
4639 
4640 	///
4641 	@property final void delegate(MouseButton) onClick() {
4642 		if(onClick_ is null)
4643 			onClick_ = delegate void(MouseButton) {};
4644 		return onClick_;
4645 	}
4646 
4647 	/// ditto
4648 	@property final void onClick(void delegate(MouseButton) handler) {
4649 		// I made this a property setter so we can wrap smaller arg
4650 		// delegates and just forward all to onClickEx or something.
4651 		onClick_ = handler;
4652 	}
4653 
4654 
4655 	string name_;
4656 	@property void name(string n) {
4657 		name_ = n;
4658 	}
4659 
4660 	@property string name() {
4661 		return name_;
4662 	}
4663 
4664 	private MemoryImage originalMemoryImage;
4665 
4666 	///
4667 	@property void icon(MemoryImage i) {
4668 		version(X11) {
4669 			this.originalMemoryImage = i;
4670 			if (!active) return;
4671 			if (i !is null) {
4672 				this.img = Image.fromMemoryImage(i);
4673 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
4674 				//import std.stdio; writeln("using pixmap ", clippixmap);
4675 				updateNetWmIcon();
4676 				redraw();
4677 			} else {
4678 				if (this.img !is null) {
4679 					this.img = null;
4680 					redraw();
4681 				}
4682 			}
4683 		} else version(Windows) {
4684 			this.win32Icon = new WindowsIcon(i);
4685 
4686 			data.uFlags = NIF_ICON;
4687 			data.hIcon = this.win32Icon.hIcon;
4688 
4689 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4690 		} else version(OSXCocoa) {
4691 			throw new NotYetImplementedException();
4692 		} else static assert(0);
4693 	}
4694 
4695 	/// ditto
4696 	@property void icon (Image i) {
4697 		version(X11) {
4698 			if (!active) return;
4699 			if (i !is img) {
4700 				originalMemoryImage = null;
4701 				img = i;
4702 				redraw();
4703 			}
4704 		} else version(Windows) {
4705 			this.icon(i is null ? null : i.toTrueColorImage());
4706 		} else version(OSXCocoa) {
4707 			throw new NotYetImplementedException();
4708 		} else static assert(0);
4709 	}
4710 
4711 	/++
4712 		Shows a balloon notification. You can only show one balloon at a time, if you call
4713 		it twice while one is already up, the first balloon will be replaced.
4714 		
4715 		
4716 		The user is free to block notifications and they will automatically disappear after
4717 		a timeout period.
4718 
4719 		Params:
4720 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
4721 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
4722 			icon = the icon to display with the notification. If null, it uses your existing icon.
4723 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
4724 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
4725 	+/
4726 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
4727 		bool useCustom = true;
4728 		version(libnotify) {
4729 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
4730 			try {
4731 				if(!active) return;
4732 
4733 				if(libnotify is null) {
4734 					libnotify = new C_DynamicLibrary("libnotify.so");
4735 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
4736 				}
4737 
4738 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
4739 
4740 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
4741 
4742 				if(onclick) {
4743 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
4744 					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);
4745 					libnotify_action_delegates_count++;
4746 				}
4747 
4748 				// FIXME icon
4749 
4750 				// set hint image-data
4751 				// set default action for onclick
4752 
4753 				void* error;
4754 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
4755 
4756 				useCustom = false;
4757 			} catch(Exception e) {
4758 
4759 			}
4760 		}
4761 		
4762 		version(X11) {
4763 		if(useCustom) {
4764 			if(!active) return;
4765 			if(balloon) {
4766 				hideBalloon();
4767 			}
4768 			// I know there are two specs for this, but one is never
4769 			// implemented by any window manager I have ever seen, and
4770 			// the other is a bloated mess and too complicated for simpledisplay...
4771 			// so doing my own little window instead.
4772 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
4773 
4774 			int x, y, width, height;
4775 			getWindowRect(x, y, width, height);
4776 
4777 			int bx = x - balloon.width;
4778 			int by = y - balloon.height;
4779 			if(bx < 0)
4780 				bx = x + width + balloon.width;
4781 			if(by < 0)
4782 				by = y + height;
4783 
4784 			// just in case, make sure it is actually on scren
4785 			if(bx < 0)
4786 				bx = 0;
4787 			if(by < 0)
4788 				by = 0;
4789 
4790 			balloon.move(bx, by);
4791 			auto painter = balloon.draw();
4792 			painter.fillColor = Color(220, 220, 220);
4793 			painter.outlineColor = Color.black;
4794 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
4795 			auto iconWidth = icon is null ? 0 : icon.width;
4796 			if(icon)
4797 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
4798 			iconWidth += 6; // margin around the icon
4799 
4800 			// draw a close button
4801 			painter.outlineColor = Color(44, 44, 44);
4802 			painter.fillColor = Color(255, 255, 255);
4803 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
4804 			painter.pen = Pen(Color.black, 3);
4805 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
4806 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
4807 			painter.pen = Pen(Color.black, 1);
4808 			painter.fillColor = Color(220, 220, 220);
4809 
4810 			// Draw the title and message
4811 			painter.drawText(Point(4 + iconWidth, 4), title);
4812 			painter.drawLine(
4813 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
4814 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
4815 			);
4816 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
4817 
4818 			balloon.setEventHandlers(
4819 				(MouseEvent ev) {
4820 					if(ev.type == MouseEventType.buttonPressed) {
4821 						if(ev.x > balloon.width - 16 && ev.y < 16)
4822 							hideBalloon();
4823 						else if(onclick)
4824 							onclick();
4825 					}
4826 				}
4827 			);
4828 			balloon.show();
4829 
4830 			version(with_timer)
4831 			timer = new Timer(timeout, &hideBalloon);
4832 			else {} // FIXME
4833 		}
4834 		} else version(Windows) {
4835 			enum NIF_INFO = 0x00000010;
4836 
4837 			data.uFlags = NIF_INFO;
4838 
4839 			// FIXME: go back to the last valid unicode code point
4840 			if(title.length > 40)
4841 				title = title[0 .. 40];
4842 			if(message.length > 220)
4843 				message = message[0 .. 220];
4844 
4845 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
4846 			enum NIIF_LARGE_ICON  = 0x00000020;
4847 			enum NIIF_NOSOUND = 0x00000010;
4848 			enum NIIF_USER = 0x00000004;
4849 			enum NIIF_ERROR = 0x00000003;
4850 			enum NIIF_WARNING = 0x00000002;
4851 			enum NIIF_INFO = 0x00000001;
4852 			enum NIIF_NONE = 0;
4853 
4854 			WCharzBuffer t = WCharzBuffer(title);
4855 			WCharzBuffer m = WCharzBuffer(message);
4856 
4857 			t.copyInto(data.szInfoTitle);
4858 			m.copyInto(data.szInfo);
4859 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
4860 
4861 			if(icon !is null) {
4862 				auto i = new WindowsIcon(icon);
4863 				data.hBalloonIcon = i.hIcon;
4864 				data.dwInfoFlags |= NIIF_USER;
4865 			}
4866 
4867 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4868 		} else version(OSXCocoa) {
4869 			throw new NotYetImplementedException();
4870 		} else static assert(0);
4871 	}
4872 
4873 	///
4874 	//version(Windows)
4875 	void show() {
4876 		version(X11) {
4877 			if(!hidden)
4878 				return;
4879 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
4880 			hidden = false;
4881 		} else version(Windows) {
4882 			data.uFlags = NIF_STATE;
4883 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4884 			data.dwStateMask = NIS_HIDDEN; // windows vista
4885 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4886 		} else version(OSXCocoa) {
4887 			throw new NotYetImplementedException();
4888 		} else static assert(0);
4889 	}
4890 
4891 	version(X11)
4892 		bool hidden = false;
4893 
4894 	///
4895 	//version(Windows)
4896 	void hide() {
4897 		version(X11) {
4898 			if(hidden)
4899 				return;
4900 			hidden = true;
4901 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
4902 		} else version(Windows) {
4903 			data.uFlags = NIF_STATE;
4904 			data.dwState = NIS_HIDDEN; // windows vista
4905 			data.dwStateMask = NIS_HIDDEN; // windows vista
4906 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4907 		} else version(OSXCocoa) {
4908 			throw new NotYetImplementedException();
4909 		} else static assert(0);
4910 	}
4911 
4912 	///
4913 	void close () {
4914 		version(X11) {
4915 			if (active) {
4916 				active = false; // event handler will set this too, but meh
4917 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
4918 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
4919 				flushGui();
4920 			}
4921 		} else version(Windows) {
4922 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
4923 		} else version(OSXCocoa) {
4924 			throw new NotYetImplementedException();
4925 		} else static assert(0);
4926 	}
4927 
4928 	~this() {
4929 		version(X11)
4930 			if(clippixmap != None)
4931 				XFreePixmap(XDisplayConnection.get, clippixmap);
4932 		close();
4933 	}
4934 }
4935 
4936 version(X11)
4937 /// Call `XFreePixmap` on the return value.
4938 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
4939 	char[] data = new char[](i.width * i.height / 8 + 2);
4940 	data[] = 0;
4941 
4942 	int bitOffset = 0;
4943 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
4944 		ubyte v = c.a > 128 ? 1 : 0;
4945 		data[bitOffset / 8] |= v << (bitOffset%8);
4946 		bitOffset++;
4947 	}
4948 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
4949 	return handle;
4950 }
4951 
4952 
4953 // basic functions to make timers
4954 /**
4955 	A timer that will trigger your function on a given interval.
4956 
4957 
4958 	You create a timer with an interval and a callback. It will continue
4959 	to fire on the interval until it is destroyed.
4960 
4961 	There are currently no one-off timers (instead, just create one and
4962 	destroy it when it is triggered) nor are there pause/resume functions -
4963 	the timer must again be destroyed and recreated if you want to pause it.
4964 
4965 	auto timer = new Timer(50, { it happened!; });
4966 	timer.destroy();
4967 
4968 	Timers can only be expected to fire when the event loop is running and only
4969 	once per iteration through the event loop.
4970 
4971 	History:
4972 		Prior to December 9, 2020, a timer pulse set too high with a handler too
4973 		slow could lock up the event loop. It now guarantees other things will
4974 		get a chance to run between timer calls, even if that means not keeping up
4975 		with the requested interval.
4976 */
4977 version(with_timer) {
4978 class Timer {
4979 // FIXME: needs pause and unpause
4980 	// FIXME: I might add overloads for ones that take a count of
4981 	// how many elapsed since last time (on Windows, it will divide
4982 	// the ticks thing given, on Linux it is just available) and
4983 	// maybe one that takes an instance of the Timer itself too
4984 	/// Create a timer with a callback when it triggers.
4985 	this(int intervalInMilliseconds, void delegate() onPulse) {
4986 		assert(onPulse !is null);
4987 
4988 		this.intervalInMilliseconds = intervalInMilliseconds;
4989 		this.onPulse = onPulse;
4990 
4991 		version(Windows) {
4992 			/*
4993 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
4994 			if(handle == 0)
4995 				throw new Exception("SetTimer fail");
4996 			*/
4997 
4998 			// thanks to Archival 998 for the WaitableTimer blocks
4999 			handle = CreateWaitableTimer(null, false, null);
5000 			long initialTime = -intervalInMilliseconds;
5001 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5002 				throw new Exception("SetWaitableTimer Failed");
5003 
5004 			mapping[handle] = this;
5005 
5006 		} else version(linux) {
5007 			static import ep = core.sys.linux.epoll;
5008 
5009 			import core.sys.linux.timerfd;
5010 
5011 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
5012 			if(fd == -1)
5013 				throw new Exception("timer create failed");
5014 
5015 			mapping[fd] = this;
5016 
5017 			itimerspec value;
5018 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5019 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5020 
5021 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
5022 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
5023 
5024 			if(timerfd_settime(fd, 0, &value, null) == -1)
5025 				throw new Exception("couldn't make pulse timer");
5026 
5027 			version(with_eventloop) {
5028 				import arsd.eventloop;
5029 				addFileEventListeners(fd, &trigger, null, null);
5030 			} else {
5031 				prepareEventLoop();
5032 
5033 				ep.epoll_event ev = void;
5034 				ev.events = ep.EPOLLIN;
5035 				ev.data.fd = fd;
5036 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5037 			}
5038 		} else featureNotImplemented();
5039 	}
5040 
5041 	private int intervalInMilliseconds;
5042 
5043 	/// Stop and destroy the timer object.
5044 	void destroy() {
5045 		version(Windows) {
5046 			if(handle) {
5047 				// KillTimer(null, handle);
5048 				CancelWaitableTimer(cast(void*)handle);
5049 				mapping.remove(handle);
5050 				CloseHandle(handle);
5051 				handle = null;
5052 			}
5053 		} else version(linux) {
5054 			if(fd != -1) {
5055 				import unix = core.sys.posix.unistd;
5056 				static import ep = core.sys.linux.epoll;
5057 
5058 				version(with_eventloop) {
5059 					import arsd.eventloop;
5060 					removeFileEventListeners(fd);
5061 				} else {
5062 					ep.epoll_event ev = void;
5063 					ev.events = ep.EPOLLIN;
5064 					ev.data.fd = fd;
5065 
5066 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5067 				}
5068 				unix.close(fd);
5069 				mapping.remove(fd);
5070 				fd = -1;
5071 			}
5072 		} else featureNotImplemented();
5073 	}
5074 
5075 	~this() {
5076 		destroy();
5077 	}
5078 
5079 
5080 	void changeTime(int intervalInMilliseconds)
5081 	{
5082 		this.intervalInMilliseconds = intervalInMilliseconds;
5083 		version(Windows)
5084 		{
5085 			if(handle)
5086 			{
5087 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
5088 				long initialTime = -intervalInMilliseconds;
5089 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
5090 					throw new Exception("couldn't change pulse timer");
5091 			}
5092 		}
5093 	}
5094 
5095 
5096 	private:
5097 
5098 	void delegate() onPulse;
5099 
5100 	int lastEventLoopRoundTriggered;
5101 
5102 	void trigger() {
5103 		version(linux) {
5104 			import unix = core.sys.posix.unistd;
5105 			long val;
5106 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
5107 		} else version(Windows) {
5108 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
5109 				return; // never try to actually run faster than the event loop
5110 			lastEventLoopRoundTriggered = eventLoopRound;
5111 		} else featureNotImplemented();
5112 
5113 		onPulse();
5114 	}
5115 
5116 	version(Windows)
5117 	void rearm() {
5118 
5119 	}
5120 
5121 	version(Windows)
5122 		extern(Windows)
5123 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
5124 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
5125 			if(Timer* t = timer in mapping) {
5126 				try
5127 				(*t).trigger();
5128 				catch(Exception e) { sdpy_abort(e); assert(0); }
5129 			}
5130 		}
5131 
5132 	version(Windows) {
5133 		//UINT_PTR handle;
5134 		//static Timer[UINT_PTR] mapping;
5135 		HANDLE handle;
5136 		__gshared Timer[HANDLE] mapping;
5137 	} else version(linux) {
5138 		int fd = -1;
5139 		__gshared Timer[int] mapping;
5140 	} else static assert(0, "timer not supported");
5141 }
5142 }
5143 
5144 version(Windows)
5145 private int eventLoopRound;
5146 
5147 version(Windows)
5148 /// 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
5149 class WindowsHandleReader {
5150 	///
5151 	this(void delegate() onReady, HANDLE handle) {
5152 		this.onReady = onReady;
5153 		this.handle = handle;
5154 
5155 		mapping[handle] = this;
5156 
5157 		enable();
5158 	}
5159 
5160 	///
5161 	void enable() {
5162 		auto el = EventLoop.get().impl;
5163 		el.handles ~= handle;
5164 	}
5165 
5166 	///
5167 	void disable() {
5168 		auto el = EventLoop.get().impl;
5169 		for(int i = 0; i < el.handles.length; i++) {
5170 			if(el.handles[i] is handle) {
5171 				el.handles[i] = el.handles[$-1];
5172 				el.handles = el.handles[0 .. $-1];
5173 				return;
5174 			}
5175 		}
5176 	}
5177 
5178 	void dispose() {
5179 		disable();
5180 		if(handle)
5181 			mapping.remove(handle);
5182 		handle = null;
5183 	}
5184 
5185 	void ready() {
5186 		if(onReady)
5187 			onReady();
5188 	}
5189 
5190 	HANDLE handle;
5191 	void delegate() onReady;
5192 
5193 	__gshared WindowsHandleReader[HANDLE] mapping;
5194 }
5195 
5196 version(Posix)
5197 /// Lets you add files to the event loop for reading. Use at your own risk.
5198 class PosixFdReader {
5199 	///
5200 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5201 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
5202 	}
5203 
5204 	///
5205 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5206 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
5207 	}
5208 
5209 	///
5210 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
5211 		this.onReady = onReady;
5212 		this.fd = fd;
5213 		this.captureWrites = captureWrites;
5214 		this.captureReads = captureReads;
5215 
5216 		mapping[fd] = this;
5217 
5218 		version(with_eventloop) {
5219 			import arsd.eventloop;
5220 			addFileEventListeners(fd, &readyel);
5221 		} else {
5222 			enable();
5223 		}
5224 	}
5225 
5226 	bool captureReads;
5227 	bool captureWrites;
5228 
5229 	version(with_eventloop) {} else
5230 	///
5231 	void enable() {
5232 		prepareEventLoop();
5233 
5234 		enabled = true;
5235 
5236 		version(linux) {
5237 			static import ep = core.sys.linux.epoll;
5238 			ep.epoll_event ev = void;
5239 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5240 			//import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites);
5241 			ev.data.fd = fd;
5242 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
5243 		} else {
5244 
5245 		}
5246 	}
5247 
5248 	version(with_eventloop) {} else
5249 	///
5250 	void disable() {
5251 		prepareEventLoop();
5252 
5253 		enabled = false;
5254 
5255 		version(linux) {
5256 			static import ep = core.sys.linux.epoll;
5257 			ep.epoll_event ev = void;
5258 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
5259 			//import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites);
5260 			ev.data.fd = fd;
5261 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
5262 		}
5263 	}
5264 
5265 	version(with_eventloop) {} else
5266 	///
5267 	void dispose() {
5268 		if(enabled)
5269 			disable();
5270 		if(fd != -1)
5271 			mapping.remove(fd);
5272 		fd = -1;
5273 	}
5274 
5275 	void delegate(int, bool, bool) onReady;
5276 
5277 	version(with_eventloop)
5278 	void readyel() {
5279 		onReady(fd, true, true);
5280 	}
5281 
5282 	void ready(uint flags) {
5283 		version(linux) {
5284 			static import ep = core.sys.linux.epoll;
5285 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
5286 		} else {
5287 			import core.sys.posix.poll;
5288 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
5289 		}
5290 	}
5291 
5292 	void hup(uint flags) {
5293 		if(onHup)
5294 			onHup();
5295 	}
5296 
5297 	void delegate() onHup;
5298 
5299 	int fd = -1;
5300 	private bool enabled;
5301 	__gshared PosixFdReader[int] mapping;
5302 }
5303 
5304 // basic functions to access the clipboard
5305 /+
5306 
5307 
5308 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
5309 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
5310 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5311 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
5312 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
5313 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
5314 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
5315 
5316 +/
5317 
5318 /++
5319 	this does a delegate because it is actually an async call on X...
5320 	the receiver may never be called if the clipboard is empty or unavailable
5321 	gets plain text from the clipboard.
5322 +/
5323 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
5324 	version(Windows) {
5325 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5326 		if(OpenClipboard(hwndOwner) == 0)
5327 			throw new Exception("OpenClipboard");
5328 		scope(exit)
5329 			CloseClipboard();
5330 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
5331 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
5332 
5333 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
5334 				scope(exit)
5335 					GlobalUnlock(dataHandle);
5336 
5337 				// FIXME: CR/LF conversions
5338 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
5339 				int len = 0;
5340 				auto d = data;
5341 				while(*d) {
5342 					d++;
5343 					len++;
5344 				}
5345 				string s;
5346 				s.reserve(len);
5347 				foreach(dchar ch; data[0 .. len]) {
5348 					s ~= ch;
5349 				}
5350 				receiver(s);
5351 			}
5352 		}
5353 	} else version(X11) {
5354 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5355 	} else version(OSXCocoa) {
5356 		throw new NotYetImplementedException();
5357 	} else static assert(0);
5358 }
5359 
5360 // FIXME: a clipboard listener might be cool btw
5361 
5362 /++
5363 	this does a delegate because it is actually an async call on X...
5364 	the receiver may never be called if the clipboard is empty or unavailable
5365 	gets image from the clipboard.
5366 
5367 	templated because it introduces an optional dependency on arsd.bmp
5368 +/
5369 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
5370 	version(Windows) {
5371 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
5372 		if(OpenClipboard(hwndOwner) == 0)
5373 			throw new Exception("OpenClipboard");
5374 		scope(exit)
5375 			CloseClipboard();
5376 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
5377 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
5378 				scope(exit)
5379 					GlobalUnlock(dataHandle);
5380 
5381 				auto len = GlobalSize(dataHandle);
5382 
5383 				import arsd.bmp;
5384 				auto img = readBmp(data[0 .. len], false);
5385 				receiver(img);
5386 			}
5387 		}
5388 	} else version(X11) {
5389 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
5390 	} else version(OSXCocoa) {
5391 		throw new NotYetImplementedException();
5392 	} else static assert(0);
5393 }
5394 
5395 version(Windows)
5396 struct WCharzBuffer {
5397 	wchar[] buffer;
5398 	wchar[256] staticBuffer = void;
5399 
5400 	size_t length() {
5401 		return buffer.length;
5402 	}
5403 
5404 	wchar* ptr() {
5405 		return buffer.ptr;
5406 	}
5407 
5408 	wchar[] slice() {
5409 		return buffer;
5410 	}
5411 
5412 	void copyInto(R)(ref R r) {
5413 		static if(is(R == wchar[N], size_t N)) {
5414 			r[0 .. this.length] = slice[];
5415 			r[this.length] = 0;
5416 		} else static assert(0, "can only copy into wchar[n], not " ~ R.stringof);
5417 	}
5418 
5419 	/++
5420 		conversionFlags = [WindowsStringConversionFlags]
5421 	+/
5422 	this(in char[] data, int conversionFlags = 0) {
5423 		conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name
5424 		auto sz = sizeOfConvertedWstring(data, conversionFlags);
5425 		if(sz > staticBuffer.length)
5426 			buffer = new wchar[](sz);
5427 		else
5428 			buffer = staticBuffer[];
5429 
5430 		buffer = makeWindowsString(data, buffer, conversionFlags);
5431 	}
5432 }
5433 
5434 version(Windows)
5435 int sizeOfConvertedWstring(in char[] s, int conversionFlags) {
5436 	int size = 0;
5437 
5438 	if(conversionFlags & WindowsStringConversionFlags.convertNewLines) {
5439 		// need to convert line endings, which means the length will get bigger.
5440 
5441 		// BTW I betcha this could be faster with some simd stuff.
5442 		char last;
5443 		foreach(char ch; s) {
5444 			if(ch == 10 && last != 13)
5445 				size++; // will add a 13 before it...
5446 			size++;
5447 			last = ch;
5448 		}
5449 	} else {
5450 		// no conversion necessary, just estimate based on length
5451 		/*
5452 			I don't think there's any string with a longer length
5453 			in code units when encoded in UTF-16 than it has in UTF-8.
5454 			This will probably over allocate, but that's OK.
5455 		*/
5456 		size = cast(int) s.length;
5457 	}
5458 
5459 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate)
5460 		size++;
5461 
5462 	return size;
5463 }
5464 
5465 version(Windows)
5466 enum WindowsStringConversionFlags : int {
5467 	zeroTerminate = 1,
5468 	convertNewLines = 2,
5469 }
5470 
5471 version(Windows)
5472 class WindowsApiException : Exception {
5473 	char[256] buffer;
5474 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5475 		assert(msg.length < 100);
5476 
5477 		auto error = GetLastError();
5478 		buffer[0 .. msg.length] = msg;
5479 		buffer[msg.length] = ' ';
5480 
5481 		int pos = cast(int) msg.length + 1;
5482 
5483 		if(error == 0)
5484 			buffer[pos++] = '0';
5485 		else {
5486 
5487 			auto ec = error;
5488 			auto init = pos;
5489 			while(ec) {
5490 				buffer[pos++] = (ec % 10) + '0';
5491 				ec /= 10;
5492 			}
5493 
5494 			buffer[pos++] = ' ';
5495 
5496 			size_t size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &(buffer[pos]), cast(DWORD) buffer.length - pos, null);
5497 
5498 			pos += size;
5499 		}
5500 
5501 
5502 		super(cast(string) buffer[0 .. pos], file, line, next);
5503 	}
5504 }
5505 
5506 class ErrnoApiException : Exception {
5507 	char[256] buffer;
5508 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5509 		assert(msg.length < 100);
5510 
5511 		import core.stdc.errno;
5512 		auto error = errno;
5513 		buffer[0 .. msg.length] = msg;
5514 		buffer[msg.length] = ' ';
5515 
5516 		int pos = cast(int) msg.length + 1;
5517 
5518 		if(error == 0)
5519 			buffer[pos++] = '0';
5520 		else {
5521 			auto init = pos;
5522 			while(error) {
5523 				buffer[pos++] = (error % 10) + '0';
5524 				error /= 10;
5525 			}
5526 			for(int i = 0; i < (pos - init) / 2; i++) {
5527 				char c = buffer[i + init];
5528 				buffer[i + init] = buffer[pos - (i + init) - 1];
5529 				buffer[pos - (i + init) - 1] = c;
5530 			}
5531 		}
5532 
5533 
5534 		super(cast(string) buffer[0 .. pos], file, line, next);
5535 	}
5536 
5537 }
5538 
5539 version(Windows)
5540 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) {
5541 	if(str.length == 0)
5542 		return null;
5543 
5544 	int pos = 0;
5545 	dchar last;
5546 	foreach(dchar c; str) {
5547 		if(c <= 0xFFFF) {
5548 			if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13)
5549 				buffer[pos++] = 13;
5550 			buffer[pos++] = cast(wchar) c;
5551 		} else if(c <= 0x10FFFF) {
5552 			buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
5553 			buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
5554 		}
5555 
5556 		last = c;
5557 	}
5558 
5559 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) {
5560 		buffer[pos] = 0;
5561 	}
5562 
5563 	return buffer[0 .. pos];
5564 }
5565 
5566 version(Windows)
5567 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
5568 	if(str.length == 0)
5569 		return null;
5570 
5571 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null);
5572 	if(got == 0) {
5573 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5574 			throw new Exception("not enough buffer");
5575 		else
5576 			throw new Exception("conversion"); // FIXME: GetLastError
5577 	}
5578 	return buffer[0 .. got];
5579 }
5580 
5581 version(Windows)
5582 string makeUtf8StringFromWindowsString(in wchar[] str) {
5583 	char[] buffer;
5584 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null);
5585 	buffer.length = got;
5586 
5587 	// it is unique because we just allocated it above!
5588 	return cast(string) makeUtf8StringFromWindowsString(str, buffer);
5589 }
5590 
5591 version(Windows)
5592 string makeUtf8StringFromWindowsString(wchar* str) {
5593 	char[] buffer;
5594 	auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null);
5595 	buffer.length = got;
5596 
5597 	got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null);
5598 	if(got == 0) {
5599 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5600 			throw new Exception("not enough buffer");
5601 		else
5602 			throw new Exception("conversion"); // FIXME: GetLastError
5603 	}
5604 	return cast(string) buffer[0 .. got];
5605 }
5606 
5607 int findIndexOfZero(in wchar[] str) {
5608 	foreach(idx, wchar ch; str)
5609 		if(ch == 0)
5610 			return cast(int) idx;
5611 	return cast(int) str.length;
5612 }
5613 int findIndexOfZero(in char[] str) {
5614 	foreach(idx, char ch; str)
5615 		if(ch == 0)
5616 			return cast(int) idx;
5617 	return cast(int) str.length;
5618 }
5619 
5620 /// Copies some text to the clipboard.
5621 void setClipboardText(SimpleWindow clipboardOwner, string text) {
5622 	assert(clipboardOwner !is null);
5623 	version(Windows) {
5624 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5625 			throw new Exception("OpenClipboard");
5626 		scope(exit)
5627 			CloseClipboard();
5628 		EmptyClipboard();
5629 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5630 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
5631 		if(handle is null) throw new Exception("GlobalAlloc");
5632 		if(auto data = cast(wchar*) GlobalLock(handle)) {
5633 			auto slice = data[0 .. sz];
5634 			scope(failure)
5635 				GlobalUnlock(handle);
5636 
5637 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5638 
5639 			GlobalUnlock(handle);
5640 			SetClipboardData(CF_UNICODETEXT, handle);
5641 		}
5642 	} else version(X11) {
5643 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
5644 	} else version(OSXCocoa) {
5645 		throw new NotYetImplementedException();
5646 	} else static assert(0);
5647 }
5648 
5649 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
5650 	assert(clipboardOwner !is null);
5651 	version(Windows) {
5652 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5653 			throw new Exception("OpenClipboard");
5654 		scope(exit)
5655 			CloseClipboard();
5656 		EmptyClipboard();
5657 
5658 
5659 		import arsd.bmp;
5660 		ubyte[] mdata;
5661 		mdata.reserve(img.width * img.height);
5662 		void sink(ubyte b) {
5663 			mdata ~= b;
5664 		}
5665 		writeBmpIndirect(img, &sink, false);
5666 
5667 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
5668 		if(handle is null) throw new Exception("GlobalAlloc");
5669 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
5670 			auto slice = data[0 .. mdata.length];
5671 			scope(failure)
5672 				GlobalUnlock(handle);
5673 
5674 			slice[] = mdata[];
5675 
5676 			GlobalUnlock(handle);
5677 			SetClipboardData(CF_DIB, handle);
5678 		}
5679 	} else version(X11) {
5680 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
5681 			mixin X11SetSelectionHandler_Basics;
5682 			private const(ubyte)[] mdata;
5683 			private const(ubyte)[] mdata_original;
5684 			this(MemoryImage img) {
5685 				import arsd.bmp;
5686 
5687 				mdata.reserve(img.width * img.height);
5688 				void sink(ubyte b) {
5689 					mdata ~= b;
5690 				}
5691 				writeBmpIndirect(img, &sink, true);
5692 
5693 				mdata_original = mdata;
5694 			}
5695 
5696 			Atom[] availableFormats() {
5697 				auto display = XDisplayConnection.get;
5698 				return [
5699 					GetAtom!"image/bmp"(display),
5700 					GetAtom!"TARGETS"(display)
5701 				];
5702 			}
5703 
5704 			ubyte[] getData(Atom format, return scope ubyte[] data) {
5705 				if(mdata.length < data.length) {
5706 					data[0 .. mdata.length] = mdata[];
5707 					auto ret = data[0 .. mdata.length];
5708 					mdata = mdata[$..$];
5709 					return ret;
5710 				} else {
5711 					data[] = mdata[0 .. data.length];
5712 					mdata = mdata[data.length .. $];
5713 					return data[];
5714 				}
5715 			}
5716 
5717 			void done() {
5718 				mdata = mdata_original;
5719 			}
5720 		}
5721 
5722 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
5723 	} else version(OSXCocoa) {
5724 		throw new NotYetImplementedException();
5725 	} else static assert(0);
5726 }
5727 
5728 
5729 version(X11) {
5730 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
5731 
5732 	private Atom*[] interredAtoms; // for discardAndRecreate
5733 
5734 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
5735 	/// Platform-specific for X11.
5736 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
5737 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
5738 		static Atom a;
5739 		if(!a) {
5740 			a = XInternAtom(display, name, !create);
5741 			interredAtoms ~= &a;
5742 		}
5743 		if(a == None)
5744 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
5745 		return a;
5746 	}
5747 
5748 	/// Platform-specific for X11 - gets atom names as a string.
5749 	string getAtomName(Atom atom, Display* display) {
5750 		auto got = XGetAtomName(display, atom);
5751 		scope(exit) XFree(got);
5752 		import core.stdc.string;
5753 		string s = got[0 .. strlen(got)].idup;
5754 		return s;
5755 	}
5756 
5757 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later.
5758 	void setPrimarySelection(SimpleWindow window, string text) {
5759 		setX11Selection!"PRIMARY"(window, text);
5760 	}
5761 
5762 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later.
5763 	void setSecondarySelection(SimpleWindow window, string text) {
5764 		setX11Selection!"SECONDARY"(window, text);
5765 	}
5766 
5767 	interface X11SetSelectionHandler {
5768 		// should include TARGETS right now
5769 		Atom[] availableFormats();
5770 		// Return the slice of data you filled, empty slice if done.
5771 		// this is to support the incremental thing
5772 		ubyte[] getData(Atom format, return scope ubyte[] data);
5773 
5774 		void done();
5775 
5776 		void handleRequest(XEvent);
5777 
5778 		bool matchesIncr(Window, Atom);
5779 		void sendMoreIncr(XPropertyEvent*);
5780 	}
5781 
5782 	mixin template X11SetSelectionHandler_Basics() {
5783 		Window incrWindow;
5784 		Atom incrAtom;
5785 		Atom selectionAtom;
5786 		Atom formatAtom;
5787 		ubyte[] toSend;
5788 		bool matchesIncr(Window w, Atom a) {
5789 			return incrAtom && incrAtom == a && w == incrWindow;
5790 		}
5791 		void sendMoreIncr(XPropertyEvent* event) {
5792 			auto display = XDisplayConnection.get;
5793 
5794 			XChangeProperty (display,
5795 				incrWindow,
5796 				incrAtom,
5797 				formatAtom,
5798 				8 /* bits */, PropModeReplace,
5799 				toSend.ptr, cast(int) toSend.length);
5800 
5801 			if(toSend.length != 0) {
5802 				toSend = this.getData(formatAtom, toSend[]);
5803 			} else {
5804 				this.done();
5805 				incrWindow = None;
5806 				incrAtom = None;
5807 				selectionAtom = None;
5808 				formatAtom = None;
5809 				toSend = null;
5810 			}
5811 		}
5812 		void handleRequest(XEvent ev) {
5813 
5814 			auto display = XDisplayConnection.get;
5815 
5816 			XSelectionRequestEvent* event = &ev.xselectionrequest;
5817 			XSelectionEvent selectionEvent;
5818 			selectionEvent.type = EventType.SelectionNotify;
5819 			selectionEvent.display = event.display;
5820 			selectionEvent.requestor = event.requestor;
5821 			selectionEvent.selection = event.selection;
5822 			selectionEvent.time = event.time;
5823 			selectionEvent.target = event.target;
5824 
5825 			bool supportedType() {
5826 				foreach(t; this.availableFormats())
5827 					if(t == event.target)
5828 						return true;
5829 				return false;
5830 			}
5831 
5832 			if(event.property == None) {
5833 				selectionEvent.property = event.target;
5834 
5835 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5836 				XFlush(display);
5837 			} if(event.target == GetAtom!"TARGETS"(display)) {
5838 				/* respond with the supported types */
5839 				auto tlist = this.availableFormats();
5840 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
5841 				selectionEvent.property = event.property;
5842 
5843 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5844 				XFlush(display);
5845 			} else if(supportedType()) {
5846 				auto buffer = new ubyte[](1024 * 64);
5847 				auto toSend = this.getData(event.target, buffer[]);
5848 
5849 				if(toSend.length < 32 * 1024) {
5850 					// small enough to send directly...
5851 					selectionEvent.property = event.property;
5852 					XChangeProperty (display,
5853 						selectionEvent.requestor,
5854 						selectionEvent.property,
5855 						event.target,
5856 						8 /* bits */, 0 /* PropModeReplace */,
5857 						toSend.ptr, cast(int) toSend.length);
5858 
5859 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5860 					XFlush(display);
5861 				} else {
5862 					// large, let's send incrementally
5863 					arch_ulong l = toSend.length;
5864 
5865 					// if I wanted other events from this window don't want to clear that out....
5866 					XWindowAttributes xwa;
5867 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
5868 
5869 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
5870 
5871 					incrWindow = event.requestor;
5872 					incrAtom = event.property;
5873 					formatAtom = event.target;
5874 					selectionAtom = event.selection;
5875 					this.toSend = toSend;
5876 
5877 					selectionEvent.property = event.property;
5878 					XChangeProperty (display,
5879 						selectionEvent.requestor,
5880 						selectionEvent.property,
5881 						GetAtom!"INCR"(display),
5882 						32 /* bits */, PropModeReplace,
5883 						&l, 1);
5884 
5885 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5886 					XFlush(display);
5887 				}
5888 				//if(after)
5889 					//after();
5890 			} else {
5891 				debug(sdpy_clip) {
5892 					import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display));
5893 				}
5894 				selectionEvent.property = None; // I don't know how to handle this type...
5895 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
5896 				XFlush(display);
5897 			}
5898 		}
5899 	}
5900 
5901 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
5902 		mixin X11SetSelectionHandler_Basics;
5903 		private const(ubyte)[] text;
5904 		private const(ubyte)[] text_original;
5905 		this(string text) {
5906 			this.text = cast(const ubyte[]) text;
5907 			this.text_original = this.text;
5908 		}
5909 		Atom[] availableFormats() {
5910 			auto display = XDisplayConnection.get;
5911 			return [
5912 				GetAtom!"UTF8_STRING"(display),
5913 				GetAtom!"text/plain"(display),
5914 				XA_STRING,
5915 				GetAtom!"TARGETS"(display)
5916 			];
5917 		}
5918 
5919 		ubyte[] getData(Atom format, return scope ubyte[] data) {
5920 			if(text.length < data.length) {
5921 				data[0 .. text.length] = text[];
5922 				return data[0 .. text.length];
5923 			} else {
5924 				data[] = text[0 .. data.length];
5925 				text = text[data.length .. $];
5926 				return data[];
5927 			}
5928 		}
5929 
5930 		void done() {
5931 			text = text_original;
5932 		}
5933 	}
5934 
5935 	/// 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?!)
5936 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
5937 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
5938 	}
5939 
5940 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
5941 		assert(window !is null);
5942 
5943 		auto display = XDisplayConnection.get();
5944 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
5945 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
5946 		else Atom a = GetAtom!atomName(display);
5947 
5948 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
5949 
5950 		window.impl.setSelectionHandlers[a] = data;
5951 	}
5952 
5953 	///
5954 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
5955 		getX11Selection!"PRIMARY"(window, handler);
5956 	}
5957 
5958 	// added July 28, 2020
5959 	// undocumented as experimental tho
5960 	interface X11GetSelectionHandler {
5961 		void handleData(Atom target, in ubyte[] data);
5962 		Atom findBestFormat(Atom[] answer);
5963 
5964 		void prepareIncremental(Window, Atom);
5965 		bool matchesIncr(Window, Atom);
5966 		void handleIncrData(Atom, in ubyte[] data);
5967 	}
5968 
5969 	mixin template X11GetSelectionHandler_Basics() {
5970 		Window incrWindow;
5971 		Atom incrAtom;
5972 
5973 		void prepareIncremental(Window w, Atom a) {
5974 			incrWindow = w;
5975 			incrAtom = a;
5976 		}
5977 		bool matchesIncr(Window w, Atom a) {
5978 			return incrWindow == w && incrAtom == a;
5979 		}
5980 
5981 		Atom incrFormatAtom;
5982 		ubyte[] incrData;
5983 		void handleIncrData(Atom format, in ubyte[] data) {
5984 			incrFormatAtom = format;
5985 
5986 			if(data.length)
5987 				incrData ~= data;
5988 			else
5989 				handleData(incrFormatAtom, incrData);
5990 
5991 		}
5992 	}
5993 
5994 	///
5995 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
5996 		assert(window !is null);
5997 
5998 		auto display = XDisplayConnection.get();
5999 		auto atom = GetAtom!atomName(display);
6000 
6001 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
6002 			this(void delegate(in char[]) handler) {
6003 				this.handler = handler;
6004 			}
6005 
6006 			mixin X11GetSelectionHandler_Basics;
6007 
6008 			void delegate(in char[]) handler;
6009 
6010 			void handleData(Atom target, in ubyte[] data) {
6011 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
6012 					handler(cast(const char[]) data);
6013 			}
6014 
6015 			Atom findBestFormat(Atom[] answer) {
6016 				Atom best = None;
6017 				foreach(option; answer) {
6018 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
6019 						best = option;
6020 						break;
6021 					} else if(option == XA_STRING) {
6022 						best = option;
6023 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
6024 						best = option;
6025 					}
6026 				}
6027 				return best;
6028 			}
6029 		}
6030 
6031 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
6032 
6033 		auto target = GetAtom!"TARGETS"(display);
6034 
6035 		// SDD_DATA is "simpledisplay.d data"
6036 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
6037 	}
6038 
6039 	/// Gets the image on the clipboard, if there is one. Added July 2020.
6040 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
6041 		assert(window !is null);
6042 
6043 		auto display = XDisplayConnection.get();
6044 		auto atom = GetAtom!atomName(display);
6045 
6046 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
6047 			this(void delegate(MemoryImage) handler) {
6048 				this.handler = handler;
6049 			}
6050 
6051 			mixin X11GetSelectionHandler_Basics;
6052 
6053 			void delegate(MemoryImage) handler;
6054 
6055 			void handleData(Atom target, in ubyte[] data) {
6056 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6057 					import arsd.bmp;
6058 					handler(readBmp(data));
6059 				}
6060 			}
6061 
6062 			Atom findBestFormat(Atom[] answer) {
6063 				Atom best = None;
6064 				foreach(option; answer) {
6065 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
6066 						best = option;
6067 					}
6068 				}
6069 				return best;
6070 			}
6071 
6072 		}
6073 
6074 
6075 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
6076 
6077 		auto target = GetAtom!"TARGETS"(display);
6078 
6079 		// SDD_DATA is "simpledisplay.d data"
6080 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
6081 	}
6082 
6083 
6084 	///
6085 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
6086 		Atom actualType;
6087 		int actualFormat;
6088 		arch_ulong actualItems;
6089 		arch_ulong bytesRemaining;
6090 		void* data;
6091 
6092 		auto display = XDisplayConnection.get();
6093 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
6094 			if(actualFormat == 0)
6095 				return null;
6096 			else {
6097 				int byteLength;
6098 				if(actualFormat == 32) {
6099 					// 32 means it is a C long... which is variable length
6100 					actualFormat = cast(int) arch_long.sizeof * 8;
6101 				}
6102 
6103 				// then it is just a bit count
6104 				byteLength = cast(int) (actualItems * actualFormat / 8);
6105 
6106 				auto d = new ubyte[](byteLength);
6107 				d[] = cast(ubyte[]) data[0 .. byteLength];
6108 				XFree(data);
6109 				return d;
6110 			}
6111 		}
6112 		return null;
6113 	}
6114 
6115 	/* defined in the systray spec */
6116 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
6117 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
6118 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
6119 
6120 
6121 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
6122 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
6123 	public class GlobalHotkey {
6124 		KeyEvent key;
6125 		void delegate () handler;
6126 
6127 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
6128 
6129 		/// Create from initialzed KeyEvent object
6130 		this (KeyEvent akey, void delegate () ahandler=null) {
6131 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
6132 			key = akey;
6133 			handler = ahandler;
6134 		}
6135 
6136 		/// Create from emacs-like key name ("C-M-Y", etc.)
6137 		this (const(char)[] akey, void delegate () ahandler=null) {
6138 			key = KeyEvent.parse(akey);
6139 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
6140 			handler = ahandler;
6141 		}
6142 
6143 	}
6144 
6145 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6146 		//conwriteln("failed to grab key");
6147 		GlobalHotkeyManager.ghfailed = true;
6148 		return 0;
6149 	}
6150 
6151 	private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6152 		Image.impl.xshmfailed = true;
6153 		return 0;
6154 	}
6155 
6156 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
6157 		import core.stdc.stdio;
6158 		char[265] buffer;
6159 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
6160 		debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, evt.serial, evt.request_code, evt.minor_code, evt.resourceid);
6161 		return 0;
6162 	}
6163 
6164 	/++
6165 		Global hotkey manager. It contains static methods to manage global hotkeys.
6166 
6167 		---
6168 		 try {
6169 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
6170 		} catch (Exception e) {
6171 			conwriteln("ERROR registering hotkey!");
6172 		}
6173 		---
6174 
6175 		The key strings are based on Emacs. In practical terms,
6176 		`M` means `alt` and `H` means the Windows logo key. `C`
6177 		is `ctrl`.
6178 
6179 		$(WARNING
6180 			This is X-specific right now. If you are on
6181 			Windows, try [registerHotKey] instead.
6182 
6183 			We will probably merge these into a single
6184 			interface later.
6185 		)
6186 	+/
6187 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
6188 		version(X11) {
6189 			void recreateAfterDisconnect() {
6190 				throw new Exception("NOT IMPLEMENTED");
6191 			}
6192 			void discardConnectionState() {
6193 				throw new Exception("NOT IMPLEMENTED");
6194 			}
6195 		}
6196 
6197 		private static immutable uint[8] masklist = [ 0,
6198 			KeyOrButtonMask.LockMask,
6199 			KeyOrButtonMask.Mod2Mask,
6200 			KeyOrButtonMask.Mod3Mask,
6201 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
6202 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
6203 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6204 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
6205 		];
6206 		private __gshared GlobalHotkeyManager ghmanager;
6207 		private __gshared bool ghfailed = false;
6208 
6209 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
6210 			if (modmask == 0) return false;
6211 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
6212 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
6213 			return true;
6214 		}
6215 
6216 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
6217 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
6218 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
6219 			return modmask;
6220 		}
6221 
6222 		private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) {
6223 			uint keycode = cast(uint)ke.key;
6224 			auto dpy = XDisplayConnection.get;
6225 			return XKeysymToKeycode(dpy, keycode);
6226 		}
6227 
6228 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
6229 
6230 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
6231 
6232 		NativeEventHandler getNativeEventHandler () {
6233 			return delegate int (XEvent e) {
6234 				if (e.type != EventType.KeyPress) return 1;
6235 				auto kev = cast(const(XKeyEvent)*)&e;
6236 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
6237 				if (auto ghkp = hash in globalHotkeyList) {
6238 					try {
6239 						ghkp.doHandle();
6240 					} catch (Exception e) {
6241 						import core.stdc.stdio : stderr, fprintf;
6242 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
6243 					}
6244 				}
6245 				return 1;
6246 			};
6247 		}
6248 
6249 		private this () {
6250 			auto dpy = XDisplayConnection.get;
6251 			auto root = RootWindow(dpy, DefaultScreen(dpy));
6252 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
6253 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
6254 		}
6255 
6256 		/// Register new global hotkey with initialized `GlobalHotkey` object.
6257 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
6258 		static void register (GlobalHotkey gh) {
6259 			if (gh is null) return;
6260 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
6261 
6262 			auto dpy = XDisplayConnection.get;
6263 			immutable keycode = keyEvent2KeyCode(gh.key);
6264 
6265 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
6266 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
6267 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
6268 			XSync(dpy, 0/*False*/);
6269 
6270 			Window root = RootWindow(dpy, DefaultScreen(dpy));
6271 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6272 			ghfailed = false;
6273 			foreach (immutable uint ormask; masklist[]) {
6274 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
6275 			}
6276 			XSync(dpy, 0/*False*/);
6277 			XSetErrorHandler(savedErrorHandler);
6278 
6279 			if (ghfailed) {
6280 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6281 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
6282 				XSync(dpy, 0/*False*/);
6283 				XSetErrorHandler(savedErrorHandler);
6284 				throw new Exception("cannot register global hotkey");
6285 			}
6286 
6287 			globalHotkeyList[hash] = gh;
6288 		}
6289 
6290 		/// Ditto
6291 		static void register (const(char)[] akey, void delegate () ahandler) {
6292 			register(new GlobalHotkey(akey, ahandler));
6293 		}
6294 
6295 		private static void removeByHash (ulong hash) {
6296 			if (auto ghp = hash in globalHotkeyList) {
6297 				auto dpy = XDisplayConnection.get;
6298 				immutable keycode = keyEvent2KeyCode(ghp.key);
6299 				Window root = RootWindow(dpy, DefaultScreen(dpy));
6300 				XSync(dpy, 0/*False*/);
6301 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
6302 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
6303 				XSync(dpy, 0/*False*/);
6304 				XSetErrorHandler(savedErrorHandler);
6305 				globalHotkeyList.remove(hash);
6306 			}
6307 		}
6308 
6309 		/// Register new global hotkey with previously used `GlobalHotkey` object.
6310 		/// It is safe to unregister unknown or invalid hotkey.
6311 		static void unregister (GlobalHotkey gh) {
6312 			//TODO: add second AA for faster search? prolly doesn't worth it.
6313 			if (gh is null) return;
6314 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
6315 				if (kv.value is gh) {
6316 					removeByHash(kv.key);
6317 					return;
6318 				}
6319 			}
6320 		}
6321 
6322 		/// Ditto.
6323 		static void unregister (const(char)[] key) {
6324 			auto kev = KeyEvent.parse(key);
6325 			immutable keycode = keyEvent2KeyCode(kev);
6326 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
6327 		}
6328 	}
6329 }
6330 
6331 version(Windows) {
6332 	/// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application).
6333 	void sendSyntheticInput(wstring s) {
6334 		INPUT[] inputs;
6335 		inputs.reserve(s.length * 2);
6336 
6337 		foreach(wchar c; s) {
6338 			INPUT input;
6339 			input.type = INPUT_KEYBOARD;
6340 			input.ki.wScan = c;
6341 			input.ki.dwFlags = KEYEVENTF_UNICODE;
6342 			inputs ~= input;
6343 
6344 			input.ki.dwFlags |= KEYEVENTF_KEYUP;
6345 			inputs ~= input;
6346 		}
6347 
6348 		if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
6349 			throw new Exception("SendInput failed");
6350 		}
6351 	}
6352 
6353 
6354 	// global hotkey helper function
6355 
6356 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID.
6357 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
6358 		__gshared int hotkeyId = 0;
6359 		int id = ++hotkeyId;
6360 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
6361 			throw new Exception("RegisterHotKey failed");
6362 
6363 		__gshared void delegate()[WPARAM][HWND] handlers;
6364 
6365 		handlers[window.impl.hwnd][id] = handler;
6366 
6367 		int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler;
6368 
6369 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
6370 			switch(msg) {
6371 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
6372 				case WM_HOTKEY:
6373 					if(auto list = hwnd in handlers) {
6374 						if(auto h = wParam in *list) {
6375 							(*h)();
6376 							return 0;
6377 						}
6378 					}
6379 				goto default;
6380 				default:
6381 			}
6382 			if(oldHandler)
6383 				return oldHandler(hwnd, msg, wParam, lParam, mustReturn);
6384 			return 1; // pass it on
6385 		};
6386 
6387 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
6388 			oldHandler = window.handleNativeEvent;
6389 			window.handleNativeEvent = nativeEventHandler;
6390 		}
6391 
6392 		return id;
6393 	}
6394 
6395 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey].
6396 	void unregisterHotKey(SimpleWindow window, int id) {
6397 		if(!UnregisterHotKey(window.impl.hwnd, id))
6398 			throw new Exception("UnregisterHotKey");
6399 	}
6400 }
6401 
6402 version (X11) {
6403 	pragma(lib, "dl");
6404 	import core.sys.posix.dlfcn;
6405 
6406 	/++
6407 		Allows for sending synthetic input to the X server via the Xtst
6408 		extension.
6409 
6410 		Please remember user input is meant to be user - don't use this
6411 		if you have some other alternative!
6412 
6413 		If you need this on Windows btw, the top-level [sendSyntheticInput] shows
6414 		the Win32 api to start it, but I only did basics there, PR welcome if you like,
6415 		it is an easy enough function to use.
6416 
6417 		History: Added May 17, 2020.
6418 	+/
6419 	struct SyntheticInput {
6420 		@disable this();
6421 
6422 		private void* lib;
6423 		private int* refcount;
6424 
6425 		private extern(C) {
6426 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
6427 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
6428 		}
6429 
6430 		/// The dummy param must be 0.
6431 		this(int dummy) {
6432 			lib = dlopen("libXtst.so", RTLD_NOW);
6433 			if(lib is null)
6434 				throw new Exception("cannot load xtest lib extension");
6435 			scope(failure)
6436 				dlclose(lib);
6437 
6438 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6439 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6440 
6441 			if(XTestFakeKeyEvent is null)
6442 				throw new Exception("No XTestFakeKeyEvent");
6443 			if(XTestFakeButtonEvent is null)
6444 				throw new Exception("No XTestFakeButtonEvent");
6445 
6446 			refcount = new int;
6447 			*refcount = 1;
6448 		}
6449 
6450 		this(this) {
6451 			if(refcount)
6452 				*refcount += 1;
6453 		}
6454 
6455 		~this() {
6456 			if(refcount) {
6457 				*refcount -= 1;
6458 				if(*refcount == 0)
6459 					// I commented this because if I close the lib before
6460 					// XCloseDisplay, it is liable to segfault... so just
6461 					// gonna keep it loaded if it is loaded, no big deal
6462 					// anyway.
6463 					{} // dlclose(lib);
6464 			}
6465 		}
6466 
6467 		/// This ONLY works with basic ascii!
6468 		void sendSyntheticInput(string s) {
6469 			int delay = 0;
6470 			foreach(ch; s) {
6471 				pressKey(cast(Key) ch, true, delay);
6472 				pressKey(cast(Key) ch, false, delay);
6473 				delay += 5;
6474 			}
6475 		}
6476 
6477 		/++
6478 			Sends a fake press key event.
6479 
6480 			Please note you need to call [flushGui] or return to the event loop for this to actually be sent.
6481 		+/
6482 		void pressKey(Key key, bool pressed, int delay = 0) {
6483 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6484 		}
6485 
6486 		///
6487 		void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6488 			int btn;
6489 
6490 			switch(button) {
6491 				case MouseButton.left: btn = 1; break;
6492 				case MouseButton.middle: btn = 2; break;
6493 				case MouseButton.right: btn = 3; break;
6494 				case MouseButton.wheelUp: btn = 4; break;
6495 				case MouseButton.wheelDown: btn = 5; break;
6496 				case MouseButton.backButton: btn = 8; break;
6497 				case MouseButton.forwardButton: btn = 9; break;
6498 				default:
6499 			}
6500 
6501 			assert(btn);
6502 
6503 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6504 		}
6505 
6506 		///
6507 		static void moveMouseArrowBy(int dx, int dy) {
6508 			auto disp = XDisplayConnection.get();
6509 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
6510 			XFlush(disp);
6511 		}
6512 
6513 		///
6514 		static void moveMouseArrowTo(int x, int y) {
6515 			auto disp = XDisplayConnection.get();
6516 			auto root = RootWindow(disp, DefaultScreen(disp));
6517 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
6518 			XFlush(disp);
6519 		}
6520 	}
6521 }
6522 
6523 
6524 
6525 /++
6526 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
6527 
6528 	See_Also:
6529 	$(LIST
6530 		*[ScreenPainter]
6531 		*[ScreenPainter.rasterOp]
6532 	)
6533 +/
6534 enum RasterOp {
6535 	normal, /// Replaces the pixel.
6536 	xor, /// Uses bitwise xor to draw.
6537 }
6538 
6539 // being phobos-free keeps the size WAY down
6540 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
6541 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
6542 package(arsd) const(wchar)* toWStringz(string s) {
6543 	wstring r;
6544 	foreach(dchar c; s)
6545 		r ~= c;
6546 	r ~= '\0';
6547 	return r.ptr;
6548 }
6549 private string[] split(in void[] a, char c) {
6550 		string[] ret;
6551 		size_t previous = 0;
6552 		foreach(i, char ch; cast(ubyte[]) a) {
6553 			if(ch == c) {
6554 				ret ~= cast(string) a[previous .. i];
6555 				previous = i + 1;
6556 			}
6557 		}
6558 		if(previous != a.length)
6559 			ret ~= cast(string) a[previous .. $];
6560 		return ret;
6561 	}
6562 
6563 version(without_opengl) {
6564 	enum OpenGlOptions {
6565 		no,
6566 	}
6567 } else {
6568 	/++
6569 		Determines if you want an OpenGL context created on the new window.
6570 
6571 
6572 		See more: [#topics-3d|in the 3d topic].
6573 
6574 		---
6575 		import arsd.simpledisplay;
6576 		void main() {
6577 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
6578 
6579 			// Set up the matrix
6580 			window.setAsCurrentOpenGlContext(); // make this window active
6581 
6582 			// This is called on each frame, we will draw our scene
6583 			window.redrawOpenGlScene = delegate() {
6584 
6585 			};
6586 
6587 			window.eventLoop(0);
6588 		}
6589 		---
6590 	+/
6591 	enum OpenGlOptions {
6592 		no, /// No OpenGL context is created
6593 		yes, /// Yes, create an OpenGL context
6594 	}
6595 
6596 	version(X11) {
6597 		static if (!SdpyIsUsingIVGLBinds) {
6598 
6599 
6600 			struct __GLXFBConfigRec {}
6601 			alias GLXFBConfig = __GLXFBConfigRec*;
6602 
6603 			//pragma(lib, "GL");
6604 			//pragma(lib, "GLU");
6605 			interface GLX {
6606 			extern(C) nothrow @nogc {
6607 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
6608 						const int *attrib_list);
6609 
6610 				 void glXCopyContext(Display *dpy, GLXContext src,
6611 						GLXContext dst, arch_ulong mask);
6612 
6613 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
6614 						GLXContext share_list, Bool direct);
6615 
6616 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
6617 						Pixmap pixmap);
6618 
6619 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
6620 
6621 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
6622 
6623 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
6624 						int attrib, int *value);
6625 
6626 				 GLXContext glXGetCurrentContext();
6627 
6628 				 GLXDrawable glXGetCurrentDrawable();
6629 
6630 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
6631 
6632 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
6633 						GLXContext ctx);
6634 
6635 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
6636 
6637 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
6638 
6639 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
6640 
6641 				 void glXUseXFont(Font font, int first, int count, int list_base);
6642 
6643 				 void glXWaitGL();
6644 
6645 				 void glXWaitX();
6646 
6647 
6648 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
6649 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
6650 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
6651 
6652 				char* glXQueryExtensionsString (Display*, int);
6653 				void* glXGetProcAddress (const(char)*);
6654 
6655 			}
6656 			}
6657 
6658 			version(OSX)
6659 			mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx;
6660 			else
6661 			mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx;
6662 			shared static this() {
6663 				glx.loadDynamicLibrary();
6664 			}
6665 
6666 			alias glbindGetProcAddress = glXGetProcAddress;
6667 		}
6668 	} else version(Windows) {
6669 		/* it is done below by interface GL */
6670 	} else
6671 		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.");
6672 }
6673 
6674 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
6675 alias Resizablity = Resizability;
6676 
6677 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
6678 enum Resizability {
6679 	fixedSize, /// the window cannot be resized
6680 	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.
6681 	automaticallyScaleIfPossible, /// if possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size. If this is impossible, it will not allow the user to resize the window at all. Note: window.width and window.height WILL be adjusted, which might throw you off if you draw based on them, so keep track of your expected width and height separately. That way, when it is scaled, things won't be thrown off.
6682 
6683 	// FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events
6684 }
6685 
6686 
6687 /++
6688 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
6689 +/
6690 enum TextAlignment : uint {
6691 	Left = 0, ///
6692 	Center = 1, ///
6693 	Right = 2, ///
6694 
6695 	VerticalTop = 0, ///
6696 	VerticalCenter = 4, ///
6697 	VerticalBottom = 8, ///
6698 }
6699 
6700 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
6701 alias Rectangle = arsd.color.Rectangle;
6702 
6703 
6704 /++
6705 	Keyboard press and release events.
6706 +/
6707 struct KeyEvent {
6708 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
6709 	Key key;
6710 	ubyte hardwareCode; /// A platform and hardware specific code for the key
6711 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
6712 
6713 	deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character;
6714 
6715 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
6716 
6717 	SimpleWindow window; /// associated Window
6718 
6719 	/++
6720 		A view into the upcoming buffer holding coming character events that are sent if and only if neither
6721 		the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))`
6722 		to predict if char events are actually coming..
6723 
6724 		Only available on X systems since this information is not given ahead of time elsewhere.
6725 		(Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.)
6726 
6727 		I'm adding this because it is useful to the terminal emulator, but given its platform specificness
6728 		and potential quirks I'd recommend avoiding it.
6729 
6730 		History:
6731 			Added April 26, 2021 (dub v9.5)
6732 	+/
6733 	version(X11)
6734 		dchar[] charsPossible;
6735 
6736 	// convert key event to simplified string representation a-la emacs
6737 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
6738 		uint dpos = 0;
6739 		void put (const(char)[] s...) nothrow @trusted {
6740 			static if (growdest) {
6741 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
6742 			} else {
6743 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
6744 			}
6745 		}
6746 
6747 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
6748 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
6749 		}
6750 
6751 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
6752 
6753 		// put modifiers
6754 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
6755 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
6756 		putMod(ModifierState.alt, Key.Alt, "Alt+");
6757 		putMod(ModifierState.windows, Key.Shift, "Windows+");
6758 		putMod(ModifierState.shift, Key.Shift, "Shift+");
6759 
6760 		if (this.key) {
6761 			foreach (string kn; __traits(allMembers, Key)) {
6762 				if (this.key == __traits(getMember, Key, kn)) {
6763 					// HACK!
6764 					static if (kn == "N0") put("0");
6765 					else static if (kn == "N1") put("1");
6766 					else static if (kn == "N2") put("2");
6767 					else static if (kn == "N3") put("3");
6768 					else static if (kn == "N4") put("4");
6769 					else static if (kn == "N5") put("5");
6770 					else static if (kn == "N6") put("6");
6771 					else static if (kn == "N7") put("7");
6772 					else static if (kn == "N8") put("8");
6773 					else static if (kn == "N9") put("9");
6774 					else put(kn);
6775 					return dest[0..dpos];
6776 				}
6777 			}
6778 			put("Unknown");
6779 		} else {
6780 			if (dpos && dest[dpos-1] == '+') --dpos;
6781 		}
6782 		return dest[0..dpos];
6783 	}
6784 
6785 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
6786 
6787 	/** Parse string into key name with modifiers. It accepts things like:
6788 	 *
6789 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
6790 	 *
6791 	 * Ctrl+Win+1 -- windows style
6792 	 *
6793 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
6794 	 *
6795 	 * Ctrl Win 1 -- and space
6796 	 *
6797 	 * and even "Win + 1 + Ctrl".
6798 	 */
6799 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
6800 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
6801 
6802 		// remove trailing spaces
6803 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
6804 
6805 		// tokens delimited by blank, '+', or '-'
6806 		// null on eol
6807 		const(char)[] getToken () nothrow @trusted @nogc {
6808 			// remove leading spaces and delimiters
6809 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
6810 			if (name.length == 0) return null; // oops, no more tokens
6811 			// get token
6812 			size_t epos = 0;
6813 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
6814 			assert(epos > 0 && epos <= name.length);
6815 			auto res = name[0..epos];
6816 			name = name[epos..$];
6817 			return res;
6818 		}
6819 
6820 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
6821 			if (s0.length != s1.length) return false;
6822 			foreach (immutable ci, char c0; s0) {
6823 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
6824 				char c1 = s1[ci];
6825 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
6826 				if (c0 != c1) return false;
6827 			}
6828 			return true;
6829 		}
6830 
6831 		if (ignoreModsOut !is null) *ignoreModsOut = false;
6832 		if (updown !is null) *updown = -1;
6833 		KeyEvent res;
6834 		res.key = cast(Key)0; // just in case
6835 		const(char)[] tk, tkn; // last token
6836 		bool allowEmascStyle = true;
6837 		bool ignoreModifiers = false;
6838 		tokenloop: for (;;) {
6839 			tk = tkn;
6840 			tkn = getToken();
6841 			//k8: yay, i took "Bloody Mess" trait from Fallout!
6842 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
6843 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
6844 			if (allowEmascStyle && tkn.length != 0) {
6845 				if (tk.length == 1) {
6846 					char mdc = tk[0];
6847 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
6848 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
6849 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
6850 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
6851 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
6852 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
6853 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
6854 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
6855 				}
6856 			}
6857 			allowEmascStyle = false;
6858 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
6859 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
6860 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
6861 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
6862 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
6863 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
6864 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
6865 			if (tk.length == 0) continue;
6866 			// try key name
6867 			if (res.key == 0) {
6868 				// little hack
6869 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
6870 					final switch (tk[0]) {
6871 						case '0': tk = "N0"; break;
6872 						case '1': tk = "N1"; break;
6873 						case '2': tk = "N2"; break;
6874 						case '3': tk = "N3"; break;
6875 						case '4': tk = "N4"; break;
6876 						case '5': tk = "N5"; break;
6877 						case '6': tk = "N6"; break;
6878 						case '7': tk = "N7"; break;
6879 						case '8': tk = "N8"; break;
6880 						case '9': tk = "N9"; break;
6881 					}
6882 				}
6883 				foreach (string kn; __traits(allMembers, Key)) {
6884 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
6885 				}
6886 			}
6887 			// unknown or duplicate key name, get out of here
6888 			break;
6889 		}
6890 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
6891 		return res; // something
6892 	}
6893 
6894 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
6895 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
6896 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
6897 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
6898 		}
6899 		bool ignoreMods;
6900 		int updown;
6901 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
6902 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
6903 		if (this.key != ke.key) {
6904 			// things like "ctrl+alt" are complicated
6905 			uint tkm = this.modifierState&modmask;
6906 			uint kkm = ke.modifierState&modmask;
6907 			Key tk = this.key;
6908 			// ke
6909 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
6910 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
6911 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
6912 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
6913 			// this
6914 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
6915 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
6916 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
6917 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
6918 			return (tk == ke.key && tkm == kkm);
6919 		}
6920 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
6921 	}
6922 }
6923 
6924 /// Sets the application name.
6925 @property string ApplicationName(string name) {
6926 	return _applicationName = name;
6927 }
6928 
6929 string _applicationName;
6930 
6931 /// ditto
6932 @property string ApplicationName() {
6933 	if(_applicationName is null) {
6934 		import core.runtime;
6935 		return Runtime.args[0];
6936 	}
6937 	return _applicationName;
6938 }
6939 
6940 
6941 /// Type of a [MouseEvent].
6942 enum MouseEventType : int {
6943 	motion = 0, /// The mouse moved inside the window
6944 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
6945 	buttonReleased = 2, /// A mouse button was released
6946 }
6947 
6948 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
6949 /++
6950 	Listen for this on your event listeners if you are interested in mouse action.
6951 
6952 	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.
6953 
6954 	Examples:
6955 
6956 	This will draw boxes on the window with the mouse as you hold the left button.
6957 	---
6958 	import arsd.simpledisplay;
6959 
6960 	void main() {
6961 		auto window = new SimpleWindow();
6962 
6963 		window.eventLoop(0,
6964 			(MouseEvent ev) {
6965 				if(ev.modifierState & ModifierState.leftButtonDown) {
6966 					auto painter = window.draw();
6967 					painter.fillColor = Color.red;
6968 					painter.outlineColor = Color.black;
6969 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
6970 				}
6971 			}
6972 		);
6973 	}
6974 	---
6975 +/
6976 struct MouseEvent {
6977 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
6978 
6979 	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.
6980 	int y; /// Current Y position of the cursor when the event fired.
6981 
6982 	int dx; /// Change in X position since last report
6983 	int dy; /// Change in Y position since last report
6984 
6985 	MouseButton button; /// See [MouseButton]
6986 	int modifierState; /// See [ModifierState]
6987 
6988 	version(X11)
6989 		private Time timestamp;
6990 
6991 	/// Returns a linear representation of mouse button,
6992 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
6993 	///
6994 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
6995 	@property ubyte buttonLinear() const {
6996 		import core.bitop;
6997 		if(button == 0)
6998 			return 0;
6999 		return (bsf(button) + 1) & 0b1111;
7000 	}
7001 
7002 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
7003 
7004 	SimpleWindow window; /// The window in which the event happened.
7005 
7006 	Point globalCoordinates() {
7007 		Point p;
7008 		if(window is null)
7009 			throw new Exception("wtf");
7010 		static if(UsingSimpledisplayX11) {
7011 			Window child;
7012 			XTranslateCoordinates(
7013 				XDisplayConnection.get,
7014 				window.impl.window,
7015 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
7016 				x, y, &p.x, &p.y, &child);
7017 			return p;
7018 		} else version(Windows) {
7019 			POINT[1] points;
7020 			points[0].x = x;
7021 			points[0].y = y;
7022 			MapWindowPoints(
7023 				window.impl.hwnd,
7024 				null,
7025 				points.ptr,
7026 				points.length
7027 			);
7028 			p.x = points[0].x;
7029 			p.y = points[0].y;
7030 
7031 			return p;
7032 		} else version(OSXCocoa) {
7033 			throw new NotYetImplementedException();
7034 		} else static assert(0);
7035 	}
7036 
7037 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
7038 
7039 	/**
7040 	can contain emacs-like modifier prefix
7041 	case-insensitive names:
7042 		lmbX/leftX
7043 		rmbX/rightX
7044 		mmbX/middleX
7045 		wheelX
7046 		motion (no prefix allowed)
7047 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
7048 	*/
7049 	static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
7050 		if (str.length == 0) return false; // just in case
7051 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
7052 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
7053 		auto anchor = str;
7054 		uint mods = 0; // uint.max == any
7055 		// interesting bits in kmod
7056 		uint kmodmask =
7057 			ModifierState.shift|
7058 			ModifierState.ctrl|
7059 			ModifierState.alt|
7060 			ModifierState.windows|
7061 			ModifierState.leftButtonDown|
7062 			ModifierState.middleButtonDown|
7063 			ModifierState.rightButtonDown|
7064 			0;
7065 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
7066 		bool wasButtons = false;
7067 		while (str.length) {
7068 			if (str.ptr[0] <= ' ') {
7069 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
7070 				continue;
7071 			}
7072 			// one-letter modifier?
7073 			if (str.length >= 2 && str.ptr[1] == '-') {
7074 				switch (str.ptr[0]) {
7075 					case '*': // "any" modifier (cannot be undone)
7076 						mods = mods.max;
7077 						break;
7078 					case 'C': case 'c': // emacs "ctrl"
7079 						if (mods != mods.max) mods |= ModifierState.ctrl;
7080 						break;
7081 					case 'M': case 'm': // emacs "meta"
7082 						if (mods != mods.max) mods |= ModifierState.alt;
7083 						break;
7084 					case 'S': case 's': // emacs "shift"
7085 						if (mods != mods.max) mods |= ModifierState.shift;
7086 						break;
7087 					case 'H': case 'h': // emacs "hyper" (aka winkey)
7088 						if (mods != mods.max) mods |= ModifierState.windows;
7089 						break;
7090 					default:
7091 						return false; // unknown modifier
7092 				}
7093 				str = str[2..$];
7094 				continue;
7095 			}
7096 			// word
7097 			char[16] buf = void; // locased
7098 			auto wep = 0;
7099 			while (str.length) {
7100 				immutable char ch = str.ptr[0];
7101 				if (ch <= ' ' || ch == '-') break;
7102 				str = str[1..$];
7103 				if (wep > buf.length) return false; // too long
7104 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7105 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7106 				else return false; // invalid char
7107 			}
7108 			if (wep == 0) return false; // just in case
7109 			uint bnum;
7110 			enum UpDown { None = -1, Up, Down, Any }
7111 			auto updown = UpDown.None; // 0: up; 1: down
7112 			switch (buf[0..wep]) {
7113 				// left button
7114 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
7115 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
7116 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
7117 				case "lmb": case "left": bnum = 0; break;
7118 				// middle button
7119 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
7120 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
7121 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
7122 				case "mmb": case "middle": bnum = 1; break;
7123 				// right button
7124 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
7125 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
7126 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
7127 				case "rmb": case "right": bnum = 2; break;
7128 				// wheel
7129 				case "wheelup": updown = UpDown.Up; goto case "wheel";
7130 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
7131 				case "wheelany": updown = UpDown.Any; goto case "wheel";
7132 				case "wheel": bnum = 3; break;
7133 				// motion
7134 				case "motion": bnum = 7; break;
7135 				// unknown
7136 				default: return false;
7137 			}
7138 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7139 			// parse possible "-up" or "-down"
7140 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
7141 				wep = 0;
7142 				foreach (immutable idx, immutable char ch; str[1..$]) {
7143 					if (ch <= ' ' || ch == '-') break;
7144 					assert(idx == wep); // for now; trick
7145 					if (wep > buf.length) { wep = 0; break; } // too long
7146 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
7147 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
7148 					else { wep = 0; break; } // invalid char
7149 				}
7150 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
7151 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
7152 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
7153 				// remove parsed part
7154 				if (updown != UpDown.None) str = str[wep+1..$];
7155 			}
7156 			if (updown == UpDown.None) {
7157 				updown = UpDown.Down;
7158 			}
7159 			wasButtons = wasButtons || (bnum <= 2);
7160 			//assert(updown != UpDown.None);
7161 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
7162 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
7163 			if (lastButt != lastButt.max) {
7164 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
7165 				if (mods != mods.max) {
7166 					uint butbit = 0;
7167 					final switch (lastButt&0x03) {
7168 						case 0: butbit = ModifierState.leftButtonDown; break;
7169 						case 1: butbit = ModifierState.middleButtonDown; break;
7170 						case 2: butbit = ModifierState.rightButtonDown; break;
7171 					}
7172 					     if (lastButt&Flag.Down) mods |= butbit;
7173 					else if (lastButt&Flag.Up) mods &= ~butbit;
7174 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
7175 				}
7176 			}
7177 			// remember last button
7178 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
7179 		}
7180 		// no button -- nothing to do
7181 		if (lastButt == lastButt.max) return false;
7182 		// done parsing, check if something's left
7183 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
7184 		// remove action button from mask
7185 		if ((lastButt&0xff) < 3) {
7186 			final switch (lastButt&0x03) {
7187 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
7188 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
7189 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
7190 			}
7191 		}
7192 		// special case: "Motion" means "ignore buttons"
7193 		if ((lastButt&0xff) == 7 && !wasButtons) {
7194 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
7195 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
7196 		}
7197 		uint kmod = event.modifierState&kmodmask;
7198 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
7199 		// check modifier state
7200 		if (mods != mods.max) {
7201 			if (kmod != mods) return false;
7202 		}
7203 		// now check type
7204 		if ((lastButt&0xff) == 7) {
7205 			// motion
7206 			if (event.type != MouseEventType.motion) return false;
7207 		} else if ((lastButt&0xff) == 3) {
7208 			// wheel
7209 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
7210 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
7211 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
7212 			return false;
7213 		} else {
7214 			// buttons
7215 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
7216 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
7217 			{
7218 				return false;
7219 			}
7220 			// button number
7221 			switch (lastButt&0x03) {
7222 				case 0: if (event.button != MouseButton.left) return false; break;
7223 				case 1: if (event.button != MouseButton.middle) return false; break;
7224 				case 2: if (event.button != MouseButton.right) return false; break;
7225 				default: return false;
7226 			}
7227 		}
7228 		return true;
7229 	}
7230 }
7231 
7232 version(arsd_mevent_strcmp_test) unittest {
7233 	MouseEvent event;
7234 	event.type = MouseEventType.buttonPressed;
7235 	event.button = MouseButton.left;
7236 	event.modifierState = ModifierState.ctrl;
7237 	assert(event == "C-LMB");
7238 	assert(event != "C-LMBUP");
7239 	assert(event != "C-LMB-UP");
7240 	assert(event != "C-S-LMB");
7241 	assert(event == "*-LMB");
7242 	assert(event != "*-LMB-UP");
7243 
7244 	event.type = MouseEventType.buttonReleased;
7245 	assert(event != "C-LMB");
7246 	assert(event == "C-LMBUP");
7247 	assert(event == "C-LMB-UP");
7248 	assert(event != "C-S-LMB");
7249 	assert(event != "*-LMB");
7250 	assert(event == "*-LMB-UP");
7251 
7252 	event.button = MouseButton.right;
7253 	event.modifierState |= ModifierState.shift;
7254 	event.type = MouseEventType.buttonPressed;
7255 	assert(event != "C-LMB");
7256 	assert(event != "C-LMBUP");
7257 	assert(event != "C-LMB-UP");
7258 	assert(event != "C-S-LMB");
7259 	assert(event != "*-LMB");
7260 	assert(event != "*-LMB-UP");
7261 
7262 	assert(event != "C-RMB");
7263 	assert(event != "C-RMBUP");
7264 	assert(event != "C-RMB-UP");
7265 	assert(event == "C-S-RMB");
7266 	assert(event == "*-RMB");
7267 	assert(event != "*-RMB-UP");
7268 }
7269 
7270 /// This gives a few more options to drawing lines and such
7271 struct Pen {
7272 	Color color; /// the foreground color
7273 	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.
7274 	Style style; /// See [Style]
7275 /+
7276 // From X.h
7277 
7278 #define LineSolid		0
7279 #define LineOnOffDash		1
7280 #define LineDoubleDash		2
7281        LineDou-        The full path of the line is drawn, but the
7282        bleDash         even dashes are filled differently from the
7283                        odd dashes (see fill-style) with CapButt
7284                        style used where even and odd dashes meet.
7285 
7286 
7287 
7288 /* capStyle */
7289 
7290 #define CapNotLast		0
7291 #define CapButt			1
7292 #define CapRound		2
7293 #define CapProjecting		3
7294 
7295 /* joinStyle */
7296 
7297 #define JoinMiter		0
7298 #define JoinRound		1
7299 #define JoinBevel		2
7300 
7301 /* fillStyle */
7302 
7303 #define FillSolid		0
7304 #define FillTiled		1
7305 #define FillStippled		2
7306 #define FillOpaqueStippled	3
7307 
7308 
7309 +/
7310 	/// Style of lines drawn
7311 	enum Style {
7312 		Solid, /// a solid line
7313 		Dashed, /// a dashed line
7314 		Dotted, /// a dotted line
7315 	}
7316 }
7317 
7318 
7319 /++
7320 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
7321 
7322 
7323 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
7324 
7325 	$(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.)
7326 
7327 	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.
7328 
7329 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
7330 
7331 	$(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.
7332 
7333 	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!
7334 
7335 	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!)
7336 
7337 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
7338 
7339 	---
7340 		auto image = new Image(256, 256);
7341 		scope(exit) destroy(image);
7342 	---
7343 
7344 	As long as you don't hold on to it outside the scope.
7345 
7346 	I might change it to be an owned pointer at some point in the future.
7347 
7348 	)
7349 
7350 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
7351 	you can also often get a fair amount of speedup by getting the raw data format and
7352 	writing some custom code.
7353 
7354 	FIXME INSERT EXAMPLES HERE
7355 
7356 
7357 +/
7358 final class Image {
7359 	///
7360 	this(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
7361 		this.width = width;
7362 		this.height = height;
7363 		this.enableAlpha = enableAlpha;
7364 
7365 		impl.createImage(width, height, forcexshm, enableAlpha);
7366 	}
7367 
7368 	///
7369 	this(Size size, bool forcexshm=false, bool enableAlpha = false) {
7370 		this(size.width, size.height, forcexshm, enableAlpha);
7371 	}
7372 
7373 	private bool suppressDestruction;
7374 
7375 	version(X11)
7376 	this(XImage* handle) {
7377 		this.handle = handle;
7378 		this.rawData = cast(ubyte*) handle.data;
7379 		this.width = handle.width;
7380 		this.height = handle.height;
7381 		this.enableAlpha = handle.depth == 32;
7382 		suppressDestruction = true;
7383 	}
7384 
7385 	~this() {
7386 		if(suppressDestruction) return;
7387 		impl.dispose();
7388 	}
7389 
7390 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
7391 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
7392 	pure const @system nothrow {
7393 		/*
7394 			To use these to draw a blue rectangle with size WxH at position X,Y...
7395 
7396 			// make certain that it will fit before we proceed
7397 			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!
7398 
7399 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
7400 			// (though calculating them isn't really that expensive).
7401 			auto nextLineAdjustment = img.adjustmentForNextLine();
7402 			auto offR = img.redByteOffset();
7403 			auto offB = img.blueByteOffset();
7404 			auto offG = img.greenByteOffset();
7405 			auto bpp = img.bytesPerPixel();
7406 
7407 			auto data = img.getDataPointer();
7408 
7409 			// figure out the starting byte offset
7410 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
7411 
7412 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
7413 
7414 			// and now our drawing loop for the rectangle
7415 			foreach(y; 0 .. H) {
7416 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
7417 				foreach(x; 0 .. W) {
7418 					// write our color
7419 					data[offR] = 0;
7420 					data[offG] = 0;
7421 					data[offB] = 255;
7422 
7423 					data += bpp; // moving to the next pixel is just an addition...
7424 				}
7425 				startOfLine += nextLineAdjustment;
7426 			}
7427 
7428 
7429 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
7430 
7431 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
7432 			can be made into a bitmask or something so we can write them as *uint...
7433 		*/
7434 
7435 		///
7436 		int offsetForTopLeftPixel() {
7437 			version(X11) {
7438 				return 0;
7439 			} else version(Windows) {
7440 				if(enableAlpha) {
7441 					return (width * 4) * (height - 1);
7442 				} else {
7443 					return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
7444 				}
7445 			} else version(OSXCocoa) {
7446 				return 0 ; //throw new NotYetImplementedException();
7447 			} else static assert(0, "fill in this info for other OSes");
7448 		}
7449 
7450 		///
7451 		int offsetForPixel(int x, int y) {
7452 			version(X11) {
7453 				auto offset = (y * width + x) * 4;
7454 				return offset;
7455 			} else version(Windows) {
7456 				if(enableAlpha) {
7457 					auto itemsPerLine = width * 4;
7458 					// remember, bmps are upside down
7459 					auto offset = itemsPerLine * (height - y - 1) + x * 4;
7460 					return offset;
7461 				} else {
7462 					auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7463 					// remember, bmps are upside down
7464 					auto offset = itemsPerLine * (height - y - 1) + x * 3;
7465 					return offset;
7466 				}
7467 			} else version(OSXCocoa) {
7468 				return 0 ; //throw new NotYetImplementedException();
7469 			} else static assert(0, "fill in this info for other OSes");
7470 		}
7471 
7472 		///
7473 		int adjustmentForNextLine() {
7474 			version(X11) {
7475 				return width * 4;
7476 			} else version(Windows) {
7477 				// windows bmps are upside down, so the adjustment is actually negative
7478 				if(enableAlpha)
7479 					return - (cast(int) width * 4);
7480 				else
7481 					return -((cast(int) width * 3 + 3) / 4) * 4;
7482 			} else version(OSXCocoa) {
7483 				return 0 ; //throw new NotYetImplementedException();
7484 			} else static assert(0, "fill in this info for other OSes");
7485 		}
7486 
7487 		/// once you have the position of a pixel, use these to get to the proper color
7488 		int redByteOffset() {
7489 			version(X11) {
7490 				return 2;
7491 			} else version(Windows) {
7492 				return 2;
7493 			} else version(OSXCocoa) {
7494 				return 0 ; //throw new NotYetImplementedException();
7495 			} else static assert(0, "fill in this info for other OSes");
7496 		}
7497 
7498 		///
7499 		int greenByteOffset() {
7500 			version(X11) {
7501 				return 1;
7502 			} else version(Windows) {
7503 				return 1;
7504 			} else version(OSXCocoa) {
7505 				return 0 ; //throw new NotYetImplementedException();
7506 			} else static assert(0, "fill in this info for other OSes");
7507 		}
7508 
7509 		///
7510 		int blueByteOffset() {
7511 			version(X11) {
7512 				return 0;
7513 			} else version(Windows) {
7514 				return 0;
7515 			} else version(OSXCocoa) {
7516 				return 0 ; //throw new NotYetImplementedException();
7517 			} else static assert(0, "fill in this info for other OSes");
7518 		}
7519 
7520 		/// Only valid if [enableAlpha] is true
7521 		int alphaByteOffset() {
7522 			version(X11) {
7523 				return 3;
7524 			} else version(Windows) {
7525 				return 3;
7526 			} else version(OSXCocoa) {
7527 				return 3; //throw new NotYetImplementedException();
7528 			} else static assert(0, "fill in this info for other OSes");
7529 		}
7530 	}
7531 
7532 	///
7533 	final void putPixel(int x, int y, Color c) {
7534 		if(x < 0 || x >= width)
7535 			return;
7536 		if(y < 0 || y >= height)
7537 			return;
7538 
7539 		impl.setPixel(x, y, c);
7540 	}
7541 
7542 	///
7543 	final Color getPixel(int x, int y) {
7544 		if(x < 0 || x >= width)
7545 			return Color.transparent;
7546 		if(y < 0 || y >= height)
7547 			return Color.transparent;
7548 
7549 		version(OSXCocoa) throw new NotYetImplementedException(); else
7550 		return impl.getPixel(x, y);
7551 	}
7552 
7553 	///
7554 	final void opIndexAssign(Color c, int x, int y) {
7555 		putPixel(x, y, c);
7556 	}
7557 
7558 	///
7559 	TrueColorImage toTrueColorImage() {
7560 		auto tci = new TrueColorImage(width, height);
7561 		convertToRgbaBytes(tci.imageData.bytes);
7562 		return tci;
7563 	}
7564 
7565 	///
7566 	static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) {
7567 		auto tci = i.getAsTrueColorImage();
7568 		auto img = new Image(tci.width, tci.height, false, enableAlpha);
7569 		img.setRgbaBytes(tci.imageData.bytes);
7570 		return img;
7571 	}
7572 
7573 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
7574 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
7575 	/// if you pass null, it will allocate a new one.
7576 	ubyte[] getRgbaBytes(ubyte[] where = null) {
7577 		if(where is null)
7578 			where = new ubyte[this.width*this.height*4];
7579 		convertToRgbaBytes(where);
7580 		return where;
7581 	}
7582 
7583 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
7584 	void setRgbaBytes(in ubyte[] from ) {
7585 		assert(from.length == this.width * this.height * 4);
7586 		setFromRgbaBytes(from);
7587 	}
7588 
7589 	// FIXME: make properly cross platform by getting rgba right
7590 
7591 	/// warning: this is not portable across platforms because the data format can change
7592 	ubyte* getDataPointer() {
7593 		return impl.rawData;
7594 	}
7595 
7596 	/// for use with getDataPointer
7597 	final int bytesPerLine() const pure @safe nothrow {
7598 		version(Windows)
7599 			return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
7600 		else version(X11)
7601 			return 4 * width;
7602 		else version(OSXCocoa)
7603 			return 4 * width;
7604 		else static assert(0);
7605 	}
7606 
7607 	/// for use with getDataPointer
7608 	final int bytesPerPixel() const pure @safe nothrow {
7609 		version(Windows)
7610 			return enableAlpha ? 4 : 3;
7611 		else version(X11)
7612 			return 4;
7613 		else version(OSXCocoa)
7614 			return 4;
7615 		else static assert(0);
7616 	}
7617 
7618 	///
7619 	immutable int width;
7620 
7621 	///
7622 	immutable int height;
7623 
7624 	///
7625 	immutable bool enableAlpha;
7626     //private:
7627 	mixin NativeImageImplementation!() impl;
7628 }
7629 
7630 /// A convenience function to pop up a window displaying the image.
7631 /// If you pass a win, it will draw the image in it. Otherwise, it will
7632 /// create a window with the size of the image and run its event loop, closing
7633 /// when a key is pressed.
7634 void displayImage(Image image, SimpleWindow win = null) {
7635 	if(win is null) {
7636 		win = new SimpleWindow(image);
7637 		{
7638 			auto p = win.draw;
7639 			p.drawImage(Point(0, 0), image);
7640 		}
7641 		win.eventLoop(0,
7642 			(KeyEvent ev) {
7643 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
7644 			} );
7645 	} else {
7646 		win.image = image;
7647 	}
7648 }
7649 
7650 enum FontWeight : int {
7651 	dontcare = 0,
7652 	thin = 100,
7653 	extralight = 200,
7654 	light = 300,
7655 	regular = 400,
7656 	medium = 500,
7657 	semibold = 600,
7658 	bold = 700,
7659 	extrabold = 800,
7660 	heavy = 900
7661 }
7662 
7663 // FIXME: i need a font cache and it needs to handle disconnects.
7664 
7665 /++
7666 	Represents a font loaded off the operating system or the X server.
7667 
7668 
7669 	While the api here is unified cross platform, the fonts are not necessarily
7670 	available, even across machines of the same platform, so be sure to always check
7671 	for null (using [isNull]) and have a fallback plan.
7672 
7673 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
7674 
7675 	Worst case, a null font will automatically fall back to the default font loaded
7676 	for your system.
7677 +/
7678 class OperatingSystemFont {
7679 	// FIXME: when the X Connection is lost, these need to be invalidated!
7680 	// that means I need to store the original stuff again to reconstruct it too.
7681 
7682 	version(X11) {
7683 		XFontStruct* font;
7684 		XFontSet fontset;
7685 
7686 		version(with_xft) {
7687 			XftFont* xftFont;
7688 			bool isXft;
7689 		}
7690 	} else version(Windows) {
7691 		HFONT font;
7692 		int width_;
7693 		int height_;
7694 	} else version(OSXCocoa) {
7695 		// FIXME
7696 	} else static assert(0);
7697 
7698 	/++
7699 		Constructs the class and immediately calls [load].
7700 	+/
7701 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7702 		load(name, size, weight, italic);
7703 	}
7704 
7705 	/++
7706 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
7707 
7708 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
7709 
7710 		History:
7711 			Added January 24, 2021.
7712 	+/
7713 	this() {
7714 		// this space intentionally left blank
7715 	}
7716 
7717 	/++
7718 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
7719 
7720 		History:
7721 			Added November 13, 2020.
7722 	+/
7723 	version(with_xft)
7724 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7725 		unload();
7726 
7727 		if(!XftLibrary.attempted) {
7728 			XftLibrary.loadDynamicLibrary();
7729 		}
7730 
7731 		if(!XftLibrary.loadSuccessful)
7732 			return false;
7733 
7734 		auto display = XDisplayConnection.get;
7735 
7736 		char[256] nameBuffer = void;
7737 		int nbp = 0;
7738 
7739 		void add(in char[] a) {
7740 			nameBuffer[nbp .. nbp + a.length] = a[];
7741 			nbp += a.length;
7742 		}
7743 		add(name);
7744 
7745 		if(size) {
7746 			add(":size=");
7747 			add(toInternal!string(size));
7748 		}
7749 		if(weight != FontWeight.dontcare) {
7750 			add(":weight=");
7751 			add(weightToString(weight));
7752 		}
7753 		if(italic)
7754 			add(":slant=100");
7755 
7756 		nameBuffer[nbp] = 0;
7757 
7758 		this.xftFont = XftFontOpenName(
7759 			display,
7760 			DefaultScreen(display),
7761 			nameBuffer.ptr
7762 		);
7763 
7764 		this.isXft = true;
7765 
7766 		if(xftFont !is null) {
7767 			isMonospace_ = stringWidth("x") == stringWidth("M");
7768 			ascent_ = xftFont.ascent;
7769 			descent_ = xftFont.descent;
7770 		}
7771 
7772 		return !isNull();
7773 	}
7774 
7775 	/++
7776 		Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor.
7777 
7778 
7779 		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.
7780 
7781 		If `pattern` is null, it returns all available font families.
7782 
7783 		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.
7784 
7785 		The format of the pattern is platform-specific.
7786 
7787 		History:
7788 			Added May 1, 2021 (dub v9.5)
7789 	+/
7790 	static void listFonts(string pattern, bool delegate(in char[] name) handler) {
7791 		version(Windows) {
7792 			auto hdc = GetDC(null);
7793 			scope(exit) ReleaseDC(null, hdc);
7794 			LOGFONT logfont;
7795 			static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) {
7796 				auto localHandler = *(cast(typeof(handler)*) p);
7797 				return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0;
7798 			}
7799 			EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0);
7800 		} else version(X11) {
7801 			//import core.stdc.stdio;
7802 			bool done = false;
7803 			version(with_xft) {
7804 				if(!XftLibrary.attempted) {
7805 					XftLibrary.loadDynamicLibrary();
7806 				}
7807 
7808 				if(!XftLibrary.loadSuccessful)
7809 					goto skipXft;
7810 
7811 				if(!FontConfigLibrary.attempted)
7812 					FontConfigLibrary.loadDynamicLibrary();
7813 				if(!FontConfigLibrary.loadSuccessful)
7814 					goto skipXft;
7815 
7816 				{
7817 					auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null);
7818 					if(got is null)
7819 						goto skipXft;
7820 					scope(exit) FcFontSetDestroy(got);
7821 
7822 					auto fontPatterns = got.fonts[0 .. got.nfont];
7823 					foreach(candidate; fontPatterns) {
7824 						char* where, whereStyle;
7825 
7826 						char* pmg = FcNameUnparse(candidate);
7827 
7828 						//FcPatternGetString(candidate, "family", 0, &where);
7829 						//FcPatternGetString(candidate, "style", 0, &whereStyle);
7830 						//if(where && whereStyle) {
7831 						if(pmg) {
7832 							if(!handler(pmg.sliceCString))
7833 								return;
7834 							//printf("%s || %s %s\n", pmg, where, whereStyle);
7835 						}
7836 					}
7837 				}
7838 			}
7839 
7840 			skipXft:
7841 
7842 			if(pattern is null)
7843 				pattern = "*";
7844 
7845 			int count;
7846 			auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count);
7847 			scope(exit) XFreeFontNames(coreFontsRaw);
7848 
7849 			auto coreFonts = coreFontsRaw[0 .. count];
7850 
7851 			foreach(font; coreFonts) {
7852 				char[128] tmp;
7853 				tmp[0 ..5] = "core:";
7854 				auto cf = font.sliceCString;
7855 				if(5 + cf.length > tmp.length)
7856 					assert(0, "a font name was too long, sorry i didn't bother implementing a fallback");
7857 				tmp[5 .. 5 + cf.length] = cf;
7858 				if(!handler(tmp[0 .. 5 + cf.length]))
7859 					return;
7860 			}
7861 		}
7862 	}
7863 
7864 	/++
7865 		Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont
7866 		to look up fonts that you then pass to things like [arsd.game.OpenGlLimitedFont] or [arsd.nanovega].
7867 
7868 		Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the
7869 		underlying system doesn't support returning the raw bytes.
7870 
7871 		History:
7872 			Added September 10, 2021 (dub v10.3)
7873 	+/
7874 	ubyte[] getTtfBytes() {
7875 		if(isNull)
7876 			return null;
7877 
7878 		version(Windows) {
7879 			auto dc = GetDC(null);
7880 			auto orig = SelectObject(dc, font);
7881 
7882 			scope(exit) {
7883 				SelectObject(dc, orig);
7884 				ReleaseDC(null, dc);
7885 			}
7886 
7887 			auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0);
7888 			if(res == GDI_ERROR)
7889 				return null;
7890 
7891 			ubyte[] buffer = new ubyte[](res);
7892 			res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length);
7893 			if(res == GDI_ERROR)
7894 				return null; // wtf really tbh
7895 
7896 			return buffer;
7897 		} else version(with_xft) {
7898 			if(isXft && xftFont) {
7899 				if(!FontConfigLibrary.attempted)
7900 					FontConfigLibrary.loadDynamicLibrary();
7901 				if(!FontConfigLibrary.loadSuccessful)
7902 					return null;
7903 
7904 				char* file;
7905 				if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) {
7906 					if (file !is null && file[0]) {
7907 						import core.stdc.stdio;
7908 						auto fp = fopen(file, "rb");
7909 						if(fp is null)
7910 							return null;
7911 						scope(exit)
7912 							fclose(fp);
7913 						fseek(fp, 0, SEEK_END);
7914 						ubyte[] buffer = new ubyte[](ftell(fp));
7915 						fseek(fp, 0, SEEK_SET);
7916 
7917 						auto got = fread(buffer.ptr, 1, buffer.length, fp);
7918 						if(got != buffer.length)
7919 							return null;
7920 
7921 						return buffer;
7922 					}
7923 				}
7924 			}
7925 			return null;
7926 		}
7927 	}
7928 
7929 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
7930 
7931 	private string weightToString(FontWeight weight) {
7932 		with(FontWeight)
7933 		final switch(weight) {
7934 			case dontcare: return "*";
7935 			case thin: return "extralight";
7936 			case extralight: return "extralight";
7937 			case light: return "light";
7938 			case regular: return "regular";
7939 			case medium: return "medium";
7940 			case semibold: return "demibold";
7941 			case bold: return "bold";
7942 			case extrabold: return "demibold";
7943 			case heavy: return "black";
7944 		}
7945 	}
7946 
7947 	/++
7948 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
7949 
7950 		History:
7951 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
7952 	+/
7953 	version(X11)
7954 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7955 		unload();
7956 
7957 		string xfontstr;
7958 
7959 		if(name.length > 3 && name[0 .. 3] == "-*-") {
7960 			// this is kinda a disgusting hack but if the user sends an exact
7961 			// string I'd like to honor it...
7962 			xfontstr = name;
7963 		} else {
7964 			string weightstr = weightToString(weight);
7965 			string sizestr;
7966 			if(size == 0)
7967 				sizestr = "*";
7968 			else
7969 				sizestr = toInternal!string(size);
7970 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
7971 		}
7972 
7973 		//import std.stdio; writeln(xfontstr);
7974 
7975 		auto display = XDisplayConnection.get;
7976 
7977 		font = XLoadQueryFont(display, xfontstr.ptr);
7978 		if(font is null)
7979 			return false;
7980 
7981 		char** lol;
7982 		int lol2;
7983 		char* lol3;
7984 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
7985 
7986 		prepareFontInfo();
7987 
7988 		return !isNull();
7989 	}
7990 
7991 	version(X11)
7992 	private void prepareFontInfo() {
7993 		if(font !is null) {
7994 			isMonospace_ = stringWidth("l") == stringWidth("M");
7995 			ascent_ = font.max_bounds.ascent;
7996 			descent_ = font.max_bounds.descent;
7997 		}
7998 	}
7999 
8000 	/++
8001 		Loads a Windows font. You probably want to use [load] instead to be more generic.
8002 
8003 		History:
8004 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
8005 	+/
8006 	version(Windows)
8007 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
8008 		unload();
8009 
8010 		WCharzBuffer buffer = WCharzBuffer(name);
8011 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
8012 
8013 		prepareFontInfo(hdc);
8014 
8015 		return !isNull();
8016 	}
8017 
8018 	version(Windows)
8019 	void prepareFontInfo(HDC hdc = null) {
8020 		if(font is null)
8021 			return;
8022 
8023 		TEXTMETRIC tm;
8024 		auto dc = hdc ? hdc : GetDC(null);
8025 		auto orig = SelectObject(dc, font);
8026 		GetTextMetrics(dc, &tm);
8027 		SelectObject(dc, orig);
8028 		if(hdc is null)
8029 			ReleaseDC(null, dc);
8030 
8031 		width_ = tm.tmAveCharWidth;
8032 		height_ = tm.tmHeight;
8033 		ascent_ = tm.tmAscent;
8034 		descent_ = tm.tmDescent;
8035 		// 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.
8036 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
8037 	}
8038 
8039 
8040 	/++
8041 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
8042 
8043 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
8044 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
8045 
8046 		On Windows, it forwards directly to [loadWin32].
8047 
8048 		Params:
8049 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
8050 			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.
8051 			weight = approximate boldness, results may vary.
8052 			italic = try to get a slanted version of the given font.
8053 
8054 		History:
8055 			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.
8056 	+/
8057 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
8058 		version(X11) {
8059 			version(with_xft) {
8060 				if(name.length > 5 && name[0 .. 5] == "core:") {
8061 					goto core;
8062 				}
8063 
8064 				if(loadXft(name, size, weight, italic))
8065 					return true;
8066 				// if xft fails, fallback to core to avoid breaking
8067 				// code that already depended on this.
8068 			}
8069 
8070 			core:
8071 
8072 			if(name.length > 5 && name[0 .. 5] == "core:") {
8073 				name = name[5 .. $];
8074 			}
8075 
8076 			return loadCoreX(name, size, weight, italic);
8077 		} else version(Windows) {
8078 			return loadWin32(name, size, weight, italic);
8079 		} else version(OSXCocoa) {
8080 			// FIXME
8081 			return false;
8082 		} else static assert(0);
8083 	}
8084 
8085 	///
8086 	void unload() {
8087 		if(isNull())
8088 			return;
8089 
8090 		version(X11) {
8091 			auto display = XDisplayConnection.display;
8092 
8093 			if(display is null)
8094 				return;
8095 
8096 			version(with_xft) {
8097 				if(isXft) {
8098 					if(xftFont)
8099 						XftFontClose(display, xftFont);
8100 					isXft = false;
8101 					xftFont = null;
8102 					return;
8103 				}
8104 			}
8105 
8106 			if(font && font !is ScreenPainterImplementation.defaultfont)
8107 				XFreeFont(display, font);
8108 			if(fontset && fontset !is ScreenPainterImplementation.defaultfontset)
8109 				XFreeFontSet(display, fontset);
8110 
8111 			font = null;
8112 			fontset = null;
8113 		} else version(Windows) {
8114 			DeleteObject(font);
8115 			font = null;
8116 		} else version(OSXCocoa) {
8117 			// FIXME
8118 		} else static assert(0);
8119 	}
8120 
8121 	private bool isMonospace_;
8122 
8123 	/++
8124 		History:
8125 			Added January 16, 2021
8126 	+/
8127 	bool isMonospace() {
8128 		return isMonospace_;
8129 	}
8130 
8131 	/++
8132 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
8133 
8134 		History:
8135 			Added March 26, 2020
8136 			Documented January 16, 2021
8137 	+/
8138 	int averageWidth() {
8139 		version(X11) {
8140 			return stringWidth("x");
8141 		} else version(Windows)
8142 			return width_;
8143 		else assert(0);
8144 	}
8145 
8146 	/++
8147 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
8148 
8149 		History:
8150 			Added January 16, 2021
8151 	+/
8152 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
8153 	// FIXME: what about tab?
8154 		if(isNull)
8155 			return 0;
8156 
8157 		version(X11) {
8158 			version(with_xft)
8159 				if(isXft && xftFont !is null) {
8160 					//return xftFont.max_advance_width;
8161 					XGlyphInfo extents;
8162 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
8163 					//import std.stdio; writeln(extents);
8164 					return extents.xOff;
8165 				}
8166 			if(font is null)
8167 				return 0;
8168 			else if(fontset) {
8169 				XRectangle rect;
8170 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
8171 
8172 				return rect.width;
8173 			} else {
8174 				return XTextWidth(font, s.ptr, cast(int) s.length);
8175 			}
8176 		} else version(Windows) {
8177 			WCharzBuffer buffer = WCharzBuffer(s);
8178 
8179 			return stringWidth(buffer.slice, window);
8180 		}
8181 		else assert(0);
8182 	}
8183 
8184 	version(Windows)
8185 	/// ditto
8186 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
8187 		if(isNull)
8188 			return 0;
8189 		version(Windows) {
8190 			SIZE size;
8191 
8192 			prepareContext(window);
8193 			scope(exit) releaseContext();
8194 
8195 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
8196 
8197 			return size.cx;
8198 		} else {
8199 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
8200 			static assert(0, "not implemented yet");
8201 			//return stringWidth(s, window);
8202 		}
8203 	}
8204 
8205 	private {
8206 		int prepRefcount;
8207 
8208 		version(Windows) {
8209 			HDC dc;
8210 			HANDLE orig;
8211 			HWND hwnd;
8212 		}
8213 	}
8214 	/++
8215 		[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.
8216 
8217 		History:
8218 			Added January 23, 2021
8219 	+/
8220 	void prepareContext(SimpleWindow window = null) {
8221 		prepRefcount++;
8222 		if(prepRefcount == 1) {
8223 			version(Windows) {
8224 				hwnd = window is null ? null : window.impl.hwnd;
8225 				dc = GetDC(hwnd);
8226 				orig = SelectObject(dc, font);
8227 			}
8228 		}
8229 	}
8230 	/// ditto
8231 	void releaseContext() {
8232 		prepRefcount--;
8233 		if(prepRefcount == 0) {
8234 			version(Windows) {
8235 				SelectObject(dc, orig);
8236 				ReleaseDC(hwnd, dc);
8237 				hwnd = null;
8238 				dc = null;
8239 				orig = null;
8240 			}
8241 		}
8242 	}
8243 
8244 	/+
8245 		FIXME: I think I need advance and kerning pair
8246 
8247 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
8248 	+/
8249 
8250 	/++
8251 		Returns the height of the font.
8252 
8253 		History:
8254 			Added March 26, 2020
8255 			Documented January 16, 2021
8256 	+/
8257 	int height() {
8258 		version(X11) {
8259 			version(with_xft)
8260 				if(isXft && xftFont !is null) {
8261 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
8262 				}
8263 			if(font is null)
8264 				return 0;
8265 			return font.max_bounds.ascent + font.max_bounds.descent;
8266 		} else version(Windows)
8267 			return height_;
8268 		else assert(0);
8269 	}
8270 
8271 	private int ascent_;
8272 	private int descent_;
8273 
8274 	/++
8275 		Max ascent above the baseline.
8276 
8277 		History:
8278 			Added January 22, 2021
8279 	+/
8280 	int ascent() {
8281 		return ascent_;
8282 	}
8283 
8284 	/++
8285 		Max descent below the baseline.
8286 
8287 		History:
8288 			Added January 22, 2021
8289 	+/
8290 	int descent() {
8291 		return descent_;
8292 	}
8293 
8294 	/++
8295 		Loads the default font used by [ScreenPainter] if none others are loaded.
8296 
8297 		Returns:
8298 			This method mutates the `this` object, but then returns `this` for
8299 			easy chaining like:
8300 
8301 			---
8302 			auto font = foo.isNull ? foo : foo.loadDefault
8303 			---
8304 
8305 		History:
8306 			Added previously, but left unimplemented until January 24, 2021.
8307 	+/
8308 	OperatingSystemFont loadDefault() {
8309 		unload();
8310 
8311 		version(X11) {
8312 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
8313 			// but meh since sdpy does its own thing, this should be ok too
8314 
8315 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8316 			this.font = ScreenPainterImplementation.defaultfont;
8317 			this.fontset = ScreenPainterImplementation.defaultfontset;
8318 
8319 			prepareFontInfo();
8320 		} else version(Windows) {
8321 			ScreenPainterImplementation.ensureDefaultFontLoaded();
8322 			this.font = ScreenPainterImplementation.defaultGuiFont;
8323 
8324 			prepareFontInfo();
8325 		} else throw new NotYetImplementedException();
8326 
8327 		return this;
8328 	}
8329 
8330 	///
8331 	bool isNull() {
8332 		version(OSXCocoa) throw new NotYetImplementedException(); else {
8333 			version(with_xft)
8334 				if(isXft)
8335 					return xftFont is null;
8336 			return font is null;
8337 		}
8338 	}
8339 
8340 	/* Metrics */
8341 	/+
8342 		GetABCWidth
8343 		GetKerningPairs
8344 
8345 		if I do it right, I can size it all here, and match
8346 		what happens when I draw the full string with the OS functions.
8347 
8348 		subclasses might do the same thing while getting the glyphs on images
8349 	struct GlyphInfo {
8350 		int glyph;
8351 
8352 		size_t stringIdxStart;
8353 		size_t stringIdxEnd;
8354 
8355 		Rectangle boundingBox;
8356 	}
8357 	GlyphInfo[] getCharBoxes() {
8358 		// XftTextExtentsUtf8
8359 		return null;
8360 
8361 	}
8362 	+/
8363 
8364 	~this() {
8365 		unload();
8366 	}
8367 }
8368 
8369 version(Windows)
8370 private string sliceCString(const(wchar)[] w) {
8371 	return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr);
8372 }
8373 
8374 private inout(char)[] sliceCString(inout(char)* s) {
8375 	import core.stdc.string;
8376 	auto len = strlen(s);
8377 	return s[0 .. len];
8378 }
8379 
8380 /**
8381 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
8382 	than constructing it directly. Then, it is reference counted so you can pass it
8383 	at around and when the last ref goes out of scope, the buffered drawing activities
8384 	are all carried out.
8385 
8386 
8387 	Most functions use the outlineColor instead of taking a color themselves.
8388 	ScreenPainter is reference counted and draws its buffer to the screen when its
8389 	final reference goes out of scope.
8390 */
8391 struct ScreenPainter {
8392 	CapableOfBeingDrawnUpon window;
8393 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) {
8394 		this.window = window;
8395 		if(window.closed)
8396 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
8397 		//currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
8398 		currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max);
8399 		if(window.activeScreenPainter !is null) {
8400 			impl = window.activeScreenPainter;
8401 			if(impl.referenceCount == 0) {
8402 				impl.window = window;
8403 				impl.create(handle);
8404 			}
8405 			impl.referenceCount++;
8406 		//	writeln("refcount ++ ", impl.referenceCount);
8407 		} else {
8408 			impl = new ScreenPainterImplementation;
8409 			impl.window = window;
8410 			impl.create(handle);
8411 			impl.referenceCount = 1;
8412 			window.activeScreenPainter = impl;
8413 			//import std.stdio; writeln("constructed");
8414 		}
8415 
8416 		copyActiveOriginals();
8417 	}
8418 
8419 	private Pen originalPen;
8420 	private Color originalFillColor;
8421 	private arsd.color.Rectangle originalClipRectangle;
8422 	void copyActiveOriginals() {
8423 		if(impl is null) return;
8424 		originalPen = impl._activePen;
8425 		originalFillColor = impl._fillColor;
8426 		originalClipRectangle = impl._clipRectangle;
8427 	}
8428 
8429 	~this() {
8430 		if(impl is null) return;
8431 		impl.referenceCount--;
8432 		//writeln("refcount -- ", impl.referenceCount);
8433 		if(impl.referenceCount == 0) {
8434 			//import std.stdio; writeln("destructed");
8435 			impl.dispose();
8436 			*window.activeScreenPainter = ScreenPainterImplementation.init;
8437 			//import std.stdio; writeln("paint finished");
8438 		} else {
8439 			// there is still an active reference, reset stuff so the
8440 			// next user doesn't get weirdness via the reference
8441 			this.rasterOp = RasterOp.normal;
8442 			pen = originalPen;
8443 			fillColor = originalFillColor;
8444 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
8445 		}
8446 	}
8447 
8448 	this(this) {
8449 		if(impl is null) return;
8450 		impl.referenceCount++;
8451 		//writeln("refcount ++ ", impl.referenceCount);
8452 
8453 		copyActiveOriginals();
8454 	}
8455 
8456 	private int _originX;
8457 	private int _originY;
8458 	@property int originX() { return _originX; }
8459 	@property int originY() { return _originY; }
8460 	@property int originX(int a) {
8461 		_originX = a;
8462 		return _originX;
8463 	}
8464 	@property int originY(int a) {
8465 		_originY = a;
8466 		return _originY;
8467 	}
8468 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
8469 	private void transform(ref Point p) {
8470 		if(impl is null) return;
8471 		p.x += _originX;
8472 		p.y += _originY;
8473 	}
8474 
8475 	// this needs to be checked BEFORE the originX/Y transformation
8476 	private bool isClipped(Point p) {
8477 		return !currentClipRectangle.contains(p);
8478 	}
8479 	private bool isClipped(Point p, int width, int height) {
8480 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
8481 	}
8482 	private bool isClipped(Point p, Size s) {
8483 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
8484 	}
8485 	private bool isClipped(Point p, Point p2) {
8486 		// need to ensure the end points are actually included inside, so the +1 does that
8487 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
8488 	}
8489 
8490 
8491 	/++
8492 		Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
8493 
8494 		Returns:
8495 			The old clip rectangle.
8496 
8497 		History:
8498 			Return value was `void` prior to May 10, 2021.
8499 
8500 	+/
8501 	arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) {
8502 		if(impl is null) return currentClipRectangle;
8503 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
8504 			return currentClipRectangle; // no need to do anything
8505 		auto old = currentClipRectangle;
8506 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
8507 		transform(pt);
8508 
8509 		impl.setClipRectangle(pt.x, pt.y, width, height);
8510 
8511 		return old;
8512 	}
8513 
8514 	/// ditto
8515 	arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) {
8516 		if(impl is null) return currentClipRectangle;
8517 		return setClipRectangle(rect.upperLeft, rect.width, rect.height);
8518 	}
8519 
8520 	///
8521 	void setFont(OperatingSystemFont font) {
8522 		if(impl is null) return;
8523 		impl.setFont(font);
8524 	}
8525 
8526 	///
8527 	int fontHeight() {
8528 		if(impl is null) return 0;
8529 		return impl.fontHeight();
8530 	}
8531 
8532 	private Pen activePen;
8533 
8534 	///
8535 	@property void pen(Pen p) {
8536 		if(impl is null) return;
8537 		activePen = p;
8538 		impl.pen(p);
8539 	}
8540 
8541 	///
8542 	@scriptable
8543 	@property void outlineColor(Color c) {
8544 		if(impl is null) return;
8545 		if(activePen.color == c)
8546 			return;
8547 		activePen.color = c;
8548 		impl.pen(activePen);
8549 	}
8550 
8551 	///
8552 	@scriptable
8553 	@property void fillColor(Color c) {
8554 		if(impl is null) return;
8555 		impl.fillColor(c);
8556 	}
8557 
8558 	///
8559 	@property void rasterOp(RasterOp op) {
8560 		if(impl is null) return;
8561 		impl.rasterOp(op);
8562 	}
8563 
8564 
8565 	void updateDisplay() {
8566 		// FIXME this should do what the dtor does
8567 	}
8568 
8569 	/// 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)
8570 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
8571 		if(impl is null) return;
8572 		if(isClipped(upperLeft, width, height)) return;
8573 		transform(upperLeft);
8574 		version(Windows) {
8575 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
8576 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
8577 			RECT clip = scroll;
8578 			RECT uncovered;
8579 			HRGN hrgn;
8580 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
8581 				throw new Exception("ScrollDC");
8582 
8583 		} else version(X11) {
8584 			// FIXME: clip stuff outside this rectangle
8585 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
8586 		} else version(OSXCocoa) {
8587 			throw new NotYetImplementedException();
8588 		} else static assert(0);
8589 	}
8590 
8591 	///
8592 	void clear(Color color = Color.white()) {
8593 		if(impl is null) return;
8594 		fillColor = color;
8595 		outlineColor = color;
8596 		drawRectangle(Point(0, 0), window.width, window.height);
8597 	}
8598 
8599 	/++
8600 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
8601 
8602 		Params:
8603 			upperLeft = point on the window where the upper left corner of the image will be drawn
8604 			imageUpperLeft = point on the image to start the slice to draw
8605 			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.
8606 		History:
8607 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
8608 	+/
8609 	version(OSXCocoa) {} else // NotYetImplementedException
8610 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
8611 		if(impl is null) return;
8612 		if(isClipped(upperLeft, s.width, s.height)) return;
8613 		transform(upperLeft);
8614 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
8615 	}
8616 
8617 	///
8618 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
8619 		if(impl is null) return;
8620 		//if(isClipped(upperLeft, w, h)) return; // FIXME
8621 		transform(upperLeft);
8622 		if(w == 0 || w > i.width)
8623 			w = i.width;
8624 		if(h == 0 || h > i.height)
8625 			h = i.height;
8626 		if(upperLeftOfImage.x < 0)
8627 			upperLeftOfImage.x = 0;
8628 		if(upperLeftOfImage.y < 0)
8629 			upperLeftOfImage.y = 0;
8630 
8631 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
8632 	}
8633 
8634 	///
8635 	Size textSize(in char[] text) {
8636 		if(impl is null) return Size(0, 0);
8637 		return impl.textSize(text);
8638 	}
8639 
8640 	/++
8641 		Draws a string in the window with the set font (see [setFont] to change it).
8642 
8643 		Params:
8644 			upperLeft = the upper left point of the bounding box of the text
8645 			text = the string to draw
8646 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
8647 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
8648 	+/
8649 	@scriptable
8650 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
8651 		if(impl is null) return;
8652 		if(lowerRight.x != 0 || lowerRight.y != 0) {
8653 			if(isClipped(upperLeft, lowerRight)) return;
8654 			transform(lowerRight);
8655 		} else {
8656 			if(isClipped(upperLeft, textSize(text))) return;
8657 		}
8658 		transform(upperLeft);
8659 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
8660 	}
8661 
8662 	/++
8663 		Draws text using a custom font.
8664 
8665 		This is still MAJOR work in progress.
8666 
8667 		Creating a [DrawableFont] can be tricky and require additional dependencies.
8668 	+/
8669 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
8670 		if(impl is null) return;
8671 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
8672 		transform(upperLeft);
8673 		font.drawString(this, upperLeft, text);
8674 	}
8675 
8676 	version(Windows)
8677 	void drawText(Point upperLeft, scope const(wchar)[] text) {
8678 		if(impl is null) return;
8679 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
8680 		transform(upperLeft);
8681 
8682 		if(text.length && text[$-1] == '\n')
8683 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
8684 
8685 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
8686 	}
8687 
8688 	static struct TextDrawingContext {
8689 		Point boundingBoxUpperLeft;
8690 		Point boundingBoxLowerRight;
8691 
8692 		Point currentLocation;
8693 
8694 		Point lastDrewUpperLeft;
8695 		Point lastDrewLowerRight;
8696 
8697 		// how do i do right aligned rich text?
8698 		// i kinda want to do a pre-made drawing then right align
8699 		// draw the whole block.
8700 		//
8701 		// That's exactly the diff: inline vs block stuff.
8702 
8703 		// I need to get coordinates of an inline section out too,
8704 		// not just a bounding box, but a series of bounding boxes
8705 		// should be ok. Consider what's needed to detect a click
8706 		// on a link in the middle of a paragraph breaking a line.
8707 		//
8708 		// Generally, we should be able to get the rectangles of
8709 		// any portion we draw.
8710 		//
8711 		// It also needs to tell what text is left if it overflows
8712 		// out of the box, so we can do stuff like float images around
8713 		// it. It should not attempt to draw a letter that would be
8714 		// clipped.
8715 		//
8716 		// I might also turn off word wrap stuff.
8717 	}
8718 
8719 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
8720 		if(impl is null) return;
8721 		// FIXME
8722 	}
8723 
8724 	/// Drawing an individual pixel is slow. Avoid it if possible.
8725 	void drawPixel(Point where) {
8726 		if(impl is null) return;
8727 		if(isClipped(where)) return;
8728 		transform(where);
8729 		impl.drawPixel(where.x, where.y);
8730 	}
8731 
8732 
8733 	/// Draws a pen using the current pen / outlineColor
8734 	@scriptable
8735 	void drawLine(Point starting, Point ending) {
8736 		if(impl is null) return;
8737 		if(isClipped(starting, ending)) return;
8738 		transform(starting);
8739 		transform(ending);
8740 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
8741 	}
8742 
8743 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
8744 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
8745 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
8746 	@scriptable
8747 	void drawRectangle(Point upperLeft, int width, int height) {
8748 		if(impl is null) return;
8749 		if(isClipped(upperLeft, width, height)) return;
8750 		transform(upperLeft);
8751 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
8752 	}
8753 
8754 	/// ditto
8755 	void drawRectangle(Point upperLeft, Size size) {
8756 		if(impl is null) return;
8757 		if(isClipped(upperLeft, size.width, size.height)) return;
8758 		transform(upperLeft);
8759 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
8760 	}
8761 
8762 	/// ditto
8763 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
8764 		if(impl is null) return;
8765 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
8766 		transform(upperLeft);
8767 		transform(lowerRightInclusive);
8768 		impl.drawRectangle(upperLeft.x, upperLeft.y,
8769 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
8770 	}
8771 
8772 	// overload added on May 12, 2021
8773 	/// ditto
8774 	void drawRectangle(Rectangle rect) {
8775 		drawRectangle(rect.upperLeft, rect.size);
8776 	}
8777 
8778 	/// Arguments are the points of the bounding rectangle
8779 	void drawEllipse(Point upperLeft, Point lowerRight) {
8780 		if(impl is null) return;
8781 		if(isClipped(upperLeft, lowerRight)) return;
8782 		transform(upperLeft);
8783 		transform(lowerRight);
8784 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
8785 	}
8786 
8787 	/++
8788 		start and finish are units of degrees * 64
8789 
8790 		History:
8791 			The Windows implementation didn't match the Linux implementation until September 24, 2021.
8792 
8793 			They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now).
8794 	+/
8795 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
8796 		if(impl is null) return;
8797 		// FIXME: not actually implemented
8798 		if(isClipped(upperLeft, width, height)) return;
8799 		transform(upperLeft);
8800 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
8801 	}
8802 
8803 	/// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
8804 	void drawCircle(Point upperLeft, int diameter) {
8805 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
8806 	}
8807 
8808 	/// .
8809 	void drawPolygon(Point[] vertexes) {
8810 		if(impl is null) return;
8811 		assert(vertexes.length);
8812 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
8813 		foreach(ref vertex; vertexes) {
8814 			if(vertex.x < minX)
8815 				minX = vertex.x;
8816 			if(vertex.y < minY)
8817 				minY = vertex.y;
8818 			if(vertex.x > maxX)
8819 				maxX = vertex.x;
8820 			if(vertex.y > maxY)
8821 				maxY = vertex.y;
8822 			transform(vertex);
8823 		}
8824 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
8825 		impl.drawPolygon(vertexes);
8826 	}
8827 
8828 	/// ditto
8829 	void drawPolygon(Point[] vertexes...) {
8830 		if(impl is null) return;
8831 		drawPolygon(vertexes);
8832 	}
8833 
8834 
8835 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
8836 
8837 	//mixin NativeScreenPainterImplementation!() impl;
8838 
8839 
8840 	// HACK: if I mixin the impl directly, it won't let me override the copy
8841 	// constructor! The linker complains about there being multiple definitions.
8842 	// I'll make the best of it and reference count it though.
8843 	ScreenPainterImplementation* impl;
8844 }
8845 
8846 	// HACK: I need a pointer to the implementation so it's separate
8847 	struct ScreenPainterImplementation {
8848 		CapableOfBeingDrawnUpon window;
8849 		int referenceCount;
8850 		mixin NativeScreenPainterImplementation!();
8851 	}
8852 
8853 // FIXME: i haven't actually tested the sprite class on MS Windows
8854 
8855 /**
8856 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
8857 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
8858 
8859 
8860 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
8861 	though I'm not sure that's ideal and the implementation might change.
8862 
8863 	You create one by giving a window and an image. It optimizes for that window,
8864 	and copies the image into it to use as the initial picture. Creating a sprite
8865 	can be quite slow (especially over a network connection) so you should do it
8866 	as little as possible and just hold on to your sprite handles after making them.
8867 	simpledisplay does try to do its best though, using the XSHM extension if available,
8868 	but you should still write your code as if it will always be slow.
8869 
8870 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
8871 	a fast operation - much faster than drawing the Image itself every time.
8872 
8873 	`Sprite` represents a scarce resource which should be freed when you
8874 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
8875 	after it has been disposed. If you are unsure about this, don't take chances,
8876 	just let the garbage collector do it for you. But ideally, you can manage its
8877 	lifetime more efficiently.
8878 
8879 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
8880 	support alpha blending in its drawing at this time. That might change in the
8881 	future, but if you need alpha blending right now, use OpenGL instead. See
8882 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
8883 
8884 	Update: on April 23, 2021, I finally added alpha blending support. You must opt
8885 	in by setting the enableAlpha = true in the constructor.
8886 */
8887 version(OSXCocoa) {} else // NotYetImplementedException
8888 class Sprite : CapableOfBeingDrawnUpon {
8889 
8890 	///
8891 	ScreenPainter draw() {
8892 		return ScreenPainter(this, handle);
8893 	}
8894 
8895 	/++
8896 		Copies the sprite's current state into a [TrueColorImage].
8897 
8898 		Be warned: this can be a very slow operation
8899 
8900 		History:
8901 			Actually implemented on March 14, 2021
8902 	+/
8903 	TrueColorImage takeScreenshot() {
8904 		return trueColorImageFromNativeHandle(handle, width, height);
8905 	}
8906 
8907 	void delegate() paintingFinishedDg() { return null; }
8908 	bool closed() { return false; }
8909 	ScreenPainterImplementation* activeScreenPainter_;
8910 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
8911 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
8912 
8913 	version(Windows)
8914 		private ubyte* rawData;
8915 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
8916 	// ditto on the XPicture stuff
8917 
8918 	version(X11) {
8919 		private static XRenderPictFormat* RGB24;
8920 		private static XRenderPictFormat* ARGB32;
8921 
8922 		private Picture xrenderPicture;
8923 	}
8924 
8925 	version(X11)
8926 	private static void requireXRender() {
8927 		if(!XRenderLibrary.loadAttempted) {
8928 			XRenderLibrary.loadDynamicLibrary();
8929 		}
8930 
8931 		if(!XRenderLibrary.loadSuccessful)
8932 			throw new Exception("XRender library load failure");
8933 
8934 		auto display = XDisplayConnection.get;
8935 
8936 		// FIXME: if we migrate X displays, these need to be changed
8937 		if(RGB24 is null)
8938 			RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24);
8939 		if(ARGB32 is null)
8940 			ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32);
8941 	}
8942 
8943 	protected this() {}
8944 
8945 	this(SimpleWindow win, int width, int height, bool enableAlpha = false) {
8946 		this._width = width;
8947 		this._height = height;
8948 		this.enableAlpha = enableAlpha;
8949 
8950 		version(X11) {
8951 			auto display = XDisplayConnection.get();
8952 
8953 			if(enableAlpha) {
8954 				requireXRender();
8955 			}
8956 
8957 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display));
8958 
8959 			if(enableAlpha) {
8960 				XRenderPictureAttributes attrs;
8961 				xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs);
8962 			}
8963 		} else version(Windows) {
8964 			version(CRuntime_DigitalMars) {
8965 				//if(enableAlpha)
8966 					//throw new Exception("Alpha support not available, try recompiling with -m32mscoff");
8967 			}
8968 
8969 			BITMAPINFO infoheader;
8970 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
8971 			infoheader.bmiHeader.biWidth = width;
8972 			infoheader.bmiHeader.biHeight = height;
8973 			infoheader.bmiHeader.biPlanes = 1;
8974 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24;
8975 			infoheader.bmiHeader.biCompression = BI_RGB;
8976 
8977 			// FIXME: this should prolly be a device dependent bitmap...
8978 			handle = CreateDIBSection(
8979 				null,
8980 				&infoheader,
8981 				DIB_RGB_COLORS,
8982 				cast(void**) &rawData,
8983 				null,
8984 				0);
8985 
8986 			if(handle is null)
8987 				throw new Exception("couldn't create pixmap");
8988 		}
8989 	}
8990 
8991 	/// Makes a sprite based on the image with the initial contents from the Image
8992 	this(SimpleWindow win, Image i) {
8993 		this(win, i.width, i.height, i.enableAlpha);
8994 
8995 		version(X11) {
8996 			auto display = XDisplayConnection.get();
8997 			auto gc = XCreateGC(display, this.handle, 0, null);
8998 			scope(exit) XFreeGC(display, gc);
8999 			if(i.usingXshm)
9000 				XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
9001 			else
9002 				XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
9003 		} else version(Windows) {
9004 			auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4);
9005 			auto arrLength = itemsPerLine * height;
9006 			rawData[0..arrLength] = i.rawData[0..arrLength];
9007 		} else version(OSXCocoa) {
9008 			// FIXME: I have no idea if this is even any good
9009 			ubyte* rawData;
9010 
9011 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
9012 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
9013 				colorSpace,
9014 				kCGImageAlphaPremultipliedLast
9015 				|kCGBitmapByteOrder32Big);
9016 			CGColorSpaceRelease(colorSpace);
9017 			rawData = CGBitmapContextGetData(context);
9018 
9019 			auto rdl = (width * height * 4);
9020 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
9021 		} else static assert(0);
9022 	}
9023 
9024 	/++
9025 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
9026 
9027 		Params:
9028 			where = point on the window where the upper left corner of the image will be drawn
9029 			imageUpperLeft = point on the image to start the slice to draw
9030 			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.
9031 		History:
9032 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
9033 	+/
9034 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
9035 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
9036 	}
9037 
9038 	/// Call this when you're ready to get rid of it
9039 	void dispose() {
9040 		version(X11) {
9041 			if(xrenderPicture) {
9042 				XRenderFreePicture(XDisplayConnection.get, xrenderPicture);
9043 				xrenderPicture = None;
9044 			}
9045 			if(handle)
9046 				XFreePixmap(XDisplayConnection.get(), handle);
9047 			handle = None;
9048 		} else version(Windows) {
9049 			if(handle)
9050 				DeleteObject(handle);
9051 			handle = null;
9052 		} else version(OSXCocoa) {
9053 			if(context)
9054 				CGContextRelease(context);
9055 			context = null;
9056 		} else static assert(0);
9057 
9058 	}
9059 
9060 	~this() {
9061 		dispose();
9062 	}
9063 
9064 	///
9065 	final @property int width() { return _width; }
9066 
9067 	///
9068 	final @property int height() { return _height; }
9069 
9070 	///
9071 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) {
9072 		return new Sprite(win, Image.fromMemoryImage(img, enableAlpha));
9073 	}
9074 
9075 	auto nativeHandle() {
9076 		return handle;
9077 	}
9078 
9079 	private:
9080 
9081 	int _width;
9082 	int _height;
9083 	bool enableAlpha;
9084 	version(X11)
9085 		Pixmap handle;
9086 	else version(Windows)
9087 		HBITMAP handle;
9088 	else version(OSXCocoa)
9089 		CGContextRef context;
9090 	else static assert(0);
9091 }
9092 
9093 /++
9094 	Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient].
9095 
9096 	History:
9097 		Added November 20, 2021 (dub v10.4)
9098 +/
9099 abstract class Gradient : Sprite {
9100 	protected this(int w, int h) {
9101 		version(X11) {
9102 			Sprite.requireXRender();
9103 
9104 			super();
9105 			enableAlpha = true;
9106 			_width = w;
9107 			_height = h;
9108 		} else version(Windows) {
9109 			super(null, w, h, true); // on Windows i'm just making a bitmap myself
9110 		}
9111 	}
9112 
9113 	version(Windows)
9114 	final void forEachPixel(scope Color delegate(int x, int y) dg) {
9115 		auto ptr = rawData;
9116 		foreach(j; 0 .. _height)
9117 		foreach(i; 0 .. _width) {
9118 			auto color = dg(i, _height - j - 1); // cuz of upside down bitmap
9119 			*rawData = (color.a * color.b) / 255; rawData++;
9120 			*rawData = (color.a * color.g) / 255; rawData++;
9121 			*rawData = (color.a * color.r) / 255; rawData++;
9122 			*rawData = color.a; rawData++;
9123 		}
9124 	}
9125 
9126 	version(X11)
9127 	protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) {
9128 		assert(stops.length > 0);
9129 		assert(stops.length <= 16, "I got lazy with buffers");
9130 
9131 		XFixed[16] stopsPositions = void;
9132 		XRenderColor[16] colors = void;
9133 
9134 		foreach(idx, stop; stops) {
9135 			stopsPositions[idx] = cast(int)(stop.percentage * ushort.max);
9136 			auto c = stop.c;
9137 			colors[idx] = XRenderColor(
9138 				cast(ushort)(c.r * ushort.max / 255),
9139 				cast(ushort)(c.g * ushort.max / 255),
9140 				cast(ushort)(c.b * ushort.max / 255),
9141 				cast(ushort)(c.a * ubyte.max) // max value here is fractional
9142 			);
9143 		}
9144 
9145 		xrenderPicture = dg(stopsPositions, colors);
9146 	}
9147 
9148 	///
9149 	static struct Stop {
9150 		float percentage; /// between 0 and 1.0
9151 		Color c;
9152 	}
9153 }
9154 
9155 /++
9156 	Creates a linear gradient between p1 and p2.
9157 
9158 	X ONLY RIGHT NOW
9159 
9160 	History:
9161 		Added November 20, 2021 (dub v10.4)
9162 
9163 	Bugs:
9164 		Not yet implemented on Windows.
9165 +/
9166 class LinearGradient : Gradient {
9167 	/++
9168 
9169 	+/
9170 	this(Point p1, Point p2, Stop[] stops...) {
9171 		super(p2.x, p2.y);
9172 
9173 		version(X11) {
9174 			XLinearGradient gradient;
9175 			gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max);
9176 			gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max);
9177 
9178 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9179 				return XRenderCreateLinearGradient(
9180 					XDisplayConnection.get,
9181 					&gradient,
9182 					stopsPositions.ptr,
9183 					colors.ptr,
9184 					cast(int) stops.length);
9185 			});
9186 		} else version(Windows) {
9187 			// FIXME
9188 			forEachPixel((int x, int y) {
9189 				import core.stdc.math;
9190 
9191 				//sqrtf(
9192 
9193 				return Color.transparent;
9194 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9195 			});
9196 		}
9197 	}
9198 }
9199 
9200 /++
9201 	A conical gradient goes from color to color around a circumference from a center point.
9202 
9203 	X ONLY RIGHT NOW
9204 
9205 	History:
9206 		Added November 20, 2021 (dub v10.4)
9207 
9208 	Bugs:
9209 		Not yet implemented on Windows.
9210 +/
9211 class ConicalGradient : Gradient {
9212 	/++
9213 
9214 	+/
9215 	this(Point center, float angleInDegrees, Stop[] stops...) {
9216 		super(center.x * 2, center.y * 2);
9217 
9218 		version(X11) {
9219 			XConicalGradient gradient;
9220 			gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max);
9221 			gradient.angle = cast(int)(angleInDegrees * ushort.max);
9222 
9223 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9224 				return XRenderCreateConicalGradient(
9225 					XDisplayConnection.get,
9226 					&gradient,
9227 					stopsPositions.ptr,
9228 					colors.ptr,
9229 					cast(int) stops.length);
9230 			});
9231 		} else version(Windows) {
9232 			// FIXME
9233 			forEachPixel((int x, int y) {
9234 				import core.stdc.math;
9235 
9236 				//sqrtf(
9237 
9238 				return Color.transparent;
9239 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9240 			});
9241 
9242 		}
9243 	}
9244 }
9245 
9246 /++
9247 	A radial gradient goes from color to color based on distance from the center.
9248 	It is like rings of color.
9249 
9250 	X ONLY RIGHT NOW
9251 
9252 
9253 	More specifically, you create two circles: an inner circle and an outer circle.
9254 	The gradient is only drawn in the area outside the inner circle but inside the outer
9255 	circle. The closest line between those two circles forms the line for the gradient
9256 	and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around.
9257 
9258 	History:
9259 		Added November 20, 2021 (dub v10.4)
9260 
9261 	Bugs:
9262 		Not yet implemented on Windows.
9263 +/
9264 class RadialGradient : Gradient {
9265 	/++
9266 
9267 	+/
9268 	this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) {
9269 		super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5));
9270 
9271 		version(X11) {
9272 			XRadialGradient gradient;
9273 			gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max));
9274 			gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max));
9275 
9276 			helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) {
9277 				return XRenderCreateRadialGradient(
9278 					XDisplayConnection.get,
9279 					&gradient,
9280 					stopsPositions.ptr,
9281 					colors.ptr,
9282 					cast(int) stops.length);
9283 			});
9284 		} else version(Windows) {
9285 			// FIXME
9286 			forEachPixel((int x, int y) {
9287 				import core.stdc.math;
9288 
9289 				//sqrtf(
9290 
9291 				return Color.transparent;
9292 				// return Color(x * 2, y * 2, 0, 128); // this result is so beautiful
9293 			});
9294 		}
9295 	}
9296 }
9297 
9298 
9299 
9300 /+
9301 	NOT IMPLEMENTED
9302 
9303 	A display-stored image optimized for relatively quick drawing, like
9304 	[Sprite], but this one supports alpha channel blending and does NOT
9305 	support direct drawing upon it with a [ScreenPainter].
9306 
9307 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
9308 	plain [ScreenPainter]... sort of.
9309 
9310 	On X11, it requires the Xrender extension and library. This is available
9311 	almost everywhere though.
9312 
9313 	History:
9314 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
9315 +/
9316 version(none)
9317 class AlphaSprite {
9318 	/++
9319 		Copies the given image into it.
9320 	+/
9321 	this(MemoryImage img) {
9322 
9323 		if(!XRenderLibrary.loadAttempted) {
9324 			XRenderLibrary.loadDynamicLibrary();
9325 
9326 			// FIXME: this needs to be reconstructed when the X server changes
9327 			repopulateX();
9328 		}
9329 		if(!XRenderLibrary.loadSuccessful)
9330 			throw new Exception("XRender library load failure");
9331 
9332 		// I probably need to put the alpha mask in a separate Picture
9333 		// ugh
9334 		// maybe the Sprite itself can have an alpha bitmask anyway
9335 
9336 
9337 		auto display = XDisplayConnection.get();
9338 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
9339 
9340 
9341 		XRenderPictureAttributes attrs;
9342 
9343 		handle = XRenderCreatePicture(
9344 			XDisplayConnection.get,
9345 			pixmap,
9346 			RGBA,
9347 			0,
9348 			&attrs
9349 		);
9350 
9351 	}
9352 
9353 	// maybe i'll use the create gradient functions too with static factories..
9354 
9355 	void drawAt(ScreenPainter painter, Point where) {
9356 		//painter.drawPixmap(this, where);
9357 
9358 		XRenderPictureAttributes attrs;
9359 
9360 		auto pic = XRenderCreatePicture(
9361 			XDisplayConnection.get,
9362 			painter.impl.d,
9363 			RGB,
9364 			0,
9365 			&attrs
9366 		);
9367 
9368 		XRenderComposite(
9369 			XDisplayConnection.get,
9370 			3, // PictOpOver
9371 			handle,
9372 			None,
9373 			pic,
9374 			0, // src
9375 			0,
9376 			0, // mask
9377 			0,
9378 			10, // dest
9379 			10,
9380 			100, // width
9381 			100
9382 		);
9383 
9384 		/+
9385 		XRenderFreePicture(
9386 			XDisplayConnection.get,
9387 			pic
9388 		);
9389 
9390 		XRenderFreePicture(
9391 			XDisplayConnection.get,
9392 			fill
9393 		);
9394 		+/
9395 		// on Windows you can stretch but Xrender still can't :(
9396 	}
9397 
9398 	static XRenderPictFormat* RGB;
9399 	static XRenderPictFormat* RGBA;
9400 	static void repopulateX() {
9401 		auto display = XDisplayConnection.get;
9402 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
9403 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB32);
9404 	}
9405 
9406 	XPixmap pixmap;
9407 	Picture handle;
9408 }
9409 
9410 ///
9411 interface CapableOfBeingDrawnUpon {
9412 	///
9413 	ScreenPainter draw();
9414 	///
9415 	int width();
9416 	///
9417 	int height();
9418 	protected ScreenPainterImplementation* activeScreenPainter();
9419 	protected void activeScreenPainter(ScreenPainterImplementation*);
9420 	bool closed();
9421 
9422 	void delegate() paintingFinishedDg();
9423 
9424 	/// Be warned: this can be a very slow operation
9425 	TrueColorImage takeScreenshot();
9426 }
9427 
9428 /// 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].
9429 void flushGui() {
9430 	version(X11) {
9431 		auto dpy = XDisplayConnection.get();
9432 		XLockDisplay(dpy);
9433 		scope(exit) XUnlockDisplay(dpy);
9434 		XFlush(dpy);
9435 	}
9436 }
9437 
9438 /++
9439 	Runs the given code in the GUI thread when its event loop
9440 	is available, blocking until it completes. This allows you
9441 	to create and manipulate windows from another thread without
9442 	invoking undefined behavior.
9443 
9444 	If this is the gui thread, it runs the code immediately.
9445 
9446 	If no gui thread exists yet, the current thread is assumed
9447 	to be it. Attempting to create windows or run the event loop
9448 	in any other thread will cause an assertion failure.
9449 
9450 
9451 	$(TIP
9452 		Did you know you can use UFCS on delegate literals?
9453 
9454 		() {
9455 			// code here
9456 		}.runInGuiThread;
9457 	)
9458 
9459 	Returns:
9460 		`true` if the function was called, `false` if it was not.
9461 		The function may not be called because the gui thread had
9462 		already terminated by the time you called this.
9463 
9464 	History:
9465 		Added April 10, 2020 (v7.2.0)
9466 
9467 		Return value added and implementation tweaked to avoid locking
9468 		at program termination on February 24, 2021 (v9.2.1).
9469 +/
9470 bool runInGuiThread(scope void delegate() dg) @trusted {
9471 	claimGuiThread();
9472 
9473 	if(thisIsGuiThread) {
9474 		dg();
9475 		return true;
9476 	}
9477 
9478 	if(guiThreadTerminating)
9479 		return false;
9480 
9481 	import core.sync.semaphore;
9482 	static Semaphore sc;
9483 	if(sc is null)
9484 		sc = new Semaphore();
9485 
9486 	static RunQueueMember* rqm;
9487 	if(rqm is null)
9488 		rqm = new RunQueueMember;
9489 	rqm.dg = cast(typeof(rqm.dg)) dg;
9490 	rqm.signal = sc;
9491 	rqm.thrown = null;
9492 
9493 	synchronized(runInGuiThreadLock) {
9494 		runInGuiThreadQueue ~= rqm;
9495 	}
9496 
9497 	if(!SimpleWindow.eventWakeUp())
9498 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
9499 
9500 	rqm.signal.wait();
9501 	auto t = rqm.thrown;
9502 
9503 	if(t)
9504 		throw t;
9505 
9506 	return true;
9507 }
9508 
9509 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow {
9510 	claimGuiThread();
9511 
9512 	try {
9513 
9514 		if(thisIsGuiThread) {
9515 			dg();
9516 			return;
9517 		}
9518 
9519 		if(guiThreadTerminating)
9520 			return;
9521 
9522 		RunQueueMember* rqm = new RunQueueMember;
9523 		rqm.dg = cast(typeof(rqm.dg)) dg;
9524 		rqm.signal = null;
9525 		rqm.thrown = null;
9526 
9527 		synchronized(runInGuiThreadLock) {
9528 			runInGuiThreadQueue ~= rqm;
9529 		}
9530 
9531 		if(!SimpleWindow.eventWakeUp())
9532 			throw new Error("runInGuiThread impossible; eventWakeUp failed");
9533 	} catch(Exception e) {
9534 		if(handleError)
9535 			handleError(e);
9536 	}
9537 }
9538 
9539 private void runPendingRunInGuiThreadDelegates() {
9540 	more:
9541 	RunQueueMember* next;
9542 	synchronized(runInGuiThreadLock) {
9543 		if(runInGuiThreadQueue.length) {
9544 			next = runInGuiThreadQueue[0];
9545 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
9546 		} else {
9547 			next = null;
9548 		}
9549 	}
9550 
9551 	if(next) {
9552 		try {
9553 			next.dg();
9554 			next.thrown = null;
9555 		} catch(Throwable t) {
9556 			next.thrown = t;
9557 		}
9558 
9559 		if(next.signal)
9560 			next.signal.notify();
9561 
9562 		goto more;
9563 	}
9564 }
9565 
9566 private void claimGuiThread() nothrow {
9567 	import core.atomic;
9568 	if(cas(&guiThreadExists, false, true))
9569 		thisIsGuiThread = true;
9570 }
9571 
9572 private struct RunQueueMember {
9573 	void delegate() dg;
9574 	import core.sync.semaphore;
9575 	Semaphore signal;
9576 	Throwable thrown;
9577 }
9578 
9579 private __gshared RunQueueMember*[] runInGuiThreadQueue;
9580 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
9581 private bool thisIsGuiThread = false;
9582 private shared bool guiThreadExists = false;
9583 private shared bool guiThreadTerminating = false;
9584 
9585 private void guiThreadFinalize() {
9586 	assert(thisIsGuiThread);
9587 
9588 	guiThreadTerminating = true; // don't add any more from this point on
9589 	runPendingRunInGuiThreadDelegates();
9590 }
9591 
9592 /+
9593 interface IPromise {
9594 	void reportProgress(int current, int max, string message);
9595 
9596 	/+ // not formally in cuz of templates but still
9597 	IPromise Then();
9598 	IPromise Catch();
9599 	IPromise Finally();
9600 	+/
9601 }
9602 
9603 /+
9604 	auto promise = async({ ... });
9605 	promise.Then(whatever).
9606 		Then(whateverelse).
9607 		Catch((exception) { });
9608 
9609 
9610 	A promise is run inside a fiber and it looks something like:
9611 
9612 	try {
9613 		auto res = whatever();
9614 		auto res2 = whateverelse(res);
9615 	} catch(Exception e) {
9616 		{ }(e);
9617 	}
9618 
9619 	When a thing succeeds, it is passed as an arg to the next
9620 +/
9621 class Promise(T) : IPromise {
9622 	auto Then() { return null; }
9623 	auto Catch() { return null; }
9624 	auto Finally() { return null; }
9625 
9626 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
9627 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
9628 	T await();
9629 }
9630 
9631 interface Task {
9632 }
9633 
9634 interface Resolvable(T) : Task {
9635 	void run();
9636 
9637 	void resolve(T);
9638 
9639 	Resolvable!T then(void delegate(T)); // returns a new promise
9640 	Resolvable!T error(Throwable); // js catch
9641 	Resolvable!T completed(); // js finally
9642 
9643 }
9644 
9645 /++
9646 	Runs `work` in a helper thread and sends its return value back to the main gui
9647 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
9648 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
9649 	kill the program.
9650 
9651 	You can call reportProgress(position, max, message) to update your parent window
9652 	on your progress.
9653 
9654 	I should also use `shared` methods. FIXME
9655 
9656 	History:
9657 		Added March 6, 2021 (dub version 9.3).
9658 +/
9659 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
9660 	uponCompletion(work(null));
9661 }
9662 
9663 +/
9664 
9665 /// Used internal to dispatch events to various classes.
9666 interface CapableOfHandlingNativeEvent {
9667 	NativeEventHandler getNativeEventHandler();
9668 
9669 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
9670 
9671 	version(X11) {
9672 		// if this is impossible, you are allowed to just throw from it
9673 		// Note: if you call it from another object, set a flag cuz the manger will call you again
9674 		void recreateAfterDisconnect();
9675 		// discard any *connection specific* state, but keep enough that you
9676 		// can be recreated if possible. discardConnectionState() is always called immediately
9677 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
9678 		// you need initialization order
9679 		void discardConnectionState();
9680 	}
9681 }
9682 
9683 version(X11)
9684 /++
9685 	State of keys on mouse events, especially motion.
9686 
9687 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
9688 +/
9689 enum ModifierState : uint {
9690 	shift = 1, ///
9691 	capsLock = 2, ///
9692 	ctrl = 4, ///
9693 	alt = 8, /// Not always available on Windows
9694 	windows = 64, /// ditto
9695 	numLock = 16, ///
9696 
9697 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
9698 	middleButtonDown = 512, /// ditto
9699 	rightButtonDown = 1024, /// ditto
9700 }
9701 else version(Windows)
9702 /// ditto
9703 enum ModifierState : uint {
9704 	shift = 4, ///
9705 	ctrl = 8, ///
9706 
9707 	// i'm not sure if the next two are available
9708 	alt = 256, /// not always available on Windows
9709 	windows = 512, /// ditto
9710 
9711 	capsLock = 1024, ///
9712 	numLock = 2048, ///
9713 
9714 	leftButtonDown = 1, /// not available on key events
9715 	middleButtonDown = 16, /// ditto
9716 	rightButtonDown = 2, /// ditto
9717 
9718 	backButtonDown = 0x20, /// not available on X
9719 	forwardButtonDown = 0x40, /// ditto
9720 }
9721 else version(OSXCocoa)
9722 // FIXME FIXME NotYetImplementedException
9723 enum ModifierState : uint {
9724 	shift = 1, ///
9725 	capsLock = 2, ///
9726 	ctrl = 4, ///
9727 	alt = 8, /// Not always available on Windows
9728 	windows = 64, /// ditto
9729 	numLock = 16, ///
9730 
9731 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
9732 	middleButtonDown = 512, /// ditto
9733 	rightButtonDown = 1024, /// ditto
9734 }
9735 
9736 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them.
9737 enum MouseButton : int {
9738 	none = 0,
9739 	left = 1, ///
9740 	right = 2, ///
9741 	middle = 4, ///
9742 	wheelUp = 8, ///
9743 	wheelDown = 16, ///
9744 	backButton = 32, /// often found on the thumb and used for back in browsers
9745 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
9746 }
9747 
9748 version(X11) {
9749 	// FIXME: match ASCII whenever we can. Most of it is already there,
9750 	// but there's a few exceptions and mismatches with Windows
9751 
9752 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
9753 	enum Key {
9754 		Escape = 0xff1b, ///
9755 		F1 = 0xffbe, ///
9756 		F2 = 0xffbf, ///
9757 		F3 = 0xffc0, ///
9758 		F4 = 0xffc1, ///
9759 		F5 = 0xffc2, ///
9760 		F6 = 0xffc3, ///
9761 		F7 = 0xffc4, ///
9762 		F8 = 0xffc5, ///
9763 		F9 = 0xffc6, ///
9764 		F10 = 0xffc7, ///
9765 		F11 = 0xffc8, ///
9766 		F12 = 0xffc9, ///
9767 		PrintScreen = 0xff61, ///
9768 		ScrollLock = 0xff14, ///
9769 		Pause = 0xff13, ///
9770 		Grave = 0x60, /// The $(BACKTICK) ~ key
9771 		// number keys across the top of the keyboard
9772 		N1 = 0x31, /// Number key atop the keyboard
9773 		N2 = 0x32, ///
9774 		N3 = 0x33, ///
9775 		N4 = 0x34, ///
9776 		N5 = 0x35, ///
9777 		N6 = 0x36, ///
9778 		N7 = 0x37, ///
9779 		N8 = 0x38, ///
9780 		N9 = 0x39, ///
9781 		N0 = 0x30, ///
9782 		Dash = 0x2d, ///
9783 		Equals = 0x3d, ///
9784 		Backslash = 0x5c, /// The \ | key
9785 		Backspace = 0xff08, ///
9786 		Insert = 0xff63, ///
9787 		Home = 0xff50, ///
9788 		PageUp = 0xff55, ///
9789 		Delete = 0xffff, ///
9790 		End = 0xff57, ///
9791 		PageDown = 0xff56, ///
9792 		Up = 0xff52, ///
9793 		Down = 0xff54, ///
9794 		Left = 0xff51, ///
9795 		Right = 0xff53, ///
9796 
9797 		Tab = 0xff09, ///
9798 		Q = 0x71, ///
9799 		W = 0x77, ///
9800 		E = 0x65, ///
9801 		R = 0x72, ///
9802 		T = 0x74, ///
9803 		Y = 0x79, ///
9804 		U = 0x75, ///
9805 		I = 0x69, ///
9806 		O = 0x6f, ///
9807 		P = 0x70, ///
9808 		LeftBracket = 0x5b, /// the [ { key
9809 		RightBracket = 0x5d, /// the ] } key
9810 		CapsLock = 0xffe5, ///
9811 		A = 0x61, ///
9812 		S = 0x73, ///
9813 		D = 0x64, ///
9814 		F = 0x66, ///
9815 		G = 0x67, ///
9816 		H = 0x68, ///
9817 		J = 0x6a, ///
9818 		K = 0x6b, ///
9819 		L = 0x6c, ///
9820 		Semicolon = 0x3b, ///
9821 		Apostrophe = 0x27, ///
9822 		Enter = 0xff0d, ///
9823 		Shift = 0xffe1, ///
9824 		Z = 0x7a, ///
9825 		X = 0x78, ///
9826 		C = 0x63, ///
9827 		V = 0x76, ///
9828 		B = 0x62, ///
9829 		N = 0x6e, ///
9830 		M = 0x6d, ///
9831 		Comma = 0x2c, ///
9832 		Period = 0x2e, ///
9833 		Slash = 0x2f, /// the / ? key
9834 		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
9835 		Ctrl = 0xffe3, ///
9836 		Windows = 0xffeb, ///
9837 		Alt = 0xffe9, ///
9838 		Space = 0x20, ///
9839 		Alt_r = 0xffea, /// ditto of shift_r
9840 		Windows_r = 0xffec, ///
9841 		Menu = 0xff67, ///
9842 		Ctrl_r = 0xffe4, ///
9843 
9844 		NumLock = 0xff7f, ///
9845 		Divide = 0xffaf, /// The / key on the number pad
9846 		Multiply = 0xffaa, /// The * key on the number pad
9847 		Minus = 0xffad, /// The - key on the number pad
9848 		Plus = 0xffab, /// The + key on the number pad
9849 		PadEnter = 0xff8d, /// Numberpad enter key
9850 		Pad1 = 0xff9c, /// Numberpad keys
9851 		Pad2 = 0xff99, ///
9852 		Pad3 = 0xff9b, ///
9853 		Pad4 = 0xff96, ///
9854 		Pad5 = 0xff9d, ///
9855 		Pad6 = 0xff98, ///
9856 		Pad7 = 0xff95, ///
9857 		Pad8 = 0xff97, ///
9858 		Pad9 = 0xff9a, ///
9859 		Pad0 = 0xff9e, ///
9860 		PadDot = 0xff9f, ///
9861 	}
9862 } else version(Windows) {
9863 	// the character here is for en-us layouts and for illustration only
9864 	// if you actually want to get characters, wait for character events
9865 	// (the argument to your event handler is simply a dchar)
9866 	// those will be converted by the OS for the right locale.
9867 
9868 	enum Key {
9869 		Escape = 0x1b,
9870 		F1 = 0x70,
9871 		F2 = 0x71,
9872 		F3 = 0x72,
9873 		F4 = 0x73,
9874 		F5 = 0x74,
9875 		F6 = 0x75,
9876 		F7 = 0x76,
9877 		F8 = 0x77,
9878 		F9 = 0x78,
9879 		F10 = 0x79,
9880 		F11 = 0x7a,
9881 		F12 = 0x7b,
9882 		PrintScreen = 0x2c,
9883 		ScrollLock = 0x91,
9884 		Pause = 0x13,
9885 		Grave = 0xc0,
9886 		// number keys across the top of the keyboard
9887 		N1 = 0x31,
9888 		N2 = 0x32,
9889 		N3 = 0x33,
9890 		N4 = 0x34,
9891 		N5 = 0x35,
9892 		N6 = 0x36,
9893 		N7 = 0x37,
9894 		N8 = 0x38,
9895 		N9 = 0x39,
9896 		N0 = 0x30,
9897 		Dash = 0xbd,
9898 		Equals = 0xbb,
9899 		Backslash = 0xdc,
9900 		Backspace = 0x08,
9901 		Insert = 0x2d,
9902 		Home = 0x24,
9903 		PageUp = 0x21,
9904 		Delete = 0x2e,
9905 		End = 0x23,
9906 		PageDown = 0x22,
9907 		Up = 0x26,
9908 		Down = 0x28,
9909 		Left = 0x25,
9910 		Right = 0x27,
9911 
9912 		Tab = 0x09,
9913 		Q = 0x51,
9914 		W = 0x57,
9915 		E = 0x45,
9916 		R = 0x52,
9917 		T = 0x54,
9918 		Y = 0x59,
9919 		U = 0x55,
9920 		I = 0x49,
9921 		O = 0x4f,
9922 		P = 0x50,
9923 		LeftBracket = 0xdb,
9924 		RightBracket = 0xdd,
9925 		CapsLock = 0x14,
9926 		A = 0x41,
9927 		S = 0x53,
9928 		D = 0x44,
9929 		F = 0x46,
9930 		G = 0x47,
9931 		H = 0x48,
9932 		J = 0x4a,
9933 		K = 0x4b,
9934 		L = 0x4c,
9935 		Semicolon = 0xba,
9936 		Apostrophe = 0xde,
9937 		Enter = 0x0d,
9938 		Shift = 0x10,
9939 		Z = 0x5a,
9940 		X = 0x58,
9941 		C = 0x43,
9942 		V = 0x56,
9943 		B = 0x42,
9944 		N = 0x4e,
9945 		M = 0x4d,
9946 		Comma = 0xbc,
9947 		Period = 0xbe,
9948 		Slash = 0xbf,
9949 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
9950 		Ctrl = 0x11,
9951 		Windows = 0x5b,
9952 		Alt = -5, // FIXME
9953 		Space = 0x20,
9954 		Alt_r = 0xffea, // ditto of shift_r
9955 		Windows_r = 0x5c, // ditto of shift_r
9956 		Menu = 0x5d,
9957 		Ctrl_r = 0xa3, // ditto of shift_r
9958 
9959 		NumLock = 0x90,
9960 		Divide = 0x6f,
9961 		Multiply = 0x6a,
9962 		Minus = 0x6d,
9963 		Plus = 0x6b,
9964 		PadEnter = -8, // FIXME
9965 		Pad1 = 0x61,
9966 		Pad2 = 0x62,
9967 		Pad3 = 0x63,
9968 		Pad4 = 0x64,
9969 		Pad5 = 0x65,
9970 		Pad6 = 0x66,
9971 		Pad7 = 0x67,
9972 		Pad8 = 0x68,
9973 		Pad9 = 0x69,
9974 		Pad0 = 0x60,
9975 		PadDot = 0x6e,
9976 	}
9977 
9978 	// I'm keeping this around for reference purposes
9979 	// ideally all these buttons will be listed for all platforms,
9980 	// but now now I'm just focusing on my US keyboard
9981 	version(none)
9982 	enum Key {
9983 		LBUTTON = 0x01,
9984 		RBUTTON = 0x02,
9985 		CANCEL = 0x03,
9986 		MBUTTON = 0x04,
9987 		//static if (_WIN32_WINNT > =  0x500) {
9988 		XBUTTON1 = 0x05,
9989 		XBUTTON2 = 0x06,
9990 		//}
9991 		BACK = 0x08,
9992 		TAB = 0x09,
9993 		CLEAR = 0x0C,
9994 		RETURN = 0x0D,
9995 		SHIFT = 0x10,
9996 		CONTROL = 0x11,
9997 		MENU = 0x12,
9998 		PAUSE = 0x13,
9999 		CAPITAL = 0x14,
10000 		KANA = 0x15,
10001 		HANGEUL = 0x15,
10002 		HANGUL = 0x15,
10003 		JUNJA = 0x17,
10004 		FINAL = 0x18,
10005 		HANJA = 0x19,
10006 		KANJI = 0x19,
10007 		ESCAPE = 0x1B,
10008 		CONVERT = 0x1C,
10009 		NONCONVERT = 0x1D,
10010 		ACCEPT = 0x1E,
10011 		MODECHANGE = 0x1F,
10012 		SPACE = 0x20,
10013 		PRIOR = 0x21,
10014 		NEXT = 0x22,
10015 		END = 0x23,
10016 		HOME = 0x24,
10017 		LEFT = 0x25,
10018 		UP = 0x26,
10019 		RIGHT = 0x27,
10020 		DOWN = 0x28,
10021 		SELECT = 0x29,
10022 		PRINT = 0x2A,
10023 		EXECUTE = 0x2B,
10024 		SNAPSHOT = 0x2C,
10025 		INSERT = 0x2D,
10026 		DELETE = 0x2E,
10027 		HELP = 0x2F,
10028 		LWIN = 0x5B,
10029 		RWIN = 0x5C,
10030 		APPS = 0x5D,
10031 		SLEEP = 0x5F,
10032 		NUMPAD0 = 0x60,
10033 		NUMPAD1 = 0x61,
10034 		NUMPAD2 = 0x62,
10035 		NUMPAD3 = 0x63,
10036 		NUMPAD4 = 0x64,
10037 		NUMPAD5 = 0x65,
10038 		NUMPAD6 = 0x66,
10039 		NUMPAD7 = 0x67,
10040 		NUMPAD8 = 0x68,
10041 		NUMPAD9 = 0x69,
10042 		MULTIPLY = 0x6A,
10043 		ADD = 0x6B,
10044 		SEPARATOR = 0x6C,
10045 		SUBTRACT = 0x6D,
10046 		DECIMAL = 0x6E,
10047 		DIVIDE = 0x6F,
10048 		F1 = 0x70,
10049 		F2 = 0x71,
10050 		F3 = 0x72,
10051 		F4 = 0x73,
10052 		F5 = 0x74,
10053 		F6 = 0x75,
10054 		F7 = 0x76,
10055 		F8 = 0x77,
10056 		F9 = 0x78,
10057 		F10 = 0x79,
10058 		F11 = 0x7A,
10059 		F12 = 0x7B,
10060 		F13 = 0x7C,
10061 		F14 = 0x7D,
10062 		F15 = 0x7E,
10063 		F16 = 0x7F,
10064 		F17 = 0x80,
10065 		F18 = 0x81,
10066 		F19 = 0x82,
10067 		F20 = 0x83,
10068 		F21 = 0x84,
10069 		F22 = 0x85,
10070 		F23 = 0x86,
10071 		F24 = 0x87,
10072 		NUMLOCK = 0x90,
10073 		SCROLL = 0x91,
10074 		LSHIFT = 0xA0,
10075 		RSHIFT = 0xA1,
10076 		LCONTROL = 0xA2,
10077 		RCONTROL = 0xA3,
10078 		LMENU = 0xA4,
10079 		RMENU = 0xA5,
10080 		//static if (_WIN32_WINNT > =  0x500) {
10081 		BROWSER_BACK = 0xA6,
10082 		BROWSER_FORWARD = 0xA7,
10083 		BROWSER_REFRESH = 0xA8,
10084 		BROWSER_STOP = 0xA9,
10085 		BROWSER_SEARCH = 0xAA,
10086 		BROWSER_FAVORITES = 0xAB,
10087 		BROWSER_HOME = 0xAC,
10088 		VOLUME_MUTE = 0xAD,
10089 		VOLUME_DOWN = 0xAE,
10090 		VOLUME_UP = 0xAF,
10091 		MEDIA_NEXT_TRACK = 0xB0,
10092 		MEDIA_PREV_TRACK = 0xB1,
10093 		MEDIA_STOP = 0xB2,
10094 		MEDIA_PLAY_PAUSE = 0xB3,
10095 		LAUNCH_MAIL = 0xB4,
10096 		LAUNCH_MEDIA_SELECT = 0xB5,
10097 		LAUNCH_APP1 = 0xB6,
10098 		LAUNCH_APP2 = 0xB7,
10099 		//}
10100 		OEM_1 = 0xBA,
10101 		//static if (_WIN32_WINNT > =  0x500) {
10102 		OEM_PLUS = 0xBB,
10103 		OEM_COMMA = 0xBC,
10104 		OEM_MINUS = 0xBD,
10105 		OEM_PERIOD = 0xBE,
10106 		//}
10107 		OEM_2 = 0xBF,
10108 		OEM_3 = 0xC0,
10109 		OEM_4 = 0xDB,
10110 		OEM_5 = 0xDC,
10111 		OEM_6 = 0xDD,
10112 		OEM_7 = 0xDE,
10113 		OEM_8 = 0xDF,
10114 		//static if (_WIN32_WINNT > =  0x500) {
10115 		OEM_102 = 0xE2,
10116 		//}
10117 		PROCESSKEY = 0xE5,
10118 		//static if (_WIN32_WINNT > =  0x500) {
10119 		PACKET = 0xE7,
10120 		//}
10121 		ATTN = 0xF6,
10122 		CRSEL = 0xF7,
10123 		EXSEL = 0xF8,
10124 		EREOF = 0xF9,
10125 		PLAY = 0xFA,
10126 		ZOOM = 0xFB,
10127 		NONAME = 0xFC,
10128 		PA1 = 0xFD,
10129 		OEM_CLEAR = 0xFE,
10130 	}
10131 
10132 } else version(OSXCocoa) {
10133 	// FIXME
10134 	enum Key {
10135 		Escape = 0x1b,
10136 		F1 = 0x70,
10137 		F2 = 0x71,
10138 		F3 = 0x72,
10139 		F4 = 0x73,
10140 		F5 = 0x74,
10141 		F6 = 0x75,
10142 		F7 = 0x76,
10143 		F8 = 0x77,
10144 		F9 = 0x78,
10145 		F10 = 0x79,
10146 		F11 = 0x7a,
10147 		F12 = 0x7b,
10148 		PrintScreen = 0x2c,
10149 		ScrollLock = -2, // FIXME
10150 		Pause = -3, // FIXME
10151 		Grave = 0xc0,
10152 		// number keys across the top of the keyboard
10153 		N1 = 0x31,
10154 		N2 = 0x32,
10155 		N3 = 0x33,
10156 		N4 = 0x34,
10157 		N5 = 0x35,
10158 		N6 = 0x36,
10159 		N7 = 0x37,
10160 		N8 = 0x38,
10161 		N9 = 0x39,
10162 		N0 = 0x30,
10163 		Dash = 0xbd,
10164 		Equals = 0xbb,
10165 		Backslash = 0xdc,
10166 		Backspace = 0x08,
10167 		Insert = 0x2d,
10168 		Home = 0x24,
10169 		PageUp = 0x21,
10170 		Delete = 0x2e,
10171 		End = 0x23,
10172 		PageDown = 0x22,
10173 		Up = 0x26,
10174 		Down = 0x28,
10175 		Left = 0x25,
10176 		Right = 0x27,
10177 
10178 		Tab = 0x09,
10179 		Q = 0x51,
10180 		W = 0x57,
10181 		E = 0x45,
10182 		R = 0x52,
10183 		T = 0x54,
10184 		Y = 0x59,
10185 		U = 0x55,
10186 		I = 0x49,
10187 		O = 0x4f,
10188 		P = 0x50,
10189 		LeftBracket = 0xdb,
10190 		RightBracket = 0xdd,
10191 		CapsLock = 0x14,
10192 		A = 0x41,
10193 		S = 0x53,
10194 		D = 0x44,
10195 		F = 0x46,
10196 		G = 0x47,
10197 		H = 0x48,
10198 		J = 0x4a,
10199 		K = 0x4b,
10200 		L = 0x4c,
10201 		Semicolon = 0xba,
10202 		Apostrophe = 0xde,
10203 		Enter = 0x0d,
10204 		Shift = 0x10,
10205 		Z = 0x5a,
10206 		X = 0x58,
10207 		C = 0x43,
10208 		V = 0x56,
10209 		B = 0x42,
10210 		N = 0x4e,
10211 		M = 0x4d,
10212 		Comma = 0xbc,
10213 		Period = 0xbe,
10214 		Slash = 0xbf,
10215 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
10216 		Ctrl = 0x11,
10217 		Windows = 0x5b,
10218 		Alt = -5, // FIXME
10219 		Space = 0x20,
10220 		Alt_r = 0xffea, // ditto of shift_r
10221 		Windows_r = -6, // FIXME
10222 		Menu = 0x5d,
10223 		Ctrl_r = -7, // FIXME
10224 
10225 		NumLock = 0x90,
10226 		Divide = 0x6f,
10227 		Multiply = 0x6a,
10228 		Minus = 0x6d,
10229 		Plus = 0x6b,
10230 		PadEnter = -8, // FIXME
10231 		// FIXME for the rest of these:
10232 		Pad1 = 0xff9c,
10233 		Pad2 = 0xff99,
10234 		Pad3 = 0xff9b,
10235 		Pad4 = 0xff96,
10236 		Pad5 = 0xff9d,
10237 		Pad6 = 0xff98,
10238 		Pad7 = 0xff95,
10239 		Pad8 = 0xff97,
10240 		Pad9 = 0xff9a,
10241 		Pad0 = 0xff9e,
10242 		PadDot = 0xff9f,
10243 	}
10244 
10245 }
10246 
10247 /* Additional utilities */
10248 
10249 
10250 Color fromHsl(real h, real s, real l) {
10251 	return arsd.color.fromHsl([h,s,l]);
10252 }
10253 
10254 
10255 
10256 /* ********** What follows is the system-specific implementations *********/
10257 version(Windows) {
10258 
10259 
10260 	// helpers for making HICONs from MemoryImages
10261 	class WindowsIcon {
10262 		struct Win32Icon(int colorCount) {
10263 		align(1):
10264 			uint biSize;
10265 			int biWidth;
10266 			int biHeight;
10267 			ushort biPlanes;
10268 			ushort biBitCount;
10269 			uint biCompression;
10270 			uint biSizeImage;
10271 			int biXPelsPerMeter;
10272 			int biYPelsPerMeter;
10273 			uint biClrUsed;
10274 			uint biClrImportant;
10275 			RGBQUAD[colorCount] biColors;
10276 			/* Pixels:
10277 			Uint8 pixels[]
10278 			*/
10279 			/* Mask:
10280 			Uint8 mask[]
10281 			*/
10282 
10283 			ubyte[4096] data;
10284 
10285 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
10286 				width = mi.width;
10287 				height = mi.height;
10288 
10289 				auto indexedImage = cast(IndexedImage) mi;
10290 				if(indexedImage is null)
10291 					indexedImage = quantize(mi.getAsTrueColorImage());
10292 
10293 				assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
10294 				assert(height %4 == 0);
10295 
10296 				int icon_plen = height*((width+3)&~3);
10297 				int icon_mlen = height*((((width+7)/8)+3)&~3);
10298 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
10299 
10300 				biSize = 40;
10301 				biWidth = width;
10302 				biHeight = height*2;
10303 				biPlanes = 1;
10304 				biBitCount = 8;
10305 				biSizeImage = icon_plen+icon_mlen;
10306 
10307 				int offset = 0;
10308 				int andOff = icon_plen * 8; // the and offset is in bits
10309 				for(int y = height - 1; y >= 0; y--) {
10310 					int off2 = y * width;
10311 					foreach(x; 0 .. width) {
10312 						const b = indexedImage.data[off2 + x];
10313 						data[offset] = b;
10314 						offset++;
10315 
10316 						const andBit = andOff % 8;
10317 						const andIdx = andOff / 8;
10318 						assert(b < indexedImage.palette.length);
10319 						// this is anded to the destination, since and 0 means erase,
10320 						// we want that to  be opaque, and 1 for transparent
10321 						auto transparent = (indexedImage.palette[b].a <= 127);
10322 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
10323 
10324 						andOff++;
10325 					}
10326 
10327 					andOff += andOff % 32;
10328 				}
10329 
10330 				foreach(idx, entry; indexedImage.palette) {
10331 					if(entry.a > 127) {
10332 						biColors[idx].rgbBlue = entry.b;
10333 						biColors[idx].rgbGreen = entry.g;
10334 						biColors[idx].rgbRed = entry.r;
10335 					} else {
10336 						biColors[idx].rgbBlue = 255;
10337 						biColors[idx].rgbGreen = 255;
10338 						biColors[idx].rgbRed = 255;
10339 					}
10340 				}
10341 
10342 				/*
10343 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
10344 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
10345 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
10346 				auto pngMap = fetchPaletteWin32(png);
10347 				biColors[0..pngMap.length] = pngMap[];
10348 				*/
10349 			}
10350 		}
10351 
10352 
10353 		Win32Icon!(256) icon_win32;
10354 
10355 
10356 		this(MemoryImage mi) {
10357 			int icon_len, width, height;
10358 
10359 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
10360 
10361 			/*
10362 			PNG* png = readPnpngData);
10363 			PNGHeader pngh = getHeader(png);
10364 			void* icon_win32;
10365 			if(pngh.depth == 4) {
10366 				auto i = new Win32Icon!(16);
10367 				i.fromPNG(png, pngh, icon_len, width, height);
10368 				icon_win32 = i;
10369 			}
10370 			else if(pngh.depth == 8) {
10371 				auto i = new Win32Icon!(256);
10372 				i.fromPNG(png, pngh, icon_len, width, height);
10373 				icon_win32 = i;
10374 			} else assert(0);
10375 			*/
10376 
10377 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
10378 
10379 			if(hIcon is null) throw new Exception("CreateIconFromResourceEx");
10380 		}
10381 
10382 		~this() {
10383 			DestroyIcon(hIcon);
10384 		}
10385 
10386 		HICON hIcon;
10387 	}
10388 
10389 
10390 
10391 
10392 
10393 
10394 	alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler;
10395 	alias HWND NativeWindowHandle;
10396 
10397 	extern(Windows)
10398 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
10399 		try {
10400 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
10401 				// it returns zero if the message is handled, so we won't do anything more there
10402 				// do I like that though?
10403 				int mustReturn;
10404 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn);
10405 				if(mustReturn)
10406 					return ret;
10407 			}
10408 
10409 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
10410 				if(window.getNativeEventHandler !is null) {
10411 					int mustReturn;
10412 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn);
10413 					if(mustReturn)
10414 						return ret;
10415 				}
10416 				if(auto w = cast(SimpleWindow) (*window))
10417 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
10418 				else
10419 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
10420 			} else {
10421 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
10422 			}
10423 		} catch (Exception e) {
10424 			try {
10425 				sdpy_abort(e);
10426 				return 0;
10427 			} catch(Exception e) { assert(0); }
10428 		}
10429 	}
10430 
10431 	void sdpy_abort(Throwable e) nothrow {
10432 		try
10433 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
10434 		catch(Exception e)
10435 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
10436 		ExitProcess(1);
10437 	}
10438 
10439 	mixin template NativeScreenPainterImplementation() {
10440 		HDC hdc;
10441 		HWND hwnd;
10442 		//HDC windowHdc;
10443 		HBITMAP oldBmp;
10444 
10445 		void create(NativeWindowHandle window) {
10446 			hwnd = window;
10447 
10448 			if(auto sw = cast(SimpleWindow) this.window) {
10449 				// drawing on a window, double buffer
10450 				auto windowHdc = GetDC(hwnd);
10451 
10452 				auto buffer = sw.impl.buffer;
10453 				if(buffer is null) {
10454 					hdc = windowHdc;
10455 					windowDc = true;
10456 				} else {
10457 					hdc = CreateCompatibleDC(windowHdc);
10458 
10459 					ReleaseDC(hwnd, windowHdc);
10460 
10461 					oldBmp = SelectObject(hdc, buffer);
10462 				}
10463 			} else {
10464 				// drawing on something else, draw directly
10465 				hdc = CreateCompatibleDC(null);
10466 				SelectObject(hdc, window);
10467 
10468 			}
10469 
10470 			// X doesn't draw a text background, so neither should we
10471 			SetBkMode(hdc, TRANSPARENT);
10472 
10473 			ensureDefaultFontLoaded();
10474 
10475 			if(defaultGuiFont) {
10476 				SelectObject(hdc, defaultGuiFont);
10477 				// DeleteObject(defaultGuiFont);
10478 			}
10479 		}
10480 
10481 		static HFONT defaultGuiFont;
10482 		static void ensureDefaultFontLoaded() {
10483 			static bool triedDefaultGuiFont = false;
10484 			if(!triedDefaultGuiFont) {
10485 				NONCLIENTMETRICS params;
10486 				params.cbSize = params.sizeof;
10487 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
10488 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
10489 				}
10490 				triedDefaultGuiFont = true;
10491 			}
10492 		}
10493 
10494 		void setFont(OperatingSystemFont font) {
10495 			if(font && font.font) {
10496 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
10497 					// error... how to handle tho?
10498 				}
10499 			}
10500 			else if(defaultGuiFont)
10501 				SelectObject(hdc, defaultGuiFont);
10502 		}
10503 
10504 		arsd.color.Rectangle _clipRectangle;
10505 
10506 		void setClipRectangle(int x, int y, int width, int height) {
10507 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
10508 
10509 			if(width == 0 || height == 0) {
10510 				SelectClipRgn(hdc, null);
10511 			} else {
10512 				auto region = CreateRectRgn(x, y, x + width, y + height);
10513 				SelectClipRgn(hdc, region);
10514 				DeleteObject(region);
10515 			}
10516 		}
10517 
10518 
10519 		// just because we can on Windows...
10520 		//void create(Image image);
10521 
10522 		void dispose() {
10523 			// FIXME: this.window.width/height is probably wrong
10524 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
10525 			// ReleaseDC(hwnd, windowHdc);
10526 
10527 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
10528 			if(cast(SimpleWindow) this.window)
10529 			InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
10530 
10531 			if(originalPen !is null)
10532 				SelectObject(hdc, originalPen);
10533 			if(currentPen !is null)
10534 				DeleteObject(currentPen);
10535 			if(originalBrush !is null)
10536 				SelectObject(hdc, originalBrush);
10537 			if(currentBrush !is null)
10538 				DeleteObject(currentBrush);
10539 
10540 			SelectObject(hdc, oldBmp);
10541 
10542 			if(windowDc)
10543 				ReleaseDC(hwnd, hdc);
10544 			else
10545 				DeleteDC(hdc);
10546 
10547 			if(window.paintingFinishedDg !is null)
10548 				window.paintingFinishedDg()();
10549 		}
10550 
10551 		bool windowDc;
10552 		HPEN originalPen;
10553 		HPEN currentPen;
10554 
10555 		Pen _activePen;
10556 
10557 		@property void pen(Pen p) {
10558 			_activePen = p;
10559 
10560 			HPEN pen;
10561 			if(p.color.a == 0) {
10562 				pen = GetStockObject(NULL_PEN);
10563 			} else {
10564 				int style = PS_SOLID;
10565 				final switch(p.style) {
10566 					case Pen.Style.Solid:
10567 						style = PS_SOLID;
10568 					break;
10569 					case Pen.Style.Dashed:
10570 						style = PS_DASH;
10571 					break;
10572 					case Pen.Style.Dotted:
10573 						style = PS_DOT;
10574 					break;
10575 				}
10576 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
10577 			}
10578 			auto orig = SelectObject(hdc, pen);
10579 			if(originalPen is null)
10580 				originalPen = orig;
10581 
10582 			if(currentPen !is null)
10583 				DeleteObject(currentPen);
10584 
10585 			currentPen = pen;
10586 
10587 			// the outline is like a foreground since it's done that way on X
10588 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
10589 
10590 		}
10591 
10592 		@property void rasterOp(RasterOp op) {
10593 			int mode;
10594 			final switch(op) {
10595 				case RasterOp.normal:
10596 					mode = R2_COPYPEN;
10597 				break;
10598 				case RasterOp.xor:
10599 					mode = R2_XORPEN;
10600 				break;
10601 			}
10602 			SetROP2(hdc, mode);
10603 		}
10604 
10605 		HBRUSH originalBrush;
10606 		HBRUSH currentBrush;
10607 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
10608 		@property void fillColor(Color c) {
10609 			if(c == _fillColor)
10610 				return;
10611 			_fillColor = c;
10612 			HBRUSH brush;
10613 			if(c.a == 0) {
10614 				brush = GetStockObject(HOLLOW_BRUSH);
10615 			} else {
10616 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
10617 			}
10618 			auto orig = SelectObject(hdc, brush);
10619 			if(originalBrush is null)
10620 				originalBrush = orig;
10621 
10622 			if(currentBrush !is null)
10623 				DeleteObject(currentBrush);
10624 
10625 			currentBrush = brush;
10626 
10627 			// background color is NOT set because X doesn't draw text backgrounds
10628 			//   SetBkColor(hdc, RGB(255, 255, 255));
10629 		}
10630 
10631 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
10632 			BITMAP bm;
10633 
10634 			HDC hdcMem = CreateCompatibleDC(hdc);
10635 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
10636 
10637 			GetObject(i.handle, bm.sizeof, &bm);
10638 
10639 			// or should I AlphaBlend!??!?!
10640 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
10641 
10642 			SelectObject(hdcMem, hbmOld);
10643 			DeleteDC(hdcMem);
10644 		}
10645 
10646 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
10647 			BITMAP bm;
10648 
10649 			HDC hdcMem = CreateCompatibleDC(hdc);
10650 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
10651 
10652 			GetObject(s.handle, bm.sizeof, &bm);
10653 
10654 			version(CRuntime_DigitalMars) goto noalpha;
10655 
10656 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
10657 			if(s.enableAlpha) {
10658 				auto dw = w ? w : bm.bmWidth;
10659 				auto dh = h ? h : bm.bmHeight;
10660 				BLENDFUNCTION bf;
10661 				bf.BlendOp = AC_SRC_OVER;
10662 				bf.SourceConstantAlpha = 255;
10663 				bf.AlphaFormat = AC_SRC_ALPHA;
10664 				AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf);
10665 			} else {
10666 				noalpha:
10667 				BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
10668 			}
10669 
10670 			SelectObject(hdcMem, hbmOld);
10671 			DeleteDC(hdcMem);
10672 		}
10673 
10674 		Size textSize(scope const(char)[] text) {
10675 			bool dummyX;
10676 			if(text.length == 0) {
10677 				text = " ";
10678 				dummyX = true;
10679 			}
10680 			RECT rect;
10681 			WCharzBuffer buffer = WCharzBuffer(text);
10682 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX);
10683 			return Size(dummyX ? 0 : rect.right, rect.bottom);
10684 		}
10685 
10686 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
10687 			if(text.length && text[$-1] == '\n')
10688 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
10689 			if(text.length && text[$-1] == '\r')
10690 				text = text[0 .. $-1];
10691 
10692 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
10693 			if(x2 == 0 && y2 == 0) {
10694 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
10695 			} else {
10696 				RECT rect;
10697 				rect.left = x;
10698 				rect.top = y;
10699 				rect.right = x2;
10700 				rect.bottom = y2;
10701 
10702 				uint mode = DT_LEFT;
10703 				if(alignment & TextAlignment.Right)
10704 					mode = DT_RIGHT;
10705 				else if(alignment & TextAlignment.Center)
10706 					mode = DT_CENTER;
10707 
10708 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
10709 				if(alignment & TextAlignment.VerticalCenter)
10710 					mode |= DT_VCENTER | DT_SINGLELINE;
10711 
10712 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX);
10713 			}
10714 
10715 			/*
10716 			uint mode;
10717 
10718 			if(alignment & TextAlignment.Center)
10719 				mode = TA_CENTER;
10720 
10721 			SetTextAlign(hdc, mode);
10722 			*/
10723 		}
10724 
10725 		int fontHeight() {
10726 			TEXTMETRIC metric;
10727 			if(GetTextMetricsW(hdc, &metric)) {
10728 				return metric.tmHeight;
10729 			}
10730 
10731 			return 16; // idk just guessing here, maybe we should throw
10732 		}
10733 
10734 		void drawPixel(int x, int y) {
10735 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
10736 		}
10737 
10738 		// The basic shapes, outlined
10739 
10740 		void drawLine(int x1, int y1, int x2, int y2) {
10741 			MoveToEx(hdc, x1, y1, null);
10742 			LineTo(hdc, x2, y2);
10743 		}
10744 
10745 		void drawRectangle(int x, int y, int width, int height) {
10746 			// FIXME: with a wider pen this might not draw quite right. im not sure.
10747 			gdi.Rectangle(hdc, x, y, x + width, y + height);
10748 		}
10749 
10750 		/// Arguments are the points of the bounding rectangle
10751 		void drawEllipse(int x1, int y1, int x2, int y2) {
10752 			Ellipse(hdc, x1, y1, x2, y2);
10753 		}
10754 
10755 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
10756 			if((start % (360*64)) == (finish % (360*64)))
10757 				drawEllipse(x1, y1, x1 + width, y1 + height);
10758 			else {
10759 				import core.stdc.math;
10760 				float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323;
10761 				float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323;
10762 
10763 				auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2);
10764 				auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2);
10765 				auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2);
10766 				auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2);
10767 
10768 				if(_activePen.color.a)
10769 					Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
10770 				if(_fillColor.a)
10771 					Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4);
10772 			}
10773 		}
10774 
10775 		void drawPolygon(Point[] vertexes) {
10776 			POINT[] points;
10777 			points.length = vertexes.length;
10778 
10779 			foreach(i, p; vertexes) {
10780 				points[i].x = p.x;
10781 				points[i].y = p.y;
10782 			}
10783 
10784 			Polygon(hdc, points.ptr, cast(int) points.length);
10785 		}
10786 	}
10787 
10788 
10789 	// Mix this into the SimpleWindow class
10790 	mixin template NativeSimpleWindowImplementation() {
10791 		int curHidden = 0; // counter
10792 		__gshared static bool[string] knownWinClasses;
10793 		static bool altPressed = false;
10794 
10795 		HANDLE oldCursor;
10796 
10797 		void hideCursor () {
10798 			if(curHidden == 0)
10799 				oldCursor = SetCursor(null);
10800 			++curHidden;
10801 		}
10802 
10803 		void showCursor () {
10804 			--curHidden;
10805 			if(curHidden == 0) {
10806 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
10807 			}
10808 		}
10809 
10810 
10811 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
10812 
10813 		void setMinSize (int minwidth, int minheight) {
10814 			minWidth = minwidth;
10815 			minHeight = minheight;
10816 		}
10817 		void setMaxSize (int maxwidth, int maxheight) {
10818 			maxWidth = maxwidth;
10819 			maxHeight = maxheight;
10820 		}
10821 
10822 		// FIXME i'm not sure that Windows has this functionality
10823 		// though it is nonessential anyway.
10824 		void setResizeGranularity (int granx, int grany) {}
10825 
10826 		ScreenPainter getPainter() {
10827 			return ScreenPainter(this, hwnd);
10828 		}
10829 
10830 		HBITMAP buffer;
10831 
10832 		void setTitle(string title) {
10833 			WCharzBuffer bfr = WCharzBuffer(title);
10834 			SetWindowTextW(hwnd, bfr.ptr);
10835 		}
10836 
10837 		string getTitle() {
10838 			auto len = GetWindowTextLengthW(hwnd);
10839 			if (!len)
10840 				return null;
10841 			wchar[256] tmpBuffer;
10842 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
10843 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
10844 			auto str = buffer[0 .. len2];
10845 			return makeUtf8StringFromWindowsString(str);
10846 		}
10847 
10848 		void move(int x, int y) {
10849 			RECT rect;
10850 			GetWindowRect(hwnd, &rect);
10851 			// move it while maintaining the same size...
10852 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
10853 		}
10854 
10855 		void resize(int w, int h) {
10856 			RECT rect;
10857 			GetWindowRect(hwnd, &rect);
10858 
10859 			RECT client;
10860 			GetClientRect(hwnd, &client);
10861 
10862 			rect.right = rect.right - client.right + w;
10863 			rect.bottom = rect.bottom - client.bottom + h;
10864 
10865 			// same position, new size for the client rectangle
10866 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
10867 
10868 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
10869 		}
10870 
10871 		void moveResize (int x, int y, int w, int h) {
10872 			// what's given is the client rectangle, we need to adjust
10873 
10874 			RECT rect;
10875 			rect.left = x;
10876 			rect.top = y;
10877 			rect.right = w + x;
10878 			rect.bottom = h + y;
10879 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
10880 				throw new Exception("AdjustWindowRect");
10881 
10882 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
10883 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
10884 			if (windowResized !is null) windowResized(w, h);
10885 		}
10886 
10887 		version(without_opengl) {} else {
10888 			HGLRC ghRC;
10889 			HDC ghDC;
10890 		}
10891 
10892 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
10893 			string cnamec;
10894 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
10895 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
10896 				cnamec = "DSimpleWindow";
10897 			} else {
10898 				cnamec = sdpyWindowClass;
10899 			}
10900 
10901 			WCharzBuffer cn = WCharzBuffer(cnamec);
10902 
10903 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
10904 
10905 			if(cnamec !in knownWinClasses) {
10906 				WNDCLASSEX wc;
10907 
10908 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
10909 				// to the object. Maybe.
10910 				wc.cbSize = wc.sizeof;
10911 				wc.cbClsExtra = 0;
10912 				wc.cbWndExtra = 0;
10913 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
10914 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
10915 				wc.hIcon = LoadIcon(hInstance, null);
10916 				wc.hInstance = hInstance;
10917 				wc.lpfnWndProc = &WndProc;
10918 				wc.lpszClassName = cn.ptr;
10919 				wc.hIconSm = null;
10920 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
10921 				if(!RegisterClassExW(&wc))
10922 					throw new WindowsApiException("RegisterClassExW");
10923 				knownWinClasses[cnamec] = true;
10924 			}
10925 
10926 			int style;
10927 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
10928 
10929 			// FIXME: windowType and customizationFlags
10930 			final switch(windowType) {
10931 				case WindowTypes.normal:
10932 					style = WS_OVERLAPPEDWINDOW;
10933 				break;
10934 				case WindowTypes.undecorated:
10935 					style = WS_POPUP | WS_SYSMENU;
10936 				break;
10937 				case WindowTypes.eventOnly:
10938 					_hidden = true;
10939 				break;
10940 				case WindowTypes.dropdownMenu:
10941 				case WindowTypes.popupMenu:
10942 				case WindowTypes.notification:
10943 					style = WS_POPUP;
10944 					flags |= WS_EX_NOACTIVATE;
10945 				break;
10946 				case WindowTypes.nestedChild:
10947 					style = WS_CHILD;
10948 				break;
10949 			}
10950 
10951 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
10952 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
10953 
10954 			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
10955 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
10956 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
10957 
10958 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
10959 				setOpacity(255);
10960 
10961 			SimpleWindow.nativeMapping[hwnd] = this;
10962 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
10963 
10964 			if(windowType == WindowTypes.eventOnly)
10965 				return;
10966 
10967 			HDC hdc = GetDC(hwnd);
10968 
10969 
10970 			version(without_opengl) {}
10971 			else {
10972 				if(opengl == OpenGlOptions.yes) {
10973 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
10974 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
10975 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
10976 					ghDC = hdc;
10977 					PIXELFORMATDESCRIPTOR pfd;
10978 
10979 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
10980 					pfd.nVersion = 1;
10981 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
10982 					pfd.dwLayerMask = PFD_MAIN_PLANE;
10983 					pfd.iPixelType = PFD_TYPE_RGBA;
10984 					pfd.cColorBits = 24;
10985 					pfd.cDepthBits = 24;
10986 					pfd.cAccumBits = 0;
10987 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
10988 
10989 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
10990 
10991 					if (pixelformat == 0)
10992 						throw new WindowsApiException("ChoosePixelFormat");
10993 
10994 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
10995 						throw new WindowsApiException("SetPixelFormat");
10996 
10997 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
10998 						// windoze is idiotic: we have to have OpenGL context to get function addresses
10999 						// so we will create fake context to get that stupid address
11000 						auto tmpcc = wglCreateContext(ghDC);
11001 						if (tmpcc !is null) {
11002 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
11003 							wglMakeCurrent(ghDC, tmpcc);
11004 							wglInitOtherFunctions();
11005 						}
11006 					}
11007 
11008 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
11009 						int[9] contextAttribs = [
11010 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
11011 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
11012 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
11013 							// for modern context, set "forward compatibility" flag too
11014 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
11015 							0/*None*/,
11016 						];
11017 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
11018 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
11019 							// activate fallback mode
11020 							// 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;
11021 							ghRC = wglCreateContext(ghDC);
11022 						}
11023 						if (ghRC is null)
11024 							throw new WindowsApiException("wglCreateContextAttribsARB");
11025 					} else {
11026 						// try to do at least something
11027 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
11028 							sdpyOpenGLContextVersion = 0;
11029 							ghRC = wglCreateContext(ghDC);
11030 						}
11031 						if (ghRC is null)
11032 							throw new WindowsApiException("wglCreateContext");
11033 					}
11034 				}
11035 			}
11036 
11037 			if(opengl == OpenGlOptions.no) {
11038 				buffer = CreateCompatibleBitmap(hdc, width, height);
11039 
11040 				auto hdcBmp = CreateCompatibleDC(hdc);
11041 				// make sure it's filled with a blank slate
11042 				auto oldBmp = SelectObject(hdcBmp, buffer);
11043 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
11044 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
11045 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
11046 				SelectObject(hdcBmp, oldBmp);
11047 				SelectObject(hdcBmp, oldBrush);
11048 				SelectObject(hdcBmp, oldPen);
11049 				DeleteDC(hdcBmp);
11050 
11051 				bmpWidth = width;
11052 				bmpHeight = height;
11053 
11054 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
11055 			}
11056 
11057 			// We want the window's client area to match the image size
11058 			RECT rcClient, rcWindow;
11059 			POINT ptDiff;
11060 			GetClientRect(hwnd, &rcClient);
11061 			GetWindowRect(hwnd, &rcWindow);
11062 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
11063 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
11064 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
11065 
11066 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
11067 				ShowWindow(hwnd, SW_SHOWNORMAL);
11068 			} else {
11069 				_hidden = true;
11070 			}
11071 			this._visibleForTheFirstTimeCalled = false; // hack!
11072 		}
11073 
11074 
11075 		void dispose() {
11076 			if(buffer)
11077 				DeleteObject(buffer);
11078 		}
11079 
11080 		void closeWindow() {
11081 			DestroyWindow(hwnd);
11082 		}
11083 
11084 		bool setOpacity(ubyte alpha) {
11085 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
11086 		}
11087 
11088 		HANDLE currentCursor;
11089 
11090 		// returns zero if it recognized the event
11091 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
11092 			MouseEvent mouse;
11093 
11094 			void mouseEvent(bool isScreen, ulong mods) {
11095 				auto x = LOWORD(lParam);
11096 				auto y = HIWORD(lParam);
11097 				if(isScreen) {
11098 					POINT p;
11099 					p.x = x;
11100 					p.y = y;
11101 					ScreenToClient(hwnd, &p);
11102 					x = cast(ushort) p.x;
11103 					y = cast(ushort) p.y;
11104 				}
11105 				mouse.x = x + offsetX;
11106 				mouse.y = y + offsetY;
11107 
11108 				wind.mdx(mouse);
11109 				mouse.modifierState = cast(int) mods;
11110 				mouse.window = wind;
11111 
11112 				if(wind.handleMouseEvent)
11113 					wind.handleMouseEvent(mouse);
11114 			}
11115 
11116 			switch(msg) {
11117 				case WM_GETMINMAXINFO:
11118 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
11119 
11120 					if(wind.minWidth > 0) {
11121 						RECT rect;
11122 						rect.left = 100;
11123 						rect.top = 100;
11124 						rect.right = wind.minWidth + 100;
11125 						rect.bottom = wind.minHeight + 100;
11126 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11127 							throw new WindowsApiException("AdjustWindowRect");
11128 
11129 						mmi.ptMinTrackSize.x = rect.right - rect.left;
11130 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
11131 					}
11132 
11133 					if(wind.maxWidth < int.max) {
11134 						RECT rect;
11135 						rect.left = 100;
11136 						rect.top = 100;
11137 						rect.right = wind.maxWidth + 100;
11138 						rect.bottom = wind.maxHeight + 100;
11139 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
11140 							throw new WindowsApiException("AdjustWindowRect");
11141 
11142 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
11143 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
11144 					}
11145 				break;
11146 				case WM_CHAR:
11147 					wchar c = cast(wchar) wParam;
11148 					if(wind.handleCharEvent)
11149 						wind.handleCharEvent(cast(dchar) c);
11150 				break;
11151 				  case WM_SETFOCUS:
11152 				  case WM_KILLFOCUS:
11153 					wind._focused = (msg == WM_SETFOCUS);
11154 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
11155 					if(wind.onFocusChange)
11156 						wind.onFocusChange(msg == WM_SETFOCUS);
11157 				  break;
11158 				case WM_SYSKEYDOWN:
11159 					goto case;
11160 				case WM_SYSKEYUP:
11161 					if(lParam & (1 << 29)) {
11162 						goto case;
11163 					} else {
11164 						// no window has keyboard focus
11165 						goto default;
11166 					}
11167 				case WM_KEYDOWN:
11168 				case WM_KEYUP:
11169 					KeyEvent ev;
11170 					ev.key = cast(Key) wParam;
11171 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
11172 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
11173 
11174 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
11175 
11176 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
11177 						ev.modifierState |= ModifierState.shift;
11178 					//k8: this doesn't work; thanks for nothing, windows
11179 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
11180 						ev.modifierState |= ModifierState.alt;*/
11181 					if (wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN);
11182 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
11183 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
11184 						ev.modifierState |= ModifierState.ctrl;
11185 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
11186 						ev.modifierState |= ModifierState.windows;
11187 					if(GetKeyState(Key.NumLock))
11188 						ev.modifierState |= ModifierState.numLock;
11189 					if(GetKeyState(Key.CapsLock))
11190 						ev.modifierState |= ModifierState.capsLock;
11191 
11192 					/+
11193 					// we always want to send the character too, so let's convert it
11194 					ubyte[256] state;
11195 					wchar[16] buffer;
11196 					GetKeyboardState(state.ptr);
11197 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
11198 
11199 					foreach(dchar d; buffer) {
11200 						ev.character = d;
11201 						break;
11202 					}
11203 					+/
11204 
11205 					ev.window = wind;
11206 					if(wind.handleKeyEvent)
11207 						wind.handleKeyEvent(ev);
11208 				break;
11209 				case 0x020a /*WM_MOUSEWHEEL*/:
11210 					// send click
11211 					mouse.type = cast(MouseEventType) 1;
11212 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11213 					mouseEvent(true, LOWORD(wParam));
11214 
11215 					// also send release
11216 					mouse.type = cast(MouseEventType) 2;
11217 					mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown);
11218 					mouseEvent(true, LOWORD(wParam));
11219 				break;
11220 				case WM_MOUSEMOVE:
11221 					mouse.type = cast(MouseEventType) 0;
11222 					mouseEvent(false, wParam);
11223 				break;
11224 				case WM_LBUTTONDOWN:
11225 				case WM_LBUTTONDBLCLK:
11226 					mouse.type = cast(MouseEventType) 1;
11227 					mouse.button = MouseButton.left;
11228 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
11229 					mouseEvent(false, wParam);
11230 				break;
11231 				case WM_LBUTTONUP:
11232 					mouse.type = cast(MouseEventType) 2;
11233 					mouse.button = MouseButton.left;
11234 					mouseEvent(false, wParam);
11235 				break;
11236 				case WM_RBUTTONDOWN:
11237 				case WM_RBUTTONDBLCLK:
11238 					mouse.type = cast(MouseEventType) 1;
11239 					mouse.button = MouseButton.right;
11240 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
11241 					mouseEvent(false, wParam);
11242 				break;
11243 				case WM_RBUTTONUP:
11244 					mouse.type = cast(MouseEventType) 2;
11245 					mouse.button = MouseButton.right;
11246 					mouseEvent(false, wParam);
11247 				break;
11248 				case WM_MBUTTONDOWN:
11249 				case WM_MBUTTONDBLCLK:
11250 					mouse.type = cast(MouseEventType) 1;
11251 					mouse.button = MouseButton.middle;
11252 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
11253 					mouseEvent(false, wParam);
11254 				break;
11255 				case WM_MBUTTONUP:
11256 					mouse.type = cast(MouseEventType) 2;
11257 					mouse.button = MouseButton.middle;
11258 					mouseEvent(false, wParam);
11259 				break;
11260 				case WM_XBUTTONDOWN:
11261 				case WM_XBUTTONDBLCLK:
11262 					mouse.type = cast(MouseEventType) 1;
11263 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11264 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
11265 					mouseEvent(false, wParam);
11266 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
11267 				case WM_XBUTTONUP:
11268 					mouse.type = cast(MouseEventType) 2;
11269 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
11270 					mouseEvent(false, wParam);
11271 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
11272 
11273 				default: return 1;
11274 			}
11275 			return 0;
11276 		}
11277 
11278 		HWND hwnd;
11279 		private int oldWidth;
11280 		private int oldHeight;
11281 		private bool inSizeMove;
11282 
11283 		/++
11284 			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.
11285 
11286 			History:
11287 				Added November 23, 2021
11288 
11289 				Not fully stable, may be moved out of the impl struct.
11290 		+/
11291 		bool doLiveResizing;
11292 
11293 		private int bmpWidth;
11294 		private int bmpHeight;
11295 
11296 		// the extern(Windows) wndproc should just forward to this
11297 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
11298 			assert(hwnd is this.hwnd);
11299 
11300 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
11301 			switch(msg) {
11302 				case WM_SETCURSOR:
11303 					if(cast(HWND) wParam !is hwnd)
11304 						return 0; // further processing elsewhere
11305 
11306 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
11307 						SetCursor(this.curHidden > 0 ? null : currentCursor);
11308 						return 1;
11309 					} else {
11310 						return DefWindowProc(hwnd, msg, wParam, lParam);
11311 					}
11312 				//break;
11313 
11314 				case WM_CLOSE:
11315 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
11316 				break;
11317 				case WM_DESTROY:
11318 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
11319 					SimpleWindow.nativeMapping.remove(hwnd);
11320 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
11321 
11322 					bool anyImportant = false;
11323 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
11324 						if(w.beingOpenKeepsAppOpen) {
11325 							anyImportant = true;
11326 							break;
11327 						}
11328 					if(!anyImportant) {
11329 						PostQuitMessage(0);
11330 					}
11331 				break;
11332 				case 0x02E0 /*WM_DPICHANGED*/:
11333 					this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs
11334 
11335 					RECT* prcNewWindow = cast(RECT*)lParam;
11336 					// docs say this is the recommended position and we should honor it
11337 					SetWindowPos(hwnd,
11338 							null,
11339 							prcNewWindow.left,
11340 							prcNewWindow.top,
11341 							prcNewWindow.right - prcNewWindow.left,
11342 							prcNewWindow.bottom - prcNewWindow.top,
11343 							SWP_NOZORDER | SWP_NOACTIVATE);
11344 
11345 					// doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp
11346 					// im not sure it is completely correct
11347 					// but without it the tabs and such do look weird as things change.
11348 					{
11349 						LOGFONT lfText;
11350 						SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_);
11351 						HFONT hFontNew = CreateFontIndirect(&lfText);
11352 						if (hFontNew)
11353 						{
11354 							//DeleteObject(hFontOld);
11355 							static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) {
11356 								SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0));
11357 								return TRUE;
11358 							}
11359 							EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew);
11360 						}
11361 					}
11362 
11363 					if(this.onDpiChanged)
11364 						this.onDpiChanged();
11365 				break;
11366 				case WM_SIZE:
11367 					if(wParam == 1 /* SIZE_MINIMIZED */)
11368 						break;
11369 					_width = LOWORD(lParam);
11370 					_height = HIWORD(lParam);
11371 
11372 					// I want to avoid tearing in the windows (my code is inefficient
11373 					// so this is a hack around that) so while sizing, we don't trigger,
11374 					// but we do want to trigger on events like mazimize.
11375 					if(!inSizeMove || doLiveResizing)
11376 						goto size_changed;
11377 				break;
11378 				/+
11379 				case WM_SIZING:
11380 					import std.stdio; writeln("size");
11381 				break;
11382 				+/
11383 				// I don't like the tearing I get when redrawing on WM_SIZE
11384 				// (I know there's other ways to fix that but I don't like that behavior anyway)
11385 				// so instead it is going to redraw only at the end of a size.
11386 				case 0x0231: /* WM_ENTERSIZEMOVE */
11387 					oldWidth = this.width;
11388 					oldHeight = this.height;
11389 					inSizeMove = true;
11390 				break;
11391 				case 0x0232: /* WM_EXITSIZEMOVE */
11392 					inSizeMove = false;
11393 
11394 					size_changed:
11395 
11396 					// nothing relevant changed, don't bother redrawing
11397 					if(oldWidth == width && oldHeight == height)
11398 						break;
11399 
11400 					// note: OpenGL windows don't use a backing bmp, so no need to change them
11401 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
11402 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
11403 						// gotta get the double buffer bmp to match the window
11404 						// FIXME: could this be more efficient? it never relinquishes a large bitmap
11405 						if(width > bmpWidth || height > bmpHeight) {
11406 							auto hdc = GetDC(hwnd);
11407 							auto oldBuffer = buffer;
11408 							buffer = CreateCompatibleBitmap(hdc, width, height);
11409 
11410 							auto hdcBmp = CreateCompatibleDC(hdc);
11411 							auto oldBmp = SelectObject(hdcBmp, buffer);
11412 
11413 							auto hdcOldBmp = CreateCompatibleDC(hdc);
11414 							auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp);
11415 
11416 							BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY);
11417 
11418 							bmpWidth = width;
11419 							bmpHeight = height;
11420 
11421 							SelectObject(hdcOldBmp, oldOldBmp);
11422 							DeleteDC(hdcOldBmp);
11423 
11424 							SelectObject(hdcBmp, oldBmp);
11425 							DeleteDC(hdcBmp);
11426 
11427 							ReleaseDC(hwnd, hdc);
11428 
11429 							DeleteObject(oldBuffer);
11430 						}
11431 					}
11432 
11433 					version(without_opengl) {} else
11434 					if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
11435 						glViewport(0, 0, width, height);
11436 					}
11437 
11438 					if(windowResized !is null)
11439 						windowResized(width, height);
11440 				break;
11441 				case WM_ERASEBKGND:
11442 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
11443 					if (!this._visibleForTheFirstTimeCalled) {
11444 						this._visibleForTheFirstTimeCalled = true;
11445 						if (this.visibleForTheFirstTime !is null) {
11446 							version(without_opengl) {} else {
11447 								if(openglMode == OpenGlOptions.yes) {
11448 									this.setAsCurrentOpenGlContextNT();
11449 									glViewport(0, 0, width, height);
11450 								}
11451 							}
11452 							this.visibleForTheFirstTime();
11453 						}
11454 					}
11455 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
11456 					version(without_opengl) {} else {
11457 						if (openglMode == OpenGlOptions.yes) return 1;
11458 					}
11459 					// call windows default handler, so it can paint standard controls
11460 					goto default;
11461 				case WM_CTLCOLORBTN:
11462 				case WM_CTLCOLORSTATIC:
11463 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
11464 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
11465 					GetSysColorBrush(COLOR_3DFACE);
11466 				//break;
11467 				case WM_SHOWWINDOW:
11468 					this._visible = (wParam != 0);
11469 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
11470 						this._visibleForTheFirstTimeCalled = true;
11471 						if (this.visibleForTheFirstTime !is null) {
11472 							version(without_opengl) {} else {
11473 								if(openglMode == OpenGlOptions.yes) {
11474 									this.setAsCurrentOpenGlContextNT();
11475 									glViewport(0, 0, width, height);
11476 								}
11477 							}
11478 							this.visibleForTheFirstTime();
11479 						}
11480 					}
11481 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
11482 					break;
11483 				case WM_PAINT: {
11484 					if (!this._visibleForTheFirstTimeCalled) {
11485 						this._visibleForTheFirstTimeCalled = true;
11486 						if (this.visibleForTheFirstTime !is null) {
11487 							version(without_opengl) {} else {
11488 								if(openglMode == OpenGlOptions.yes) {
11489 									this.setAsCurrentOpenGlContextNT();
11490 									glViewport(0, 0, width, height);
11491 								}
11492 							}
11493 							this.visibleForTheFirstTime();
11494 						}
11495 					}
11496 
11497 					BITMAP bm;
11498 					PAINTSTRUCT ps;
11499 
11500 					HDC hdc = BeginPaint(hwnd, &ps);
11501 
11502 					if(openglMode == OpenGlOptions.no) {
11503 
11504 						HDC hdcMem = CreateCompatibleDC(hdc);
11505 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
11506 
11507 						GetObject(buffer, bm.sizeof, &bm);
11508 
11509 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
11510 						if(resizability == Resizability.automaticallyScaleIfPossible)
11511 						StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
11512 						else
11513 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
11514 
11515 						SelectObject(hdcMem, hbmOld);
11516 						DeleteDC(hdcMem);
11517 						EndPaint(hwnd, &ps);
11518 					} else {
11519 						EndPaint(hwnd, &ps);
11520 						version(without_opengl) {} else
11521 							redrawOpenGlSceneNow();
11522 					}
11523 				} break;
11524 				  default:
11525 					return DefWindowProc(hwnd, msg, wParam, lParam);
11526 			}
11527 			 return 0;
11528 
11529 		}
11530 	}
11531 
11532 	mixin template NativeImageImplementation() {
11533 		HBITMAP handle;
11534 		ubyte* rawData;
11535 
11536 	final:
11537 
11538 		Color getPixel(int x, int y) {
11539 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
11540 			// remember, bmps are upside down
11541 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
11542 
11543 			Color c;
11544 			if(enableAlpha)
11545 				c.a = rawData[offset + 3];
11546 			else
11547 				c.a = 255;
11548 			c.b = rawData[offset + 0];
11549 			c.g = rawData[offset + 1];
11550 			c.r = rawData[offset + 2];
11551 			c.unPremultiply();
11552 			return c;
11553 		}
11554 
11555 		void setPixel(int x, int y, Color c) {
11556 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
11557 			// remember, bmps are upside down
11558 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
11559 
11560 			if(enableAlpha)
11561 				c.premultiply();
11562 
11563 			rawData[offset + 0] = c.b;
11564 			rawData[offset + 1] = c.g;
11565 			rawData[offset + 2] = c.r;
11566 			if(enableAlpha)
11567 				rawData[offset + 3] = c.a;
11568 		}
11569 
11570 		void convertToRgbaBytes(ubyte[] where) {
11571 			assert(where.length == this.width * this.height * 4);
11572 
11573 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
11574 			int idx = 0;
11575 			int offset = itemsPerLine * (height - 1);
11576 			// remember, bmps are upside down
11577 			for(int y = height - 1; y >= 0; y--) {
11578 				auto offsetStart = offset;
11579 				for(int x = 0; x < width; x++) {
11580 					where[idx + 0] = rawData[offset + 2]; // r
11581 					where[idx + 1] = rawData[offset + 1]; // g
11582 					where[idx + 2] = rawData[offset + 0]; // b
11583 					if(enableAlpha) {
11584 						where[idx + 3] = rawData[offset + 3]; // a
11585 						unPremultiplyRgba(where[idx .. idx + 4]);
11586 						offset++;
11587 					} else
11588 						where[idx + 3] = 255; // a
11589 					idx += 4;
11590 					offset += 3;
11591 				}
11592 
11593 				offset = offsetStart - itemsPerLine;
11594 			}
11595 		}
11596 
11597 		void setFromRgbaBytes(in ubyte[] what) {
11598 			assert(what.length == this.width * this.height * 4);
11599 
11600 			auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4);
11601 			int idx = 0;
11602 			int offset = itemsPerLine * (height - 1);
11603 			// remember, bmps are upside down
11604 			for(int y = height - 1; y >= 0; y--) {
11605 				auto offsetStart = offset;
11606 				for(int x = 0; x < width; x++) {
11607 					if(enableAlpha) {
11608 						auto a = what[idx + 3];
11609 
11610 						rawData[offset + 2] = (a * what[idx + 0]) / 255; // r
11611 						rawData[offset + 1] = (a * what[idx + 1]) / 255; // g
11612 						rawData[offset + 0] = (a * what[idx + 2]) / 255; // b
11613 						rawData[offset + 3] = a; // a
11614 						//premultiplyBgra(rawData[offset .. offset + 4]);
11615 						offset++;
11616 					} else {
11617 						rawData[offset + 2] = what[idx + 0]; // r
11618 						rawData[offset + 1] = what[idx + 1]; // g
11619 						rawData[offset + 0] = what[idx + 2]; // b
11620 					}
11621 					idx += 4;
11622 					offset += 3;
11623 				}
11624 
11625 				offset = offsetStart - itemsPerLine;
11626 			}
11627 		}
11628 
11629 
11630 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
11631 			BITMAPINFO infoheader;
11632 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
11633 			infoheader.bmiHeader.biWidth = width;
11634 			infoheader.bmiHeader.biHeight = height;
11635 			infoheader.bmiHeader.biPlanes = 1;
11636 			infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24;
11637 			infoheader.bmiHeader.biCompression = BI_RGB;
11638 
11639 			handle = CreateDIBSection(
11640 				null,
11641 				&infoheader,
11642 				DIB_RGB_COLORS,
11643 				cast(void**) &rawData,
11644 				null,
11645 				0);
11646 			if(handle is null)
11647 				throw new WindowsApiException("create image failed");
11648 
11649 		}
11650 
11651 		void dispose() {
11652 			DeleteObject(handle);
11653 		}
11654 	}
11655 
11656 	enum KEY_ESCAPE = 27;
11657 }
11658 version(X11) {
11659 	/// This is the default font used. You might change this before doing anything else with
11660 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
11661 	/// for cross-platform compatibility.
11662 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
11663 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
11664 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
11665 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
11666 
11667 	alias int delegate(XEvent) NativeEventHandler;
11668 	alias Window NativeWindowHandle;
11669 
11670 	enum KEY_ESCAPE = 9;
11671 
11672 	mixin template NativeScreenPainterImplementation() {
11673 		Display* display;
11674 		Drawable d;
11675 		Drawable destiny;
11676 
11677 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
11678 		GC gc;
11679 
11680 		__gshared bool fontAttempted;
11681 
11682 		__gshared XFontStruct* defaultfont;
11683 		__gshared XFontSet defaultfontset;
11684 
11685 		XFontStruct* font;
11686 		XFontSet fontset;
11687 
11688 		void create(NativeWindowHandle window) {
11689 			this.display = XDisplayConnection.get();
11690 
11691 			Drawable buffer = None;
11692 			if(auto sw = cast(SimpleWindow) this.window) {
11693 				buffer = sw.impl.buffer;
11694 				this.destiny = cast(Drawable) window;
11695 			} else {
11696 				buffer = cast(Drawable) window;
11697 				this.destiny = None;
11698 			}
11699 
11700 			this.d = cast(Drawable) buffer;
11701 
11702 			auto dgc = DefaultGC(display, DefaultScreen(display));
11703 
11704 			this.gc = XCreateGC(display, d, 0, null);
11705 
11706 			XCopyGC(display, dgc, 0xffffffff, this.gc);
11707 
11708 			ensureDefaultFontLoaded();
11709 
11710 			font = defaultfont;
11711 			fontset = defaultfontset;
11712 
11713 			if(font) {
11714 				XSetFont(display, gc, font.fid);
11715 			}
11716 		}
11717 
11718 		static void ensureDefaultFontLoaded() {
11719 			if(!fontAttempted) {
11720 				auto display = XDisplayConnection.get;
11721 				auto font = XLoadQueryFont(display, xfontstr.ptr);
11722 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
11723 				if(font is null) {
11724 					xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*";
11725 					font = XLoadQueryFont(display, xfontstr.ptr);
11726 				}
11727 
11728 				char** lol;
11729 				int lol2;
11730 				char* lol3;
11731 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
11732 
11733 				fontAttempted = true;
11734 
11735 				defaultfont = font;
11736 				defaultfontset = fontset;
11737 			}
11738 		}
11739 
11740 		arsd.color.Rectangle _clipRectangle;
11741 		void setClipRectangle(int x, int y, int width, int height) {
11742 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
11743 			if(width == 0 || height == 0) {
11744 				XSetClipMask(display, gc, None);
11745 
11746 				version(with_xft) {
11747 					if(xftFont is null || xftDraw is null)
11748 						return;
11749 					XftDrawSetClip(xftDraw, null);
11750 				}
11751 			} else {
11752 				XRectangle[1] rects;
11753 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
11754 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
11755 
11756 				version(with_xft) {
11757 					if(xftFont is null || xftDraw is null)
11758 						return;
11759 					XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1);
11760 				}
11761 			}
11762 		}
11763 
11764 		version(with_xft) {
11765 			XftFont* xftFont;
11766 			XftDraw* xftDraw;
11767 
11768 			XftColor xftColor;
11769 
11770 			void updateXftColor() {
11771 				if(xftFont is null)
11772 					return;
11773 
11774 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
11775 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
11776 
11777 				XftColorAllocValue(
11778 					display,
11779 					DefaultVisual(display, DefaultScreen(display)),
11780 					DefaultColormap(display, 0),
11781 					&colorIn,
11782 					&xftColor
11783 				);
11784 			}
11785 		}
11786 
11787 		void setFont(OperatingSystemFont font) {
11788 			version(with_xft) {
11789 				if(font && font.isXft && font.xftFont)
11790 					this.xftFont = font.xftFont;
11791 				else
11792 					this.xftFont = null;
11793 
11794 				if(this.xftFont) {
11795 					if(xftDraw is null) {
11796 						xftDraw = XftDrawCreate(
11797 							display,
11798 							d,
11799 							DefaultVisual(display, DefaultScreen(display)),
11800 							DefaultColormap(display, 0)
11801 						);
11802 
11803 						updateXftColor();
11804 					}
11805 
11806 					return;
11807 				}
11808 			}
11809 
11810 			if(font && font.font) {
11811 				this.font = font.font;
11812 				this.fontset = font.fontset;
11813 				XSetFont(display, gc, font.font.fid);
11814 			} else {
11815 				this.font = defaultfont;
11816 				this.fontset = defaultfontset;
11817 			}
11818 
11819 		}
11820 
11821 		private Picture xrenderPicturePainter;
11822 
11823 		void dispose() {
11824 			this.rasterOp = RasterOp.normal;
11825 
11826 			if(xrenderPicturePainter) {
11827 				XRenderFreePicture(display, xrenderPicturePainter);
11828 				xrenderPicturePainter = None;
11829 			}
11830 
11831 			// FIXME: this.window.width/height is probably wrong
11832 
11833 			// src x,y     then dest x, y
11834 			if(destiny != None) {
11835 				XSetClipMask(display, gc, None);
11836 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
11837 			}
11838 
11839 			XFreeGC(display, gc);
11840 
11841 			version(with_xft)
11842 			if(xftDraw) {
11843 				XftDrawDestroy(xftDraw);
11844 				xftDraw = null;
11845 			}
11846 
11847 			/+
11848 			// this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource.
11849 			if(font && font !is defaultfont) {
11850 				XFreeFont(display, font);
11851 				font = null;
11852 			}
11853 			if(fontset && fontset !is defaultfontset) {
11854 				XFreeFontSet(display, fontset);
11855 				fontset = null;
11856 			}
11857 			+/
11858 			XFlush(display);
11859 
11860 			if(window.paintingFinishedDg !is null)
11861 				window.paintingFinishedDg()();
11862 		}
11863 
11864 		bool backgroundIsNotTransparent = true;
11865 		bool foregroundIsNotTransparent = true;
11866 
11867 		bool _penInitialized = false;
11868 		Pen _activePen;
11869 
11870 		Color _outlineColor;
11871 		Color _fillColor;
11872 
11873 		@property void pen(Pen p) {
11874 			if(_penInitialized && p == _activePen) {
11875 				return;
11876 			}
11877 			_penInitialized = true;
11878 			_activePen = p;
11879 			_outlineColor = p.color;
11880 
11881 			int style;
11882 
11883 			byte dashLength;
11884 
11885 			final switch(p.style) {
11886 				case Pen.Style.Solid:
11887 					style = 0 /*LineSolid*/;
11888 				break;
11889 				case Pen.Style.Dashed:
11890 					style = 1 /*LineOnOffDash*/;
11891 					dashLength = 4;
11892 				break;
11893 				case Pen.Style.Dotted:
11894 					style = 1 /*LineOnOffDash*/;
11895 					dashLength = 1;
11896 				break;
11897 			}
11898 
11899 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
11900 			if(dashLength)
11901 				XSetDashes(display, gc, 0, &dashLength, 1);
11902 
11903 			if(p.color.a == 0) {
11904 				foregroundIsNotTransparent = false;
11905 				return;
11906 			}
11907 
11908 			foregroundIsNotTransparent = true;
11909 
11910 			XSetForeground(display, gc, colorToX(p.color, display));
11911 
11912 			version(with_xft)
11913 				updateXftColor();
11914 		}
11915 
11916 		RasterOp _currentRasterOp;
11917 		bool _currentRasterOpInitialized = false;
11918 		@property void rasterOp(RasterOp op) {
11919 			if(_currentRasterOpInitialized && _currentRasterOp == op)
11920 				return;
11921 			_currentRasterOp = op;
11922 			_currentRasterOpInitialized = true;
11923 			int mode;
11924 			final switch(op) {
11925 				case RasterOp.normal:
11926 					mode = GXcopy;
11927 				break;
11928 				case RasterOp.xor:
11929 					mode = GXxor;
11930 				break;
11931 			}
11932 			XSetFunction(display, gc, mode);
11933 		}
11934 
11935 
11936 		bool _fillColorInitialized = false;
11937 
11938 		@property void fillColor(Color c) {
11939 			if(_fillColorInitialized && _fillColor == c)
11940 				return; // already good, no need to waste time calling it
11941 			_fillColor = c;
11942 			_fillColorInitialized = true;
11943 			if(c.a == 0) {
11944 				backgroundIsNotTransparent = false;
11945 				return;
11946 			}
11947 
11948 			backgroundIsNotTransparent = true;
11949 
11950 			XSetBackground(display, gc, colorToX(c, display));
11951 
11952 		}
11953 
11954 		void swapColors() {
11955 			auto tmp = _fillColor;
11956 			fillColor = _outlineColor;
11957 			auto newPen = _activePen;
11958 			newPen.color = tmp;
11959 			pen(newPen);
11960 		}
11961 
11962 		uint colorToX(Color c, Display* display) {
11963 			auto visual = DefaultVisual(display, DefaultScreen(display));
11964 			import core.bitop;
11965 			uint color = 0;
11966 			{
11967 			auto startBit = bsf(visual.red_mask);
11968 			auto lastBit = bsr(visual.red_mask);
11969 			auto r = cast(uint) c.r;
11970 			r >>= 7 - (lastBit - startBit);
11971 			r <<= startBit;
11972 			color |= r;
11973 			}
11974 			{
11975 			auto startBit = bsf(visual.green_mask);
11976 			auto lastBit = bsr(visual.green_mask);
11977 			auto g = cast(uint) c.g;
11978 			g >>= 7 - (lastBit - startBit);
11979 			g <<= startBit;
11980 			color |= g;
11981 			}
11982 			{
11983 			auto startBit = bsf(visual.blue_mask);
11984 			auto lastBit = bsr(visual.blue_mask);
11985 			auto b = cast(uint) c.b;
11986 			b >>= 7 - (lastBit - startBit);
11987 			b <<= startBit;
11988 			color |= b;
11989 			}
11990 
11991 
11992 
11993 			return color;
11994 		}
11995 
11996 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
11997 			// source x, source y
11998 			if(ix >= i.width) return;
11999 			if(iy >= i.height) return;
12000 			if(ix + w > i.width) w = i.width - ix;
12001 			if(iy + h > i.height) h = i.height - iy;
12002 			if(i.usingXshm)
12003 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
12004 			else
12005 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
12006 		}
12007 
12008 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
12009 			if(s.enableAlpha) {
12010 				// the Sprite must be created first, meaning if we're here, XRender is already loaded
12011 				if(this.xrenderPicturePainter == None) {
12012 					XRenderPictureAttributes attrs;
12013 					// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
12014 					xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
12015 				}
12016 
12017 				XRenderComposite(
12018 					display,
12019 					3, // PicOpOver
12020 					s.xrenderPicture,
12021 					None,
12022 					this.xrenderPicturePainter,
12023 					ix,
12024 					iy,
12025 					0,
12026 					0,
12027 					x,
12028 					y,
12029 					w ? w : s.width,
12030 					h ? h : s.height
12031 				);
12032 			} else {
12033 				XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
12034 			}
12035 		}
12036 
12037 		int fontHeight() {
12038 			version(with_xft)
12039 				if(xftFont !is null)
12040 					return xftFont.height;
12041 			if(font)
12042 				return font.max_bounds.ascent + font.max_bounds.descent;
12043 			return 12; // pretty common default...
12044 		}
12045 
12046 		int textWidth(in char[] line) {
12047 			version(with_xft)
12048 			if(xftFont) {
12049 				if(line.length == 0)
12050 					return 0;
12051 				XGlyphInfo extents;
12052 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
12053 				return extents.width;
12054 			}
12055 
12056 			if(fontset) {
12057 				if(line.length == 0)
12058 					return 0;
12059 				XRectangle rect;
12060 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
12061 
12062 				return rect.width;
12063 			}
12064 
12065 			if(font)
12066 				// FIXME: unicode
12067 				return XTextWidth( font, line.ptr, cast(int) line.length);
12068 			else
12069 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
12070 		}
12071 
12072 		Size textSize(in char[] text) {
12073 			auto maxWidth = 0;
12074 			auto lineHeight = fontHeight;
12075 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
12076 			foreach(line; text.split('\n')) {
12077 				int textWidth = this.textWidth(line);
12078 				if(textWidth > maxWidth)
12079 					maxWidth = textWidth;
12080 				h += lineHeight + 4;
12081 			}
12082 			return Size(maxWidth, h);
12083 		}
12084 
12085 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
12086 			const(char)[] text;
12087 			version(with_xft)
12088 			if(xftFont) {
12089 				text = originalText;
12090 				goto loaded;
12091 			}
12092 
12093 			if(fontset)
12094 				text = originalText;
12095 			else {
12096 				text.reserve(originalText.length);
12097 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
12098 				// then strip the rest so there isn't garbage
12099 				foreach(dchar ch; originalText)
12100 					if(ch < 256)
12101 						text ~= cast(ubyte) ch;
12102 					else
12103 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
12104 			}
12105 			loaded:
12106 			if(text.length == 0)
12107 				return;
12108 
12109 			// FIXME: should we clip it to the bounding box?
12110 			int textHeight = fontHeight;
12111 
12112 			auto lines = text.split('\n');
12113 
12114 			const lineHeight = textHeight;
12115 			textHeight *= lines.length;
12116 
12117 			int cy = y;
12118 
12119 			if(alignment & TextAlignment.VerticalBottom) {
12120 				assert(y2);
12121 				auto h = y2 - y;
12122 				if(h > textHeight) {
12123 					cy += h - textHeight;
12124 					cy -= lineHeight / 2;
12125 				}
12126 			} else if(alignment & TextAlignment.VerticalCenter) {
12127 				assert(y2);
12128 				auto h = y2 - y;
12129 				if(textHeight < h) {
12130 					cy += (h - textHeight) / 2;
12131 					//cy -= lineHeight / 4;
12132 				}
12133 			}
12134 
12135 			foreach(line; text.split('\n')) {
12136 				int textWidth = this.textWidth(line);
12137 
12138 				int px = x, py = cy;
12139 
12140 				if(alignment & TextAlignment.Center) {
12141 					assert(x2);
12142 					auto w = x2 - x;
12143 					if(w > textWidth)
12144 						px += (w - textWidth) / 2;
12145 				} else if(alignment & TextAlignment.Right) {
12146 					assert(x2);
12147 					auto pos = x2 - textWidth;
12148 					if(pos > x)
12149 						px = pos;
12150 				}
12151 
12152 				version(with_xft)
12153 				if(xftFont) {
12154 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
12155 
12156 					goto carry_on;
12157 				}
12158 
12159 				if(fontset)
12160 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12161 				else
12162 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
12163 				carry_on:
12164 				cy += lineHeight + 4;
12165 			}
12166 		}
12167 
12168 		void drawPixel(int x, int y) {
12169 			XDrawPoint(display, d, gc, x, y);
12170 		}
12171 
12172 		// The basic shapes, outlined
12173 
12174 		void drawLine(int x1, int y1, int x2, int y2) {
12175 			if(foregroundIsNotTransparent)
12176 				XDrawLine(display, d, gc, x1, y1, x2, y2);
12177 		}
12178 
12179 		void drawRectangle(int x, int y, int width, int height) {
12180 			if(backgroundIsNotTransparent) {
12181 				swapColors();
12182 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
12183 				swapColors();
12184 			}
12185 			// 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
12186 			if(foregroundIsNotTransparent)
12187 				XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2);
12188 		}
12189 
12190 		/// Arguments are the points of the bounding rectangle
12191 		void drawEllipse(int x1, int y1, int x2, int y2) {
12192 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
12193 		}
12194 
12195 		// NOTE: start and finish are in units of degrees * 64
12196 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
12197 			if(backgroundIsNotTransparent) {
12198 				swapColors();
12199 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
12200 				swapColors();
12201 			}
12202 			if(foregroundIsNotTransparent) {
12203 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
12204 				// Windows draws the straight lines on the edges too so FIXME sort of
12205 			}
12206 		}
12207 
12208 		void drawPolygon(Point[] vertexes) {
12209 			XPoint[16] pointsBuffer;
12210 			XPoint[] points;
12211 			if(vertexes.length <= pointsBuffer.length)
12212 				points = pointsBuffer[0 .. vertexes.length];
12213 			else
12214 				points.length = vertexes.length;
12215 
12216 			foreach(i, p; vertexes) {
12217 				points[i].x = cast(short) p.x;
12218 				points[i].y = cast(short) p.y;
12219 			}
12220 
12221 			if(backgroundIsNotTransparent) {
12222 				swapColors();
12223 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
12224 				swapColors();
12225 			}
12226 			if(foregroundIsNotTransparent) {
12227 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
12228 			}
12229 		}
12230 	}
12231 
12232 	/* XRender { */
12233 
12234 	struct XRenderColor {
12235 		ushort red;
12236 		ushort green;
12237 		ushort blue;
12238 		ushort alpha;
12239 	}
12240 
12241 	alias Picture = XID;
12242 	alias PictFormat = XID;
12243 
12244 	struct XGlyphInfo {
12245 		ushort width;
12246 		ushort height;
12247 		short x;
12248 		short y;
12249 		short xOff;
12250 		short yOff;
12251 	}
12252 
12253 struct XRenderDirectFormat {
12254     short   red;
12255     short   redMask;
12256     short   green;
12257     short   greenMask;
12258     short   blue;
12259     short   blueMask;
12260     short   alpha;
12261     short   alphaMask;
12262 }
12263 
12264 struct XRenderPictFormat {
12265     PictFormat		id;
12266     int			type;
12267     int			depth;
12268     XRenderDirectFormat	direct;
12269     Colormap		colormap;
12270 }
12271 
12272 enum PictFormatID	=   (1 << 0);
12273 enum PictFormatType	=   (1 << 1);
12274 enum PictFormatDepth	=   (1 << 2);
12275 enum PictFormatRed	=   (1 << 3);
12276 enum PictFormatRedMask  =(1 << 4);
12277 enum PictFormatGreen	=   (1 << 5);
12278 enum PictFormatGreenMask=(1 << 6);
12279 enum PictFormatBlue	=   (1 << 7);
12280 enum PictFormatBlueMask =(1 << 8);
12281 enum PictFormatAlpha	=   (1 << 9);
12282 enum PictFormatAlphaMask=(1 << 10);
12283 enum PictFormatColormap =(1 << 11);
12284 
12285 struct XRenderPictureAttributes {
12286 	int 		repeat;
12287 	Picture		alpha_map;
12288 	int			alpha_x_origin;
12289 	int			alpha_y_origin;
12290 	int			clip_x_origin;
12291 	int			clip_y_origin;
12292 	Pixmap		clip_mask;
12293 	Bool		graphics_exposures;
12294 	int			subwindow_mode;
12295 	int			poly_edge;
12296 	int			poly_mode;
12297 	Atom		dither;
12298 	Bool		component_alpha;
12299 }
12300 
12301 alias int XFixed;
12302 
12303 struct XPointFixed {
12304     XFixed  x, y;
12305 }
12306 
12307 struct XCircle {
12308     XFixed x;
12309     XFixed y;
12310     XFixed radius;
12311 }
12312 
12313 struct XTransform {
12314     XFixed[3][3]  matrix;
12315 }
12316 
12317 struct XFilters {
12318     int	    nfilter;
12319     char    **filter;
12320     int	    nalias;
12321     short   *alias_;
12322 }
12323 
12324 struct XIndexValue {
12325     c_ulong    pixel;
12326     ushort   red, green, blue, alpha;
12327 }
12328 
12329 struct XAnimCursor {
12330     Cursor	    cursor;
12331     c_ulong   delay;
12332 }
12333 
12334 struct XLinearGradient {
12335     XPointFixed p1;
12336     XPointFixed p2;
12337 }
12338 
12339 struct XRadialGradient {
12340     XCircle inner;
12341     XCircle outer;
12342 }
12343 
12344 struct XConicalGradient {
12345     XPointFixed center;
12346     XFixed angle; /* in degrees */
12347 }
12348 
12349 enum PictStandardARGB32  = 0;
12350 enum PictStandardRGB24   = 1;
12351 enum PictStandardA8	 =  2;
12352 enum PictStandardA4	 =  3;
12353 enum PictStandardA1	 =  4;
12354 enum PictStandardNUM	 =  5;
12355 
12356 interface XRender {
12357 extern(C) @nogc:
12358 
12359 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
12360 
12361 	Status XRenderQueryVersion (Display *dpy,
12362 			int     *major_versionp,
12363 			int     *minor_versionp);
12364 
12365 	Status XRenderQueryFormats (Display *dpy);
12366 
12367 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
12368 
12369 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
12370 
12371 	XRenderPictFormat *
12372 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
12373 
12374 	XRenderPictFormat *
12375 		XRenderFindFormat (Display			*dpy,
12376 				c_ulong		mask,
12377 				const XRenderPictFormat	*templ,
12378 				int				count);
12379 	XRenderPictFormat *
12380 		XRenderFindStandardFormat (Display		*dpy,
12381 				int			format);
12382 
12383 	XIndexValue *
12384 		XRenderQueryPictIndexValues(Display			*dpy,
12385 				const XRenderPictFormat	*format,
12386 				int				*num);
12387 
12388 	Picture XRenderCreatePicture(
12389 		Display *dpy,
12390 		Drawable drawable,
12391 		const XRenderPictFormat *format,
12392 		c_ulong valuemask,
12393 		const XRenderPictureAttributes *attributes);
12394 
12395 	void XRenderChangePicture (Display				*dpy,
12396 				Picture				picture,
12397 				c_ulong			valuemask,
12398 				const XRenderPictureAttributes  *attributes);
12399 
12400 	void
12401 		XRenderSetPictureClipRectangles (Display	    *dpy,
12402 				Picture	    picture,
12403 				int		    xOrigin,
12404 				int		    yOrigin,
12405 				const XRectangle *rects,
12406 				int		    n);
12407 
12408 	void
12409 		XRenderSetPictureClipRegion (Display	    *dpy,
12410 				Picture	    picture,
12411 				Region	    r);
12412 
12413 	void
12414 		XRenderSetPictureTransform (Display	    *dpy,
12415 				Picture	    picture,
12416 				XTransform	    *transform);
12417 
12418 	void
12419 		XRenderFreePicture (Display                   *dpy,
12420 				Picture                   picture);
12421 
12422 	void
12423 		XRenderComposite (Display   *dpy,
12424 				int	    op,
12425 				Picture   src,
12426 				Picture   mask,
12427 				Picture   dst,
12428 				int	    src_x,
12429 				int	    src_y,
12430 				int	    mask_x,
12431 				int	    mask_y,
12432 				int	    dst_x,
12433 				int	    dst_y,
12434 				uint	width,
12435 				uint	height);
12436 
12437 
12438 	Picture XRenderCreateSolidFill (Display *dpy,
12439 			const XRenderColor *color);
12440 
12441 	Picture XRenderCreateLinearGradient (Display *dpy,
12442 			const XLinearGradient *gradient,
12443 			const XFixed *stops,
12444 			const XRenderColor *colors,
12445 			int nstops);
12446 
12447 	Picture XRenderCreateRadialGradient (Display *dpy,
12448 			const XRadialGradient *gradient,
12449 			const XFixed *stops,
12450 			const XRenderColor *colors,
12451 			int nstops);
12452 
12453 	Picture XRenderCreateConicalGradient (Display *dpy,
12454 			const XConicalGradient *gradient,
12455 			const XFixed *stops,
12456 			const XRenderColor *colors,
12457 			int nstops);
12458 
12459 
12460 
12461 	Cursor
12462 		XRenderCreateCursor (Display	    *dpy,
12463 				Picture	    source,
12464 				uint   x,
12465 				uint   y);
12466 
12467 	XFilters *
12468 		XRenderQueryFilters (Display *dpy, Drawable drawable);
12469 
12470 	void
12471 		XRenderSetPictureFilter (Display    *dpy,
12472 				Picture    picture,
12473 				const char *filter,
12474 				XFixed	    *params,
12475 				int	    nparams);
12476 
12477 	Cursor
12478 		XRenderCreateAnimCursor (Display	*dpy,
12479 				int		ncursor,
12480 				XAnimCursor	*cursors);
12481 }
12482 
12483 __gshared bool XRenderLibrarySuccessfullyLoaded = true;
12484 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary;
12485 
12486 	/* XRender } */
12487 
12488 	/* Xrandr { */
12489 
12490 struct XRRMonitorInfo {
12491     Atom name;
12492     Bool primary;
12493     Bool automatic;
12494     int noutput;
12495     int x;
12496     int y;
12497     int width;
12498     int height;
12499     int mwidth;
12500     int mheight;
12501     /*RROutput*/ void *outputs;
12502 }
12503 
12504 struct XRRScreenChangeNotifyEvent {
12505     int type;                   /* event base */
12506     c_ulong serial;       /* # of last request processed by server */
12507     Bool send_event;            /* true if this came from a SendEvent request */
12508     Display *display;           /* Display the event was read from */
12509     Window window;              /* window which selected for this event */
12510     Window root;                /* Root window for changed screen */
12511     Time timestamp;             /* when the screen change occurred */
12512     Time config_timestamp;      /* when the last configuration change */
12513     ushort/*SizeID*/ size_index;
12514     ushort/*SubpixelOrder*/ subpixel_order;
12515     ushort/*Rotation*/ rotation;
12516     int width;
12517     int height;
12518     int mwidth;
12519     int mheight;
12520 }
12521 
12522 enum RRScreenChangeNotify = 0;
12523 
12524 enum RRScreenChangeNotifyMask = 1;
12525 
12526 __gshared int xrrEventBase = -1;
12527 
12528 
12529 interface XRandr {
12530 extern(C) @nogc:
12531 	Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return);
12532 	Status XRRQueryVersion (Display *dpy, int     *major_version_return, int     *minor_version_return);
12533 
12534 	XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
12535 	void XRRFreeMonitors(XRRMonitorInfo *monitors);
12536 
12537 	void XRRSelectInput(Display *dpy, Window window, int mask);
12538 }
12539 
12540 __gshared bool XRandrLibrarySuccessfullyLoaded = true;
12541 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary;
12542 	/* Xrandr } */
12543 
12544 	/* Xft { */
12545 
12546 	// actually freetype
12547 	alias void FT_Face;
12548 
12549 	// actually fontconfig
12550 	private alias FcBool = int;
12551 	alias void FcCharSet;
12552 	alias void FcPattern;
12553 	alias void FcResult;
12554 	enum FcEndian { FcEndianBig, FcEndianLittle }
12555 	struct FcFontSet {
12556 		int nfont;
12557 		int sfont;
12558 		FcPattern** fonts;
12559 	}
12560 
12561 	// actually XRegion
12562 	struct BOX {
12563 		short x1, x2, y1, y2;
12564 	}
12565 	struct _XRegion {
12566 		c_long size;
12567 		c_long numRects;
12568 		BOX* rects;
12569 		BOX extents;
12570 	}
12571 
12572 	alias Region = _XRegion*;
12573 
12574 	// ok actually Xft
12575 
12576 	struct XftFontInfo;
12577 
12578 	struct XftFont {
12579 		int         ascent;
12580 		int         descent;
12581 		int         height;
12582 		int         max_advance_width;
12583 		FcCharSet*  charset;
12584 		FcPattern*  pattern;
12585 	}
12586 
12587 	struct XftDraw;
12588 
12589 	struct XftColor {
12590 		c_ulong pixel;
12591 		XRenderColor color;
12592 	}
12593 
12594 	struct XftCharSpec {
12595 		dchar           ucs4;
12596 		short           x;
12597 		short           y;
12598 	}
12599 
12600 	struct XftCharFontSpec {
12601 		XftFont         *font;
12602 		dchar           ucs4;
12603 		short           x;
12604 		short           y;
12605 	}
12606 
12607 	struct XftGlyphSpec {
12608 		uint            glyph;
12609 		short           x;
12610 		short           y;
12611 	}
12612 
12613 	struct XftGlyphFontSpec {
12614 		XftFont         *font;
12615 		uint            glyph;
12616 		short           x;
12617 		short           y;
12618 	}
12619 
12620 	interface Xft {
12621 	extern(C) @nogc pure:
12622 
12623 	Bool XftColorAllocName (Display  *dpy,
12624 				const Visual   *visual,
12625 				Colormap cmap,
12626 				const char     *name,
12627 				XftColor *result);
12628 
12629 	Bool XftColorAllocValue (Display         *dpy,
12630 				Visual          *visual,
12631 				Colormap        cmap,
12632 				const XRenderColor    *color,
12633 				XftColor        *result);
12634 
12635 	void XftColorFree (Display   *dpy,
12636 				Visual    *visual,
12637 				Colormap  cmap,
12638 				XftColor  *color);
12639 
12640 	Bool XftDefaultHasRender (Display *dpy);
12641 
12642 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
12643 
12644 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
12645 
12646 	XftDraw * XftDrawCreate (Display   *dpy,
12647 		       Drawable  drawable,
12648 		       Visual    *visual,
12649 		       Colormap  colormap);
12650 
12651 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
12652 			     Pixmap   bitmap);
12653 
12654 	XftDraw * XftDrawCreateAlpha (Display *dpy,
12655 			    Pixmap  pixmap,
12656 			    int     depth);
12657 
12658 	void XftDrawChange (XftDraw  *draw,
12659 		       Drawable drawable);
12660 
12661 	Display * XftDrawDisplay (XftDraw *draw);
12662 
12663 	Drawable XftDrawDrawable (XftDraw *draw);
12664 
12665 	Colormap XftDrawColormap (XftDraw *draw);
12666 
12667 	Visual * XftDrawVisual (XftDraw *draw);
12668 
12669 	void XftDrawDestroy (XftDraw *draw);
12670 
12671 	Picture XftDrawPicture (XftDraw *draw);
12672 
12673 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
12674 
12675 	void XftDrawGlyphs (XftDraw          *draw,
12676 				const XftColor *color,
12677 				XftFont          *pub,
12678 				int              x,
12679 				int              y,
12680 				const uint  *glyphs,
12681 				int              nglyphs);
12682 
12683 	void XftDrawString8 (XftDraw             *draw,
12684 				const XftColor    *color,
12685 				XftFont             *pub,
12686 				int                 x,
12687 				int                 y,
12688 				const char     *string,
12689 				int                 len);
12690 
12691 	void XftDrawString16 (XftDraw            *draw,
12692 				const XftColor   *color,
12693 				XftFont            *pub,
12694 				int                x,
12695 				int                y,
12696 				const wchar   *string,
12697 				int                len);
12698 
12699 	void XftDrawString32 (XftDraw            *draw,
12700 				const XftColor   *color,
12701 				XftFont            *pub,
12702 				int                x,
12703 				int                y,
12704 				const dchar   *string,
12705 				int                len);
12706 
12707 	void XftDrawStringUtf8 (XftDraw          *draw,
12708 				const XftColor *color,
12709 				XftFont          *pub,
12710 				int              x,
12711 				int              y,
12712 				const char  *string,
12713 				int              len);
12714 	void XftDrawStringUtf16 (XftDraw             *draw,
12715 				const XftColor    *color,
12716 				XftFont             *pub,
12717 				int                 x,
12718 				int                 y,
12719 				const char     *string,
12720 				FcEndian            endian,
12721 				int                 len);
12722 
12723 	void XftDrawCharSpec (XftDraw                *draw,
12724 				const XftColor       *color,
12725 				XftFont                *pub,
12726 				const XftCharSpec    *chars,
12727 				int                    len);
12728 
12729 	void XftDrawCharFontSpec (XftDraw                    *draw,
12730 				const XftColor           *color,
12731 				const XftCharFontSpec    *chars,
12732 				int                        len);
12733 
12734 	void XftDrawGlyphSpec (XftDraw               *draw,
12735 				const XftColor      *color,
12736 				XftFont               *pub,
12737 				const XftGlyphSpec  *glyphs,
12738 				int                   len);
12739 
12740 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
12741 				const XftColor          *color,
12742 				const XftGlyphFontSpec  *glyphs,
12743 				int                       len);
12744 
12745 	void XftDrawRect (XftDraw            *draw,
12746 				const XftColor   *color,
12747 				int                x,
12748 				int                y,
12749 				uint       width,
12750 				uint       height);
12751 
12752 	Bool XftDrawSetClip (XftDraw     *draw,
12753 				Region      r);
12754 
12755 
12756 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
12757 				int                   xOrigin,
12758 				int                   yOrigin,
12759 				const XRectangle    *rects,
12760 				int                   n);
12761 
12762 	void XftDrawSetSubwindowMode (XftDraw    *draw,
12763 				int        mode);
12764 
12765 	void XftGlyphExtents (Display            *dpy,
12766 				XftFont            *pub,
12767 				const uint    *glyphs,
12768 				int                nglyphs,
12769 				XGlyphInfo         *extents);
12770 
12771 	void XftTextExtents8 (Display            *dpy,
12772 				XftFont            *pub,
12773 				const char    *string,
12774 				int                len,
12775 				XGlyphInfo         *extents);
12776 
12777 	void XftTextExtents16 (Display           *dpy,
12778 				XftFont           *pub,
12779 				const wchar  *string,
12780 				int               len,
12781 				XGlyphInfo        *extents);
12782 
12783 	void XftTextExtents32 (Display           *dpy,
12784 				XftFont           *pub,
12785 				const dchar  *string,
12786 				int               len,
12787 				XGlyphInfo        *extents);
12788 
12789 	void XftTextExtentsUtf8 (Display         *dpy,
12790 				XftFont         *pub,
12791 				const char *string,
12792 				int             len,
12793 				XGlyphInfo      *extents);
12794 
12795 	void XftTextExtentsUtf16 (Display            *dpy,
12796 				XftFont            *pub,
12797 				const char    *string,
12798 				FcEndian           endian,
12799 				int                len,
12800 				XGlyphInfo         *extents);
12801 
12802 	FcPattern * XftFontMatch (Display           *dpy,
12803 				int               screen,
12804 				const FcPattern *pattern,
12805 				FcResult          *result);
12806 
12807 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
12808 
12809 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
12810 
12811 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
12812 
12813 	FT_Face XftLockFace (XftFont *pub);
12814 
12815 	void XftUnlockFace (XftFont *pub);
12816 
12817 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
12818 
12819 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
12820 
12821 	dchar XftFontInfoHash (const XftFontInfo *fi);
12822 
12823 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
12824 
12825 	XftFont * XftFontOpenInfo (Display        *dpy,
12826 				FcPattern      *pattern,
12827 				XftFontInfo    *fi);
12828 
12829 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
12830 
12831 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
12832 
12833 	void XftFontClose (Display *dpy, XftFont *pub);
12834 
12835 	FcBool XftInitFtLibrary();
12836 	void XftFontLoadGlyphs (Display          *dpy,
12837 				XftFont          *pub,
12838 				FcBool           need_bitmaps,
12839 				const uint  *glyphs,
12840 				int              nglyph);
12841 
12842 	void XftFontUnloadGlyphs (Display            *dpy,
12843 				XftFont            *pub,
12844 				const uint    *glyphs,
12845 				int                nglyph);
12846 
12847 	FcBool XftFontCheckGlyph (Display  *dpy,
12848 				XftFont  *pub,
12849 				FcBool   need_bitmaps,
12850 				uint  glyph,
12851 				uint  *missing,
12852 				int      *nmissing);
12853 
12854 	FcBool XftCharExists (Display      *dpy,
12855 				XftFont      *pub,
12856 				dchar    ucs4);
12857 
12858 	uint XftCharIndex (Display       *dpy,
12859 				XftFont       *pub,
12860 				dchar      ucs4);
12861 	FcBool XftInit (const char *config);
12862 
12863 	int XftGetVersion ();
12864 
12865 	FcFontSet * XftListFonts (Display   *dpy,
12866 				int       screen,
12867 				...);
12868 
12869 	FcPattern *XftNameParse (const char *name);
12870 
12871 	void XftGlyphRender (Display         *dpy,
12872 				int             op,
12873 				Picture         src,
12874 				XftFont         *pub,
12875 				Picture         dst,
12876 				int             srcx,
12877 				int             srcy,
12878 				int             x,
12879 				int             y,
12880 				const uint *glyphs,
12881 				int             nglyphs);
12882 
12883 	void XftGlyphSpecRender (Display                 *dpy,
12884 				int                     op,
12885 				Picture                 src,
12886 				XftFont                 *pub,
12887 				Picture                 dst,
12888 				int                     srcx,
12889 				int                     srcy,
12890 				const XftGlyphSpec    *glyphs,
12891 				int                     nglyphs);
12892 
12893 	void XftCharSpecRender (Display              *dpy,
12894 				int                  op,
12895 				Picture              src,
12896 				XftFont              *pub,
12897 				Picture              dst,
12898 				int                  srcx,
12899 				int                  srcy,
12900 				const XftCharSpec  *chars,
12901 				int                  len);
12902 	void XftGlyphFontSpecRender (Display                     *dpy,
12903 				int                         op,
12904 				Picture                     src,
12905 				Picture                     dst,
12906 				int                         srcx,
12907 				int                         srcy,
12908 				const XftGlyphFontSpec    *glyphs,
12909 				int                         nglyphs);
12910 
12911 	void XftCharFontSpecRender (Display                  *dpy,
12912 				int                      op,
12913 				Picture                  src,
12914 				Picture                  dst,
12915 				int                      srcx,
12916 				int                      srcy,
12917 				const XftCharFontSpec  *chars,
12918 				int                      len);
12919 
12920 	void XftTextRender8 (Display         *dpy,
12921 				int             op,
12922 				Picture         src,
12923 				XftFont         *pub,
12924 				Picture         dst,
12925 				int             srcx,
12926 				int             srcy,
12927 				int             x,
12928 				int             y,
12929 				const char *string,
12930 				int             len);
12931 	void XftTextRender16 (Display            *dpy,
12932 				int                op,
12933 				Picture            src,
12934 				XftFont            *pub,
12935 				Picture            dst,
12936 				int                srcx,
12937 				int                srcy,
12938 				int                x,
12939 				int                y,
12940 				const wchar   *string,
12941 				int                len);
12942 
12943 	void XftTextRender16BE (Display          *dpy,
12944 				int              op,
12945 				Picture          src,
12946 				XftFont          *pub,
12947 				Picture          dst,
12948 				int              srcx,
12949 				int              srcy,
12950 				int              x,
12951 				int              y,
12952 				const char  *string,
12953 				int              len);
12954 
12955 	void XftTextRender16LE (Display          *dpy,
12956 				int              op,
12957 				Picture          src,
12958 				XftFont          *pub,
12959 				Picture          dst,
12960 				int              srcx,
12961 				int              srcy,
12962 				int              x,
12963 				int              y,
12964 				const char  *string,
12965 				int              len);
12966 
12967 	void XftTextRender32 (Display            *dpy,
12968 				int                op,
12969 				Picture            src,
12970 				XftFont            *pub,
12971 				Picture            dst,
12972 				int                srcx,
12973 				int                srcy,
12974 				int                x,
12975 				int                y,
12976 				const dchar   *string,
12977 				int                len);
12978 
12979 	void XftTextRender32BE (Display          *dpy,
12980 				int              op,
12981 				Picture          src,
12982 				XftFont          *pub,
12983 				Picture          dst,
12984 				int              srcx,
12985 				int              srcy,
12986 				int              x,
12987 				int              y,
12988 				const char  *string,
12989 				int              len);
12990 
12991 	void XftTextRender32LE (Display          *dpy,
12992 				int              op,
12993 				Picture          src,
12994 				XftFont          *pub,
12995 				Picture          dst,
12996 				int              srcx,
12997 				int              srcy,
12998 				int              x,
12999 				int              y,
13000 				const char  *string,
13001 				int              len);
13002 
13003 	void XftTextRenderUtf8 (Display          *dpy,
13004 				int              op,
13005 				Picture          src,
13006 				XftFont          *pub,
13007 				Picture          dst,
13008 				int              srcx,
13009 				int              srcy,
13010 				int              x,
13011 				int              y,
13012 				const char  *string,
13013 				int              len);
13014 
13015 	void XftTextRenderUtf16 (Display         *dpy,
13016 				int             op,
13017 				Picture         src,
13018 				XftFont         *pub,
13019 				Picture         dst,
13020 				int             srcx,
13021 				int             srcy,
13022 				int             x,
13023 				int             y,
13024 				const char *string,
13025 				FcEndian        endian,
13026 				int             len);
13027 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
13028 
13029 	}
13030 
13031 	interface FontConfig {
13032 	extern(C) @nogc pure:
13033 		int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s);
13034 		void FcFontSetDestroy(FcFontSet*);
13035 		char* FcNameUnparse(const FcPattern *);
13036 	}
13037 
13038 	mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary;
13039 	mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary;
13040 
13041 
13042 	/* Xft } */
13043 
13044 	class XDisconnectException : Exception {
13045 		bool userRequested;
13046 		this(bool userRequested = true) {
13047 			this.userRequested = userRequested;
13048 			super("X disconnected");
13049 		}
13050 	}
13051 
13052 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
13053 	class XDisplayConnection {
13054 		private __gshared Display* display;
13055 		private __gshared XIM xim;
13056 		private __gshared char* displayName;
13057 
13058 		private __gshared int connectionSequence_;
13059 		private __gshared bool isLocal_;
13060 
13061 		/// use this for lazy caching when reconnection
13062 		static int connectionSequenceNumber() { return connectionSequence_; }
13063 
13064 		/++
13065 			Guesses if the connection appears to be local.
13066 
13067 			History:
13068 				Added June 3, 2021
13069 		+/
13070 		static @property bool isLocal() nothrow @trusted @nogc {
13071 			return isLocal_;
13072 		}
13073 
13074 		/// Attempts recreation of state, may require application assistance
13075 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
13076 		/// then call this, and if successful, reenter the loop.
13077 		static void discardAndRecreate(string newDisplayString = null) {
13078 			if(insideXEventLoop)
13079 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
13080 
13081 			// 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
13082 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
13083 
13084 			foreach(handle; chnenhm) {
13085 				handle.discardConnectionState();
13086 			}
13087 
13088 			discardState();
13089 
13090 			if(newDisplayString !is null)
13091 				setDisplayName(newDisplayString);
13092 
13093 			auto display = get();
13094 
13095 			foreach(handle; chnenhm) {
13096 				handle.recreateAfterDisconnect();
13097 			}
13098 		}
13099 
13100 		private __gshared EventMask rootEventMask;
13101 
13102 		/++
13103 			Requests the specified input from the root window on the connection, in addition to any other request.
13104 
13105 			
13106 			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.
13107 
13108 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
13109 		+/
13110 		static void addRootInput(EventMask mask) {
13111 			auto old = rootEventMask;
13112 			rootEventMask |= mask;
13113 			get(); // to ensure display connected
13114 			if(display !is null && rootEventMask != old)
13115 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
13116 		}
13117 
13118 		static void discardState() {
13119 			freeImages();
13120 
13121 			foreach(atomPtr; interredAtoms)
13122 				*atomPtr = 0;
13123 			interredAtoms = null;
13124 			interredAtoms.assumeSafeAppend();
13125 
13126 			ScreenPainterImplementation.fontAttempted = false;
13127 			ScreenPainterImplementation.defaultfont = null;
13128 			ScreenPainterImplementation.defaultfontset = null;
13129 
13130 			Image.impl.xshmQueryCompleted = false;
13131 			Image.impl._xshmAvailable = false;
13132 
13133 			SimpleWindow.nativeMapping = null;
13134 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
13135 			// GlobalHotkeyManager
13136 
13137 			display = null;
13138 			xim = null;
13139 		}
13140 
13141 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
13142 		private static void createXIM () {
13143 			import core.stdc.locale : setlocale, LC_ALL;
13144 			import core.stdc.stdio : stderr, fprintf;
13145 			import core.stdc.stdlib : free;
13146 			import core.stdc.string : strdup;
13147 
13148 			static immutable string[3] mtry = [ null, "@im=local", "@im=" ];
13149 
13150 			auto olocale = strdup(setlocale(LC_ALL, null));
13151 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
13152 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
13153 
13154 			//fprintf(stderr, "opening IM...\n");
13155 			foreach (string s; mtry) {
13156 				if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
13157 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
13158 			}
13159 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
13160 		}
13161 
13162 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
13163 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
13164 		static struct ImgList {
13165 			size_t img; // class; hide it from GC
13166 			ImgList* next;
13167 		}
13168 
13169 		static __gshared ImgList* imglist = null;
13170 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
13171 
13172 		static void registerImage (Image img) {
13173 			if (!imglistLocked && img !is null) {
13174 				import core.stdc.stdlib : malloc;
13175 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
13176 				assert(it !is null); // do proper checks
13177 				it.img = cast(size_t)cast(void*)img;
13178 				it.next = imglist;
13179 				imglist = it;
13180 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
13181 			}
13182 		}
13183 
13184 		static void unregisterImage (Image img) {
13185 			if (!imglistLocked && img !is null) {
13186 				import core.stdc.stdlib : free;
13187 				ImgList* prev = null;
13188 				ImgList* cur = imglist;
13189 				while (cur !is null) {
13190 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
13191 					prev = cur;
13192 					cur = cur.next;
13193 				}
13194 				if (cur !is null) {
13195 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
13196 					free(cur);
13197 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
13198 				} else {
13199 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
13200 				}
13201 			}
13202 		}
13203 
13204 		static void freeImages () { // needed for discardAndRecreate
13205 			imglistLocked = true;
13206 			scope(exit) imglistLocked = false;
13207 			ImgList* cur = imglist;
13208 			ImgList* next = null;
13209 			while (cur !is null) {
13210 				import core.stdc.stdlib : free;
13211 				next = cur.next;
13212 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
13213 				(cast(Image)cast(void*)cur.img).dispose();
13214 				free(cur);
13215 				cur = next;
13216 			}
13217 			imglist = null;
13218 		}
13219 
13220 		/// can be used to override normal handling of display name
13221 		/// from environment and/or command line
13222 		static setDisplayName(string newDisplayName) {
13223 			displayName = cast(char*) (newDisplayName ~ '\0');
13224 		}
13225 
13226 		/// resets to the default display string
13227 		static resetDisplayName() {
13228 			displayName = null;
13229 		}
13230 
13231 		///
13232 		static Display* get() {
13233 			if(display is null) {
13234 				if(!librariesSuccessfullyLoaded)
13235 					throw new Exception("Unable to load X11 client libraries");
13236 				display = XOpenDisplay(displayName);
13237 
13238 				isLocal_ = false;
13239 
13240 				connectionSequence_++;
13241 				if(display is null)
13242 					throw new Exception("Unable to open X display");
13243 
13244 				auto str = display.display_name;
13245 				// this is a bit of a hack but like if it looks like a unix socket we assume it is local
13246 				// and otherwise it probably isn't
13247 				if(str is null || (str[0] != ':' && str[0] != '/'))
13248 					isLocal_ = false;
13249 				else
13250 					isLocal_ = true;
13251 
13252 				//XSetErrorHandler(&adrlogger);
13253 				//XSynchronize(display, true);
13254 
13255 
13256 				XSetIOErrorHandler(&x11ioerrCB);
13257 				Bool sup;
13258 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
13259 				createXIM();
13260 				version(with_eventloop) {
13261 					import arsd.eventloop;
13262 					addFileEventListeners(display.fd, &eventListener, null, null);
13263 				}
13264 			}
13265 
13266 			return display;
13267 		}
13268 
13269 		extern(C)
13270 		static int x11ioerrCB(Display* dpy) {
13271 			throw new XDisconnectException(false);
13272 		}
13273 
13274 		version(with_eventloop) {
13275 			import arsd.eventloop;
13276 			static void eventListener(OsFileHandle fd) {
13277 				//this.mtLock();
13278 				//scope(exit) this.mtUnlock();
13279 				while(XPending(display))
13280 					doXNextEvent(display);
13281 			}
13282 		}
13283 
13284 		// close connection on program exit -- we need this to properly free all images
13285 		static ~this () {
13286 			// the gui thread must clean up after itself or else Xlib might deadlock
13287 			// using this flag on any thread destruction is the easiest way i know of
13288 			// (shared static this is run by the LAST thread to exit, which may not be
13289 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
13290 			if(thisIsGuiThread)
13291 				close();
13292 		}
13293 
13294 		///
13295 		static void close() {
13296 			if(display is null)
13297 				return;
13298 
13299 			version(with_eventloop) {
13300 				import arsd.eventloop;
13301 				removeFileEventListeners(display.fd);
13302 			}
13303 
13304 			// now remove all registered images to prevent shared memory leaks
13305 			freeImages();
13306 
13307 			// tbh I don't know why it is doing this but like if this happens to run
13308 			// from the other thread there's frequent hanging inside here.
13309 			if(thisIsGuiThread)
13310 				XCloseDisplay(display);
13311 			display = null;
13312 		}
13313 	}
13314 
13315 	mixin template NativeImageImplementation() {
13316 		XImage* handle;
13317 		ubyte* rawData;
13318 
13319 		XShmSegmentInfo shminfo;
13320 
13321 		__gshared bool xshmQueryCompleted;
13322 		__gshared bool _xshmAvailable;
13323 		public static @property bool xshmAvailable() {
13324 			if(!xshmQueryCompleted) {
13325 				int i1, i2, i3;
13326 				xshmQueryCompleted = true;
13327 
13328 				if(!XDisplayConnection.isLocal)
13329 					_xshmAvailable = false;
13330 				else
13331 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
13332 			}
13333 			return _xshmAvailable;
13334 		}
13335 
13336 		bool usingXshm;
13337 	final:
13338 
13339 		private __gshared bool xshmfailed;
13340 
13341 		void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) {
13342 			auto display = XDisplayConnection.get();
13343 			assert(display !is null);
13344 			auto screen = DefaultScreen(display);
13345 
13346 			// it will only use shared memory for somewhat largish images,
13347 			// since otherwise we risk wasting shared memory handles on a lot of little ones
13348 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
13349 
13350 
13351 				// it is possible for the query extension to return true, the DISPLAY check to pass, yet
13352 				// the actual use still fails. For example, if the program is in a container and permission denied
13353 				// on shared memory, or if it is a local thing forwarded to a remote server, etc.
13354 				//
13355 				// If it does fail, we need to detect it now, abort the xshm and fall back to core protocol.
13356 
13357 
13358 				// synchronize so preexisting buffers are clear
13359 				XSync(display, false);
13360 				xshmfailed = false;
13361 
13362 				auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler);
13363 
13364 
13365 				usingXshm = true;
13366 				handle = XShmCreateImage(
13367 					display,
13368 					DefaultVisual(display, screen),
13369 					enableAlpha ? 32: 24,
13370 					ImageFormat.ZPixmap,
13371 					null,
13372 					&shminfo,
13373 					width, height);
13374 				if(handle is null)
13375 					goto abortXshm1;
13376 
13377 				if(handle.bytes_per_line != 4 * width)
13378 					goto abortXshm2;
13379 
13380 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
13381 				if(shminfo.shmid < 0)
13382 					goto abortXshm3;
13383 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
13384 				if(rawData == cast(ubyte*) -1)
13385 					goto abortXshm4;
13386 				shminfo.readOnly = 0;
13387 				XShmAttach(display, &shminfo);
13388 
13389 				// and now to the final error check to ensure it actually worked.
13390 				XSync(display, false);
13391 				if(xshmfailed)
13392 					goto abortXshm5;
13393 
13394 				XSetErrorHandler(oldErrorHandler);
13395 
13396 				XDisplayConnection.registerImage(this);
13397 				// if I don't flush here there's a chance the dtor will run before the
13398 				// ctor and lead to a bad value X error. While this hurts the efficiency
13399 				// it is local anyway so prolly better to keep it simple
13400 				XFlush(display);
13401 
13402 				return;
13403 
13404 				abortXshm5:
13405 					shmdt(shminfo.shmaddr);
13406 					rawData = null;
13407 
13408 				abortXshm4:
13409 					shmctl(shminfo.shmid, IPC_RMID, null);
13410 
13411 				abortXshm3:
13412 					// nothing needed, the shmget failed so there's nothing to free
13413 
13414 				abortXshm2:
13415 					XDestroyImage(handle);
13416 					handle = null;
13417 
13418 				abortXshm1:
13419 					XSetErrorHandler(oldErrorHandler);
13420 					usingXshm = false;
13421 					handle = null;
13422 
13423 					shminfo = typeof(shminfo).init;
13424 
13425 					_xshmAvailable = false; // don't try again in the future
13426 
13427 					//import std.stdio; writeln("fallingback");
13428 
13429 					goto fallback;
13430 
13431 			} else {
13432 				fallback:
13433 
13434 				if (forcexshm) throw new Exception("can't create XShm Image");
13435 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
13436 				import core.stdc.stdlib : malloc;
13437 				rawData = cast(ubyte*) malloc(width * height * 4);
13438 
13439 				handle = XCreateImage(
13440 					display,
13441 					DefaultVisual(display, screen),
13442 					enableAlpha ? 32 : 24, // bpp
13443 					ImageFormat.ZPixmap,
13444 					0, // offset
13445 					rawData,
13446 					width, height,
13447 					enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line
13448 			}
13449 		}
13450 
13451 		void dispose() {
13452 			// note: this calls free(rawData) for us
13453 			if(handle) {
13454 				if (usingXshm) {
13455 					XDisplayConnection.unregisterImage(this);
13456 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
13457 				}
13458 				XDestroyImage(handle);
13459 				if(usingXshm) {
13460 					shmdt(shminfo.shmaddr);
13461 					shmctl(shminfo.shmid, IPC_RMID, null);
13462 				}
13463 				handle = null;
13464 			}
13465 		}
13466 
13467 		Color getPixel(int x, int y) {
13468 			auto offset = (y * width + x) * 4;
13469 			Color c;
13470 			c.a = enableAlpha ? rawData[offset + 3] : 255;
13471 			c.b = rawData[offset + 0];
13472 			c.g = rawData[offset + 1];
13473 			c.r = rawData[offset + 2];
13474 			if(enableAlpha)
13475 				c.unPremultiply;
13476 			return c;
13477 		}
13478 
13479 		void setPixel(int x, int y, Color c) {
13480 			if(enableAlpha)
13481 				c.premultiply();
13482 			auto offset = (y * width + x) * 4;
13483 			rawData[offset + 0] = c.b;
13484 			rawData[offset + 1] = c.g;
13485 			rawData[offset + 2] = c.r;
13486 			if(enableAlpha)
13487 				rawData[offset + 3] = c.a;
13488 		}
13489 
13490 		void convertToRgbaBytes(ubyte[] where) {
13491 			assert(where.length == this.width * this.height * 4);
13492 
13493 			// if rawData had a length....
13494 			//assert(rawData.length == where.length);
13495 			for(int idx = 0; idx < where.length; idx += 4) {
13496 				where[idx + 0] = rawData[idx + 2]; // r
13497 				where[idx + 1] = rawData[idx + 1]; // g
13498 				where[idx + 2] = rawData[idx + 0]; // b
13499 				where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a
13500 
13501 				if(enableAlpha)
13502 					unPremultiplyRgba(where[idx .. idx + 4]);
13503 			}
13504 		}
13505 
13506 		void setFromRgbaBytes(in ubyte[] where) {
13507 			assert(where.length == this.width * this.height * 4);
13508 
13509 			// if rawData had a length....
13510 			//assert(rawData.length == where.length);
13511 			for(int idx = 0; idx < where.length; idx += 4) {
13512 				rawData[idx + 2] = where[idx + 0]; // r
13513 				rawData[idx + 1] = where[idx + 1]; // g
13514 				rawData[idx + 0] = where[idx + 2]; // b
13515 				if(enableAlpha) {
13516 					rawData[idx + 3] = where[idx + 3]; // a
13517 					premultiplyBgra(rawData[idx .. idx + 4]);
13518 				}
13519 			}
13520 		}
13521 
13522 	}
13523 
13524 	mixin template NativeSimpleWindowImplementation() {
13525 		GC gc;
13526 		Window window;
13527 		Display* display;
13528 
13529 		Pixmap buffer;
13530 		int bufferw, bufferh; // size of the buffer; can be bigger than window
13531 		XIC xic; // input context
13532 		int curHidden = 0; // counter
13533 		Cursor blankCurPtr = 0;
13534 		int cursorSequenceNumber = 0;
13535 		int warpEventCount = 0; // number of mouse movement events to eat
13536 
13537 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
13538 		X11GetSelectionHandler[Atom] getSelectionHandlers;
13539 
13540 		version(without_opengl) {} else
13541 		GLXContext glc;
13542 
13543 		private void fixFixedSize(bool forced=false) (int width, int height) {
13544 			if (forced || this.resizability == Resizability.fixedSize) {
13545 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
13546 				XSizeHints sh;
13547 				static if (!forced) {
13548 					c_long spr;
13549 					XGetWMNormalHints(display, window, &sh, &spr);
13550 					sh.flags |= PMaxSize | PMinSize;
13551 				} else {
13552 					sh.flags = PMaxSize | PMinSize;
13553 				}
13554 				sh.min_width = width;
13555 				sh.min_height = height;
13556 				sh.max_width = width;
13557 				sh.max_height = height;
13558 				XSetWMNormalHints(display, window, &sh);
13559 				//XFlush(display);
13560 			}
13561 		}
13562 
13563 		ScreenPainter getPainter() {
13564 			return ScreenPainter(this, window);
13565 		}
13566 
13567 		void move(int x, int y) {
13568 			XMoveWindow(display, window, x, y);
13569 		}
13570 
13571 		void resize(int w, int h) {
13572 			if (w < 1) w = 1;
13573 			if (h < 1) h = 1;
13574 			XResizeWindow(display, window, w, h);
13575 
13576 			// calling this now to avoid waiting for the server to
13577 			// acknowledge the resize; draws without returning to the
13578 			// event loop will thus actually work. the server's event
13579 			// btw might overrule this and resize it again
13580 			recordX11Resize(display, this, w, h);
13581 
13582 			// FIXME: do we need to set this as the opengl context to do the glViewport change?
13583 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
13584 		}
13585 
13586 		void moveResize (int x, int y, int w, int h) {
13587 			if (w < 1) w = 1;
13588 			if (h < 1) h = 1;
13589 			XMoveResizeWindow(display, window, x, y, w, h);
13590 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
13591 		}
13592 
13593 		void hideCursor () {
13594 			if (curHidden++ == 0) {
13595 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
13596 					static const(char)[1] cmbmp = 0;
13597 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
13598 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
13599 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
13600 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
13601 					XFreePixmap(display, pm);
13602 				}
13603 				XDefineCursor(display, window, blankCurPtr);
13604 			}
13605 		}
13606 
13607 		void showCursor () {
13608 			if (--curHidden == 0) XUndefineCursor(display, window);
13609 		}
13610 
13611 		void warpMouse (int x, int y) {
13612 			// here i will send dummy "ignore next mouse motion" event,
13613 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
13614 			// and we don't need to report it to the user (as warping is
13615 			// used when the user needs movement deltas).
13616 			//XClientMessageEvent xclient;
13617 			XEvent e;
13618 			e.xclient.type = EventType.ClientMessage;
13619 			e.xclient.window = window;
13620 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
13621 			e.xclient.format = 32;
13622 			e.xclient.data.l[0] = 0;
13623 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
13624 			//{ 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]); }
13625 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
13626 			// now warp pointer...
13627 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
13628 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
13629 			// ...and flush
13630 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
13631 			XFlush(display);
13632 		}
13633 
13634 		void sendDummyEvent () {
13635 			// here i will send dummy event to ping event queue
13636 			XEvent e;
13637 			e.xclient.type = EventType.ClientMessage;
13638 			e.xclient.window = window;
13639 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
13640 			e.xclient.format = 32;
13641 			e.xclient.data.l[0] = 0;
13642 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
13643 			XFlush(display);
13644 		}
13645 
13646 		void setTitle(string title) {
13647 			if (title.ptr is null) title = "";
13648 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
13649 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
13650 			XTextProperty windowName;
13651 			windowName.value = title.ptr;
13652 			windowName.encoding = XA_UTF8; //XA_STRING;
13653 			windowName.format = 8;
13654 			windowName.nitems = cast(uint)title.length;
13655 			XSetWMName(display, window, &windowName);
13656 			char[1024] namebuf = 0;
13657 			auto maxlen = namebuf.length-1;
13658 			if (maxlen > title.length) maxlen = title.length;
13659 			namebuf[0..maxlen] = title[0..maxlen];
13660 			XStoreName(display, window, namebuf.ptr);
13661 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
13662 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
13663 		}
13664 
13665 		string[] getTitles() {
13666 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
13667 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
13668 			XTextProperty textProp;
13669 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
13670 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
13671 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
13672 				} else
13673 					return [];
13674 			} else
13675 				return null;
13676 		}
13677 
13678 		string getTitle() {
13679 			auto titles = getTitles();
13680 			return titles.length ? titles[0] : null;
13681 		}
13682 
13683 		void setMinSize (int minwidth, int minheight) {
13684 			import core.stdc.config : c_long;
13685 			if (minwidth < 1) minwidth = 1;
13686 			if (minheight < 1) minheight = 1;
13687 			XSizeHints sh;
13688 			c_long spr;
13689 			XGetWMNormalHints(display, window, &sh, &spr);
13690 			sh.min_width = minwidth;
13691 			sh.min_height = minheight;
13692 			sh.flags |= PMinSize;
13693 			XSetWMNormalHints(display, window, &sh);
13694 			flushGui();
13695 		}
13696 
13697 		void setMaxSize (int maxwidth, int maxheight) {
13698 			import core.stdc.config : c_long;
13699 			if (maxwidth < 1) maxwidth = 1;
13700 			if (maxheight < 1) maxheight = 1;
13701 			XSizeHints sh;
13702 			c_long spr;
13703 			XGetWMNormalHints(display, window, &sh, &spr);
13704 			sh.max_width = maxwidth;
13705 			sh.max_height = maxheight;
13706 			sh.flags |= PMaxSize;
13707 			XSetWMNormalHints(display, window, &sh);
13708 			flushGui();
13709 		}
13710 
13711 		void setResizeGranularity (int granx, int grany) {
13712 			import core.stdc.config : c_long;
13713 			if (granx < 1) granx = 1;
13714 			if (grany < 1) grany = 1;
13715 			XSizeHints sh;
13716 			c_long spr;
13717 			XGetWMNormalHints(display, window, &sh, &spr);
13718 			sh.width_inc = granx;
13719 			sh.height_inc = grany;
13720 			sh.flags |= PResizeInc;
13721 			XSetWMNormalHints(display, window, &sh);
13722 			flushGui();
13723 		}
13724 
13725 		void setOpacity (uint opacity) {
13726 			arch_ulong o = opacity;
13727 			if (opacity == uint.max)
13728 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
13729 			else
13730 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
13731 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
13732 		}
13733 
13734 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
13735 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
13736 			display = XDisplayConnection.get();
13737 			auto screen = DefaultScreen(display);
13738 
13739 			version(without_opengl) {}
13740 			else {
13741 				if(opengl == OpenGlOptions.yes) {
13742 					GLXFBConfig fbconf = null;
13743 					XVisualInfo* vi = null;
13744 					bool useLegacy = false;
13745 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
13746 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
13747 						int[23] visualAttribs = [
13748 							GLX_X_RENDERABLE , 1/*True*/,
13749 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
13750 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
13751 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
13752 							GLX_RED_SIZE     , 8,
13753 							GLX_GREEN_SIZE   , 8,
13754 							GLX_BLUE_SIZE    , 8,
13755 							GLX_ALPHA_SIZE   , 8,
13756 							GLX_DEPTH_SIZE   , 24,
13757 							GLX_STENCIL_SIZE , 8,
13758 							GLX_DOUBLEBUFFER , 1/*True*/,
13759 							0/*None*/,
13760 						];
13761 						int fbcount;
13762 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
13763 						if (fbcount == 0) {
13764 							useLegacy = true; // try to do at least something
13765 						} else {
13766 							// pick the FB config/visual with the most samples per pixel
13767 							int bestidx = -1, bestns = -1;
13768 							foreach (int fbi; 0..fbcount) {
13769 								int sb, samples;
13770 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
13771 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
13772 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
13773 							}
13774 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
13775 							fbconf = fbc[bestidx];
13776 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
13777 							XFree(fbc);
13778 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
13779 						}
13780 					}
13781 					if (vi is null || useLegacy) {
13782 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
13783 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
13784 						useLegacy = true;
13785 					}
13786 					if (vi is null) throw new Exception("no open gl visual found");
13787 
13788 					XSetWindowAttributes swa;
13789 					auto root = RootWindow(display, screen);
13790 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
13791 
13792 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
13793 						0, 0, width, height,
13794 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa);
13795 
13796 					// now try to use `glXCreateContextAttribsARB()` if it's here
13797 					if (!useLegacy) {
13798 						// request fairly advanced context, even with stencil buffer!
13799 						int[9] contextAttribs = [
13800 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
13801 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
13802 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
13803 							// for modern context, set "forward compatibility" flag too
13804 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
13805 							0/*None*/,
13806 						];
13807 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
13808 						if (glc is null && sdpyOpenGLContextAllowFallback) {
13809 							sdpyOpenGLContextVersion = 0;
13810 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
13811 						}
13812 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
13813 					} else {
13814 						// fallback to old GLX call
13815 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
13816 							sdpyOpenGLContextVersion = 0;
13817 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
13818 						}
13819 					}
13820 					// sync to ensure any errors generated are processed
13821 					XSync(display, 0/*False*/);
13822 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
13823 					if(glc is null)
13824 						throw new Exception("glc");
13825 				}
13826 			}
13827 
13828 			if(opengl == OpenGlOptions.no) {
13829 
13830 				bool overrideRedirect = false;
13831 				if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)
13832 					overrideRedirect = true;
13833 
13834 				XSetWindowAttributes swa;
13835 				swa.background_pixel = WhitePixel(display, screen);
13836 				swa.border_pixel = BlackPixel(display, screen);
13837 				swa.override_redirect = overrideRedirect;
13838 				auto root = RootWindow(display, screen);
13839 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
13840 
13841 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
13842 					0, 0, width, height,
13843 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa);
13844 
13845 
13846 
13847 				/*
13848 				window = XCreateSimpleWindow(
13849 					display,
13850 					parent is null ? RootWindow(display, screen) : parent.impl.window,
13851 					0, 0, // x, y
13852 					width, height,
13853 					1, // border width
13854 					BlackPixel(display, screen), // border
13855 					WhitePixel(display, screen)); // background
13856 				*/
13857 
13858 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
13859 				bufferw = width;
13860 				bufferh = height;
13861 
13862 				gc = DefaultGC(display, screen);
13863 
13864 				// clear out the buffer to get us started...
13865 				XSetForeground(display, gc, WhitePixel(display, screen));
13866 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
13867 				XSetForeground(display, gc, BlackPixel(display, screen));
13868 			}
13869 
13870 			// input context
13871 			//TODO: create this only for top-level windows, and reuse that?
13872 			if (XDisplayConnection.xim !is null) {
13873 				xic = XCreateIC(XDisplayConnection.xim,
13874 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
13875 						/*XNClientWindow*/"clientWindow".ptr, window,
13876 						/*XNFocusWindow*/"focusWindow".ptr, window,
13877 						null);
13878 				if (xic is null) {
13879 					import core.stdc.stdio : stderr, fprintf;
13880 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
13881 				}
13882 			}
13883 
13884 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
13885 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
13886 			// window class
13887 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
13888 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
13889 				XClassHint klass;
13890 				XWMHints wh;
13891 				XSizeHints size;
13892 				klass.res_name = sdpyWindowClassStr;
13893 				klass.res_class = sdpyWindowClassStr;
13894 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
13895 			}
13896 
13897 			setTitle(title);
13898 			SimpleWindow.nativeMapping[window] = this;
13899 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
13900 
13901 			// This gives our window a close button
13902 			if (windowType != WindowTypes.eventOnly) {
13903 				// FIXME: actually implement the WM_TAKE_FOCUS correctly
13904 				//Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
13905 				Atom[1] atoms = [GetAtom!"WM_DELETE_WINDOW"(display)];
13906 				XSetWMProtocols(display, window, atoms.ptr, cast(int) atoms.length);
13907 			}
13908 
13909 			// FIXME: windowType and customizationFlags
13910 			Atom[8] wsatoms; // here, due to goto
13911 			int wmsacount = 0; // here, due to goto
13912 
13913 			try
13914 			final switch(windowType) {
13915 				case WindowTypes.normal:
13916 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
13917 				break;
13918 				case WindowTypes.undecorated:
13919 					motifHideDecorations();
13920 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
13921 				break;
13922 				case WindowTypes.eventOnly:
13923 					_hidden = true;
13924 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
13925 					goto hiddenWindow;
13926 				//break;
13927 				case WindowTypes.nestedChild:
13928 					// handled in XCreateWindow calls
13929 				break;
13930 
13931 				case WindowTypes.dropdownMenu:
13932 					motifHideDecorations();
13933 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
13934 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
13935 				break;
13936 				case WindowTypes.popupMenu:
13937 					motifHideDecorations();
13938 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
13939 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
13940 				break;
13941 				case WindowTypes.notification:
13942 					motifHideDecorations();
13943 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
13944 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
13945 				break;
13946 				/+
13947 				case WindowTypes.menu:
13948 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
13949 					motifHideDecorations();
13950 				break;
13951 				case WindowTypes.desktop:
13952 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
13953 				break;
13954 				case WindowTypes.dock:
13955 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
13956 				break;
13957 				case WindowTypes.toolbar:
13958 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
13959 				break;
13960 				case WindowTypes.menu:
13961 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
13962 				break;
13963 				case WindowTypes.utility:
13964 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
13965 				break;
13966 				case WindowTypes.splash:
13967 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
13968 				break;
13969 				case WindowTypes.dialog:
13970 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
13971 				break;
13972 				case WindowTypes.tooltip:
13973 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
13974 				break;
13975 				case WindowTypes.notification:
13976 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
13977 				break;
13978 				case WindowTypes.combo:
13979 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
13980 				break;
13981 				case WindowTypes.dnd:
13982 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
13983 				break;
13984 				+/
13985 			}
13986 			catch(Exception e) {
13987 				// XInternAtom failed, prolly a WM
13988 				// that doesn't support these things
13989 			}
13990 
13991 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
13992 			// the two following flags may be ignored by WM
13993 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
13994 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
13995 
13996 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
13997 
13998 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
13999 
14000 			// What would be ideal here is if they only were
14001 			// selected if there was actually an event handler
14002 			// for them...
14003 
14004 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
14005 
14006 			hiddenWindow:
14007 
14008 			// set the pid property for lookup later by window managers
14009 			// a standard convenience
14010 			import core.sys.posix.unistd;
14011 			arch_ulong pid = getpid();
14012 
14013 			XChangeProperty(
14014 				display,
14015 				impl.window,
14016 				GetAtom!("_NET_WM_PID", true)(display),
14017 				XA_CARDINAL,
14018 				32 /* bits */,
14019 				0 /*PropModeReplace*/,
14020 				&pid,
14021 				1);
14022 
14023 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
14024 				if(parent is null) assert(0);
14025 				XChangeProperty(
14026 					display,
14027 					impl.window,
14028 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
14029 					XA_WINDOW,
14030 					32 /* bits */,
14031 					0 /*PropModeReplace*/,
14032 					&parent.impl.window,
14033 					1);
14034 
14035 			}
14036 
14037 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
14038 				XMapWindow(display, window);
14039 			} else {
14040 				_hidden = true;
14041 			}
14042 		}
14043 
14044 		void selectDefaultInput(bool forceIncludeMouseMotion) {
14045 			auto mask = EventMask.ExposureMask |
14046 				EventMask.KeyPressMask |
14047 				EventMask.KeyReleaseMask |
14048 				EventMask.PropertyChangeMask |
14049 				EventMask.FocusChangeMask |
14050 				EventMask.StructureNotifyMask |
14051 				EventMask.VisibilityChangeMask
14052 				| EventMask.ButtonPressMask
14053 				| EventMask.ButtonReleaseMask
14054 			;
14055 
14056 			// xshm is our shortcut for local connections
14057 			if(XDisplayConnection.isLocal || forceIncludeMouseMotion)
14058 				mask |= EventMask.PointerMotionMask;
14059 			else
14060 				mask |= EventMask.ButtonMotionMask;
14061 
14062 			XSelectInput(display, window, mask);
14063 		}
14064 
14065 
14066 		void setNetWMWindowType(Atom type) {
14067 			Atom[2] atoms;
14068 
14069 			atoms[0] = type;
14070 			// generic fallback
14071 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
14072 
14073 			XChangeProperty(
14074 				display,
14075 				impl.window,
14076 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
14077 				XA_ATOM,
14078 				32 /* bits */,
14079 				0 /*PropModeReplace*/,
14080 				atoms.ptr,
14081 				cast(int) atoms.length);
14082 		}
14083 
14084 		void motifHideDecorations(bool hide = true) {
14085 			MwmHints hints;
14086 			hints.flags = MWM_HINTS_DECORATIONS;
14087 			hints.decorations = hide ? 0 : 1;
14088 
14089 			XChangeProperty(
14090 				display,
14091 				impl.window,
14092 				GetAtom!"_MOTIF_WM_HINTS"(display),
14093 				GetAtom!"_MOTIF_WM_HINTS"(display),
14094 				32 /* bits */,
14095 				0 /*PropModeReplace*/,
14096 				&hints,
14097 				hints.sizeof / 4);
14098 		}
14099 
14100 		/*k8: unused
14101 		void createOpenGlContext() {
14102 
14103 		}
14104 		*/
14105 
14106 		void closeWindow() {
14107 			// I can't close this or a child window closing will
14108 			// break events for everyone. So I'm just leaking it right
14109 			// now and that is probably perfectly fine...
14110 			version(none)
14111 			if (customEventFDRead != -1) {
14112 				import core.sys.posix.unistd : close;
14113 				auto same = customEventFDRead == customEventFDWrite;
14114 
14115 				close(customEventFDRead);
14116 				if(!same)
14117 					close(customEventFDWrite);
14118 				customEventFDRead = -1;
14119 				customEventFDWrite = -1;
14120 			}
14121 			if(buffer)
14122 				XFreePixmap(display, buffer);
14123 			bufferw = bufferh = 0;
14124 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
14125 			XDestroyWindow(display, window);
14126 			XFlush(display);
14127 		}
14128 
14129 		void dispose() {
14130 		}
14131 
14132 		bool destroyed = false;
14133 	}
14134 
14135 	bool insideXEventLoop;
14136 }
14137 
14138 version(X11) {
14139 
14140 	int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this.
14141 
14142 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
14143 		if(width != win.width || height != win.height) {
14144 			win._width = width;
14145 			win._height = height;
14146 
14147 			if(win.openglMode == OpenGlOptions.no) {
14148 				// FIXME: could this be more efficient?
14149 
14150 				if (win.bufferw < width || win.bufferh < height) {
14151 					//{ 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); }
14152 					// grow the internal buffer to match the window...
14153 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
14154 					{
14155 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
14156 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
14157 						scope(exit) XFreeGC(win.display, xgc);
14158 						XSetClipMask(win.display, xgc, None);
14159 						XSetForeground(win.display, xgc, 0);
14160 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
14161 					}
14162 					XCopyArea(display,
14163 						cast(Drawable) win.buffer,
14164 						cast(Drawable) newPixmap,
14165 						win.gc, 0, 0,
14166 						win.bufferw < width ? win.bufferw : win.width,
14167 						win.bufferh < height ? win.bufferh : win.height,
14168 						0, 0);
14169 
14170 					XFreePixmap(display, win.buffer);
14171 					win.buffer = newPixmap;
14172 					win.bufferw = width;
14173 					win.bufferh = height;
14174 				}
14175 
14176 				// clear unused parts of the buffer
14177 				if (win.bufferw > width || win.bufferh > height) {
14178 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
14179 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
14180 					scope(exit) XFreeGC(win.display, xgc);
14181 					XSetClipMask(win.display, xgc, None);
14182 					XSetForeground(win.display, xgc, 0);
14183 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
14184 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
14185 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
14186 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
14187 				}
14188 
14189 			}
14190 
14191 			version(without_opengl) {} else
14192 			if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) {
14193 				glViewport(0, 0, width, height);
14194 			}
14195 
14196 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
14197 
14198 			if(win.windowResized !is null) {
14199 				XUnlockDisplay(display);
14200 				scope(exit) XLockDisplay(display);
14201 				win.windowResized(width, height);
14202 			}
14203 		}
14204 	}
14205 
14206 
14207 	/// Platform-specific, you might use it when doing a custom event loop.
14208 	bool doXNextEvent(Display* display) {
14209 		bool done;
14210 		XEvent e;
14211 		XNextEvent(display, &e);
14212 		version(sddddd) {
14213 			import std.stdio, std.conv : to;
14214 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
14215 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
14216 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
14217 			}
14218 		}
14219 
14220 		// filter out compose events
14221 		if (XFilterEvent(&e, None)) {
14222 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
14223 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
14224 			return false;
14225 		}
14226 		// process keyboard mapping changes
14227 		if (e.type == EventType.KeymapNotify) {
14228 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
14229 			XRefreshKeyboardMapping(&e.xmapping);
14230 			return false;
14231 		}
14232 
14233 		version(with_eventloop)
14234 			import arsd.eventloop;
14235 
14236 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
14237 			// see windows impl's comments
14238 			XUnlockDisplay(display);
14239 			scope(exit) XLockDisplay(display);
14240 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
14241 			if(ret == 0)
14242 				return done;
14243 		}
14244 
14245 
14246 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
14247 			if(win.getNativeEventHandler !is null) {
14248 				XUnlockDisplay(display);
14249 				scope(exit) XLockDisplay(display);
14250 				auto ret = win.getNativeEventHandler()(e);
14251 				if(ret == 0)
14252 					return done;
14253 			}
14254 		}
14255 
14256 		if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) {
14257 		  	if(auto win = e.xany.window in SimpleWindow.nativeMapping) {
14258 				// we get this because of the RRScreenChangeNotifyMask
14259 
14260 				// this isn't actually an ideal way to do it since it wastes time
14261 				// but meh it is simple and it works.
14262 				win.actualDpiLoadAttempted = false;
14263 				SimpleWindow.xRandrInfoLoadAttemped = false;
14264 				win.updateActualDpi(); // trigger a reload
14265 			}
14266 		}
14267 
14268 		switch(e.type) {
14269 		  case EventType.SelectionClear:
14270 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
14271 				// FIXME so it is supposed to finish any in progress transfers... but idk...
14272 				//import std.stdio; writeln("SelectionClear");
14273 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
14274 			}
14275 		  break;
14276 		  case EventType.SelectionRequest:
14277 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
14278 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
14279 				// import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
14280 				XUnlockDisplay(display);
14281 				scope(exit) XLockDisplay(display);
14282 				(*ssh).handleRequest(e);
14283 			}
14284 		  break;
14285 		  case EventType.PropertyNotify:
14286 			// import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
14287 
14288 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
14289 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
14290 					ssh.sendMoreIncr(&e.xproperty);
14291 			}
14292 
14293 
14294 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
14295 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
14296 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
14297 					Atom target;
14298 					int format;
14299 					arch_ulong bytesafter, length;
14300 					void* value;
14301 
14302 					ubyte[] s;
14303 					Atom targetToKeep;
14304 
14305 					XGetWindowProperty(
14306 						e.xproperty.display,
14307 						e.xproperty.window,
14308 						e.xproperty.atom,
14309 						0,
14310 						100000 /* length */,
14311 						true, /* erase it to signal we got it and want more */
14312 						0 /*AnyPropertyType*/,
14313 						&target, &format, &length, &bytesafter, &value);
14314 
14315 					if(!targetToKeep)
14316 						targetToKeep = target;
14317 
14318 					auto id = (cast(ubyte*) value)[0 .. length];
14319 
14320 					handler.handleIncrData(targetToKeep, id);
14321 
14322 					XFree(value);
14323 				}
14324 			}
14325 		  break;
14326 		  case EventType.SelectionNotify:
14327 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
14328 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
14329 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
14330 					XUnlockDisplay(display);
14331 					scope(exit) XLockDisplay(display);
14332 					handler.handleData(None, null);
14333 				} else {
14334 					Atom target;
14335 					int format;
14336 					arch_ulong bytesafter, length;
14337 					void* value;
14338 					XGetWindowProperty(
14339 						e.xselection.display,
14340 						e.xselection.requestor,
14341 						e.xselection.property,
14342 						0,
14343 						100000 /* length */,
14344 						//false, /* don't erase it */
14345 						true, /* do erase it lol */
14346 						0 /*AnyPropertyType*/,
14347 						&target, &format, &length, &bytesafter, &value);
14348 
14349 					// FIXME: I don't have to copy it now since it is in char[] instead of string
14350 
14351 					{
14352 						XUnlockDisplay(display);
14353 						scope(exit) XLockDisplay(display);
14354 
14355 						if(target == XA_ATOM) {
14356 							// initial request, see what they are able to work with and request the best one
14357 							// we can handle, if available
14358 
14359 							Atom[] answer = (cast(Atom*) value)[0 .. length];
14360 							Atom best = handler.findBestFormat(answer);
14361 
14362 							/+
14363 							writeln("got ", answer);
14364 							foreach(a; answer)
14365 								printf("%s\n", XGetAtomName(display, a));
14366 							writeln("best ", best);
14367 							+/
14368 
14369 							if(best != None) {
14370 								// actually request the best format
14371 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
14372 							}
14373 						} else if(target == GetAtom!"INCR"(display)) {
14374 							// incremental
14375 
14376 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
14377 
14378 							// signal the sending program that we see
14379 							// the incr and are ready to receive more.
14380 							XDeleteProperty(
14381 								e.xselection.display,
14382 								e.xselection.requestor,
14383 								e.xselection.property);
14384 						} else {
14385 							// unsupported type... maybe, forward
14386 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
14387 						}
14388 					}
14389 					XFree(value);
14390 					/*
14391 					XDeleteProperty(
14392 						e.xselection.display,
14393 						e.xselection.requestor,
14394 						e.xselection.property);
14395 					*/
14396 				}
14397 			}
14398 		  break;
14399 		  case EventType.ConfigureNotify:
14400 			auto event = e.xconfigure;
14401 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
14402 					//version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); }
14403 
14404 				win.screenPositionX = event.x;
14405 				win.screenPositionY = event.y;
14406 				win.updateActualDpi();
14407 
14408 				recordX11Resize(display, *win, event.width, event.height);
14409 			}
14410 		  break;
14411 		  case EventType.Expose:
14412 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
14413 				// if it is closing from a popup menu, it can get
14414 				// an Expose event right by the end and trigger a
14415 				// BadDrawable error ... we'll just check
14416 				// closed to handle that.
14417 				if((*win).closed) break;
14418 				if((*win).openglMode == OpenGlOptions.no) {
14419 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
14420 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
14421 					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);
14422 				} else {
14423 					// need to redraw the scene somehow
14424 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
14425 						XUnlockDisplay(display);
14426 						scope(exit) XLockDisplay(display);
14427 						version(without_opengl) {} else
14428 						win.redrawOpenGlSceneSoon();
14429 					}
14430 				}
14431 			}
14432 		  break;
14433 		  case EventType.FocusIn:
14434 		  case EventType.FocusOut:
14435 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
14436 				if (win.xic !is null) {
14437 					//{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); }
14438 					if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic);
14439 				}
14440 
14441 				win._focused = e.type == EventType.FocusIn;
14442 
14443 				if(win.demandingAttention)
14444 					demandAttention(*win, false);
14445 
14446 				if(win.onFocusChange) {
14447 					XUnlockDisplay(display);
14448 					scope(exit) XLockDisplay(display);
14449 					win.onFocusChange(e.type == EventType.FocusIn);
14450 				}
14451 			}
14452 		  break;
14453 		  case EventType.VisibilityNotify:
14454 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
14455 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
14456 						if (win.visibilityChanged !is null) {
14457 								XUnlockDisplay(display);
14458 								scope(exit) XLockDisplay(display);
14459 								win.visibilityChanged(false);
14460 							}
14461 					} else {
14462 						if (win.visibilityChanged !is null) {
14463 							XUnlockDisplay(display);
14464 							scope(exit) XLockDisplay(display);
14465 							win.visibilityChanged(true);
14466 						}
14467 					}
14468 				}
14469 				break;
14470 		  case EventType.ClientMessage:
14471 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
14472 					// "ignore next mouse motion" event, increment ignore counter for teh window
14473 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
14474 						++(*win).warpEventCount;
14475 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
14476 					} else {
14477 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
14478 					}
14479 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
14480 					// user clicked the close button on the window manager
14481 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
14482 						XUnlockDisplay(display);
14483 						scope(exit) XLockDisplay(display);
14484 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
14485 					}
14486 
14487 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
14488 					import std.stdio; writeln("HAPPENED");
14489 					// user clicked the close button on the window manager
14490 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
14491 						XUnlockDisplay(display);
14492 						scope(exit) XLockDisplay(display);
14493 
14494 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
14495 						XSetInputFocus(display, e.xclient.window, RevertToParent, e.xclient.data.l[1]);
14496 					}
14497 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
14498 					foreach(nai; NotificationAreaIcon.activeIcons)
14499 						nai.newManager();
14500 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
14501 
14502 					bool xDragWindow = true;
14503 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
14504 						//XDefineCursor(display, xDragWindow.impl.window,
14505 							//import std.stdio; writeln("XdndStatus ", e.xclient.data.l);
14506 					}
14507 					if(auto dh = win.dropHandler) {
14508 
14509 						static Atom[3] xFormatsBuffer;
14510 						static Atom[] xFormats;
14511 
14512 						void resetXFormats() {
14513 							xFormatsBuffer[] = 0;
14514 							xFormats = xFormatsBuffer[];
14515 						}
14516 
14517 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
14518 							// on Windows it is supposed to return the effect you actually do FIXME
14519 
14520 							auto sourceWindow =  e.xclient.data.l[0];
14521 
14522 							xFormatsBuffer[0] = e.xclient.data.l[2];
14523 							xFormatsBuffer[1] = e.xclient.data.l[3];
14524 							xFormatsBuffer[2] = e.xclient.data.l[4];
14525 
14526 							if(e.xclient.data.l[1] & 1) {
14527 								// can just grab it all but like we don't necessarily need them...
14528 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
14529 							} else {
14530 								int len;
14531 								foreach(fmt; xFormatsBuffer)
14532 									if(fmt) len++;
14533 								xFormats = xFormatsBuffer[0 .. len];
14534 							}
14535 
14536 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
14537 
14538 							dh.dragEnter(&pkg);
14539 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
14540 
14541 							auto pack = e.xclient.data.l[2];
14542 
14543 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
14544 
14545 
14546 							XClientMessageEvent xclient;
14547 
14548 							xclient.type = EventType.ClientMessage;
14549 							xclient.window = e.xclient.data.l[0];
14550 							xclient.message_type = GetAtom!"XdndStatus"(display);
14551 							xclient.format = 32;
14552 							xclient.data.l[0] = win.impl.window;
14553 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
14554 							auto r = result.consistentWithin;
14555 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
14556 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
14557 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
14558 
14559 							XSendEvent(
14560 								display,
14561 								e.xclient.data.l[0],
14562 								false,
14563 								EventMask.NoEventMask,
14564 								cast(XEvent*) &xclient
14565 							);
14566 
14567 
14568 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
14569 							//import std.stdio; writeln("XdndLeave");
14570 							// drop cancelled.
14571 							// data.l[0] is the source window
14572 							dh.dragLeave();
14573 
14574 							resetXFormats();
14575 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
14576 							// drop happening, should fetch data, then send finished
14577 							//import std.stdio; writeln("XdndDrop");
14578 
14579 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
14580 
14581 							dh.drop(&pkg);
14582 
14583 							resetXFormats();
14584 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
14585 							// import std.stdio; writeln("XdndFinished");
14586 
14587 							dh.finish();
14588 						}
14589 
14590 					}
14591 				}
14592 		  break;
14593 		  case EventType.MapNotify:
14594 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
14595 					(*win)._visible = true;
14596 					if (!(*win)._visibleForTheFirstTimeCalled) {
14597 						(*win)._visibleForTheFirstTimeCalled = true;
14598 						if ((*win).visibleForTheFirstTime !is null) {
14599 							XUnlockDisplay(display);
14600 							scope(exit) XLockDisplay(display);
14601 							version(without_opengl) {} else {
14602 								if((*win).openglMode == OpenGlOptions.yes) {
14603 									(*win).setAsCurrentOpenGlContextNT();
14604 									glViewport(0, 0, (*win).width, (*win).height);
14605 								}
14606 							}
14607 							(*win).visibleForTheFirstTime();
14608 						}
14609 					}
14610 					if ((*win).visibilityChanged !is null) {
14611 						XUnlockDisplay(display);
14612 						scope(exit) XLockDisplay(display);
14613 						(*win).visibilityChanged(true);
14614 					}
14615 				}
14616 		  break;
14617 		  case EventType.UnmapNotify:
14618 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
14619 					win._visible = false;
14620 					if (win.visibilityChanged !is null) {
14621 						XUnlockDisplay(display);
14622 						scope(exit) XLockDisplay(display);
14623 						win.visibilityChanged(false);
14624 					}
14625 			}
14626 		  break;
14627 		  case EventType.DestroyNotify:
14628 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
14629 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
14630 				win._closed = true; // just in case
14631 				win.destroyed = true;
14632 				if (win.xic !is null) {
14633 					XDestroyIC(win.xic);
14634 					win.xic = null; // just in calse
14635 				}
14636 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
14637 				bool anyImportant = false;
14638 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
14639 					if(w.beingOpenKeepsAppOpen) {
14640 						anyImportant = true;
14641 						break;
14642 					}
14643 				if(!anyImportant)
14644 					done = true;
14645 			}
14646 			auto window = e.xdestroywindow.window;
14647 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
14648 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
14649 
14650 			version(with_eventloop) {
14651 				if(done) exit();
14652 			}
14653 		  break;
14654 
14655 		  case EventType.MotionNotify:
14656 			MouseEvent mouse;
14657 			auto event = e.xmotion;
14658 
14659 			mouse.type = MouseEventType.motion;
14660 			mouse.x = event.x;
14661 			mouse.y = event.y;
14662 			mouse.modifierState = event.state;
14663 
14664 			mouse.timestamp = event.time;
14665 
14666 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
14667 				mouse.window = *win;
14668 				if (win.warpEventCount > 0) {
14669 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
14670 					--(*win).warpEventCount;
14671 					(*win).mdx(mouse); // so deltas will be correctly updated
14672 				} else {
14673 					win.warpEventCount = 0; // just in case
14674 					(*win).mdx(mouse);
14675 					if((*win).handleMouseEvent) {
14676 						XUnlockDisplay(display);
14677 						scope(exit) XLockDisplay(display);
14678 						(*win).handleMouseEvent(mouse);
14679 					}
14680 				}
14681 			}
14682 
14683 		  	version(with_eventloop)
14684 				send(mouse);
14685 		  break;
14686 		  case EventType.ButtonPress:
14687 		  case EventType.ButtonRelease:
14688 			MouseEvent mouse;
14689 			auto event = e.xbutton;
14690 
14691 			mouse.timestamp = event.time;
14692 
14693 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
14694 			mouse.x = event.x;
14695 			mouse.y = event.y;
14696 
14697 			static Time lastMouseDownTime = 0;
14698 
14699 			mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
14700 			if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time;
14701 
14702 			switch(event.button) {
14703 				case 1: mouse.button = MouseButton.left; break; // left
14704 				case 2: mouse.button = MouseButton.middle; break; // middle
14705 				case 3: mouse.button = MouseButton.right; break; // right
14706 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
14707 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
14708 				case 6: break; // idk
14709 				case 7: break; // idk
14710 				case 8: mouse.button = MouseButton.backButton; break;
14711 				case 9: mouse.button = MouseButton.forwardButton; break;
14712 				default:
14713 			}
14714 
14715 			// FIXME: double check this
14716 			mouse.modifierState = event.state;
14717 
14718 			//mouse.modifierState = event.detail;
14719 
14720 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
14721 				mouse.window = *win;
14722 				(*win).mdx(mouse);
14723 				if((*win).handleMouseEvent) {
14724 					XUnlockDisplay(display);
14725 					scope(exit) XLockDisplay(display);
14726 					(*win).handleMouseEvent(mouse);
14727 				}
14728 			}
14729 			version(with_eventloop)
14730 				send(mouse);
14731 		  break;
14732 
14733 		  case EventType.KeyPress:
14734 		  case EventType.KeyRelease:
14735 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
14736 			KeyEvent ke;
14737 			ke.pressed = e.type == EventType.KeyPress;
14738 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
14739 
14740 			auto sym = XKeycodeToKeysym(
14741 				XDisplayConnection.get(),
14742 				e.xkey.keycode,
14743 				0);
14744 
14745 			ke.key = cast(Key) sym;//e.xkey.keycode;
14746 
14747 			ke.modifierState = e.xkey.state;
14748 
14749 			// import std.stdio; writefln("%x", sym);
14750 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
14751 			int charbuflen = 0; // return value of XwcLookupString
14752 			if (ke.pressed) {
14753 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
14754 				if (win !is null && win.xic !is null) {
14755 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
14756 					Status status;
14757 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
14758 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
14759 				} else {
14760 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
14761 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
14762 					char[16] buffer;
14763 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
14764 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
14765 				}
14766 			}
14767 
14768 			// if there's no char, subst one
14769 			if (charbuflen == 0) {
14770 				switch (sym) {
14771 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
14772 					case 0xff8d: // keypad enter
14773 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
14774 					default : // ignore
14775 				}
14776 			}
14777 
14778 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
14779 				ke.window = *win;
14780 
14781 
14782 				if(win.inputProxy)
14783 					win = &win.inputProxy;
14784 
14785 				// char events are separate since they are on Windows too
14786 				// also, xcompose can generate long char sequences
14787 				// don't send char events if Meta and/or Hyper is pressed
14788 				// TODO: ctrl+char should only send control chars; not yet
14789 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
14790 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
14791 				}
14792 
14793 				dchar[32] charsComingBuffer;
14794 				int charsComingPosition;
14795 				dchar[] charsComing = charsComingBuffer[];
14796 
14797 				if (ke.pressed && charbuflen > 0) {
14798 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
14799 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
14800 						if(charsComingPosition >= charsComing.length)
14801 							charsComing.length = charsComingPosition + 8;
14802 
14803 						charsComing[charsComingPosition++] = ch;
14804 					}
14805 
14806 					charsComing = charsComing[0 .. charsComingPosition];
14807 				} else {
14808 					charsComing = null;
14809 				}
14810 
14811 				ke.charsPossible = charsComing;
14812 
14813 				if (win.handleKeyEvent) {
14814 					XUnlockDisplay(display);
14815 					scope(exit) XLockDisplay(display);
14816 					win.handleKeyEvent(ke);
14817 				}
14818 
14819 				// Super and alt modifier keys never actually send the chars, they are assumed to be special.
14820 				if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) {
14821 					XUnlockDisplay(display);
14822 					scope(exit) XLockDisplay(display);
14823 					foreach(ch; charsComing)
14824 						win.handleCharEvent(ch);
14825 				}
14826 			}
14827 
14828 			version(with_eventloop)
14829 				send(ke);
14830 		  break;
14831 		  default:
14832 		}
14833 
14834 		return done;
14835 	}
14836 }
14837 
14838 /* *************************************** */
14839 /*      Done with simpledisplay stuff      */
14840 /* *************************************** */
14841 
14842 // Necessary C library bindings follow
14843 version(Windows) {} else
14844 version(X11) {
14845 
14846 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
14847 
14848 // X11 bindings needed here
14849 /*
14850 	A little of this is from the bindings project on
14851 	D Source and some of it is copy/paste from the C
14852 	header.
14853 
14854 	The DSource listing consistently used D's long
14855 	where C used long. That's wrong - C long is 32 bit, so
14856 	it should be int in D. I changed that here.
14857 
14858 	Note:
14859 	This isn't complete, just took what I needed for myself.
14860 */
14861 
14862 import core.stdc.stddef : wchar_t;
14863 
14864 interface XLib {
14865 extern(C) nothrow @nogc {
14866 	char* XResourceManagerString(Display*);
14867 	void XrmInitialize();
14868 	XrmDatabase XrmGetStringDatabase(char* data);
14869 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
14870 
14871 	Cursor XCreateFontCursor(Display*, uint shape);
14872 	int XDefineCursor(Display* display, Window w, Cursor cursor);
14873 	int XUndefineCursor(Display* display, Window w);
14874 
14875 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
14876 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
14877 	int XFreeCursor(Display* display, Cursor cursor);
14878 
14879 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
14880 
14881 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
14882 
14883 	char *XKeysymToString(KeySym keysym);
14884 	KeySym XKeycodeToKeysym(
14885 		Display*		/* display */,
14886 		KeyCode		/* keycode */,
14887 		int			/* index */
14888 	);
14889 
14890 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
14891 
14892 	int XFree(void*);
14893 	int XDeleteProperty(Display *display, Window w, Atom property);
14894 
14895 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements);
14896 
14897 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
14898 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
14899 		*actual_type_return, int *actual_format_return, arch_ulong
14900 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
14901 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
14902 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
14903 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
14904 
14905 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
14906 
14907 	Window XGetSelectionOwner(Display *display, Atom selection);
14908 
14909 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
14910 
14911 	char** XListFonts(Display*, const char*, int, int*);
14912 	void XFreeFontNames(char**);
14913 
14914 	Display* XOpenDisplay(const char*);
14915 	int XCloseDisplay(Display*);
14916 
14917 	int XSynchronize(Display*, bool);
14918 
14919 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
14920 
14921 	Bool XSupportsLocale();
14922 	char* XSetLocaleModifiers(const(char)* modifier_list);
14923 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
14924 	Status XCloseOM(XOM om);
14925 
14926 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
14927 	Status XCloseIM(XIM im);
14928 
14929 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
14930 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
14931 	Display* XDisplayOfIM(XIM im);
14932 	char* XLocaleOfIM(XIM im);
14933 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
14934 	void XDestroyIC(XIC ic);
14935 	void XSetICFocus(XIC ic);
14936 	void XUnsetICFocus(XIC ic);
14937 	//wchar_t* XwcResetIC(XIC ic);
14938 	char* XmbResetIC(XIC ic);
14939 	char* Xutf8ResetIC(XIC ic);
14940 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
14941 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
14942 	XIM XIMOfIC(XIC ic);
14943 
14944 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
14945 
14946 
14947 	XFontStruct *XLoadQueryFont(Display *display, in char *name);
14948 	int XFreeFont(Display *display, XFontStruct *font_struct);
14949 	int XSetFont(Display* display, GC gc, Font font);
14950 	int XTextWidth(XFontStruct*, in char*, int);
14951 
14952 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
14953 	int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n);
14954 
14955 	Window XCreateSimpleWindow(
14956 		Display*	/* display */,
14957 		Window		/* parent */,
14958 		int			/* x */,
14959 		int			/* y */,
14960 		uint		/* width */,
14961 		uint		/* height */,
14962 		uint		/* border_width */,
14963 		uint		/* border */,
14964 		uint		/* background */
14965 	);
14966 	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);
14967 
14968 	int XReparentWindow(Display*, Window, Window, int, int);
14969 	int XClearWindow(Display*, Window);
14970 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
14971 	int XMoveWindow(Display*, Window, int, int);
14972 	int XResizeWindow(Display *display, Window w, uint width, uint height);
14973 
14974 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
14975 
14976 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
14977 
14978 	XImage *XCreateImage(
14979 		Display*		/* display */,
14980 		Visual*		/* visual */,
14981 		uint	/* depth */,
14982 		int			/* format */,
14983 		int			/* offset */,
14984 		ubyte*		/* data */,
14985 		uint	/* width */,
14986 		uint	/* height */,
14987 		int			/* bitmap_pad */,
14988 		int			/* bytes_per_line */
14989 	);
14990 
14991 	Status XInitImage (XImage* image);
14992 
14993 	Atom XInternAtom(
14994 		Display*		/* display */,
14995 		const char*	/* atom_name */,
14996 		Bool		/* only_if_exists */
14997 	);
14998 
14999 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
15000 	char* XGetAtomName(Display*, Atom);
15001 	Status XGetAtomNames(Display*, Atom*, int count, char**);
15002 
15003 	int XPutImage(
15004 		Display*	/* display */,
15005 		Drawable	/* d */,
15006 		GC			/* gc */,
15007 		XImage*	/* image */,
15008 		int			/* src_x */,
15009 		int			/* src_y */,
15010 		int			/* dest_x */,
15011 		int			/* dest_y */,
15012 		uint		/* width */,
15013 		uint		/* height */
15014 	);
15015 
15016 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
15017 
15018 
15019 	int XDestroyWindow(
15020 		Display*	/* display */,
15021 		Window		/* w */
15022 	);
15023 
15024 	int XDestroyImage(XImage*);
15025 
15026 	int XSelectInput(
15027 		Display*	/* display */,
15028 		Window		/* w */,
15029 		EventMask	/* event_mask */
15030 	);
15031 
15032 	int XMapWindow(
15033 		Display*	/* display */,
15034 		Window		/* w */
15035 	);
15036 
15037 	Status XIconifyWindow(Display*, Window, int);
15038 	int XMapRaised(Display*, Window);
15039 	int XMapSubwindows(Display*, Window);
15040 
15041 	int XNextEvent(
15042 		Display*	/* display */,
15043 		XEvent*		/* event_return */
15044 	);
15045 
15046 	int XMaskEvent(Display*, arch_long, XEvent*);
15047 
15048 	Bool XFilterEvent(XEvent *event, Window window);
15049 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
15050 
15051 	Status XSetWMProtocols(
15052 		Display*	/* display */,
15053 		Window		/* w */,
15054 		Atom*		/* protocols */,
15055 		int			/* count */
15056 	);
15057 
15058 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
15059 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
15060 
15061 
15062 	Status XInitThreads();
15063 	void XLockDisplay (Display* display);
15064 	void XUnlockDisplay (Display* display);
15065 
15066 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
15067 
15068 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
15069 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
15070 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
15071 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
15072 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
15073 
15074 
15075 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
15076 	int XDrawString(Display*, Drawable, GC, int, int, in char*, int);
15077 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
15078 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
15079 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
15080 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
15081 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
15082 	int XDrawPoint(Display*, Drawable, GC, int, int);
15083 	int XSetForeground(Display*, GC, uint);
15084 	int XSetBackground(Display*, GC, uint);
15085 
15086 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
15087 	void XFreeFontSet(Display*, XFontSet);
15088 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int);
15089 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
15090 
15091 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
15092 	 	
15093 
15094 //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);
15095 
15096 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
15097 	int XSetFunction(Display*, GC, int);
15098 
15099 	GC XCreateGC(Display*, Drawable, uint, void*);
15100 	int XCopyGC(Display*, GC, uint, GC);
15101 	int XFreeGC(Display*, GC);
15102 
15103 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
15104 	bool XCheckMaskEvent(Display*, int, XEvent*);
15105 
15106 	int XPending(Display*);
15107 	int XEventsQueued(Display* display, int mode);
15108 
15109 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
15110 	int XFreePixmap(Display*, Pixmap);
15111 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
15112 	int XFlush(Display*);
15113 	int XBell(Display*, int);
15114 	int XSync(Display*, bool);
15115 
15116 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
15117 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
15118 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
15119 
15120 	KeySym XStringToKeysym(const char *string);
15121 
15122 	Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return);
15123 
15124 	Window XDefaultRootWindow(Display*);
15125 
15126 	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);
15127 
15128 	int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 
15129 
15130 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
15131 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
15132 
15133 	Status XAllocColor(Display*, Colormap, XColor*);
15134 
15135 	int XWithdrawWindow(Display*, Window, int);
15136 	int XUnmapWindow(Display*, Window);
15137 	int XLowerWindow(Display*, Window);
15138 	int XRaiseWindow(Display*, Window);
15139 
15140 	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);
15141 	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);
15142 
15143 	int XGetInputFocus(Display*, Window*, int*);
15144 	int XSetInputFocus(Display*, Window, int, Time);
15145 
15146 	XErrorHandler XSetErrorHandler(XErrorHandler);
15147 
15148 	int XGetErrorText(Display*, int, char*, int);
15149 
15150 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
15151 
15152 
15153 	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);
15154 	int XUngrabPointer(Display *display, Time time);
15155 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
15156 
15157 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
15158 
15159 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
15160 	int XSetClipMask(Display*, GC, Pixmap);
15161 	int XSetClipOrigin(Display*, GC, int, int);
15162 
15163 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
15164 
15165 	void XSetWMName(Display*, Window, XTextProperty*);
15166 	Status XGetWMName(Display*, Window, XTextProperty*);
15167 	int XStoreName(Display* display, Window w, const(char)* window_name);
15168 
15169 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
15170 
15171 }
15172 }
15173 
15174 interface Xext {
15175 extern(C) nothrow @nogc {
15176 	Status XShmAttach(Display*, XShmSegmentInfo*);
15177 	Status XShmDetach(Display*, XShmSegmentInfo*);
15178 	Status XShmPutImage(
15179 		Display*            /* dpy */,
15180 		Drawable            /* d */,
15181 		GC                  /* gc */,
15182 		XImage*             /* image */,
15183 		int                 /* src_x */,
15184 		int                 /* src_y */,
15185 		int                 /* dst_x */,
15186 		int                 /* dst_y */,
15187 		uint        /* src_width */,
15188 		uint        /* src_height */,
15189 		Bool                /* send_event */
15190 	);
15191 
15192 	Status XShmQueryExtension(Display*);
15193 
15194 	XImage *XShmCreateImage(
15195 		Display*            /* dpy */,
15196 		Visual*             /* visual */,
15197 		uint        /* depth */,
15198 		int                 /* format */,
15199 		char*               /* data */,
15200 		XShmSegmentInfo*    /* shminfo */,
15201 		uint        /* width */,
15202 		uint        /* height */
15203 	);
15204 
15205 	Pixmap XShmCreatePixmap(
15206 		Display*            /* dpy */,
15207 		Drawable            /* d */,
15208 		char*               /* data */,
15209 		XShmSegmentInfo*    /* shminfo */,
15210 		uint        /* width */,
15211 		uint        /* height */,
15212 		uint        /* depth */
15213 	);
15214 
15215 }
15216 }
15217 
15218 	// this requires -lXpm
15219 	//int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
15220 
15221 
15222 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib;
15223 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext;
15224 shared static this() {
15225 	xlib.loadDynamicLibrary();
15226 	xext.loadDynamicLibrary();
15227 }
15228 
15229 
15230 extern(C) nothrow @nogc {
15231 
15232 alias XrmDatabase = void*;
15233 struct XrmValue {
15234 	uint size;
15235 	void* addr;
15236 }
15237 
15238 struct XVisualInfo {
15239 	Visual* visual;
15240 	VisualID visualid;
15241 	int screen;
15242 	uint depth;
15243 	int c_class;
15244 	c_ulong red_mask;
15245 	c_ulong green_mask;
15246 	c_ulong blue_mask;
15247 	int colormap_size;
15248 	int bits_per_rgb;
15249 }
15250 
15251 enum VisualNoMask=	0x0;
15252 enum VisualIDMask=	0x1;
15253 enum VisualScreenMask=0x2;
15254 enum VisualDepthMask=	0x4;
15255 enum VisualClassMask=	0x8;
15256 enum VisualRedMaskMask=0x10;
15257 enum VisualGreenMaskMask=0x20;
15258 enum VisualBlueMaskMask=0x40;
15259 enum VisualColormapSizeMask=0x80;
15260 enum VisualBitsPerRGBMask=0x100;
15261 enum VisualAllMask=	0x1FF;
15262 
15263 
15264 // XIM and other crap
15265 struct _XOM {}
15266 struct _XIM {}
15267 struct _XIC {}
15268 alias XOM = _XOM*;
15269 alias XIM = _XIM*;
15270 alias XIC = _XIC*;
15271 
15272 alias XIMStyle = arch_ulong;
15273 enum : arch_ulong {
15274 	XIMPreeditArea      = 0x0001,
15275 	XIMPreeditCallbacks = 0x0002,
15276 	XIMPreeditPosition  = 0x0004,
15277 	XIMPreeditNothing   = 0x0008,
15278 	XIMPreeditNone      = 0x0010,
15279 	XIMStatusArea       = 0x0100,
15280 	XIMStatusCallbacks  = 0x0200,
15281 	XIMStatusNothing    = 0x0400,
15282 	XIMStatusNone       = 0x0800,
15283 }
15284 
15285 
15286 /* X Shared Memory Extension functions */
15287 	//pragma(lib, "Xshm");
15288 	alias arch_ulong ShmSeg;
15289 	struct XShmSegmentInfo {
15290 		ShmSeg shmseg;
15291 		int shmid;
15292 		ubyte* shmaddr;
15293 		Bool readOnly;
15294 	}
15295 
15296 	// and the necessary OS functions
15297 	int shmget(int, size_t, int);
15298 	void* shmat(int, in void*, int);
15299 	int shmdt(in void*);
15300 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
15301 
15302 	enum IPC_PRIVATE = 0;
15303 	enum IPC_CREAT = 512;
15304 	enum IPC_RMID = 0;
15305 
15306 /* MIT-SHM end */
15307 
15308 
15309 enum MappingType:int {
15310 	MappingModifier		=0,
15311 	MappingKeyboard		=1,
15312 	MappingPointer		=2
15313 }
15314 
15315 /* ImageFormat -- PutImage, GetImage */
15316 enum ImageFormat:int {
15317 	XYBitmap	=0,	/* depth 1, XYFormat */
15318 	XYPixmap	=1,	/* depth == drawable depth */
15319 	ZPixmap	=2	/* depth == drawable depth */
15320 }
15321 
15322 enum ModifierName:int {
15323 	ShiftMapIndex	=0,
15324 	LockMapIndex	=1,
15325 	ControlMapIndex	=2,
15326 	Mod1MapIndex	=3,
15327 	Mod2MapIndex	=4,
15328 	Mod3MapIndex	=5,
15329 	Mod4MapIndex	=6,
15330 	Mod5MapIndex	=7
15331 }
15332 
15333 enum ButtonMask:int {
15334 	Button1Mask	=1<<8,
15335 	Button2Mask	=1<<9,
15336 	Button3Mask	=1<<10,
15337 	Button4Mask	=1<<11,
15338 	Button5Mask	=1<<12,
15339 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
15340 }
15341 
15342 enum KeyOrButtonMask:uint {
15343 	ShiftMask	=1<<0,
15344 	LockMask	=1<<1,
15345 	ControlMask	=1<<2,
15346 	Mod1Mask	=1<<3,
15347 	Mod2Mask	=1<<4,
15348 	Mod3Mask	=1<<5,
15349 	Mod4Mask	=1<<6,
15350 	Mod5Mask	=1<<7,
15351 	Button1Mask	=1<<8,
15352 	Button2Mask	=1<<9,
15353 	Button3Mask	=1<<10,
15354 	Button4Mask	=1<<11,
15355 	Button5Mask	=1<<12,
15356 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
15357 }
15358 
15359 enum ButtonName:int {
15360 	Button1	=1,
15361 	Button2	=2,
15362 	Button3	=3,
15363 	Button4	=4,
15364 	Button5	=5
15365 }
15366 
15367 /* Notify modes */
15368 enum NotifyModes:int
15369 {
15370 	NotifyNormal		=0,
15371 	NotifyGrab			=1,
15372 	NotifyUngrab		=2,
15373 	NotifyWhileGrabbed	=3
15374 }
15375 enum NotifyHint = 1;	/* for MotionNotify events */
15376 
15377 /* Notify detail */
15378 enum NotifyDetail:int
15379 {
15380 	NotifyAncestor			=0,
15381 	NotifyVirtual			=1,
15382 	NotifyInferior			=2,
15383 	NotifyNonlinear			=3,
15384 	NotifyNonlinearVirtual	=4,
15385 	NotifyPointer			=5,
15386 	NotifyPointerRoot		=6,
15387 	NotifyDetailNone		=7
15388 }
15389 
15390 /* Visibility notify */
15391 
15392 enum VisibilityNotify:int
15393 {
15394 VisibilityUnobscured		=0,
15395 VisibilityPartiallyObscured	=1,
15396 VisibilityFullyObscured		=2
15397 }
15398 
15399 
15400 enum WindowStackingMethod:int
15401 {
15402 	Above		=0,
15403 	Below		=1,
15404 	TopIf		=2,
15405 	BottomIf	=3,
15406 	Opposite	=4
15407 }
15408 
15409 /* Circulation request */
15410 enum CirculationRequest:int
15411 {
15412 	PlaceOnTop		=0,
15413 	PlaceOnBottom	=1
15414 }
15415 
15416 enum PropertyNotification:int
15417 {
15418 	PropertyNewValue	=0,
15419 	PropertyDelete		=1
15420 }
15421 
15422 enum ColorMapNotification:int
15423 {
15424 	ColormapUninstalled	=0,
15425 	ColormapInstalled		=1
15426 }
15427 
15428 
15429 	struct _XPrivate {}
15430 	struct _XrmHashBucketRec {}
15431 
15432 	alias void* XPointer;
15433 	alias void* XExtData;
15434 
15435 	version( X86_64 ) {
15436 		alias ulong XID;
15437 		alias ulong arch_ulong;
15438 		alias long arch_long;
15439 	} else version (AArch64) {
15440 		alias ulong XID;
15441 		alias ulong arch_ulong;
15442 		alias long arch_long;
15443 	} else {
15444 		alias uint XID;
15445 		alias uint arch_ulong;
15446 		alias int arch_long;
15447 	}
15448 
15449 	alias XID Window;
15450 	alias XID Drawable;
15451 	alias XID Pixmap;
15452 
15453 	alias arch_ulong Atom;
15454 	alias int Bool;
15455 	alias Display XDisplay;
15456 
15457 	alias int ByteOrder;
15458 	alias arch_ulong Time;
15459 	alias void ScreenFormat;
15460 
15461 	struct XImage {
15462 		int width, height;			/* size of image */
15463 		int xoffset;				/* number of pixels offset in X direction */
15464 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
15465 		void *data;					/* pointer to image data */
15466 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
15467 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
15468 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
15469 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
15470 		int depth;					/* depth of image */
15471 		int bytes_per_line;			/* accelarator to next line */
15472 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
15473 		arch_ulong red_mask;	/* bits in z arrangment */
15474 		arch_ulong green_mask;
15475 		arch_ulong blue_mask;
15476 		XPointer obdata;			/* hook for the object routines to hang on */
15477 		static struct F {				/* image manipulation routines */
15478 			XImage* function(
15479 				XDisplay* 			/* display */,
15480 				Visual*				/* visual */,
15481 				uint				/* depth */,
15482 				int					/* format */,
15483 				int					/* offset */,
15484 				ubyte*				/* data */,
15485 				uint				/* width */,
15486 				uint				/* height */,
15487 				int					/* bitmap_pad */,
15488 				int					/* bytes_per_line */) create_image;
15489 			int function(XImage *) destroy_image;
15490 			arch_ulong function(XImage *, int, int) get_pixel;
15491 			int function(XImage *, int, int, arch_ulong) put_pixel;
15492 			XImage* function(XImage *, int, int, uint, uint) sub_image;
15493 			int function(XImage *, arch_long) add_pixel;
15494 		}
15495 		F f;
15496 	}
15497 	version(X86_64) static assert(XImage.sizeof == 136);
15498 	else version(X86) static assert(XImage.sizeof == 88);
15499 
15500 struct XCharStruct {
15501 	short       lbearing;       /* origin to left edge of raster */
15502 	short       rbearing;       /* origin to right edge of raster */
15503 	short       width;          /* advance to next char's origin */
15504 	short       ascent;         /* baseline to top edge of raster */
15505 	short       descent;        /* baseline to bottom edge of raster */
15506 	ushort attributes;  /* per char flags (not predefined) */
15507 }
15508 
15509 /*
15510  * To allow arbitrary information with fonts, there are additional properties
15511  * returned.
15512  */
15513 struct XFontProp {
15514 	Atom name;
15515 	arch_ulong card32;
15516 }
15517 
15518 alias Atom Font;
15519 
15520 struct XFontStruct {
15521 	XExtData *ext_data;           /* Hook for extension to hang data */
15522 	Font fid;                     /* Font ID for this font */
15523 	uint direction;           /* Direction the font is painted */
15524 	uint min_char_or_byte2;   /* First character */
15525 	uint max_char_or_byte2;   /* Last character */
15526 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
15527 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
15528 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
15529 	uint default_char;        /* Char to print for undefined character */
15530 	int n_properties;             /* How many properties there are */
15531 	XFontProp *properties;        /* Pointer to array of additional properties*/
15532 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
15533 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
15534 	XCharStruct *per_char;        /* first_char to last_char information */
15535 	int ascent;                   /* Max extent above baseline for spacing */
15536 	int descent;                  /* Max descent below baseline for spacing */
15537 }
15538 
15539 
15540 /*
15541  * Definitions of specific events.
15542  */
15543 struct XKeyEvent
15544 {
15545 	int type;			/* of event */
15546 	arch_ulong serial;		/* # of last request processed by server */
15547 	Bool send_event;	/* true if this came from a SendEvent request */
15548 	Display *display;	/* Display the event was read from */
15549 	Window window;	        /* "event" window it is reported relative to */
15550 	Window root;	        /* root window that the event occurred on */
15551 	Window subwindow;	/* child window */
15552 	Time time;		/* milliseconds */
15553 	int x, y;		/* pointer x, y coordinates in event window */
15554 	int x_root, y_root;	/* coordinates relative to root */
15555 	KeyOrButtonMask state;	/* key or button mask */
15556 	uint keycode;	/* detail */
15557 	Bool same_screen;	/* same screen flag */
15558 }
15559 version(X86_64) static assert(XKeyEvent.sizeof == 96);
15560 alias XKeyEvent XKeyPressedEvent;
15561 alias XKeyEvent XKeyReleasedEvent;
15562 
15563 struct XButtonEvent
15564 {
15565 	int type;		/* of event */
15566 	arch_ulong serial;	/* # of last request processed by server */
15567 	Bool send_event;	/* true if this came from a SendEvent request */
15568 	Display *display;	/* Display the event was read from */
15569 	Window window;	        /* "event" window it is reported relative to */
15570 	Window root;	        /* root window that the event occurred on */
15571 	Window subwindow;	/* child window */
15572 	Time time;		/* milliseconds */
15573 	int x, y;		/* pointer x, y coordinates in event window */
15574 	int x_root, y_root;	/* coordinates relative to root */
15575 	KeyOrButtonMask state;	/* key or button mask */
15576 	uint button;	/* detail */
15577 	Bool same_screen;	/* same screen flag */
15578 }
15579 alias XButtonEvent XButtonPressedEvent;
15580 alias XButtonEvent XButtonReleasedEvent;
15581 
15582 struct XMotionEvent{
15583 	int type;		/* of event */
15584 	arch_ulong serial;	/* # of last request processed by server */
15585 	Bool send_event;	/* true if this came from a SendEvent request */
15586 	Display *display;	/* Display the event was read from */
15587 	Window window;	        /* "event" window reported relative to */
15588 	Window root;	        /* root window that the event occurred on */
15589 	Window subwindow;	/* child window */
15590 	Time time;		/* milliseconds */
15591 	int x, y;		/* pointer x, y coordinates in event window */
15592 	int x_root, y_root;	/* coordinates relative to root */
15593 	KeyOrButtonMask state;	/* key or button mask */
15594 	byte is_hint;		/* detail */
15595 	Bool same_screen;	/* same screen flag */
15596 }
15597 alias XMotionEvent XPointerMovedEvent;
15598 
15599 struct XCrossingEvent{
15600 	int type;		/* of event */
15601 	arch_ulong serial;	/* # of last request processed by server */
15602 	Bool send_event;	/* true if this came from a SendEvent request */
15603 	Display *display;	/* Display the event was read from */
15604 	Window window;	        /* "event" window reported relative to */
15605 	Window root;	        /* root window that the event occurred on */
15606 	Window subwindow;	/* child window */
15607 	Time time;		/* milliseconds */
15608 	int x, y;		/* pointer x, y coordinates in event window */
15609 	int x_root, y_root;	/* coordinates relative to root */
15610 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
15611 	NotifyDetail detail;
15612 	/*
15613 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
15614 	 * NotifyNonlinear,NotifyNonlinearVirtual
15615 	 */
15616 	Bool same_screen;	/* same screen flag */
15617 	Bool focus;		/* Boolean focus */
15618 	KeyOrButtonMask state;	/* key or button mask */
15619 }
15620 alias XCrossingEvent XEnterWindowEvent;
15621 alias XCrossingEvent XLeaveWindowEvent;
15622 
15623 struct XFocusChangeEvent{
15624 	int type;		/* FocusIn or FocusOut */
15625 	arch_ulong serial;	/* # of last request processed by server */
15626 	Bool send_event;	/* true if this came from a SendEvent request */
15627 	Display *display;	/* Display the event was read from */
15628 	Window window;		/* window of event */
15629 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
15630 				   NotifyGrab, NotifyUngrab */
15631 	NotifyDetail detail;
15632 	/*
15633 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
15634 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
15635 	 * NotifyPointerRoot, NotifyDetailNone
15636 	 */
15637 }
15638 alias XFocusChangeEvent XFocusInEvent;
15639 alias XFocusChangeEvent XFocusOutEvent;
15640 
15641 enum CWBackPixmap              = (1L<<0);
15642 enum CWBackPixel               = (1L<<1);
15643 enum CWBorderPixmap            = (1L<<2);
15644 enum CWBorderPixel             = (1L<<3);
15645 enum CWBitGravity              = (1L<<4);
15646 enum CWWinGravity              = (1L<<5);
15647 enum CWBackingStore            = (1L<<6);
15648 enum CWBackingPlanes           = (1L<<7);
15649 enum CWBackingPixel            = (1L<<8);
15650 enum CWOverrideRedirect        = (1L<<9);
15651 enum CWSaveUnder               = (1L<<10);
15652 enum CWEventMask               = (1L<<11);
15653 enum CWDontPropagate           = (1L<<12);
15654 enum CWColormap                = (1L<<13);
15655 enum CWCursor                  = (1L<<14);
15656 
15657 struct XWindowAttributes {
15658 	int x, y;			/* location of window */
15659 	int width, height;		/* width and height of window */
15660 	int border_width;		/* border width of window */
15661 	int depth;			/* depth of window */
15662 	Visual *visual;			/* the associated visual structure */
15663 	Window root;			/* root of screen containing window */
15664 	int class_;			/* InputOutput, InputOnly*/
15665 	int bit_gravity;		/* one of the bit gravity values */
15666 	int win_gravity;		/* one of the window gravity values */
15667 	int backing_store;		/* NotUseful, WhenMapped, Always */
15668 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
15669 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
15670 	Bool save_under;		/* boolean, should bits under be saved? */
15671 	Colormap colormap;		/* color map to be associated with window */
15672 	Bool map_installed;		/* boolean, is color map currently installed*/
15673 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
15674 	arch_long all_event_masks;		/* set of events all people have interest in*/
15675 	arch_long your_event_mask;		/* my event mask */
15676 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
15677 	Bool override_redirect;		/* boolean value for override-redirect */
15678 	Screen *screen;			/* back pointer to correct screen */
15679 }
15680 
15681 enum IsUnmapped = 0;
15682 enum IsUnviewable = 1;
15683 enum IsViewable = 2;
15684 
15685 struct XSetWindowAttributes {
15686 	Pixmap background_pixmap;/* background, None, or ParentRelative */
15687 	arch_ulong background_pixel;/* background pixel */
15688 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
15689 	arch_ulong border_pixel;/* border pixel value */
15690 	int bit_gravity;         /* one of bit gravity values */
15691 	int win_gravity;         /* one of the window gravity values */
15692 	int backing_store;       /* NotUseful, WhenMapped, Always */
15693 	arch_ulong backing_planes;/* planes to be preserved if possible */
15694 	arch_ulong backing_pixel;/* value to use in restoring planes */
15695 	Bool save_under;         /* should bits under be saved? (popups) */
15696 	arch_long event_mask;         /* set of events that should be saved */
15697 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
15698 	Bool override_redirect;  /* boolean value for override_redirect */
15699 	Colormap colormap;       /* color map to be associated with window */
15700 	Cursor cursor;           /* cursor to be displayed (or None) */
15701 }
15702 
15703 
15704 alias int Status;
15705 
15706 
15707 enum EventMask:int
15708 {
15709 	NoEventMask				=0,
15710 	KeyPressMask			=1<<0,
15711 	KeyReleaseMask			=1<<1,
15712 	ButtonPressMask			=1<<2,
15713 	ButtonReleaseMask		=1<<3,
15714 	EnterWindowMask			=1<<4,
15715 	LeaveWindowMask			=1<<5,
15716 	PointerMotionMask		=1<<6,
15717 	PointerMotionHintMask	=1<<7,
15718 	Button1MotionMask		=1<<8,
15719 	Button2MotionMask		=1<<9,
15720 	Button3MotionMask		=1<<10,
15721 	Button4MotionMask		=1<<11,
15722 	Button5MotionMask		=1<<12,
15723 	ButtonMotionMask		=1<<13,
15724 	KeymapStateMask		=1<<14,
15725 	ExposureMask			=1<<15,
15726 	VisibilityChangeMask	=1<<16,
15727 	StructureNotifyMask		=1<<17,
15728 	ResizeRedirectMask		=1<<18,
15729 	SubstructureNotifyMask	=1<<19,
15730 	SubstructureRedirectMask=1<<20,
15731 	FocusChangeMask			=1<<21,
15732 	PropertyChangeMask		=1<<22,
15733 	ColormapChangeMask		=1<<23,
15734 	OwnerGrabButtonMask		=1<<24
15735 }
15736 
15737 struct MwmHints {
15738 	c_ulong flags;
15739 	c_ulong functions;
15740 	c_ulong decorations;
15741 	c_long input_mode;
15742 	c_ulong status;
15743 }
15744 
15745 enum {
15746 	MWM_HINTS_FUNCTIONS = (1L << 0),
15747 	MWM_HINTS_DECORATIONS =  (1L << 1),
15748 
15749 	MWM_FUNC_ALL = (1L << 0),
15750 	MWM_FUNC_RESIZE = (1L << 1),
15751 	MWM_FUNC_MOVE = (1L << 2),
15752 	MWM_FUNC_MINIMIZE = (1L << 3),
15753 	MWM_FUNC_MAXIMIZE = (1L << 4),
15754 	MWM_FUNC_CLOSE = (1L << 5),
15755 
15756 	MWM_DECOR_ALL = (1L << 0),
15757 	MWM_DECOR_BORDER = (1L << 1),
15758 	MWM_DECOR_RESIZEH = (1L << 2),
15759 	MWM_DECOR_TITLE = (1L << 3),
15760 	MWM_DECOR_MENU = (1L << 4),
15761 	MWM_DECOR_MINIMIZE = (1L << 5),
15762 	MWM_DECOR_MAXIMIZE = (1L << 6),
15763 }
15764 
15765 import core.stdc.config : c_long, c_ulong;
15766 
15767 	/* Size hints mask bits */
15768 
15769 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
15770 	enum   USSize      = (1L << 1)          /* user specified width, height */;
15771 	enum   PPosition   = (1L << 2)          /* program specified position */;
15772 	enum   PSize       = (1L << 3)          /* program specified size */;
15773 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
15774 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
15775 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
15776 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
15777 	enum   PBaseSize   = (1L << 8);
15778 	enum   PWinGravity = (1L << 9);
15779 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
15780 	struct XSizeHints {
15781 		arch_long flags;         /* marks which fields in this structure are defined */
15782 		int x, y;           /* Obsolete */
15783 		int width, height;  /* Obsolete */
15784 		int min_width, min_height;
15785 		int max_width, max_height;
15786 		int width_inc, height_inc;
15787 		struct Aspect {
15788 			int x;       /* numerator */
15789 			int y;       /* denominator */
15790 		}
15791 
15792 		Aspect min_aspect;
15793 		Aspect max_aspect;
15794 		int base_width, base_height;
15795 		int win_gravity;
15796 		/* this structure may be extended in the future */
15797 	}
15798 
15799 
15800 
15801 enum EventType:int
15802 {
15803 	KeyPress			=2,
15804 	KeyRelease			=3,
15805 	ButtonPress			=4,
15806 	ButtonRelease		=5,
15807 	MotionNotify		=6,
15808 	EnterNotify			=7,
15809 	LeaveNotify			=8,
15810 	FocusIn				=9,
15811 	FocusOut			=10,
15812 	KeymapNotify		=11,
15813 	Expose				=12,
15814 	GraphicsExpose		=13,
15815 	NoExpose			=14,
15816 	VisibilityNotify	=15,
15817 	CreateNotify		=16,
15818 	DestroyNotify		=17,
15819 	UnmapNotify		=18,
15820 	MapNotify			=19,
15821 	MapRequest			=20,
15822 	ReparentNotify		=21,
15823 	ConfigureNotify		=22,
15824 	ConfigureRequest	=23,
15825 	GravityNotify		=24,
15826 	ResizeRequest		=25,
15827 	CirculateNotify		=26,
15828 	CirculateRequest	=27,
15829 	PropertyNotify		=28,
15830 	SelectionClear		=29,
15831 	SelectionRequest	=30,
15832 	SelectionNotify		=31,
15833 	ColormapNotify		=32,
15834 	ClientMessage		=33,
15835 	MappingNotify		=34,
15836 	LASTEvent			=35	/* must be bigger than any event # */
15837 }
15838 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
15839 struct XKeymapEvent
15840 {
15841 	int type;
15842 	arch_ulong serial;	/* # of last request processed by server */
15843 	Bool send_event;	/* true if this came from a SendEvent request */
15844 	Display *display;	/* Display the event was read from */
15845 	Window window;
15846 	byte[32] key_vector;
15847 }
15848 
15849 struct XExposeEvent
15850 {
15851 	int type;
15852 	arch_ulong serial;	/* # of last request processed by server */
15853 	Bool send_event;	/* true if this came from a SendEvent request */
15854 	Display *display;	/* Display the event was read from */
15855 	Window window;
15856 	int x, y;
15857 	int width, height;
15858 	int count;		/* if non-zero, at least this many more */
15859 }
15860 
15861 struct XGraphicsExposeEvent{
15862 	int type;
15863 	arch_ulong serial;	/* # of last request processed by server */
15864 	Bool send_event;	/* true if this came from a SendEvent request */
15865 	Display *display;	/* Display the event was read from */
15866 	Drawable drawable;
15867 	int x, y;
15868 	int width, height;
15869 	int count;		/* if non-zero, at least this many more */
15870 	int major_code;		/* core is CopyArea or CopyPlane */
15871 	int minor_code;		/* not defined in the core */
15872 }
15873 
15874 struct XNoExposeEvent{
15875 	int type;
15876 	arch_ulong serial;	/* # of last request processed by server */
15877 	Bool send_event;	/* true if this came from a SendEvent request */
15878 	Display *display;	/* Display the event was read from */
15879 	Drawable drawable;
15880 	int major_code;		/* core is CopyArea or CopyPlane */
15881 	int minor_code;		/* not defined in the core */
15882 }
15883 
15884 struct XVisibilityEvent{
15885 	int type;
15886 	arch_ulong serial;	/* # of last request processed by server */
15887 	Bool send_event;	/* true if this came from a SendEvent request */
15888 	Display *display;	/* Display the event was read from */
15889 	Window window;
15890 	VisibilityNotify state;		/* Visibility state */
15891 }
15892 
15893 struct XCreateWindowEvent{
15894 	int type;
15895 	arch_ulong serial;	/* # of last request processed by server */
15896 	Bool send_event;	/* true if this came from a SendEvent request */
15897 	Display *display;	/* Display the event was read from */
15898 	Window parent;		/* parent of the window */
15899 	Window window;		/* window id of window created */
15900 	int x, y;		/* window location */
15901 	int width, height;	/* size of window */
15902 	int border_width;	/* border width */
15903 	Bool override_redirect;	/* creation should be overridden */
15904 }
15905 
15906 struct XDestroyWindowEvent
15907 {
15908 	int type;
15909 	arch_ulong serial;		/* # of last request processed by server */
15910 	Bool send_event;	/* true if this came from a SendEvent request */
15911 	Display *display;	/* Display the event was read from */
15912 	Window event;
15913 	Window window;
15914 }
15915 
15916 struct XUnmapEvent
15917 {
15918 	int type;
15919 	arch_ulong serial;		/* # of last request processed by server */
15920 	Bool send_event;	/* true if this came from a SendEvent request */
15921 	Display *display;	/* Display the event was read from */
15922 	Window event;
15923 	Window window;
15924 	Bool from_configure;
15925 }
15926 
15927 struct XMapEvent
15928 {
15929 	int type;
15930 	arch_ulong serial;		/* # of last request processed by server */
15931 	Bool send_event;	/* true if this came from a SendEvent request */
15932 	Display *display;	/* Display the event was read from */
15933 	Window event;
15934 	Window window;
15935 	Bool override_redirect;	/* Boolean, is override set... */
15936 }
15937 
15938 struct XMapRequestEvent
15939 {
15940 	int type;
15941 	arch_ulong serial;	/* # of last request processed by server */
15942 	Bool send_event;	/* true if this came from a SendEvent request */
15943 	Display *display;	/* Display the event was read from */
15944 	Window parent;
15945 	Window window;
15946 }
15947 
15948 struct XReparentEvent
15949 {
15950 	int type;
15951 	arch_ulong serial;	/* # of last request processed by server */
15952 	Bool send_event;	/* true if this came from a SendEvent request */
15953 	Display *display;	/* Display the event was read from */
15954 	Window event;
15955 	Window window;
15956 	Window parent;
15957 	int x, y;
15958 	Bool override_redirect;
15959 }
15960 
15961 struct XConfigureEvent
15962 {
15963 	int type;
15964 	arch_ulong serial;	/* # of last request processed by server */
15965 	Bool send_event;	/* true if this came from a SendEvent request */
15966 	Display *display;	/* Display the event was read from */
15967 	Window event;
15968 	Window window;
15969 	int x, y;
15970 	int width, height;
15971 	int border_width;
15972 	Window above;
15973 	Bool override_redirect;
15974 }
15975 
15976 struct XGravityEvent
15977 {
15978 	int type;
15979 	arch_ulong serial;	/* # of last request processed by server */
15980 	Bool send_event;	/* true if this came from a SendEvent request */
15981 	Display *display;	/* Display the event was read from */
15982 	Window event;
15983 	Window window;
15984 	int x, y;
15985 }
15986 
15987 struct XResizeRequestEvent
15988 {
15989 	int type;
15990 	arch_ulong serial;	/* # of last request processed by server */
15991 	Bool send_event;	/* true if this came from a SendEvent request */
15992 	Display *display;	/* Display the event was read from */
15993 	Window window;
15994 	int width, height;
15995 }
15996 
15997 struct  XConfigureRequestEvent
15998 {
15999 	int type;
16000 	arch_ulong serial;	/* # of last request processed by server */
16001 	Bool send_event;	/* true if this came from a SendEvent request */
16002 	Display *display;	/* Display the event was read from */
16003 	Window parent;
16004 	Window window;
16005 	int x, y;
16006 	int width, height;
16007 	int border_width;
16008 	Window above;
16009 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
16010 	arch_ulong value_mask;
16011 }
16012 
16013 struct XCirculateEvent
16014 {
16015 	int type;
16016 	arch_ulong serial;	/* # of last request processed by server */
16017 	Bool send_event;	/* true if this came from a SendEvent request */
16018 	Display *display;	/* Display the event was read from */
16019 	Window event;
16020 	Window window;
16021 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16022 }
16023 
16024 struct XCirculateRequestEvent
16025 {
16026 	int type;
16027 	arch_ulong serial;	/* # of last request processed by server */
16028 	Bool send_event;	/* true if this came from a SendEvent request */
16029 	Display *display;	/* Display the event was read from */
16030 	Window parent;
16031 	Window window;
16032 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
16033 }
16034 
16035 struct XPropertyEvent
16036 {
16037 	int type;
16038 	arch_ulong serial;	/* # of last request processed by server */
16039 	Bool send_event;	/* true if this came from a SendEvent request */
16040 	Display *display;	/* Display the event was read from */
16041 	Window window;
16042 	Atom atom;
16043 	Time time;
16044 	PropertyNotification state;		/* NewValue, Deleted */
16045 }
16046 
16047 struct XSelectionClearEvent
16048 {
16049 	int type;
16050 	arch_ulong serial;	/* # of last request processed by server */
16051 	Bool send_event;	/* true if this came from a SendEvent request */
16052 	Display *display;	/* Display the event was read from */
16053 	Window window;
16054 	Atom selection;
16055 	Time time;
16056 }
16057 
16058 struct XSelectionRequestEvent
16059 {
16060 	int type;
16061 	arch_ulong serial;	/* # of last request processed by server */
16062 	Bool send_event;	/* true if this came from a SendEvent request */
16063 	Display *display;	/* Display the event was read from */
16064 	Window owner;
16065 	Window requestor;
16066 	Atom selection;
16067 	Atom target;
16068 	Atom property;
16069 	Time time;
16070 }
16071 
16072 struct XSelectionEvent
16073 {
16074 	int type;
16075 	arch_ulong serial;	/* # of last request processed by server */
16076 	Bool send_event;	/* true if this came from a SendEvent request */
16077 	Display *display;	/* Display the event was read from */
16078 	Window requestor;
16079 	Atom selection;
16080 	Atom target;
16081 	Atom property;		/* ATOM or None */
16082 	Time time;
16083 }
16084 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
16085 
16086 struct XColormapEvent
16087 {
16088 	int type;
16089 	arch_ulong serial;	/* # of last request processed by server */
16090 	Bool send_event;	/* true if this came from a SendEvent request */
16091 	Display *display;	/* Display the event was read from */
16092 	Window window;
16093 	Colormap colormap;	/* COLORMAP or None */
16094 	Bool new_;		/* C++ */
16095 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
16096 }
16097 version(X86_64) static assert(XColormapEvent.sizeof == 56);
16098 
16099 struct XClientMessageEvent
16100 {
16101 	int type;
16102 	arch_ulong serial;	/* # of last request processed by server */
16103 	Bool send_event;	/* true if this came from a SendEvent request */
16104 	Display *display;	/* Display the event was read from */
16105 	Window window;
16106 	Atom message_type;
16107 	int format;
16108 	union Data{
16109 		byte[20] b;
16110 		short[10] s;
16111 		arch_ulong[5] l;
16112 	}
16113 	Data data;
16114 
16115 }
16116 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
16117 
16118 struct XMappingEvent
16119 {
16120 	int type;
16121 	arch_ulong serial;	/* # of last request processed by server */
16122 	Bool send_event;	/* true if this came from a SendEvent request */
16123 	Display *display;	/* Display the event was read from */
16124 	Window window;		/* unused */
16125 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
16126 				   MappingPointer */
16127 	int first_keycode;	/* first keycode */
16128 	int count;		/* defines range of change w. first_keycode*/
16129 }
16130 
16131 struct XErrorEvent
16132 {
16133 	int type;
16134 	Display *display;	/* Display the event was read from */
16135 	XID resourceid;		/* resource id */
16136 	arch_ulong serial;	/* serial number of failed request */
16137 	ubyte error_code;	/* error code of failed request */
16138 	ubyte request_code;	/* Major op-code of failed request */
16139 	ubyte minor_code;	/* Minor op-code of failed request */
16140 }
16141 
16142 struct XAnyEvent
16143 {
16144 	int type;
16145 	arch_ulong serial;	/* # of last request processed by server */
16146 	Bool send_event;	/* true if this came from a SendEvent request */
16147 	Display *display;/* Display the event was read from */
16148 	Window window;	/* window on which event was requested in event mask */
16149 }
16150 
16151 union XEvent{
16152 	int type;		/* must not be changed; first element */
16153 	XAnyEvent xany;
16154 	XKeyEvent xkey;
16155 	XButtonEvent xbutton;
16156 	XMotionEvent xmotion;
16157 	XCrossingEvent xcrossing;
16158 	XFocusChangeEvent xfocus;
16159 	XExposeEvent xexpose;
16160 	XGraphicsExposeEvent xgraphicsexpose;
16161 	XNoExposeEvent xnoexpose;
16162 	XVisibilityEvent xvisibility;
16163 	XCreateWindowEvent xcreatewindow;
16164 	XDestroyWindowEvent xdestroywindow;
16165 	XUnmapEvent xunmap;
16166 	XMapEvent xmap;
16167 	XMapRequestEvent xmaprequest;
16168 	XReparentEvent xreparent;
16169 	XConfigureEvent xconfigure;
16170 	XGravityEvent xgravity;
16171 	XResizeRequestEvent xresizerequest;
16172 	XConfigureRequestEvent xconfigurerequest;
16173 	XCirculateEvent xcirculate;
16174 	XCirculateRequestEvent xcirculaterequest;
16175 	XPropertyEvent xproperty;
16176 	XSelectionClearEvent xselectionclear;
16177 	XSelectionRequestEvent xselectionrequest;
16178 	XSelectionEvent xselection;
16179 	XColormapEvent xcolormap;
16180 	XClientMessageEvent xclient;
16181 	XMappingEvent xmapping;
16182 	XErrorEvent xerror;
16183 	XKeymapEvent xkeymap;
16184 	arch_ulong[24] pad;
16185 }
16186 
16187 
16188 	struct Display {
16189 		XExtData *ext_data;	/* hook for extension to hang data */
16190 		_XPrivate *private1;
16191 		int fd;			/* Network socket. */
16192 		int private2;
16193 		int proto_major_version;/* major version of server's X protocol */
16194 		int proto_minor_version;/* minor version of servers X protocol */
16195 		char *vendor;		/* vendor of the server hardware */
16196 	    	XID private3;
16197 		XID private4;
16198 		XID private5;
16199 		int private6;
16200 		XID function(Display*)resource_alloc;/* allocator function */
16201 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
16202 		int bitmap_unit;	/* padding and data requirements */
16203 		int bitmap_pad;		/* padding requirements on bitmaps */
16204 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
16205 		int nformats;		/* number of pixmap formats in list */
16206 		ScreenFormat *pixmap_format;	/* pixmap format list */
16207 		int private8;
16208 		int release;		/* release of the server */
16209 		_XPrivate *private9;
16210 		_XPrivate *private10;
16211 		int qlen;		/* Length of input event queue */
16212 		arch_ulong last_request_read; /* seq number of last event read */
16213 		arch_ulong request;	/* sequence number of last request. */
16214 		XPointer private11;
16215 		XPointer private12;
16216 		XPointer private13;
16217 		XPointer private14;
16218 		uint max_request_size; /* maximum number 32 bit words in request*/
16219 		_XrmHashBucketRec *db;
16220 		int function  (Display*)private15;
16221 		char *display_name;	/* "host:display" string used on this connect*/
16222 		int default_screen;	/* default screen for operations */
16223 		int nscreens;		/* number of screens on this server*/
16224 		Screen *screens;	/* pointer to list of screens */
16225 		arch_ulong motion_buffer;	/* size of motion buffer */
16226 		arch_ulong private16;
16227 		int min_keycode;	/* minimum defined keycode */
16228 		int max_keycode;	/* maximum defined keycode */
16229 		XPointer private17;
16230 		XPointer private18;
16231 		int private19;
16232 		byte *xdefaults;	/* contents of defaults from server */
16233 		/* there is more to this structure, but it is private to Xlib */
16234 	}
16235 
16236 	// I got these numbers from a C program as a sanity test
16237 	version(X86_64) {
16238 		static assert(Display.sizeof == 296);
16239 		static assert(XPointer.sizeof == 8);
16240 		static assert(XErrorEvent.sizeof == 40);
16241 		static assert(XAnyEvent.sizeof == 40);
16242 		static assert(XMappingEvent.sizeof == 56);
16243 		static assert(XEvent.sizeof == 192);
16244     	} else version (AArch64) {
16245         	// omit check for aarch64
16246 	} else {
16247 		static assert(Display.sizeof == 176);
16248 		static assert(XPointer.sizeof == 4);
16249 		static assert(XEvent.sizeof == 96);
16250 	}
16251 
16252 struct Depth
16253 {
16254 	int depth;		/* this depth (Z) of the depth */
16255 	int nvisuals;		/* number of Visual types at this depth */
16256 	Visual *visuals;	/* list of visuals possible at this depth */
16257 }
16258 
16259 alias void* GC;
16260 alias c_ulong VisualID;
16261 alias XID Colormap;
16262 alias XID Cursor;
16263 alias XID KeySym;
16264 alias uint KeyCode;
16265 enum None = 0;
16266 }
16267 
16268 version(without_opengl) {}
16269 else {
16270 extern(C) nothrow @nogc {
16271 
16272 
16273 static if(!SdpyIsUsingIVGLBinds) {
16274 enum GLX_USE_GL=            1;       /* support GLX rendering */
16275 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
16276 enum GLX_LEVEL=             3;       /* level in plane stacking */
16277 enum GLX_RGBA=              4;       /* true if RGBA mode */
16278 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
16279 enum GLX_STEREO=            6;       /* stereo buffering supported */
16280 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
16281 enum GLX_RED_SIZE=          8;       /* number of red component bits */
16282 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
16283 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
16284 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
16285 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
16286 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
16287 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
16288 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
16289 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
16290 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
16291 
16292 
16293 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
16294 
16295 
16296 
16297 enum GL_TRUE = 1;
16298 enum GL_FALSE = 0;
16299 alias int GLint;
16300 }
16301 
16302 alias XID GLXContextID;
16303 alias XID GLXPixmap;
16304 alias XID GLXDrawable;
16305 alias XID GLXPbuffer;
16306 alias XID GLXWindow;
16307 alias XID GLXFBConfigID;
16308 alias void* GLXContext;
16309 
16310 }
16311 }
16312 
16313 enum AllocNone = 0;
16314 
16315 extern(C) {
16316 	/* WARNING, this type not in Xlib spec */
16317 	extern(C) alias XIOErrorHandler = int function (Display* display);
16318 }
16319 
16320 extern(C) nothrow @nogc {
16321 struct Screen{
16322 	XExtData *ext_data;		/* hook for extension to hang data */
16323 	Display *display;		/* back pointer to display structure */
16324 	Window root;			/* Root window id. */
16325 	int width, height;		/* width and height of screen */
16326 	int mwidth, mheight;	/* width and height of  in millimeters */
16327 	int ndepths;			/* number of depths possible */
16328 	Depth *depths;			/* list of allowable depths on the screen */
16329 	int root_depth;			/* bits per pixel */
16330 	Visual *root_visual;	/* root visual */
16331 	GC default_gc;			/* GC for the root root visual */
16332 	Colormap cmap;			/* default color map */
16333 	uint white_pixel;
16334 	uint black_pixel;		/* White and Black pixel values */
16335 	int max_maps, min_maps;	/* max and min color maps */
16336 	int backing_store;		/* Never, WhenMapped, Always */
16337 	bool save_unders;
16338 	int root_input_mask;	/* initial root input mask */
16339 }
16340 
16341 struct Visual
16342 {
16343 	XExtData *ext_data;	/* hook for extension to hang data */
16344 	VisualID visualid;	/* visual id of this visual */
16345 	int class_;			/* class of screen (monochrome, etc.) */
16346 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
16347 	int bits_per_rgb;	/* log base 2 of distinct color values */
16348 	int map_entries;	/* color map entries */
16349 }
16350 
16351 	alias Display* _XPrivDisplay;
16352 
16353 	extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) {
16354 		assert(dpy !is null);
16355 		return &dpy.screens[scr];
16356 	}
16357 
16358 	extern(D) Window RootWindow(Display *dpy,int scr) {
16359 		return ScreenOfDisplay(dpy,scr).root;
16360 	}
16361 
16362 	struct XWMHints {
16363 		arch_long flags;
16364 		Bool input;
16365 		int initial_state;
16366 		Pixmap icon_pixmap;
16367 		Window icon_window;
16368 		int icon_x, icon_y;
16369 		Pixmap icon_mask;
16370 		XID window_group;
16371 	}
16372 
16373 	struct XClassHint {
16374 		char* res_name;
16375 		char* res_class;
16376 	}
16377 
16378 	extern(D) int DefaultScreen(Display *dpy) {
16379 		return dpy.default_screen;
16380 	}
16381 
16382 	extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
16383 	extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
16384 	extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
16385 	extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
16386 	extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
16387 	extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
16388 
16389 	extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; }
16390 
16391 	enum int AnyPropertyType = 0;
16392 	enum int Success = 0;
16393 
16394 	enum int RevertToNone = None;
16395 	enum int PointerRoot = 1;
16396 	enum Time CurrentTime = 0;
16397 	enum int RevertToPointerRoot = PointerRoot;
16398 	enum int RevertToParent = 2;
16399 
16400 	extern(D) int DefaultDepthOfDisplay(Display* dpy) {
16401 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
16402 	}
16403 
16404 	extern(D) Visual* DefaultVisual(Display *dpy,int scr) {
16405 		return ScreenOfDisplay(dpy,scr).root_visual;
16406 	}
16407 
16408 	extern(D) GC DefaultGC(Display *dpy,int scr) {
16409 		return ScreenOfDisplay(dpy,scr).default_gc;
16410 	}
16411 
16412 	extern(D) uint BlackPixel(Display *dpy,int scr) {
16413 		return ScreenOfDisplay(dpy,scr).black_pixel;
16414 	}
16415 
16416 	extern(D) uint WhitePixel(Display *dpy,int scr) {
16417 		return ScreenOfDisplay(dpy,scr).white_pixel;
16418 	}
16419 
16420 	alias void* XFontSet; // i think
16421 	struct XmbTextItem {
16422 		char* chars;
16423 		int nchars;
16424 		int delta;
16425 		XFontSet font_set;
16426 	}
16427 
16428 	struct XTextItem {
16429 		char* chars;
16430 		int nchars;
16431 		int delta;
16432 		Font font;
16433 	}
16434 
16435 	enum {
16436 		GXclear        = 0x0, /* 0 */
16437 		GXand          = 0x1, /* src AND dst */
16438 		GXandReverse   = 0x2, /* src AND NOT dst */
16439 		GXcopy         = 0x3, /* src */
16440 		GXandInverted  = 0x4, /* NOT src AND dst */
16441 		GXnoop         = 0x5, /* dst */
16442 		GXxor          = 0x6, /* src XOR dst */
16443 		GXor           = 0x7, /* src OR dst */
16444 		GXnor          = 0x8, /* NOT src AND NOT dst */
16445 		GXequiv        = 0x9, /* NOT src XOR dst */
16446 		GXinvert       = 0xa, /* NOT dst */
16447 		GXorReverse    = 0xb, /* src OR NOT dst */
16448 		GXcopyInverted = 0xc, /* NOT src */
16449 		GXorInverted   = 0xd, /* NOT src OR dst */
16450 		GXnand         = 0xe, /* NOT src OR NOT dst */
16451 		GXset          = 0xf, /* 1 */
16452 	}
16453 	enum QueueMode : int {
16454 		QueuedAlready,
16455 		QueuedAfterReading,
16456 		QueuedAfterFlush
16457 	}
16458 
16459 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
16460 
16461 	struct XPoint {
16462 		short x;
16463 		short y;
16464 	}
16465 
16466 	enum CoordMode:int {
16467 		CoordModeOrigin = 0,
16468 		CoordModePrevious = 1
16469 	}
16470 
16471 	enum PolygonShape:int {
16472 		Complex = 0,
16473 		Nonconvex = 1,
16474 		Convex = 2
16475 	}
16476 
16477 	struct XTextProperty {
16478 		const(char)* value;		/* same as Property routines */
16479 		Atom encoding;			/* prop type */
16480 		int format;				/* prop data format: 8, 16, or 32 */
16481 		arch_ulong nitems;		/* number of data items in value */
16482 	}
16483 
16484 	version( X86_64 ) {
16485 		static assert(XTextProperty.sizeof == 32);
16486 	}
16487 
16488 
16489 	struct XGCValues {
16490 		int function_;           /* logical operation */
16491 		arch_ulong plane_mask;/* plane mask */
16492 		arch_ulong foreground;/* foreground pixel */
16493 		arch_ulong background;/* background pixel */
16494 		int line_width;         /* line width */
16495 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
16496 		int cap_style;          /* CapNotLast, CapButt,
16497 					   CapRound, CapProjecting */
16498 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
16499 		int fill_style;         /* FillSolid, FillTiled,
16500 					   FillStippled, FillOpaeueStippled */
16501 		int fill_rule;          /* EvenOddRule, WindingRule */
16502 		int arc_mode;           /* ArcChord, ArcPieSlice */
16503 		Pixmap tile;            /* tile pixmap for tiling operations */
16504 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
16505 		int ts_x_origin;        /* offset for tile or stipple operations */
16506 		int ts_y_origin;
16507 		Font font;              /* default text font for text operations */
16508 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
16509 		Bool graphics_exposures;/* boolean, should exposures be generated */
16510 		int clip_x_origin;      /* origin for clipping */
16511 		int clip_y_origin;
16512 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
16513 		int dash_offset;        /* patterned/dashed line information */
16514 		char dashes;
16515 	}
16516 
16517 	struct XColor {
16518 		arch_ulong pixel;
16519 		ushort red, green, blue;
16520 		byte flags;
16521 		byte pad;
16522 	}
16523 
16524 	alias XErrorHandler = int function(Display*, XErrorEvent*);
16525 
16526 	struct XRectangle {
16527 		short x;
16528 		short y;
16529 		ushort width;
16530 		ushort height;
16531 	}
16532 
16533 	enum ClipByChildren = 0;
16534 	enum IncludeInferiors = 1;
16535 
16536 	enum Atom XA_PRIMARY = 1;
16537 	enum Atom XA_SECONDARY = 2;
16538 	enum Atom XA_STRING = 31;
16539 	enum Atom XA_CARDINAL = 6;
16540 	enum Atom XA_WM_NAME = 39;
16541 	enum Atom XA_ATOM = 4;
16542 	enum Atom XA_WINDOW = 33;
16543 	enum Atom XA_WM_HINTS = 35;
16544 	enum int PropModeAppend = 2;
16545 	enum int PropModeReplace = 0;
16546 	enum int PropModePrepend = 1;
16547 
16548 	enum int CopyFromParent = 0;
16549 	enum int InputOutput = 1;
16550 
16551 	// XWMHints
16552 	enum InputHint = 1 << 0;
16553 	enum StateHint = 1 << 1;
16554 	enum IconPixmapHint = (1L << 2);
16555 	enum IconWindowHint = (1L << 3);
16556 	enum IconPositionHint = (1L << 4);
16557 	enum IconMaskHint = (1L << 5);
16558 	enum WindowGroupHint = (1L << 6);
16559 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
16560 	enum XUrgencyHint = (1L << 8);
16561 
16562 	// GC Components
16563 	enum GCFunction           =   (1L<<0);
16564 	enum GCPlaneMask         =    (1L<<1);
16565 	enum GCForeground       =     (1L<<2);
16566 	enum GCBackground      =      (1L<<3);
16567 	enum GCLineWidth      =       (1L<<4);
16568 	enum GCLineStyle     =        (1L<<5);
16569 	enum GCCapStyle     =         (1L<<6);
16570 	enum GCJoinStyle   =          (1L<<7);
16571 	enum GCFillStyle  =           (1L<<8);
16572 	enum GCFillRule  =            (1L<<9);
16573 	enum GCTile     =             (1L<<10);
16574 	enum GCStipple           =    (1L<<11);
16575 	enum GCTileStipXOrigin  =     (1L<<12);
16576 	enum GCTileStipYOrigin =      (1L<<13);
16577 	enum GCFont               =   (1L<<14);
16578 	enum GCSubwindowMode     =    (1L<<15);
16579 	enum GCGraphicsExposures=     (1L<<16);
16580 	enum GCClipXOrigin     =      (1L<<17);
16581 	enum GCClipYOrigin    =       (1L<<18);
16582 	enum GCClipMask      =        (1L<<19);
16583 	enum GCDashOffset   =         (1L<<20);
16584 	enum GCDashList    =          (1L<<21);
16585 	enum GCArcMode    =           (1L<<22);
16586 	enum GCLastBit   =            22;
16587 
16588 
16589 	enum int WithdrawnState = 0;
16590 	enum int NormalState = 1;
16591 	enum int IconicState = 3;
16592 
16593 }
16594 } else version (OSXCocoa) {
16595 private:
16596 	alias void* id;
16597 	alias void* Class;
16598 	alias void* SEL;
16599 	alias void* IMP;
16600 	alias void* Ivar;
16601 	alias byte BOOL;
16602 	alias const(void)* CFStringRef;
16603 	alias const(void)* CFAllocatorRef;
16604 	alias const(void)* CFTypeRef;
16605 	alias const(void)* CGContextRef;
16606 	alias const(void)* CGColorSpaceRef;
16607 	alias const(void)* CGImageRef;
16608 	alias ulong CGBitmapInfo;
16609 
16610 	struct objc_super {
16611 		id self;
16612 		Class superclass;
16613 	}
16614 
16615 	struct CFRange {
16616 		long location, length;
16617 	}
16618 
16619 	struct NSPoint {
16620 		double x, y;
16621 
16622 		static fromTuple(T)(T tupl) {
16623 			return NSPoint(tupl.tupleof);
16624 		}
16625 	}
16626 	struct NSSize {
16627 		double width, height;
16628 	}
16629 	struct NSRect {
16630 		NSPoint origin;
16631 		NSSize size;
16632 	}
16633 	alias NSPoint CGPoint;
16634 	alias NSSize CGSize;
16635 	alias NSRect CGRect;
16636 
16637 	struct CGAffineTransform {
16638 		double a, b, c, d, tx, ty;
16639 	}
16640 
16641 	enum NSApplicationActivationPolicyRegular = 0;
16642 	enum NSBackingStoreBuffered = 2;
16643 	enum kCFStringEncodingUTF8 = 0x08000100;
16644 
16645 	enum : size_t {
16646 		NSBorderlessWindowMask = 0,
16647 		NSTitledWindowMask = 1 << 0,
16648 		NSClosableWindowMask = 1 << 1,
16649 		NSMiniaturizableWindowMask = 1 << 2,
16650 		NSResizableWindowMask = 1 << 3,
16651 		NSTexturedBackgroundWindowMask = 1 << 8
16652 	}
16653 
16654 	enum : ulong {
16655 		kCGImageAlphaNone,
16656 		kCGImageAlphaPremultipliedLast,
16657 		kCGImageAlphaPremultipliedFirst,
16658 		kCGImageAlphaLast,
16659 		kCGImageAlphaFirst,
16660 		kCGImageAlphaNoneSkipLast,
16661 		kCGImageAlphaNoneSkipFirst
16662 	}
16663 	enum : ulong {
16664 		kCGBitmapAlphaInfoMask = 0x1F,
16665 		kCGBitmapFloatComponents = (1 << 8),
16666 		kCGBitmapByteOrderMask = 0x7000,
16667 		kCGBitmapByteOrderDefault = (0 << 12),
16668 		kCGBitmapByteOrder16Little = (1 << 12),
16669 		kCGBitmapByteOrder32Little = (2 << 12),
16670 		kCGBitmapByteOrder16Big = (3 << 12),
16671 		kCGBitmapByteOrder32Big = (4 << 12)
16672 	}
16673 	enum CGPathDrawingMode {
16674 		kCGPathFill,
16675 		kCGPathEOFill,
16676 		kCGPathStroke,
16677 		kCGPathFillStroke,
16678 		kCGPathEOFillStroke
16679 	}
16680 	enum objc_AssociationPolicy : size_t {
16681 		OBJC_ASSOCIATION_ASSIGN = 0,
16682 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
16683 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
16684 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
16685 		OBJC_ASSOCIATION_COPY = 0x303 //01403
16686 	}
16687 
16688 	extern(C) {
16689 		id objc_msgSend(id receiver, SEL selector, ...);
16690 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
16691 		id objc_getClass(const(char)* name);
16692 		SEL sel_registerName(const(char)* str);
16693 		Class objc_allocateClassPair(Class superclass, const(char)* name,
16694 									 size_t extra_bytes);
16695 		void objc_registerClassPair(Class cls);
16696 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
16697 		id objc_getAssociatedObject(id object, void* key);
16698 		void objc_setAssociatedObject(id object, void* key, id value,
16699 									  objc_AssociationPolicy policy);
16700 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
16701 		id object_getIvar(id object, Ivar ivar);
16702 		void object_setIvar(id object, Ivar ivar, id value);
16703 		BOOL class_addIvar(Class cls, const(char)* name,
16704 						   size_t size, ubyte alignment, const(char)* types);
16705 
16706 		extern __gshared id NSApp;
16707 
16708 		void CFRelease(CFTypeRef obj);
16709 
16710 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
16711 											const(char)* bytes, long numBytes,
16712 											long encoding,
16713 											BOOL isExternalRepresentation);
16714 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
16715 							 char lossByte, bool isExternalRepresentation,
16716 							 char* buffer, long maxBufLen, long* usedBufLen);
16717 		long CFStringGetLength(CFStringRef theString);
16718 
16719 		CGContextRef CGBitmapContextCreate(void* data,
16720 										   size_t width, size_t height,
16721 										   size_t bitsPerComponent,
16722 										   size_t bytesPerRow,
16723 										   CGColorSpaceRef colorspace,
16724 										   CGBitmapInfo bitmapInfo);
16725 		void CGContextRelease(CGContextRef c);
16726 		ubyte* CGBitmapContextGetData(CGContextRef c);
16727 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
16728 		size_t CGBitmapContextGetWidth(CGContextRef c);
16729 		size_t CGBitmapContextGetHeight(CGContextRef c);
16730 
16731 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
16732 		void CGColorSpaceRelease(CGColorSpaceRef cs);
16733 
16734 		void CGContextSetRGBStrokeColor(CGContextRef c,
16735 										double red, double green, double blue,
16736 										double alpha);
16737 		void CGContextSetRGBFillColor(CGContextRef c,
16738 									  double red, double green, double blue,
16739 									  double alpha);
16740 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
16741 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
16742 									  const(char)* str, size_t length);
16743 		void CGContextStrokeLineSegments(CGContextRef c,
16744 										 const(CGPoint)* points, size_t count);
16745 
16746 		void CGContextBeginPath(CGContextRef c);
16747 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
16748 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
16749 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
16750 							 double startAngle, double endAngle, long clockwise);
16751 		void CGContextAddRect(CGContextRef c, CGRect rect);
16752 		void CGContextAddLines(CGContextRef c,
16753 							   const(CGPoint)* points, size_t count);
16754 		void CGContextSaveGState(CGContextRef c);
16755 		void CGContextRestoreGState(CGContextRef c);
16756 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
16757 								 ulong textEncoding);
16758 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
16759 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
16760 
16761 		void CGImageRelease(CGImageRef image);
16762 	}
16763 
16764 private:
16765     // A convenient method to create a CFString (=NSString) from a D string.
16766     CFStringRef createCFString(string str) {
16767         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
16768                                              kCFStringEncodingUTF8, false);
16769     }
16770 
16771     // Objective-C calls.
16772     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
16773         auto _cmd = sel_registerName(selector.ptr);
16774         alias extern(C) RetType function(id, SEL, T) ExpectedType;
16775         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
16776     }
16777     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
16778         auto _cmd = sel_registerName(selector.ptr);
16779         auto cls = objc_getClass(className);
16780         alias extern(C) RetType function(id, SEL, T) ExpectedType;
16781         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
16782     }
16783     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
16784         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
16785     }
16786 
16787     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
16788     alias objc_msgSend_classMethod!("alloc", id) alloc;
16789     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
16790                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
16791     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
16792     alias objc_msgSend_specialized!("center", void) center;
16793     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
16794     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
16795     alias objc_msgSend_specialized!("release", void) release;
16796     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
16797     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
16798     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
16799     alias objc_msgSend_specialized!("invalidate", void) invalidate;
16800     alias objc_msgSend_specialized!("close", void) close;
16801     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
16802                                     id, double, id, SEL, id, BOOL) scheduledTimer;
16803     alias objc_msgSend_specialized!("run", void) run;
16804     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
16805                                     id) currentNSGraphicsContext;
16806     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
16807     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
16808     alias objc_msgSend_specialized!("superclass", Class) superclass;
16809     alias objc_msgSend_specialized!("init", id) init;
16810     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
16811     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
16812     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
16813                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
16814     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
16815     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
16816     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
16817                                     void, BOOL) activateIgnoringOtherApps;
16818     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
16819                                     id) sharedNSApplication;
16820     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
16821 } else static assert(0, "Unsupported operating system");
16822 
16823 
16824 version(OSXCocoa) {
16825 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
16826 	//
16827 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
16828 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
16829 	//
16830 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
16831 	// Probably won't even fully compile right now
16832 
16833     import std.math : PI;
16834     import std.algorithm : map;
16835     import std.array : array;
16836 
16837     alias SimpleWindow NativeWindowHandle;
16838     alias void delegate(id) NativeEventHandler;
16839 
16840     __gshared Ivar simpleWindowIvar;
16841 
16842     enum KEY_ESCAPE = 27;
16843 
16844     mixin template NativeImageImplementation() {
16845         CGContextRef context;
16846         ubyte* rawData;
16847     final:
16848 
16849 	void convertToRgbaBytes(ubyte[] where) {
16850 		assert(where.length == this.width * this.height * 4);
16851 
16852 		// if rawData had a length....
16853 		//assert(rawData.length == where.length);
16854 		for(long idx = 0; idx < where.length; idx += 4) {
16855 			auto alpha = rawData[idx + 3];
16856 			if(alpha == 255) {
16857 				where[idx + 0] = rawData[idx + 0]; // r
16858 				where[idx + 1] = rawData[idx + 1]; // g
16859 				where[idx + 2] = rawData[idx + 2]; // b
16860 				where[idx + 3] = rawData[idx + 3]; // a
16861 			} else {
16862 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
16863 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
16864 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
16865 				where[idx + 3] = rawData[idx + 3]; // a
16866 
16867 			}
16868 		}
16869 	}
16870 
16871 	void setFromRgbaBytes(in ubyte[] where) {
16872 		// FIXME: this is probably wrong
16873 		assert(where.length == this.width * this.height * 4);
16874 
16875 		// if rawData had a length....
16876 		//assert(rawData.length == where.length);
16877 		for(long idx = 0; idx < where.length; idx += 4) {
16878 			auto alpha = rawData[idx + 3];
16879 			if(alpha == 255) {
16880 				rawData[idx + 0] = where[idx + 0]; // r
16881 				rawData[idx + 1] = where[idx + 1]; // g
16882 				rawData[idx + 2] = where[idx + 2]; // b
16883 				rawData[idx + 3] = where[idx + 3]; // a
16884 			} else {
16885 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
16886 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
16887 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
16888 				rawData[idx + 3] = where[idx + 3]; // a
16889 
16890 			}
16891 		}
16892 	}
16893 
16894 
16895         void createImage(int width, int height, bool forcexshm=false) {
16896             auto colorSpace = CGColorSpaceCreateDeviceRGB();
16897             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
16898                                             colorSpace,
16899                                             kCGImageAlphaPremultipliedLast
16900                                                    |kCGBitmapByteOrder32Big);
16901             CGColorSpaceRelease(colorSpace);
16902             rawData = CGBitmapContextGetData(context);
16903         }
16904         void dispose() {
16905             CGContextRelease(context);
16906         }
16907 
16908         void setPixel(int x, int y, Color c) {
16909             auto offset = (y * width + x) * 4;
16910             if (c.a == 255) {
16911                 rawData[offset + 0] = c.r;
16912                 rawData[offset + 1] = c.g;
16913                 rawData[offset + 2] = c.b;
16914                 rawData[offset + 3] = c.a;
16915             } else {
16916                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
16917                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
16918                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
16919                 rawData[offset + 3] = c.a;
16920             }
16921         }
16922     }
16923 
16924     mixin template NativeScreenPainterImplementation() {
16925         CGContextRef context;
16926         ubyte[4] _outlineComponents;
16927 	id view;
16928 
16929         void create(NativeWindowHandle window) {
16930             context = window.drawingContext;
16931 	    view = window.view;
16932         }
16933 
16934         void dispose() {
16935             	setNeedsDisplay(view, true);
16936         }
16937 
16938 	// NotYetImplementedException
16939 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
16940 	void rasterOp(RasterOp op) {}
16941 	Pen _activePen;
16942 	Color _fillColor;
16943 	Rectangle _clipRectangle;
16944 	void setClipRectangle(int, int, int, int) {}
16945 	void setFont(OperatingSystemFont) {}
16946 	int fontHeight() { return 14; }
16947 
16948 	// end
16949 
16950         void pen(Pen pen) {
16951 	    _activePen = pen;
16952 	    auto color = pen.color; // FIXME
16953             double alphaComponent = color.a/255.0f;
16954             CGContextSetRGBStrokeColor(context,
16955                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
16956 
16957             if (color.a != 255) {
16958                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
16959                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
16960                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
16961                 _outlineComponents[3] = color.a;
16962             } else {
16963                 _outlineComponents[0] = color.r;
16964                 _outlineComponents[1] = color.g;
16965                 _outlineComponents[2] = color.b;
16966                 _outlineComponents[3] = color.a;
16967             }
16968         }
16969 
16970         @property void fillColor(Color color) {
16971             CGContextSetRGBFillColor(context,
16972                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
16973         }
16974 
16975         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
16976 		// NotYetImplementedException for upper left/width/height
16977             auto cgImage = CGBitmapContextCreateImage(image.context);
16978             auto size = CGSize(CGBitmapContextGetWidth(image.context),
16979                                CGBitmapContextGetHeight(image.context));
16980             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
16981             CGImageRelease(cgImage);
16982         }
16983 
16984 	version(OSXCocoa) {} else // NotYetImplementedException
16985         void drawPixmap(Sprite image, int x, int y) {
16986 		// FIXME: is this efficient?
16987             auto cgImage = CGBitmapContextCreateImage(image.context);
16988             auto size = CGSize(CGBitmapContextGetWidth(image.context),
16989                                CGBitmapContextGetHeight(image.context));
16990             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
16991             CGImageRelease(cgImage);
16992         }
16993 
16994 
16995         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
16996 		// FIXME: alignment
16997             if (_outlineComponents[3] != 0) {
16998                 CGContextSaveGState(context);
16999                 auto invAlpha = 1.0f/_outlineComponents[3];
17000                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
17001                                                   _outlineComponents[1]*invAlpha,
17002                                                   _outlineComponents[2]*invAlpha,
17003                                                   _outlineComponents[3]/255.0f);
17004                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
17005 // auto cfstr = cast(id)createCFString(text);
17006 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
17007 // NSPoint(x, y), null);
17008 // CFRelease(cfstr);
17009                 CGContextRestoreGState(context);
17010             }
17011         }
17012 
17013         void drawPixel(int x, int y) {
17014             auto rawData = CGBitmapContextGetData(context);
17015             auto width = CGBitmapContextGetWidth(context);
17016             auto height = CGBitmapContextGetHeight(context);
17017             auto offset = ((height - y - 1) * width + x) * 4;
17018             rawData[offset .. offset+4] = _outlineComponents;
17019         }
17020 
17021         void drawLine(int x1, int y1, int x2, int y2) {
17022             CGPoint[2] linePoints;
17023             linePoints[0] = CGPoint(x1, y1);
17024             linePoints[1] = CGPoint(x2, y2);
17025             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
17026         }
17027 
17028         void drawRectangle(int x, int y, int width, int height) {
17029             CGContextBeginPath(context);
17030             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
17031             CGContextAddRect(context, rect);
17032             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17033         }
17034 
17035         void drawEllipse(int x1, int y1, int x2, int y2) {
17036             CGContextBeginPath(context);
17037             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
17038             CGContextAddEllipseInRect(context, rect);
17039             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17040         }
17041 
17042         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
17043             // @@@BUG@@@ Does not support elliptic arc (width != height).
17044             CGContextBeginPath(context);
17045             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
17046                             start*PI/(180*64), finish*PI/(180*64), 0);
17047             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17048         }
17049 
17050         void drawPolygon(Point[] intPoints) {
17051             CGContextBeginPath(context);
17052             auto points = array(map!(CGPoint.fromTuple)(intPoints));
17053             CGContextAddLines(context, points.ptr, points.length);
17054             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
17055         }
17056     }
17057 
17058     mixin template NativeSimpleWindowImplementation() {
17059         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
17060             synchronized {
17061                 if (NSApp == null) initializeApp();
17062             }
17063 
17064             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
17065 
17066             // create the window.
17067             window = initWithContentRect(alloc("NSWindow"),
17068                                          contentRect,
17069                                          NSTitledWindowMask
17070                                             |NSClosableWindowMask
17071                                             |NSMiniaturizableWindowMask
17072                                             |NSResizableWindowMask,
17073                                          NSBackingStoreBuffered,
17074                                          true);
17075 
17076             // set the title & move the window to center.
17077             auto windowTitle = createCFString(title);
17078             setTitle(window, windowTitle);
17079             CFRelease(windowTitle);
17080             center(window);
17081 
17082             // create area to draw on.
17083             auto colorSpace = CGColorSpaceCreateDeviceRGB();
17084             drawingContext = CGBitmapContextCreate(null, width, height,
17085                                                    8, 4*width, colorSpace,
17086                                                    kCGImageAlphaPremultipliedLast
17087                                                       |kCGBitmapByteOrder32Big);
17088             CGColorSpaceRelease(colorSpace);
17089             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
17090             auto matrix = CGContextGetTextMatrix(drawingContext);
17091             matrix.c = -matrix.c;
17092             matrix.d = -matrix.d;
17093             CGContextSetTextMatrix(drawingContext, matrix);
17094 
17095             // create the subview that things will be drawn on.
17096             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
17097             setContentView(window, view);
17098             object_setIvar(view, simpleWindowIvar, cast(id)this);
17099             release(view);
17100 
17101             setBackgroundColor(window, whiteNSColor);
17102             makeKeyAndOrderFront(window, null);
17103         }
17104         void dispose() {
17105             closeWindow();
17106             release(window);
17107         }
17108         void closeWindow() {
17109             invalidate(timer);
17110             .close(window);
17111         }
17112 
17113         ScreenPainter getPainter() {
17114 		return ScreenPainter(this, this);
17115 	}
17116 
17117         id window;
17118         id timer;
17119         id view;
17120         CGContextRef drawingContext;
17121     }
17122 
17123     extern(C) {
17124     private:
17125         BOOL returnTrue3(id self, SEL _cmd, id app) {
17126             return true;
17127         }
17128         BOOL returnTrue2(id self, SEL _cmd) {
17129             return true;
17130         }
17131 
17132         void pulse(id self, SEL _cmd) {
17133             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
17134             simpleWindow.handlePulse();
17135             setNeedsDisplay(self, true);
17136         }
17137         void drawRect(id self, SEL _cmd, NSRect rect) {
17138             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
17139             auto curCtx = graphicsPort(currentNSGraphicsContext);
17140             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
17141             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
17142                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
17143             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
17144             CGImageRelease(cgImage);
17145         }
17146         void keyDown(id self, SEL _cmd, id event) {
17147             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
17148 
17149             // the event may have multiple characters, and we send them all at
17150             // once.
17151             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
17152                 auto chars = characters(event);
17153                 auto range = CFRange(0, CFStringGetLength(chars));
17154                 auto buffer = new char[range.length*3];
17155                 long actualLength;
17156                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
17157                                  buffer.ptr, cast(int) buffer.length, &actualLength);
17158                 foreach (dchar dc; buffer[0..actualLength]) {
17159                     if (simpleWindow.handleCharEvent)
17160                         simpleWindow.handleCharEvent(dc);
17161 		    // NotYetImplementedException
17162                     //if (simpleWindow.handleKeyEvent)
17163                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
17164                 }
17165             }
17166 
17167             // the event's 'keyCode' is hardware-dependent. I don't think people
17168             // will like it. Let's leave it to the native handler.
17169 
17170             // perform the default action.
17171 
17172 	    // so the default action is to make a bomp sound and i dont want that
17173 	    // sooooooooo yeah not gonna do that.
17174 
17175             //auto superData = objc_super(self, superclass(self));
17176             //alias extern(C) void function(objc_super*, SEL, id) T;
17177             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
17178         }
17179     }
17180 
17181     // initialize the app so that it can be interacted with the user.
17182     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
17183     private void initializeApp() {
17184         // push an autorelease pool to avoid leaking.
17185         init(alloc("NSAutoreleasePool"));
17186 
17187         // create a new NSApp instance
17188         sharedNSApplication;
17189         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
17190 
17191         // create the "Quit" menu.
17192         auto menuBar = init(alloc("NSMenu"));
17193         auto appMenuItem = init(alloc("NSMenuItem"));
17194         addItem(menuBar, appMenuItem);
17195         setMainMenu(NSApp, menuBar);
17196         release(appMenuItem);
17197         release(menuBar);
17198 
17199         auto appMenu = init(alloc("NSMenu"));
17200         auto quitTitle = createCFString("Quit");
17201         auto q = createCFString("q");
17202         auto quitItem = initWithTitle(alloc("NSMenuItem"),
17203                                       quitTitle, sel_registerName("terminate:"), q);
17204         addItem(appMenu, quitItem);
17205         setSubmenu(appMenuItem, appMenu);
17206         release(quitItem);
17207         release(appMenu);
17208         CFRelease(q);
17209         CFRelease(quitTitle);
17210 
17211         // assign a delegate for the application, allow it to quit when the last
17212         // window is closed.
17213         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
17214                                                     "SDWindowCloseDelegate", 0);
17215         class_addMethod(delegateClass,
17216                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
17217                         &returnTrue3, "c@:@");
17218         objc_registerClassPair(delegateClass);
17219 
17220         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
17221         setDelegate(NSApp, appDelegate);
17222         activateIgnoringOtherApps(NSApp, true);
17223 
17224         // create a new view that draws the graphics and respond to keyDown
17225         // events.
17226         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
17227                                                 "SDGraphicsView", (void*).sizeof);
17228         class_addIvar(viewClass, "simpledisplay_simpleWindow",
17229                       (void*).sizeof, (void*).alignof, "^v");
17230         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
17231                         &pulse, "v@:");
17232         class_addMethod(viewClass, sel_registerName("drawRect:"),
17233                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
17234         class_addMethod(viewClass, sel_registerName("isFlipped"),
17235                         &returnTrue2, "c@:");
17236         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
17237                         &returnTrue2, "c@:");
17238         class_addMethod(viewClass, sel_registerName("keyDown:"),
17239                         &keyDown, "v@:@");
17240         objc_registerClassPair(viewClass);
17241         simpleWindowIvar = class_getInstanceVariable(viewClass,
17242                                                      "simpledisplay_simpleWindow");
17243     }
17244 }
17245 
17246 version(without_opengl) {} else
17247 extern(System) nothrow @nogc {
17248 	//enum uint GL_VERSION = 0x1F02;
17249 	//const(char)* glGetString (/*GLenum*/uint);
17250 	version(X11) {
17251 	static if (!SdpyIsUsingIVGLBinds) {
17252 
17253 		enum GLX_X_RENDERABLE = 0x8012;
17254 		enum GLX_DRAWABLE_TYPE = 0x8010;
17255 		enum GLX_RENDER_TYPE = 0x8011;
17256 		enum GLX_X_VISUAL_TYPE = 0x22;
17257 		enum GLX_TRUE_COLOR = 0x8002;
17258 		enum GLX_WINDOW_BIT = 0x00000001;
17259 		enum GLX_RGBA_BIT = 0x00000001;
17260 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
17261 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
17262 		enum GLX_SAMPLES = 0x186a1;
17263 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
17264 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
17265 	}
17266 
17267 		// GLX_EXT_swap_control
17268 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
17269 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
17270 
17271 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
17272 		extern(System) {
17273 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
17274 		}
17275 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
17276 
17277 		// this made public so we don't have to get it again and again
17278 		public bool glXCreateContextAttribsARB_present () {
17279 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
17280 				// get it
17281 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
17282 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
17283 			}
17284 			return (glXCreateContextAttribsARBFn !is null);
17285 		}
17286 
17287 		// this made public so we don't have to get it again and again
17288 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
17289 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
17290 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
17291 		}
17292 
17293 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
17294 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
17295 
17296 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
17297 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
17298 			if (_glx_swapInterval_fn is null) {
17299 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
17300 				if (_glx_swapInterval_fn is null) {
17301 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
17302 					return;
17303 				}
17304 				version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); }
17305 			}
17306 
17307 			if(glXSwapIntervalMESA is null) {
17308 				// it seems to require both to actually take effect on many computers
17309 				// idk why
17310 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
17311 				if(glXSwapIntervalMESA is null)
17312 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
17313 			}
17314 
17315 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
17316 				glXSwapIntervalMESA(wait ? 1 : 0);
17317 
17318 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
17319 		}
17320 	} else version(Windows) {
17321 	static if (!SdpyIsUsingIVGLBinds) {
17322 	enum GL_TRUE = 1;
17323 	enum GL_FALSE = 0;
17324 	alias int GLint;
17325 
17326 	public void* glbindGetProcAddress (const(char)* name) {
17327 		void* res = wglGetProcAddress(name);
17328 		if (res is null) {
17329 			/+
17330 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
17331 			import core.sys.windows.windef, core.sys.windows.winbase;
17332 			__gshared HINSTANCE dll = null;
17333 			if (dll is null) {
17334 				dll = LoadLibraryA("opengl32.dll");
17335 				if (dll is null) return null; // <32, but idc
17336 			}
17337 			res = GetProcAddress(dll, name);
17338 			+/
17339 			res = GetProcAddress(gl.libHandle, name);
17340 		}
17341 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
17342 		return res;
17343 	}
17344 	}
17345 
17346  
17347  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
17348         void wglSetVSync(bool wait) {
17349 		if(wglSwapIntervalEXT is null) {
17350 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
17351 			if(wglSwapIntervalEXT is null)
17352 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
17353 		}
17354 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
17355 			return;
17356 
17357 		wglSwapIntervalEXT(wait ? 1 : 0);
17358 	}
17359 
17360 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
17361 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
17362 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
17363 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
17364 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
17365 
17366 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
17367 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
17368 
17369 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
17370 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
17371 
17372 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
17373 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
17374 
17375 		void wglInitOtherFunctions () {
17376 			if (wglCreateContextAttribsARB is null) {
17377 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
17378 			}
17379 		}
17380 	}
17381 
17382 	static if (!SdpyIsUsingIVGLBinds) {
17383 
17384 	interface GL {
17385 		extern(System) @nogc nothrow:
17386 
17387 		void glGetIntegerv(int, void*);
17388 		void glMatrixMode(int);
17389 		void glPushMatrix();
17390 		void glLoadIdentity();
17391 		void glOrtho(double, double, double, double, double, double);
17392 		void glFrustum(double, double, double, double, double, double);
17393 
17394 		void glPopMatrix();
17395 		void glEnable(int);
17396 		void glDisable(int);
17397 		void glClear(int);
17398 		void glBegin(int);
17399 		void glVertex2f(float, float);
17400 		void glVertex3f(float, float, float);
17401 		void glEnd();
17402 		void glColor3b(byte, byte, byte);
17403 		void glColor3ub(ubyte, ubyte, ubyte);
17404 		void glColor4b(byte, byte, byte, byte);
17405 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
17406 		void glColor3i(int, int, int);
17407 		void glColor3ui(uint, uint, uint);
17408 		void glColor4i(int, int, int, int);
17409 		void glColor4ui(uint, uint, uint, uint);
17410 		void glColor3f(float, float, float);
17411 		void glColor4f(float, float, float, float);
17412 		void glTranslatef(float, float, float);
17413 		void glScalef(float, float, float);
17414 		version(X11) {
17415 			void glSecondaryColor3b(byte, byte, byte);
17416 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
17417 			void glSecondaryColor3i(int, int, int);
17418 			void glSecondaryColor3ui(uint, uint, uint);
17419 			void glSecondaryColor3f(float, float, float);
17420 		}
17421 
17422 		void glDrawElements(int, int, int, void*);
17423 
17424 		void glRotatef(float, float, float, float);
17425 
17426 		uint glGetError();
17427 
17428 		void glDeleteTextures(int, uint*);
17429 
17430 
17431 		void glRasterPos2i(int, int);
17432 		void glDrawPixels(int, int, uint, uint, void*);
17433 		void glClearColor(float, float, float, float);
17434 
17435 
17436 		void glPixelStorei(uint, int);
17437 
17438 		void glGenTextures(uint, uint*);
17439 		void glBindTexture(int, int);
17440 		void glTexParameteri(uint, uint, int);
17441 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
17442 		void glTexImage2D(int, int, int, int, int, int, int, int, in void*);
17443 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
17444 			/*GLsizei*/int width, /*GLsizei*/int height,
17445 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
17446 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
17447 
17448 		void glLineWidth(int);
17449 
17450 
17451 		void glTexCoord2f(float, float);
17452 		void glVertex2i(int, int);
17453 		void glBlendFunc (int, int);
17454 		void glDepthFunc (int);
17455 		void glViewport(int, int, int, int);
17456 
17457 		void glClearDepth(double);
17458 
17459 		void glReadBuffer(uint);
17460 		void glReadPixels(int, int, int, int, int, int, void*);
17461 
17462 		void glFlush();
17463 		void glFinish();
17464 
17465 		version(Windows) {
17466 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
17467 			HGLRC wglCreateContext(HDC);
17468 			HGLRC wglCreateLayerContext(HDC, int);
17469 			BOOL wglDeleteContext(HGLRC);
17470 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
17471 			HGLRC wglGetCurrentContext();
17472 			HDC wglGetCurrentDC();
17473 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
17474 			PROC wglGetProcAddress(LPCSTR);
17475 			BOOL wglMakeCurrent(HDC, HGLRC);
17476 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
17477 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
17478 			BOOL wglShareLists(HGLRC, HGLRC);
17479 			BOOL wglSwapLayerBuffers(HDC, UINT);
17480 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
17481 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
17482 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
17483 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
17484 		}
17485 
17486 	}
17487 
17488 	interface GL3 {
17489 		extern(System) @nogc nothrow:
17490 
17491 		void glGenVertexArrays(GLsizei, GLuint*);
17492 		void glBindVertexArray(GLuint);
17493 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
17494 		void glGenerateMipmap(GLenum);
17495 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
17496 		void glStencilMask(GLuint);
17497 		void glStencilFunc(GLenum, GLint, GLuint);
17498 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
17499 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
17500 		GLuint glCreateProgram();
17501 		GLuint glCreateShader(GLenum);
17502 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
17503 		void glCompileShader(GLuint);
17504 		void glGetShaderiv(GLuint, GLenum, GLint*);
17505 		void glAttachShader(GLuint, GLuint);
17506 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
17507 		void glLinkProgram(GLuint);
17508 		void glGetProgramiv(GLuint, GLenum, GLint*);
17509 		void glDeleteProgram(GLuint);
17510 		void glDeleteShader(GLuint);
17511 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
17512 		void glGenBuffers(GLsizei, GLuint*);
17513 		void glUniform4fv(GLint, GLsizei, const(GLfloat)*);
17514 		void glUniform1f(GLint, float);
17515 		void glUniform2f(GLint, float, float);
17516 		void glUniform4f(GLint, float, float, float, float);
17517 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
17518 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
17519 		void glDrawArrays(GLenum, GLint, GLsizei);
17520 		void glStencilOp(GLenum, GLenum, GLenum);
17521 		void glUseProgram(GLuint);
17522 		void glCullFace(GLenum);
17523 		void glFrontFace(GLenum);
17524 		void glActiveTexture(GLenum);
17525 		void glBindBuffer(GLenum, GLuint);
17526 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
17527 		void glEnableVertexAttribArray(GLuint);
17528 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
17529 		void glUniform1i(GLint, GLint);
17530 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
17531 		void glDisableVertexAttribArray(GLuint);
17532 		void glDeleteBuffers(GLsizei, const(GLuint)*);
17533 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
17534 		void glLogicOp (GLenum opcode);
17535 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
17536 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
17537 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
17538 		GLenum glCheckFramebufferStatus (GLenum target);
17539 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
17540 	}
17541 
17542 	interface GL4 {
17543 		extern(System) @nogc nothrow:
17544 
17545 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
17546 			/*GLsizei*/int width, /*GLsizei*/int height,
17547 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
17548 	}
17549 
17550 	interface GLU {
17551 		extern(System) @nogc nothrow:
17552 
17553 		void gluLookAt(double, double, double, double, double, double, double, double, double);
17554 		void gluPerspective(double, double, double, double);
17555 
17556 		char* gluErrorString(uint);
17557 	}
17558 
17559 
17560 	enum GL_RED = 0x1903;
17561 	enum GL_ALPHA = 0x1906;
17562 
17563 	enum uint GL_FRONT = 0x0404;
17564 
17565 	enum uint GL_BLEND = 0x0be2;
17566 	enum uint GL_LEQUAL = 0x0203;
17567 
17568 
17569 	enum uint GL_RGB = 0x1907;
17570 	enum uint GL_BGRA = 0x80e1;
17571 	enum uint GL_RGBA = 0x1908;
17572 	enum uint GL_TEXTURE_2D =   0x0DE1;
17573 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
17574 	enum uint GL_NEAREST = 0x2600;
17575 	enum uint GL_LINEAR = 0x2601;
17576 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
17577 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
17578 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
17579 	enum uint GL_REPEAT = 0x2901;
17580 	enum uint GL_CLAMP = 0x2900;
17581 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
17582 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
17583 	enum uint GL_DECAL = 0x2101;
17584 	enum uint GL_MODULATE = 0x2100;
17585 	enum uint GL_TEXTURE_ENV = 0x2300;
17586 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
17587 	enum uint GL_REPLACE = 0x1E01;
17588 	enum uint GL_LIGHTING = 0x0B50;
17589 	enum uint GL_DITHER = 0x0BD0;
17590 
17591 	enum uint GL_NO_ERROR = 0;
17592 
17593 
17594 
17595 	enum int GL_VIEWPORT = 0x0BA2;
17596 	enum int GL_MODELVIEW = 0x1700;
17597 	enum int GL_TEXTURE = 0x1702;
17598 	enum int GL_PROJECTION = 0x1701;
17599 	enum int GL_DEPTH_TEST = 0x0B71;
17600 
17601 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
17602 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
17603 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
17604 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
17605 
17606 	enum int GL_POINTS = 0x0000;
17607 	enum int GL_LINES =  0x0001;
17608 	enum int GL_LINE_LOOP = 0x0002;
17609 	enum int GL_LINE_STRIP = 0x0003;
17610 	enum int GL_TRIANGLES = 0x0004;
17611 	enum int GL_TRIANGLE_STRIP = 5;
17612 	enum int GL_TRIANGLE_FAN = 6;
17613 	enum int GL_QUADS = 7;
17614 	enum int GL_QUAD_STRIP = 8;
17615 	enum int GL_POLYGON = 9;
17616 
17617 	alias GLvoid = void;
17618 	alias GLboolean = ubyte;
17619 	alias GLuint = uint;
17620 	alias GLenum = uint;
17621 	alias GLchar = char;
17622 	alias GLsizei = int;
17623 	alias GLfloat = float;
17624 	alias GLintptr = size_t;
17625 	alias GLsizeiptr = ptrdiff_t;
17626 
17627 
17628 	enum uint GL_INVALID_ENUM = 0x0500;
17629 
17630 	enum uint GL_ZERO = 0;
17631 	enum uint GL_ONE = 1;
17632 
17633 	enum uint GL_BYTE = 0x1400;
17634 	enum uint GL_UNSIGNED_BYTE = 0x1401;
17635 	enum uint GL_SHORT = 0x1402;
17636 	enum uint GL_UNSIGNED_SHORT = 0x1403;
17637 	enum uint GL_INT = 0x1404;
17638 	enum uint GL_UNSIGNED_INT = 0x1405;
17639 	enum uint GL_FLOAT = 0x1406;
17640 	enum uint GL_2_BYTES = 0x1407;
17641 	enum uint GL_3_BYTES = 0x1408;
17642 	enum uint GL_4_BYTES = 0x1409;
17643 	enum uint GL_DOUBLE = 0x140A;
17644 
17645 	enum uint GL_STREAM_DRAW = 0x88E0;
17646 
17647 	enum uint GL_CCW = 0x0901;
17648 
17649 	enum uint GL_STENCIL_TEST = 0x0B90;
17650 	enum uint GL_SCISSOR_TEST = 0x0C11;
17651 
17652 	enum uint GL_EQUAL = 0x0202;
17653 	enum uint GL_NOTEQUAL = 0x0205;
17654 
17655 	enum uint GL_ALWAYS = 0x0207;
17656 	enum uint GL_KEEP = 0x1E00;
17657 
17658 	enum uint GL_INCR = 0x1E02;
17659 
17660 	enum uint GL_INCR_WRAP = 0x8507;
17661 	enum uint GL_DECR_WRAP = 0x8508;
17662 
17663 	enum uint GL_CULL_FACE = 0x0B44;
17664 	enum uint GL_BACK = 0x0405;
17665 
17666 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
17667 	enum uint GL_VERTEX_SHADER = 0x8B31;
17668 
17669 	enum uint GL_COMPILE_STATUS = 0x8B81;
17670 	enum uint GL_LINK_STATUS = 0x8B82;
17671 
17672 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
17673 
17674 	enum uint GL_STATIC_DRAW = 0x88E4;
17675 
17676 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
17677 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
17678 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
17679 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
17680 
17681 	enum uint GL_GENERATE_MIPMAP = 0x8191;
17682 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
17683 
17684 	enum uint GL_TEXTURE0 = 0x84C0U;
17685 	enum uint GL_TEXTURE1 = 0x84C1U;
17686 
17687 	enum uint GL_ARRAY_BUFFER = 0x8892;
17688 
17689 	enum uint GL_SRC_COLOR = 0x0300;
17690 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
17691 	enum uint GL_SRC_ALPHA = 0x0302;
17692 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
17693 	enum uint GL_DST_ALPHA = 0x0304;
17694 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
17695 	enum uint GL_DST_COLOR = 0x0306;
17696 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
17697 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
17698 
17699 	enum uint GL_INVERT = 0x150AU;
17700 
17701 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
17702 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
17703 
17704 	enum uint GL_FRAMEBUFFER = 0x8D40U;
17705 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
17706 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
17707 
17708 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
17709 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
17710 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
17711 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
17712 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
17713 
17714 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
17715 	enum uint GL_CLEAR = 0x1500U;
17716 	enum uint GL_COPY = 0x1503U;
17717 	enum uint GL_XOR = 0x1506U;
17718 
17719 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
17720 
17721 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
17722 
17723 	}
17724 }
17725 
17726 /++
17727 	History:
17728 		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.
17729 +/
17730 __gshared bool gluSuccessfullyLoaded = true;
17731 
17732 version(without_opengl) {} else {
17733 static if(!SdpyIsUsingIVGLBinds) {
17734 	version(Windows) {
17735 		mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl;
17736 		mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu;
17737 	} else {
17738 		mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl;
17739 		mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu;
17740 	}
17741 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
17742 
17743 
17744 	shared static this() {
17745 		gl.loadDynamicLibrary();
17746 
17747 		// FIXME: this is NOT actually required and should NOT fail if it is not loaded
17748 		// unless those functions are actually used
17749 		// go to mark b openGlLibrariesSuccessfullyLoaded = false;
17750 		glu.loadDynamicLibrary();
17751 	}
17752 }
17753 }
17754 
17755 /++
17756 	Convenience method for converting D arrays to opengl buffer data
17757 
17758 	I would LOVE to overload it with the original glBufferData, but D won't
17759 	let me since glBufferData is a function pointer :(
17760 
17761 	Added: August 25, 2020 (version 8.5)
17762 +/
17763 version(without_opengl) {} else
17764 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
17765 	glBufferData(target, data.length, data.ptr, usage);
17766 }
17767 
17768 /+
17769 /++
17770 	A matrix for simple uses that easily integrates with [OpenGlShader].
17771 
17772 	Might not be useful to you since it only as some simple functions and
17773 	probably isn't that fast.
17774 
17775 	Note it uses an inline static array for its storage, so copying it
17776 	may be expensive.
17777 +/
17778 struct BasicMatrix(int columns, int rows, T = float) {
17779 	import core.stdc.math;
17780 
17781 	T[columns * rows] data = 0.0;
17782 
17783 	/++
17784 		Basic operations that operate *in place*.
17785 	+/
17786 	void translate() {
17787 
17788 	}
17789 
17790 	/// ditto
17791 	void scale() {
17792 
17793 	}
17794 
17795 	/// ditto
17796 	void rotate() {
17797 
17798 	}
17799 
17800 	/++
17801 
17802 	+/
17803 	static if(columns == rows)
17804 	static BasicMatrix identity() {
17805 		BasicMatrix m;
17806 		foreach(i; 0 .. columns)
17807 			data[0 + i + i * columns] = 1.0;
17808 		return m;
17809 	}
17810 
17811 	static BasicMatrix ortho() {
17812 		return BasicMatrix.init;
17813 	}
17814 }
17815 +/
17816 
17817 /++
17818 	Convenience class for using opengl shaders.
17819 
17820 	Ensure that you've loaded opengl 3+ and set your active
17821 	context before trying to use this.
17822 
17823 	Added: August 25, 2020 (version 8.5)
17824 +/
17825 version(without_opengl) {} else
17826 final class OpenGlShader {
17827 	private int shaderProgram_;
17828 	private @property void shaderProgram(int a) {
17829 		shaderProgram_ = a;
17830 	}
17831 	/// Get the program ID for use in OpenGL functions.
17832 	public @property int shaderProgram() {
17833 		return shaderProgram_;
17834 	}
17835 
17836 	/++
17837 
17838 	+/
17839 	static struct Source {
17840 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
17841 		string code; ///
17842 	}
17843 
17844 	/++
17845 		Helper method to just compile some shader code and check for errors
17846 		while you do glCreateShader, etc. on the outside yourself.
17847 
17848 		This just does `glShaderSource` and `glCompileShader` for the given code.
17849 
17850 		If you the OpenGlShader class constructor, you never need to call this yourself.
17851 	+/
17852 	static void compile(int sid, Source code) {
17853 		const(char)*[1] buffer;
17854 		int[1] lengthBuffer;
17855 
17856 		buffer[0] = code.code.ptr;
17857 		lengthBuffer[0] = cast(int) code.code.length;
17858 
17859 		glShaderSource(sid, 1, buffer.ptr, lengthBuffer.ptr);
17860 		glCompileShader(sid);
17861 
17862 		int success;
17863 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
17864 		if(!success) {
17865 			char[512] info;
17866 			int len;
17867 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
17868 
17869 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
17870 		}
17871 	}
17872 
17873 	/++
17874 		Calls `glLinkProgram` and throws if error a occurs.
17875 
17876 		If you the OpenGlShader class constructor, you never need to call this yourself.
17877 	+/
17878 	static void link(int shaderProgram) {
17879 		glLinkProgram(shaderProgram);
17880 		int success;
17881 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
17882 		if(!success) {
17883 			char[512] info;
17884 			int len;
17885 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
17886 
17887 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
17888 		}
17889 	}
17890 
17891 	/++
17892 		Constructs the shader object by calling `glCreateProgram`, then
17893 		compiling each given [Source], and finally, linking them together.
17894 
17895 		Throws: on compile or link failure.
17896 	+/
17897 	this(Source[] codes...) {
17898 		shaderProgram = glCreateProgram();
17899 
17900 		int[16] shadersBufferStack;
17901 
17902 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 
17903 			shadersBufferStack[0 .. codes.length] :
17904 			new int[](codes.length);
17905 
17906 		foreach(idx, code; codes) {
17907 			shadersBuffer[idx] = glCreateShader(code.type);
17908 
17909 			compile(shadersBuffer[idx], code);
17910 
17911 			glAttachShader(shaderProgram, shadersBuffer[idx]);
17912 		}
17913 
17914 		link(shaderProgram);
17915 
17916 		foreach(s; shadersBuffer)
17917 			glDeleteShader(s);
17918 	}
17919 
17920 	/// Calls `glUseProgram(this.shaderProgram)`
17921 	void use() {
17922 		glUseProgram(this.shaderProgram);
17923 	}
17924 
17925 	/// Deletes the program.
17926 	void delete_() {
17927 		glDeleteProgram(shaderProgram);
17928 		shaderProgram = 0;
17929 	}
17930 
17931 	/++
17932 		[OpenGlShader.uniforms].name gives you one of these.
17933 
17934 		You can get the id out of it or just assign
17935 	+/
17936 	static struct Uniform {
17937 		/// the id passed to glUniform*
17938 		int id;
17939 
17940 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
17941 		void opAssign(float x, float y, float z, float w) {
17942 			if(id != -1)
17943 			glUniform4f(id, x, y, z, w);
17944 		}
17945 
17946 		void opAssign(float x) {
17947 			if(id != -1)
17948 			glUniform1f(id, x);
17949 		}
17950 
17951 		void opAssign(float x, float y) {
17952 			if(id != -1)
17953 			glUniform2f(id, x, y);
17954 		}
17955 	}
17956 
17957 	static struct UniformsHelper {
17958 		OpenGlShader _shader;
17959 
17960 		@property Uniform opDispatch(string name)() {
17961 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
17962 			// FIXME: decide what to do here; the exception is liable to be swallowed by the event syste
17963 			//if(i == -1)
17964 				//throw new Exception("Could not find uniform " ~ name);
17965 			return Uniform(i);
17966 		}
17967 	}
17968 
17969 	/++
17970 		Gives access to the uniforms through dot access.
17971 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
17972 	+/
17973 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
17974 }
17975 
17976 version(linux) {
17977 	version(with_eventloop) {} else {
17978 		private int epollFd = -1;
17979 		void prepareEventLoop() {
17980 			if(epollFd != -1)
17981 				return; // already initialized, no need to do it again
17982 			import ep = core.sys.linux.epoll;
17983 
17984 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
17985 			if(epollFd == -1)
17986 				throw new Exception("epoll create failure");
17987 		}
17988 	}
17989 } else version(Posix) {
17990 	void prepareEventLoop() {}
17991 }
17992 
17993 version(X11) {
17994 	import core.stdc.locale : LC_ALL; // rdmd fix
17995 	__gshared bool sdx_isUTF8Locale;
17996 
17997 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
17998 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
17999 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
18000 	// anal magic is here. I (Ketmar) hope you like it.
18001 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
18002 	// always return correct unicode symbols. The detection is here 'cause user can change locale
18003 	// later.
18004 
18005 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
18006 	shared static this () {
18007 		if(!librariesSuccessfullyLoaded)
18008 			return;
18009 
18010 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
18011 
18012 		// this doesn't hurt; it may add some locking, but the speed is still
18013 		// allows doing 60 FPS videogames; also, ignore the result, as most
18014 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
18015 		// never seen this failing).
18016 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
18017 
18018 		setlocale(LC_ALL, "");
18019 		// check if out locale is UTF-8
18020 		auto lct = setlocale(LC_CTYPE, null);
18021 		if (lct is null) {
18022 			sdx_isUTF8Locale = false;
18023 		} else {
18024 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
18025 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
18026 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
18027 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
18028 				{
18029 					sdx_isUTF8Locale = true;
18030 					break;
18031 				}
18032 			}
18033 		}
18034 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
18035 	}
18036 }
18037 
18038 class ExperimentalTextComponent2 {
18039 	/+
18040 		Stage 1: get it working monospace
18041 		Stage 2: use proportional font
18042 		Stage 3: allow changes in inline style
18043 		Stage 4: allow new fonts and sizes in the middle
18044 		Stage 5: optimize gap buffer
18045 		Stage 6: optimize layout
18046 		Stage 7: word wrap
18047 		Stage 8: justification
18048 		Stage 9: editing, selection, etc.
18049 
18050 			Operations:
18051 				insert text
18052 				overstrike text
18053 				select
18054 				cut
18055 				modify
18056 	+/
18057 
18058 	/++
18059 		It asks for a window so it can translate abstract font sizes to actual on-screen values depending on the window's current dpi, scaling settings, etc.
18060 	+/
18061 	this(SimpleWindow window) {
18062 		this.window = window;
18063 	}
18064 
18065 	private SimpleWindow window;
18066 
18067 
18068 	/++
18069 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
18070 		representing the internal parts. The first pass is focused on the x parameter, then the
18071 		renderer is responsible for going back to the parts in the current line and calling
18072 		adjustDownForAscent to change the y params.
18073 	+/
18074 	static interface ComponentRenderHelper {
18075 
18076 		/+
18077 			When you do an edit, possibly stuff on the same line previously need to move (to adjust
18078 			the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs
18079 			to move (adjust y to make room for new line) until you get back to the same position,
18080 			then you can stop - if one thing is unchanged, nothing after it is changed too.
18081 
18082 			Word wrap might change this as if can rewrap tons of stuff, but the same idea applies,
18083 			once you reach something that is unchanged, you can stop.
18084 		+/
18085 
18086 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
18087 
18088 		int ascent() const;
18089 		int descent() const;
18090 
18091 		int advance() const;
18092 
18093 		bool endsWithExplititLineBreak() const;
18094 	}
18095 
18096 	static interface RenderResult {
18097 		/++
18098 			This is responsible for using what space is left (your object is responsible for keeping its own state after getting it updated from [repositionForNextLine]) and not going over if at all possible. If you can word wrap, you should when space is out. Otherwise, you can keep going if it means overflow hidden or scroll.
18099 		+/
18100 		void popFront();
18101 		@property bool empty() const;
18102 		@property ComponentRenderHelper front() const;
18103 
18104 		void repositionForNextLine(Point baseline, int availableWidth);
18105 	}
18106 
18107 	static interface ComponentInFlow {
18108 		void draw(ScreenPainter painter);
18109 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
18110 
18111 		bool startsWithExplicitLineBreak() const;
18112 	}
18113 
18114 	static class TextFlowComponent : ComponentInFlow {
18115 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
18116 
18117 		Color foreground;
18118 		Color background;
18119 
18120 		OperatingSystemFont font; // should NEVER be null
18121 
18122 		ubyte attributes; // underline, strike through, display on new block
18123 
18124 		version(Windows)
18125 			const(wchar)[] content;
18126 		else
18127 			const(char)[] content; // this should NEVER have a newline, except at the end
18128 
18129 		RenderedComponent[] rendered; // entirely controlled by [rerender]
18130 
18131 		// could prolly put some spacing around it too like margin / padding
18132 
18133 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
18134 			in { assert(font !is null);
18135 			     assert(!font.isNull); }
18136 			do
18137 		{
18138 			this.foreground = f;
18139 			this.background = b;
18140 			this.font = font;
18141 
18142 			this.attributes = attr;
18143 			version(Windows) {
18144 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
18145 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
18146 				auto buffer = new wchar[](sz);
18147 				this.content = makeWindowsString(c, buffer, conversionFlags);
18148 			} else {
18149 				this.content = c.dup;
18150 			}
18151 		}
18152 
18153 		void draw(ScreenPainter painter) {
18154 			painter.setFont(this.font);
18155 			painter.outlineColor = this.foreground;
18156 			painter.fillColor = Color.transparent;
18157 			foreach(rendered; this.rendered) {
18158 				// the component works in term of baseline,
18159 				// but the painter works in term of upper left bounding box
18160 				// so need to translate that
18161 
18162 				if(this.background.a) {
18163 					painter.fillColor = this.background;
18164 					painter.outlineColor = this.background;
18165 
18166 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
18167 
18168 					painter.outlineColor = this.foreground;
18169 					painter.fillColor = Color.transparent;
18170 				}
18171 
18172 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
18173 
18174 				// FIXME: strike through, underline, highlight selection, etc.
18175 			}
18176 		}
18177 	}
18178 
18179 	// I could split the parts into words on render
18180 	// for easier word-wrap, each one being an unbreakable "inline-block"
18181 	private TextFlowComponent[] parts;
18182 	private int needsRerenderFrom;
18183 
18184 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
18185 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
18186 		parts ~= new TextFlowComponent(f, b, font, attr, c);
18187 	}
18188 
18189 	static struct RenderedComponent {
18190 		int startX;
18191 		int startY;
18192 		short width;
18193 		// height is always from the containing part's font. This saves some space and means recalculations need not continue past the current line, unless a new part is added with a different font!
18194 		// for individual chars in here you've gotta process on demand
18195 		version(Windows)
18196 			const(wchar)[] slice;
18197 		else
18198 			const(char)[] slice;
18199 	}
18200 
18201 
18202 	void rerender(Rectangle boundingBox) {
18203 		Point baseline = boundingBox.upperLeft;
18204 
18205 		this.boundingBox.left = boundingBox.left;
18206 		this.boundingBox.top = boundingBox.top;
18207 
18208 		auto remainingParts = parts;
18209 
18210 		int largestX;
18211 
18212 
18213 		foreach(part; parts)
18214 			part.font.prepareContext(window);
18215 		scope(exit)
18216 		foreach(part; parts)
18217 			part.font.releaseContext();
18218 
18219 		calculateNextLine:
18220 
18221 		int nextLineHeight = 0;
18222 		int nextBiggestDescent = 0;
18223 
18224 		foreach(part; remainingParts) {
18225 			auto height = part.font.ascent;
18226 			if(height > nextLineHeight)
18227 				nextLineHeight = height;
18228 			if(part.font.descent > nextBiggestDescent)
18229 				nextBiggestDescent = part.font.descent;
18230 			if(part.content.length && part.content[$-1] == '\n')
18231 				break;
18232 		}
18233 
18234 		baseline.y += nextLineHeight;
18235 		auto lineStart = baseline;
18236 
18237 		while(remainingParts.length) {
18238 			remainingParts[0].rendered = null;
18239 
18240 			bool eol;
18241 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
18242 				eol = true;
18243 
18244 			// FIXME: word wrap
18245 			auto font = remainingParts[0].font;
18246 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
18247 			auto width = font.stringWidth(slice, window);
18248 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
18249 
18250 			remainingParts = remainingParts[1 .. $];
18251 			baseline.x += width;
18252 
18253 			if(eol) {
18254 				baseline.y += nextBiggestDescent;
18255 				if(baseline.x > largestX)
18256 					largestX = baseline.x;
18257 				baseline.x = lineStart.x;
18258 				goto calculateNextLine;
18259 			}
18260 		}
18261 
18262 		if(baseline.x > largestX)
18263 			largestX = baseline.x;
18264 
18265 		this.boundingBox.right = largestX;
18266 		this.boundingBox.bottom = baseline.y;
18267 	}
18268 
18269 	// you must call rerender first!
18270 	void draw(ScreenPainter painter) {
18271 		foreach(part; parts) {
18272 			part.draw(painter);
18273 		}
18274 	}
18275 
18276 	struct IdentifyResult {
18277 		TextFlowComponent part;
18278 		int charIndexInPart;
18279 		int totalCharIndex = -1; // if this is -1, it just means the end
18280 
18281 		Rectangle boundingBox;
18282 	}
18283 
18284 	IdentifyResult identify(Point pt, bool exact = false) {
18285 		if(parts.length == 0)
18286 			return IdentifyResult(null, 0);
18287 
18288 		if(pt.y < boundingBox.top) {
18289 			if(exact)
18290 				return IdentifyResult(null, 1);
18291 			return IdentifyResult(parts[0], 0);
18292 		}
18293 		if(pt.y > boundingBox.bottom) {
18294 			if(exact)
18295 				return IdentifyResult(null, 2);
18296 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
18297 		}
18298 
18299 		int tci = 0;
18300 
18301 		// I should probably like binary search this or something...
18302 		foreach(ref part; parts) {
18303 			foreach(rendered; part.rendered) {
18304 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
18305 				if(rect.contains(pt)) {
18306 					auto x = pt.x - rendered.startX;
18307 					auto estimatedIdx = x / part.font.averageWidth;
18308 
18309 					if(estimatedIdx < 0)
18310 						estimatedIdx = 0;
18311 
18312 					if(estimatedIdx > rendered.slice.length)
18313 						estimatedIdx = cast(int) rendered.slice.length;
18314 
18315 					int idx;
18316 					int x1, x2;
18317 					if(part.font.isMonospace) {
18318 						auto w = part.font.averageWidth;
18319 						if(!exact && x > (estimatedIdx + 1) * w)
18320 							return IdentifyResult(null, 4);
18321 						idx = estimatedIdx;
18322 						x1 = idx * w;
18323 						x2 = (idx + 1) * w;
18324 					} else {
18325 						idx = estimatedIdx;
18326 
18327 						part.font.prepareContext(window);
18328 						scope(exit) part.font.releaseContext();
18329 
18330 						// int iterations;
18331 
18332 						while(true) {
18333 							// iterations++;
18334 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
18335 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
18336 
18337 							x1 += rendered.startX;
18338 							x2 += rendered.startX;
18339 
18340 							if(pt.x < x1) {
18341 								if(idx == 0) {
18342 									if(exact)
18343 										return IdentifyResult(null, 6);
18344 									else
18345 										break;
18346 								}
18347 								idx--;
18348 							} else if(pt.x > x2) {
18349 								idx++;
18350 								if(idx > rendered.slice.length) {
18351 									if(exact)
18352 										return IdentifyResult(null, 5);
18353 									else
18354 										break;
18355 								}
18356 							} else if(pt.x >= x1 && pt.x <= x2) {
18357 								if(idx)
18358 									idx--; // point it at the original index
18359 								break; // we fit
18360 							}
18361 						}
18362 
18363 						// import std.stdio; writeln(iterations)
18364 					}
18365 
18366 
18367 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
18368 				}
18369 			}
18370 			tci += cast(int) part.content.length; // FIXME: utf-8?
18371 		}
18372 		return IdentifyResult(null, 3);
18373 	}
18374 
18375 	Rectangle boundingBox; // only set after [rerender]
18376 
18377 	// text will be positioned around the exclusion zone
18378 	static struct ExclusionZone {
18379 
18380 	}
18381 
18382 	ExclusionZone[] exclusionZones;
18383 }
18384 
18385 
18386 // Don't use this yet. When I'm happy with it, I will move it to the
18387 // regular module namespace.
18388 mixin template ExperimentalTextComponent() {
18389 
18390 static:
18391 
18392 	alias Rectangle = arsd.color.Rectangle;
18393 
18394 	struct ForegroundColor {
18395 		Color color;
18396 		alias color this;
18397 
18398 		this(Color c) {
18399 			color = c;
18400 		}
18401 
18402 		this(int r, int g, int b, int a = 255) {
18403 			color = Color(r, g, b, a);
18404 		}
18405 
18406 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
18407 			return ForegroundColor(mixin("Color." ~ s));
18408 		}
18409 	}
18410 
18411 	struct BackgroundColor {
18412 		Color color;
18413 		alias color this;
18414 
18415 		this(Color c) {
18416 			color = c;
18417 		}
18418 
18419 		this(int r, int g, int b, int a = 255) {
18420 			color = Color(r, g, b, a);
18421 		}
18422 
18423 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
18424 			return BackgroundColor(mixin("Color." ~ s));
18425 		}
18426 	}
18427 
18428 	static class InlineElement {
18429 		string text;
18430 
18431 		BlockElement containingBlock;
18432 
18433 		Color color = Color.black;
18434 		Color backgroundColor = Color.transparent;
18435 		ushort styles;
18436 
18437 		string font;
18438 		int fontSize;
18439 
18440 		int lineHeight;
18441 
18442 		void* identifier;
18443 
18444 		Rectangle boundingBox;
18445 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
18446 
18447 		bool isMergeCompatible(InlineElement other) {
18448 			return
18449 				containingBlock is other.containingBlock &&
18450 				color == other.color &&
18451 				backgroundColor == other.backgroundColor &&
18452 				styles == other.styles &&
18453 				font == other.font &&
18454 				fontSize == other.fontSize &&
18455 				lineHeight == other.lineHeight &&
18456 				true;
18457 		}
18458 
18459 		int xOfIndex(size_t index) {
18460 			if(index < letterXs.length)
18461 				return letterXs[index];
18462 			else
18463 				return boundingBox.right;
18464 		}
18465 
18466 		InlineElement clone() {
18467 			auto ie = new InlineElement();
18468 			ie.tupleof = this.tupleof;
18469 			return ie;
18470 		}
18471 
18472 		InlineElement getPreviousInlineElement() {
18473 			InlineElement prev = null;
18474 			foreach(ie; this.containingBlock.parts) {
18475 				if(ie is this)
18476 					break;
18477 				prev = ie;
18478 			}
18479 			if(prev is null) {
18480 				BlockElement pb;
18481 				BlockElement cb = this.containingBlock;
18482 				moar:
18483 				foreach(ie; this.containingBlock.containingLayout.blocks) {
18484 					if(ie is cb)
18485 						break;
18486 					pb = ie;
18487 				}
18488 				if(pb is null)
18489 					return null;
18490 				if(pb.parts.length == 0) {
18491 					cb = pb;
18492 					goto moar;
18493 				}
18494 
18495 				prev = pb.parts[$-1];
18496 
18497 			}
18498 			return prev;
18499 		}
18500 
18501 		InlineElement getNextInlineElement() {
18502 			InlineElement next = null;
18503 			foreach(idx, ie; this.containingBlock.parts) {
18504 				if(ie is this) {
18505 					if(idx + 1 < this.containingBlock.parts.length)
18506 						next = this.containingBlock.parts[idx + 1];
18507 					break;
18508 				}
18509 			}
18510 			if(next is null) {
18511 				BlockElement n;
18512 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
18513 					if(ie is this.containingBlock) {
18514 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
18515 							n = this.containingBlock.containingLayout.blocks[idx + 1];
18516 						break;
18517 					}
18518 				}
18519 				if(n is null)
18520 					return null;
18521 
18522 				if(n.parts.length)
18523 					next = n.parts[0];
18524 				else {} // FIXME
18525 
18526 			}
18527 			return next;
18528 		}
18529 
18530 	}
18531 
18532 	// Block elements are used entirely for positioning inline elements,
18533 	// which are the things that are actually drawn.
18534 	class BlockElement {
18535 		InlineElement[] parts;
18536 		uint alignment;
18537 
18538 		int whiteSpace; // pre, pre-wrap, wrap
18539 
18540 		TextLayout containingLayout;
18541 
18542 		// inputs
18543 		Point where;
18544 		Size minimumSize;
18545 		Size maximumSize;
18546 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
18547 		void* identifier;
18548 
18549 		Rectangle margin;
18550 		Rectangle padding;
18551 
18552 		// outputs
18553 		Rectangle[] boundingBoxes;
18554 	}
18555 
18556 	struct TextIdentifyResult {
18557 		InlineElement element;
18558 		int offset;
18559 
18560 		private TextIdentifyResult fixupNewline() {
18561 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
18562 				offset--;
18563 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
18564 				offset--;
18565 			}
18566 			return this;
18567 		}
18568 	}
18569 
18570 	class TextLayout {
18571 		BlockElement[] blocks;
18572 		Rectangle boundingBox_;
18573 		Rectangle boundingBox() { return boundingBox_; }
18574 		void boundingBox(Rectangle r) {
18575 			if(r != boundingBox_) {
18576 				boundingBox_ = r;
18577 				layoutInvalidated = true;
18578 			}
18579 		}
18580 
18581 		Rectangle contentBoundingBox() {
18582 			Rectangle r;
18583 			foreach(block; blocks)
18584 			foreach(ie; block.parts) {
18585 				if(ie.boundingBox.right > r.right)
18586 					r.right = ie.boundingBox.right;
18587 				if(ie.boundingBox.bottom > r.bottom)
18588 					r.bottom = ie.boundingBox.bottom;
18589 			}
18590 			return r;
18591 		}
18592 
18593 		BlockElement[] getBlocks() {
18594 			return blocks;
18595 		}
18596 
18597 		InlineElement[] getTexts() {
18598 			InlineElement[] elements;
18599 			foreach(block; blocks)
18600 				elements ~= block.parts;
18601 			return elements;
18602 		}
18603 
18604 		string getPlainText() {
18605 			string text;
18606 			foreach(block; blocks)
18607 				foreach(part; block.parts)
18608 					text ~= part.text;
18609 			return text;
18610 		}
18611 
18612 		string getHtml() {
18613 			return null; // FIXME
18614 		}
18615 
18616 		this(Rectangle boundingBox) {
18617 			this.boundingBox = boundingBox;
18618 		}
18619 
18620 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
18621 			auto be = new BlockElement();
18622 			be.containingLayout = this;
18623 			if(after is null)
18624 				blocks ~= be;
18625 			else {
18626 				foreach(idx, b; blocks) {
18627 					if(b is after.containingBlock) {
18628 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
18629 						break;
18630 					}
18631 				}
18632 			}
18633 			return be;
18634 		}
18635 
18636 		void clear() {
18637 			blocks = null;
18638 			selectionStart = selectionEnd = caret = Caret.init;
18639 		}
18640 
18641 		void addText(Args...)(Args args) {
18642 			if(blocks.length == 0)
18643 				addBlock();
18644 
18645 			InlineElement ie = new InlineElement();
18646 			foreach(idx, arg; args) {
18647 				static if(is(typeof(arg) == ForegroundColor))
18648 					ie.color = arg;
18649 				else static if(is(typeof(arg) == TextFormat)) {
18650 					if(arg & 0x8000) // ~TextFormat.something turns it off
18651 						ie.styles &= arg;
18652 					else
18653 						ie.styles |= arg;
18654 				} else static if(is(typeof(arg) == string)) {
18655 					static if(idx == 0 && args.length > 1)
18656 						static assert(0, "Put styles before the string.");
18657 					size_t lastLineIndex;
18658 					foreach(cidx, char a; arg) {
18659 						if(a == '\n') {
18660 							ie.text = arg[lastLineIndex .. cidx + 1];
18661 							lastLineIndex = cidx + 1;
18662 							ie.containingBlock = blocks[$-1];
18663 							blocks[$-1].parts ~= ie.clone;
18664 							ie.text = null;
18665 						} else {
18666 
18667 						}
18668 					}
18669 
18670 					ie.text = arg[lastLineIndex .. $];
18671 					ie.containingBlock = blocks[$-1];
18672 					blocks[$-1].parts ~= ie.clone;
18673 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
18674 				}
18675 			}
18676 
18677 			invalidateLayout();
18678 		}
18679 
18680 		void tryMerge(InlineElement into, InlineElement what) {
18681 			if(!into.isMergeCompatible(what)) {
18682 				return; // cannot merge, different configs
18683 			}
18684 
18685 			// cool, can merge, bring text together...
18686 			into.text ~= what.text;
18687 
18688 			// and remove what
18689 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
18690 				if(what.containingBlock.parts[a] is what) {
18691 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
18692 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
18693 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
18694 
18695 				}
18696 			}
18697 
18698 			// FIXME: ensure no other carets have a reference to it
18699 		}
18700 
18701 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
18702 		TextIdentifyResult identify(int x, int y, bool exact = false) {
18703 			TextIdentifyResult inexactMatch;
18704 			foreach(block; blocks) {
18705 				foreach(part; block.parts) {
18706 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
18707 
18708 						// FIXME binary search
18709 						int tidx;
18710 						int lastX;
18711 						foreach_reverse(idxo, lx; part.letterXs) {
18712 							int idx = cast(int) idxo;
18713 							if(lx <= x) {
18714 								if(lastX && lastX - x < x - lx)
18715 									tidx = idx + 1;
18716 								else
18717 									tidx = idx;
18718 								break;
18719 							}
18720 							lastX = lx;
18721 						}
18722 
18723 						return TextIdentifyResult(part, tidx).fixupNewline;
18724 					} else if(!exact) {
18725 						// we're not in the box, but are we on the same line?
18726 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
18727 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
18728 					}
18729 				}
18730 			}
18731 
18732 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
18733 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
18734 
18735 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
18736 		}
18737 
18738 		void moveCaretToPixelCoordinates(int x, int y) {
18739 			auto result = identify(x, y);
18740 			caret.inlineElement = result.element;
18741 			caret.offset = result.offset;
18742 		}
18743 
18744 		void selectToPixelCoordinates(int x, int y) {
18745 			auto result = identify(x, y);
18746 
18747 			if(y < caretLastDrawnY1) {
18748 				// on a previous line, carat is selectionEnd
18749 				selectionEnd = caret;
18750 
18751 				selectionStart = Caret(this, result.element, result.offset);
18752 			} else if(y > caretLastDrawnY2) {
18753 				// on a later line
18754 				selectionStart = caret;
18755 
18756 				selectionEnd = Caret(this, result.element, result.offset);
18757 			} else {
18758 				// on the same line...
18759 				if(x <= caretLastDrawnX) {
18760 					selectionEnd = caret;
18761 					selectionStart = Caret(this, result.element, result.offset);
18762 				} else {
18763 					selectionStart = caret;
18764 					selectionEnd = Caret(this, result.element, result.offset);
18765 				}
18766 
18767 			}
18768 		}
18769 
18770 
18771 		/// Call this if the inputs change. It will reflow everything
18772 		void redoLayout(ScreenPainter painter) {
18773 			//painter.setClipRectangle(boundingBox);
18774 			auto pos = Point(boundingBox.left, boundingBox.top);
18775 
18776 			int lastHeight;
18777 			void nl() {
18778 				pos.x = boundingBox.left;
18779 				pos.y += lastHeight;
18780 			}
18781 			foreach(block; blocks) {
18782 				nl();
18783 				foreach(part; block.parts) {
18784 					part.letterXs = null;
18785 
18786 					auto size = painter.textSize(part.text);
18787 					version(Windows)
18788 						if(part.text.length && part.text[$-1] == '\n')
18789 							size.height /= 2; // windows counts the new line at the end, but we don't want that
18790 
18791 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
18792 
18793 					foreach(idx, char c; part.text) {
18794 							// FIXME: unicode
18795 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
18796 					}
18797 
18798 					pos.x += size.width;
18799 					if(pos.x >= boundingBox.right) {
18800 						pos.y += size.height;
18801 						pos.x = boundingBox.left;
18802 						lastHeight = 0;
18803 					} else {
18804 						lastHeight = size.height;
18805 					}
18806 
18807 					if(part.text.length && part.text[$-1] == '\n')
18808 						nl();
18809 				}
18810 			}
18811 
18812 			layoutInvalidated = false;
18813 		}
18814 
18815 		bool layoutInvalidated = true;
18816 		void invalidateLayout() {
18817 			layoutInvalidated = true;
18818 		}
18819 
18820 // FIXME: caret can remain sometimes when inserting
18821 // FIXME: inserting at the beginning once you already have something can eff it up.
18822 		void drawInto(ScreenPainter painter, bool focused = false) {
18823 			if(layoutInvalidated)
18824 				redoLayout(painter);
18825 			foreach(block; blocks) {
18826 				foreach(part; block.parts) {
18827 					painter.outlineColor = part.color;
18828 					painter.fillColor = part.backgroundColor;
18829 
18830 					auto pos = part.boundingBox.upperLeft;
18831 					auto size = part.boundingBox.size;
18832 
18833 					painter.drawText(pos, part.text);
18834 					if(part.styles & TextFormat.underline)
18835 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
18836 					if(part.styles & TextFormat.strikethrough)
18837 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
18838 				}
18839 			}
18840 
18841 			// on every redraw, I will force the caret to be
18842 			// redrawn too, in order to eliminate perceived lag
18843 			// when moving around with the mouse.
18844 			eraseCaret(painter);
18845 
18846 			if(focused) {
18847 				highlightSelection(painter);
18848 				drawCaret(painter);
18849 			}
18850 		}
18851 
18852 		Color selectionXorColor = Color(255, 255, 127);
18853 
18854 		void highlightSelection(ScreenPainter painter) {
18855 			if(selectionStart is selectionEnd)
18856 				return; // no selection
18857 
18858 			if(selectionStart.inlineElement is null) return;
18859 			if(selectionEnd.inlineElement is null) return;
18860 
18861 			assert(selectionStart.inlineElement !is null);
18862 			assert(selectionEnd.inlineElement !is null);
18863 
18864 			painter.rasterOp = RasterOp.xor;
18865 			painter.outlineColor = Color.transparent;
18866 			painter.fillColor = selectionXorColor;
18867 
18868 			auto at = selectionStart.inlineElement;
18869 			auto atOffset = selectionStart.offset;
18870 			bool done;
18871 			while(at) {
18872 				auto box = at.boundingBox;
18873 				if(atOffset < at.letterXs.length)
18874 					box.left = at.letterXs[atOffset];
18875 
18876 				if(at is selectionEnd.inlineElement) {
18877 					if(selectionEnd.offset < at.letterXs.length)
18878 						box.right = at.letterXs[selectionEnd.offset];
18879 					done = true;
18880 				}
18881 
18882 				painter.drawRectangle(box.upperLeft, box.width, box.height);
18883 
18884 				if(done)
18885 					break;
18886 
18887 				at = at.getNextInlineElement();
18888 				atOffset = 0;
18889 			}
18890 		}
18891 
18892 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
18893 		bool caretShowingOnScreen = false;
18894 		void drawCaret(ScreenPainter painter) {
18895 			//painter.setClipRectangle(boundingBox);
18896 			int x, y1, y2;
18897 			if(caret.inlineElement is null) {
18898 				x = boundingBox.left;
18899 				y1 = boundingBox.top + 2;
18900 				y2 = boundingBox.top + painter.fontHeight;
18901 			} else {
18902 				x = caret.inlineElement.xOfIndex(caret.offset);
18903 				y1 = caret.inlineElement.boundingBox.top + 2;
18904 				y2 = caret.inlineElement.boundingBox.bottom - 2;
18905 			}
18906 
18907 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
18908 				eraseCaret(painter);
18909 
18910 			painter.pen = Pen(Color.white, 1);
18911 			painter.rasterOp = RasterOp.xor;
18912 			painter.drawLine(
18913 				Point(x, y1),
18914 				Point(x, y2)
18915 			);
18916 			painter.rasterOp = RasterOp.normal;
18917 			caretShowingOnScreen = !caretShowingOnScreen;
18918 
18919 			if(caretShowingOnScreen) {
18920 				caretLastDrawnX = x;
18921 				caretLastDrawnY1 = y1;
18922 				caretLastDrawnY2 = y2;
18923 			}
18924 		}
18925 
18926 		Rectangle caretBoundingBox() {
18927 			int x, y1, y2;
18928 			if(caret.inlineElement is null) {
18929 				x = boundingBox.left;
18930 				y1 = boundingBox.top + 2;
18931 				y2 = boundingBox.top + 16;
18932 			} else {
18933 				x = caret.inlineElement.xOfIndex(caret.offset);
18934 				y1 = caret.inlineElement.boundingBox.top + 2;
18935 				y2 = caret.inlineElement.boundingBox.bottom - 2;
18936 			}
18937 
18938 			return Rectangle(x, y1, x + 1, y2);
18939 		}
18940 
18941 		void eraseCaret(ScreenPainter painter) {
18942 			//painter.setClipRectangle(boundingBox);
18943 			if(!caretShowingOnScreen) return;
18944 			painter.pen = Pen(Color.white, 1);
18945 			painter.rasterOp = RasterOp.xor;
18946 			painter.drawLine(
18947 				Point(caretLastDrawnX, caretLastDrawnY1),
18948 				Point(caretLastDrawnX, caretLastDrawnY2)
18949 			);
18950 
18951 			caretShowingOnScreen = false;
18952 			painter.rasterOp = RasterOp.normal;
18953 		}
18954 
18955 		/// Caret movement api
18956 		/// These should give the user a logical result based on what they see on screen...
18957 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
18958 		void moveUp() {
18959 			if(caret.inlineElement is null) return;
18960 			auto x = caret.inlineElement.xOfIndex(caret.offset);
18961 			auto y = caret.inlineElement.boundingBox.top + 2;
18962 
18963 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
18964 			if(y < 0)
18965 				return;
18966 
18967 			auto i = identify(x, y);
18968 
18969 			if(i.element) {
18970 				caret.inlineElement = i.element;
18971 				caret.offset = i.offset;
18972 			}
18973 		}
18974 		void moveDown() {
18975 			if(caret.inlineElement is null) return;
18976 			auto x = caret.inlineElement.xOfIndex(caret.offset);
18977 			auto y = caret.inlineElement.boundingBox.bottom - 2;
18978 
18979 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
18980 
18981 			auto i = identify(x, y);
18982 			if(i.element) {
18983 				caret.inlineElement = i.element;
18984 				caret.offset = i.offset;
18985 			}
18986 		}
18987 		void moveLeft() {
18988 			if(caret.inlineElement is null) return;
18989 			if(caret.offset)
18990 				caret.offset--;
18991 			else {
18992 				auto p = caret.inlineElement.getPreviousInlineElement();
18993 				if(p) {
18994 					caret.inlineElement = p;
18995 					if(p.text.length && p.text[$-1] == '\n')
18996 						caret.offset = cast(int) p.text.length - 1;
18997 					else
18998 						caret.offset = cast(int) p.text.length;
18999 				}
19000 			}
19001 		}
19002 		void moveRight() {
19003 			if(caret.inlineElement is null) return;
19004 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
19005 				caret.offset++;
19006 			} else {
19007 				auto p = caret.inlineElement.getNextInlineElement();
19008 				if(p) {
19009 					caret.inlineElement = p;
19010 					caret.offset = 0;
19011 				}
19012 			}
19013 		}
19014 		void moveHome() {
19015 			if(caret.inlineElement is null) return;
19016 			auto x = 0;
19017 			auto y = caret.inlineElement.boundingBox.top + 2;
19018 
19019 			auto i = identify(x, y);
19020 
19021 			if(i.element) {
19022 				caret.inlineElement = i.element;
19023 				caret.offset = i.offset;
19024 			}
19025 		}
19026 		void moveEnd() {
19027 			if(caret.inlineElement is null) return;
19028 			auto x = int.max;
19029 			auto y = caret.inlineElement.boundingBox.top + 2;
19030 
19031 			auto i = identify(x, y);
19032 
19033 			if(i.element) {
19034 				caret.inlineElement = i.element;
19035 				caret.offset = i.offset;
19036 			}
19037 
19038 		}
19039 		void movePageUp(ref Caret caret) {}
19040 		void movePageDown(ref Caret caret) {}
19041 
19042 		void moveDocumentStart(ref Caret caret) {
19043 			if(blocks.length && blocks[0].parts.length)
19044 				caret = Caret(this, blocks[0].parts[0], 0);
19045 			else
19046 				caret = Caret.init;
19047 		}
19048 
19049 		void moveDocumentEnd(ref Caret caret) {
19050 			if(blocks.length) {
19051 				auto parts = blocks[$-1].parts;
19052 				if(parts.length) {
19053 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
19054 				} else {
19055 					caret = Caret.init;
19056 				}
19057 			} else
19058 				caret = Caret.init;
19059 		}
19060 
19061 		void deleteSelection() {
19062 			if(selectionStart is selectionEnd)
19063 				return;
19064 
19065 			if(selectionStart.inlineElement is null) return;
19066 			if(selectionEnd.inlineElement is null) return;
19067 
19068 			assert(selectionStart.inlineElement !is null);
19069 			assert(selectionEnd.inlineElement !is null);
19070 
19071 			auto at = selectionStart.inlineElement;
19072 
19073 			if(selectionEnd.inlineElement is at) {
19074 				// same element, need to chop out
19075 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
19076 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
19077 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
19078 			} else {
19079 				// different elements, we can do it with slicing
19080 				at.text = at.text[0 .. selectionStart.offset];
19081 				if(selectionStart.offset < at.letterXs.length)
19082 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
19083 
19084 				at = at.getNextInlineElement();
19085 
19086 				while(at) {
19087 					if(at is selectionEnd.inlineElement) {
19088 						at.text = at.text[selectionEnd.offset .. $];
19089 						if(selectionEnd.offset < at.letterXs.length)
19090 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
19091 						selectionEnd.offset = 0;
19092 						break;
19093 					} else {
19094 						auto cfd = at;
19095 						cfd.text = null; // delete the whole thing
19096 
19097 						at = at.getNextInlineElement();
19098 
19099 						if(cfd.text.length == 0) {
19100 							// and remove cfd
19101 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
19102 								if(cfd.containingBlock.parts[a] is cfd) {
19103 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
19104 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
19105 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
19106 
19107 								}
19108 							}
19109 						}
19110 					}
19111 				}
19112 			}
19113 
19114 			caret = selectionEnd;
19115 			selectNone();
19116 
19117 			invalidateLayout();
19118 
19119 		}
19120 
19121 		/// Plain text editing api. These work at the current caret inside the selected inline element.
19122 		void insert(in char[] text) {
19123 			foreach(dchar ch; text)
19124 				insert(ch);
19125 		}
19126 		/// ditto
19127 		void insert(dchar ch) {
19128 
19129 			bool selectionDeleted = false;
19130 			if(selectionStart !is selectionEnd) {
19131 				deleteSelection();
19132 				selectionDeleted = true;
19133 			}
19134 
19135 			if(ch == 127) {
19136 				delete_();
19137 				return;
19138 			}
19139 			if(ch == 8) {
19140 				if(!selectionDeleted)
19141 					backspace();
19142 				return;
19143 			}
19144 
19145 			invalidateLayout();
19146 
19147 			if(ch == 13) ch = 10;
19148 			auto e = caret.inlineElement;
19149 			if(e is null) {
19150 				addText("" ~ cast(char) ch) ; // FIXME
19151 				return;
19152 			}
19153 
19154 			if(caret.offset == e.text.length) {
19155 				e.text ~= cast(char) ch; // FIXME
19156 				caret.offset++;
19157 				if(ch == 10) {
19158 					auto c = caret.inlineElement.clone;
19159 					c.text = null;
19160 					c.letterXs = null;
19161 					insertPartAfter(c,e);
19162 					caret = Caret(this, c, 0);
19163 				}
19164 			} else {
19165 				// FIXME cast char sucks
19166 				if(ch == 10) {
19167 					auto c = caret.inlineElement.clone;
19168 					c.text = e.text[caret.offset .. $];
19169 					if(caret.offset < c.letterXs.length)
19170 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
19171 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
19172 					if(caret.offset <= e.letterXs.length) {
19173 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
19174 					}
19175 					insertPartAfter(c,e);
19176 					caret = Caret(this, c, 0);
19177 				} else {
19178 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
19179 					caret.offset++;
19180 				}
19181 			}
19182 		}
19183 
19184 		void insertPartAfter(InlineElement what, InlineElement where) {
19185 			foreach(idx, p; where.containingBlock.parts) {
19186 				if(p is where) {
19187 					if(idx + 1 == where.containingBlock.parts.length)
19188 						where.containingBlock.parts ~= what;
19189 					else
19190 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
19191 					return;
19192 				}
19193 			}
19194 		}
19195 
19196 		void cleanupStructures() {
19197 			for(size_t i = 0; i < blocks.length; i++) {
19198 				auto block = blocks[i];
19199 				for(size_t a = 0; a < block.parts.length; a++) {
19200 					auto part = block.parts[a];
19201 					if(part.text.length == 0) {
19202 						for(size_t b = a; b < block.parts.length - 1; b++)
19203 							block.parts[b] = block.parts[b+1];
19204 						block.parts = block.parts[0 .. $-1];
19205 					}
19206 				}
19207 				if(block.parts.length == 0) {
19208 					for(size_t a = i; a < blocks.length - 1; a++)
19209 						blocks[a] = blocks[a+1];
19210 					blocks = blocks[0 .. $-1];
19211 				}
19212 			}
19213 		}
19214 
19215 		void backspace() {
19216 			try_again:
19217 			auto e = caret.inlineElement;
19218 			if(e is null)
19219 				return;
19220 			if(caret.offset == 0) {
19221 				auto prev = e.getPreviousInlineElement();
19222 				if(prev is null)
19223 					return;
19224 				auto newOffset = cast(int) prev.text.length;
19225 				tryMerge(prev, e);
19226 				caret.inlineElement = prev;
19227 				caret.offset = prev is null ? 0 : newOffset;
19228 
19229 				goto try_again;
19230 			} else if(caret.offset == e.text.length) {
19231 				e.text = e.text[0 .. $-1];
19232 				caret.offset--;
19233 			} else {
19234 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
19235 				caret.offset--;
19236 			}
19237 			//cleanupStructures();
19238 
19239 			invalidateLayout();
19240 		}
19241 		void delete_() {
19242 			if(selectionStart !is selectionEnd)
19243 				deleteSelection();
19244 			else {
19245 				auto before = caret;
19246 				moveRight();
19247 				if(caret != before) {
19248 					backspace();
19249 				}
19250 			}
19251 
19252 			invalidateLayout();
19253 		}
19254 		void overstrike() {}
19255 
19256 		/// Selection API. See also: caret movement.
19257 		void selectAll() {
19258 			moveDocumentStart(selectionStart);
19259 			moveDocumentEnd(selectionEnd);
19260 		}
19261 		bool selectNone() {
19262 			if(selectionStart != selectionEnd) {
19263 				selectionStart = selectionEnd = Caret.init;
19264 				return true;
19265 			}
19266 			return false;
19267 		}
19268 
19269 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
19270 		/// They will modify the current selection if there is one and will splice one in if needed.
19271 		void changeAttributes() {}
19272 
19273 
19274 		/// Text search api. They manipulate the selection and/or caret.
19275 		void findText(string text) {}
19276 		void findIndex(size_t textIndex) {}
19277 
19278 		// sample event handlers
19279 
19280 		void handleEvent(KeyEvent event) {
19281 			//if(event.type == KeyEvent.Type.KeyPressed) {
19282 
19283 			//}
19284 		}
19285 
19286 		void handleEvent(dchar ch) {
19287 
19288 		}
19289 
19290 		void handleEvent(MouseEvent event) {
19291 
19292 		}
19293 
19294 		bool contentEditable; // can it be edited?
19295 		bool contentCaretable; // is there a caret/cursor that moves around in there?
19296 		bool contentSelectable; // selectable?
19297 
19298 		Caret caret;
19299 		Caret selectionStart;
19300 		Caret selectionEnd;
19301 
19302 		bool insertMode;
19303 	}
19304 
19305 	struct Caret {
19306 		TextLayout layout;
19307 		InlineElement inlineElement;
19308 		int offset;
19309 	}
19310 
19311 	enum TextFormat : ushort {
19312 		// decorations
19313 		underline = 1,
19314 		strikethrough = 2,
19315 
19316 		// font selectors
19317 
19318 		bold = 0x4000 | 1, // weight 700
19319 		light = 0x4000 | 2, // weight 300
19320 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
19321 		// bold | light is really invalid but should give weight 500
19322 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
19323 
19324 		italic = 0x4000 | 8,
19325 		smallcaps = 0x4000 | 16,
19326 	}
19327 
19328 	void* findFont(string family, int weight, TextFormat formats) {
19329 		return null;
19330 	}
19331 
19332 }
19333 
19334 /++
19335 	$(PITFALL This is not yet stable and may break in future versions without notice.)
19336 
19337 	History:
19338 		Added February 19, 2021
19339 +/
19340 /// Group: drag_and_drop
19341 interface DropHandler {
19342 	/++
19343 		Called when the drag enters the handler's area.
19344 	+/
19345 	DragAndDropAction dragEnter(DropPackage*);
19346 	/++
19347 		Called when the drag leaves the handler's area or is
19348 		cancelled. You should free your resources when this is called.
19349 	+/
19350 	void dragLeave();
19351 	/++
19352 		Called continually as the drag moves over the handler's area.
19353 
19354 		Returns: feedback to the dragger
19355 	+/
19356 	DropParameters dragOver(Point pt);
19357 	/++
19358 		The user dropped the data and you should process it now. You can
19359 		access the data through the given [DropPackage].
19360 	+/
19361 	void drop(scope DropPackage*);
19362 	/++
19363 		Called when the drop is complete. You should free whatever temporary
19364 		resources you were using. It is often reasonable to simply forward
19365 		this call to [dragLeave].
19366 	+/
19367 	void finish();
19368 
19369 	/++
19370 		Parameters returned by [DropHandler.drop].
19371 	+/
19372 	static struct DropParameters {
19373 		/++
19374 			Acceptable action over this area.
19375 		+/
19376 		DragAndDropAction action;
19377 		/++
19378 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
19379 
19380 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
19381 		+/
19382 		Rectangle consistentWithin;
19383 	}
19384 }
19385 
19386 /++
19387 	History:
19388 		Added February 19, 2021
19389 +/
19390 /// Group: drag_and_drop
19391 enum DragAndDropAction {
19392 	none = 0,
19393 	copy,
19394 	move,
19395 	link,
19396 	ask,
19397 	custom
19398 }
19399 
19400 /++
19401 	An opaque structure representing dropped data. It contains
19402 	private, platform-specific data that your `drop` function
19403 	should simply forward.
19404 
19405 	$(PITFALL This is not yet stable and may break in future versions without notice.)
19406 
19407 	History:
19408 		Added February 19, 2021
19409 +/
19410 /// Group: drag_and_drop
19411 struct DropPackage {
19412 	/++
19413 		Lists the available formats as magic numbers. You should compare these
19414 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
19415 		understand the passed data.
19416 	+/
19417 	DraggableData.FormatId[] availableFormats() {
19418 		version(X11) {
19419 			return xFormats;
19420 		} else version(Windows) {
19421 			if(pDataObj is null)
19422 				return null;
19423 
19424 			typeof(return) ret;
19425 
19426 			IEnumFORMATETC ef;
19427 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
19428 				FORMATETC fmt;
19429 				ULONG fetched;
19430 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
19431 					if(fetched == 0)
19432 						break;
19433 
19434 					if(fmt.lindex != -1)
19435 						continue;
19436 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
19437 						continue;
19438 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
19439 						continue;
19440 
19441 					ret ~= fmt.cfFormat;
19442 				}
19443 			}
19444 
19445 			return ret;
19446 		}
19447 	}
19448 
19449 	/++
19450 		Gets data from the drop and optionally accepts it.
19451 
19452 		Returns:
19453 			void because the data is fed asynchronously through the `dg` parameter.
19454 
19455 		Params:
19456 			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.
19457 
19458 			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.
19459 
19460 			Calling `getData` again after accepting a drop is not permitted.
19461 
19462 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
19463 
19464 			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.
19465 
19466 		Throws:
19467 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
19468 
19469 		History:
19470 			Included in first release of [DropPackage].
19471 	+/
19472 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
19473 		version(X11) {
19474 
19475 			auto display = XDisplayConnection.get();
19476 			auto selectionAtom = GetAtom!"XdndSelection"(display);
19477 			auto best = format;
19478 
19479 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
19480 
19481 				XDisplay* display;
19482 				Atom selectionAtom;
19483 				DraggableData.FormatId best;
19484 				DraggableData.FormatId format;
19485 				void delegate(scope ubyte[] data) dg;
19486 				DragAndDropAction acceptedAction;
19487 				Window sourceWindow;
19488 				SimpleWindow win;
19489 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
19490 					this.display = display;
19491 					this.win = win;
19492 					this.sourceWindow = sourceWindow;
19493 					this.format = format;
19494 					this.selectionAtom = selectionAtom;
19495 					this.best = best;
19496 					this.dg = dg;
19497 					this.acceptedAction = acceptedAction;
19498 				}
19499 
19500 
19501 				mixin X11GetSelectionHandler_Basics;
19502 
19503 				void handleData(Atom target, in ubyte[] data) {
19504 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
19505 
19506 					dg(cast(ubyte[]) data);
19507 
19508 					if(acceptedAction != DragAndDropAction.none) {
19509 						auto display = XDisplayConnection.get;
19510 
19511 						XClientMessageEvent xclient;
19512 
19513 						xclient.type = EventType.ClientMessage;
19514 						xclient.window = sourceWindow;
19515 						xclient.message_type = GetAtom!"XdndFinished"(display);
19516 						xclient.format = 32;
19517 						xclient.data.l[0] = win.impl.window;
19518 						xclient.data.l[1] = 1; // drop successful
19519 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
19520 
19521 						XSendEvent(
19522 							display,
19523 							sourceWindow,
19524 							false,
19525 							EventMask.NoEventMask,
19526 							cast(XEvent*) &xclient
19527 						);
19528 
19529 						XFlush(display);
19530 					}
19531 				}
19532 
19533 				Atom findBestFormat(Atom[] answer) {
19534 					Atom best = None;
19535 					foreach(option; answer) {
19536 						if(option == format) {
19537 							best = option;
19538 							break;
19539 						}
19540 						/*
19541 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
19542 							best = option;
19543 							break;
19544 						} else if(option == XA_STRING) {
19545 							best = option;
19546 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
19547 							best = option;
19548 						}
19549 						*/
19550 					}
19551 					return best;
19552 				}
19553 			}
19554 
19555 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
19556 
19557 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
19558 
19559 		} else version(Windows) {
19560 
19561 			// clean up like DragLeave
19562 			// pass effect back up
19563 
19564 			FORMATETC t;
19565 			assert(format >= 0 && format <= ushort.max);
19566 			t.cfFormat = cast(ushort) format;
19567 			t.lindex = -1;
19568 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
19569 			t.tymed = TYMED.TYMED_HGLOBAL;
19570 
19571 			STGMEDIUM m;
19572 
19573 			if(pDataObj.GetData(&t, &m) != S_OK) {
19574 				// fail
19575 			} else {
19576 				// succeed, take the data and clean up
19577 
19578 				// FIXME: ensure it is legit HGLOBAL
19579 				auto handle = m.hGlobal;
19580 
19581 				if(handle) {
19582 					auto sz = GlobalSize(handle);
19583 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
19584 						scope(exit) GlobalUnlock(handle);
19585 						scope(exit) GlobalFree(handle);
19586 
19587 						auto data = ptr[0 .. sz];
19588 
19589 						dg(data);
19590 					}
19591 				}
19592 			}
19593 		}
19594 	}
19595 
19596 	private:
19597 
19598 	version(X11) {
19599 		SimpleWindow win;
19600 		Window sourceWindow;
19601 		Time dataTimestamp;
19602 
19603 		Atom[] xFormats;
19604 	}
19605 	version(Windows) {
19606 		IDataObject pDataObj;
19607 	}
19608 }
19609 
19610 /++
19611 	A generic helper base class for making a drop handler with a preference list of custom types.
19612 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
19613 	droppers too.
19614 
19615 	It assumes the whole window it used, but you can subclass to change that.
19616 
19617 	$(PITFALL This is not yet stable and may break in future versions without notice.)
19618 
19619 	History:
19620 		Added February 19, 2021
19621 +/
19622 /// Group: drag_and_drop
19623 class GenericDropHandlerBase : DropHandler {
19624 	// no fancy state here so no need to do anything here
19625 	void finish() { }
19626 	void dragLeave() { }
19627 
19628 	private DragAndDropAction acceptedAction;
19629 	private DraggableData.FormatId acceptedFormat;
19630 	private void delegate(scope ubyte[]) acceptedHandler;
19631 
19632 	struct FormatHandler {
19633 		DraggableData.FormatId format;
19634 		void delegate(scope ubyte[]) handler;
19635 	}
19636 
19637 	protected abstract FormatHandler[] formatHandlers();
19638 
19639 	DragAndDropAction dragEnter(DropPackage* pkg) {
19640 		debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
19641 		foreach(fmt; formatHandlers())
19642 		foreach(f; pkg.availableFormats())
19643 			if(f == fmt.format) {
19644 				acceptedFormat = f;
19645 				acceptedHandler = fmt.handler;
19646 				return acceptedAction = DragAndDropAction.copy;
19647 			}
19648 		return acceptedAction = DragAndDropAction.none;
19649 	}
19650 	DropParameters dragOver(Point pt) {
19651 		return DropParameters(acceptedAction);
19652 	}
19653 
19654 	void drop(scope DropPackage* dropPackage) {
19655 		if(!acceptedFormat || acceptedHandler is null) {
19656 			debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
19657 			return; // prolly shouldn't happen anyway...
19658 		}
19659 
19660 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
19661 	}
19662 }
19663 
19664 /++
19665 	A simple handler for making your window accept drops of plain text.
19666 
19667 	$(PITFALL This is not yet stable and may break in future versions without notice.)
19668 
19669 	History:
19670 		Added February 22, 2021
19671 +/
19672 /// Group: drag_and_drop
19673 class TextDropHandler : GenericDropHandlerBase {
19674 	private void delegate(in char[] text) dg;
19675 
19676 	/++
19677 
19678 	+/
19679 	this(void delegate(in char[] text) dg) {
19680 		this.dg = dg;
19681 	}
19682 
19683 	protected override FormatHandler[] formatHandlers() {
19684 		version(X11)
19685 			return [
19686 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
19687 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
19688 			];
19689 		else version(Windows)
19690 			return [
19691 				FormatHandler(CF_UNICODETEXT, &translator),
19692 			];
19693 	}
19694 
19695 	private void translator(scope ubyte[] data) {
19696 		version(X11)
19697 			dg(cast(char[]) data);
19698 		else version(Windows)
19699 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
19700 	}
19701 }
19702 
19703 /++
19704 	A simple handler for making your window accept drops of files, issued to you as file names.
19705 
19706 	$(PITFALL This is not yet stable and may break in future versions without notice.)
19707 
19708 	History:
19709 		Added February 22, 2021
19710 +/
19711 /// Group: drag_and_drop
19712 
19713 class FilesDropHandler : GenericDropHandlerBase {
19714 	private void delegate(in char[][]) dg;
19715 
19716 	/++
19717 
19718 	+/
19719 	this(void delegate(in char[][] fileNames) dg) {
19720 		this.dg = dg;
19721 	}
19722 
19723 	protected override FormatHandler[] formatHandlers() {
19724 		version(X11)
19725 			return [
19726 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
19727 			];
19728 		else version(Windows)
19729 			return [
19730 				FormatHandler(CF_HDROP, &translator),
19731 			];
19732 	}
19733 
19734 	private void translator(scope ubyte[] data) {
19735 		version(X11) {
19736 			char[] listString = cast(char[]) data;
19737 			char[][16] buffer;
19738 			int count;
19739 			char[][] result = buffer[];
19740 
19741 			void commit(char[] s) {
19742 				if(count == result.length)
19743 					result.length += 16;
19744 				if(s.length > 7 && s[0 ..7] == "file://")
19745 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
19746 				result[count++] = s;
19747 			}
19748 
19749 			size_t last;
19750 			foreach(idx, char c; listString) {
19751 				if(c == '\n') {
19752 					commit(listString[last .. idx - 1]); // a \r
19753 					last = idx + 1; // a \n
19754 				}
19755 			}
19756 
19757 			if(last < listString.length) {
19758 				commit(listString[last .. $]);
19759 			}
19760 
19761 			// FIXME: they are uris now, should I translate it to local file names?
19762 			// of course the host name is supposed to be there cuz of X rokking...
19763 
19764 			dg(result[0 .. count]);
19765 		} else version(Windows) {
19766 
19767 			static struct DROPFILES {
19768 				DWORD pFiles;
19769 				POINT pt;
19770 				BOOL  fNC;
19771 				BOOL  fWide;
19772 			}
19773 
19774 
19775 			const(char)[][16] buffer;
19776 			int count;
19777 			const(char)[][] result = buffer[];
19778 			size_t last;
19779 
19780 			void commitA(in char[] stuff) {
19781 				if(count == result.length)
19782 					result.length += 16;
19783 				result[count++] = stuff;
19784 			}
19785 
19786 			void commitW(in wchar[] stuff) {
19787 				commitA(makeUtf8StringFromWindowsString(stuff));
19788 			}
19789 
19790 			void magic(T)(T chars) {
19791 				size_t idx;
19792 				while(chars[idx]) {
19793 					last = idx;
19794 					while(chars[idx]) {
19795 						idx++;
19796 					}
19797 					static if(is(T == char*))
19798 						commitA(chars[last .. idx]);
19799 					else
19800 						commitW(chars[last .. idx]);
19801 					idx++;
19802 				}
19803 			}
19804 
19805 			auto df = cast(DROPFILES*) data.ptr;
19806 			if(df.fWide) {
19807 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
19808 				magic(chars);
19809 			} else {
19810 				char* chars = cast(char*) (data.ptr + df.pFiles);
19811 				magic(chars);
19812 			}
19813 			dg(result[0 .. count]);
19814 		}
19815 	}
19816 }
19817 
19818 /++
19819 	Interface to describe data being dragged. See also [draggable] helper function.
19820 
19821 	$(PITFALL This is not yet stable and may break in future versions without notice.)
19822 
19823 	History:
19824 		Added February 19, 2021
19825 +/
19826 interface DraggableData {
19827 	version(X11)
19828 		alias FormatId = Atom;
19829 	else
19830 		alias FormatId = uint;
19831 	/++
19832 		Gets the platform-specific FormatId associated with the given named format.
19833 
19834 		This may be a MIME type, but may also be other various strings defined by the
19835 		programs you want to interoperate with.
19836 
19837 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
19838 		and convert it to some particular type for you.
19839 	+/
19840 	static FormatId getFormatId(string name)() {
19841 		version(X11)
19842 			return GetAtom!name(XDisplayConnection.get);
19843 		else version(Windows) {
19844 			static UINT cache;
19845 			if(!cache)
19846 				cache = RegisterClipboardFormatA(name);
19847 			return cache;
19848 		} else
19849 			throw new NotYetImplementedException();
19850 	}
19851 
19852 	/++
19853 		Looks up a string to represent the name for the given format, if there is one.
19854 
19855 		You should avoid using this function because it is slow. It is provided more for
19856 		debugging than for primary use.
19857 	+/
19858 	static string getFormatName(FormatId format) {
19859 		version(X11) {
19860 			if(format == 0)
19861 				return "None";
19862 			else
19863 				return getAtomName(format, XDisplayConnection.get);
19864 		} else version(Windows) {
19865 			switch(format) {
19866 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
19867 				case CF_DIBV5: return "CF_DIBV5";
19868 				case CF_RIFF: return "CF_RIFF";
19869 				case CF_WAVE: return "CF_WAVE";
19870 				case CF_HDROP: return "CF_HDROP";
19871 				default:
19872 					char[1024] name;
19873 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
19874 					return name[0 .. count].idup;
19875 			}
19876 		}
19877 	}
19878 
19879 	FormatId[] availableFormats();
19880 	// Return the slice of data you filled, empty slice if done.
19881 	// this is to support the incremental thing
19882 	ubyte[] getData(FormatId format, return scope ubyte[] data);
19883 
19884 	size_t dataLength(FormatId format);
19885 }
19886 
19887 /++
19888 	$(PITFALL This is not yet stable and may break in future versions without notice.)
19889 
19890 	History:
19891 		Added February 19, 2021
19892 +/
19893 DraggableData draggable(string s) {
19894 	version(X11)
19895 	return new class X11SetSelectionHandler_Text, DraggableData {
19896 		this() {
19897 			super(s);
19898 		}
19899 
19900 		override FormatId[] availableFormats() {
19901 			return X11SetSelectionHandler_Text.availableFormats();
19902 		}
19903 
19904 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
19905 			return X11SetSelectionHandler_Text.getData(format, data);
19906 		}
19907 
19908 		size_t dataLength(FormatId format) {
19909 			return s.length;
19910 		}
19911 	};
19912 	version(Windows)
19913 	return new class DraggableData {
19914 		FormatId[] availableFormats() {
19915 			return [CF_UNICODETEXT];
19916 		}
19917 
19918 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
19919 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
19920 		}
19921 
19922 		size_t dataLength(FormatId format) {
19923 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
19924 		}
19925 	};
19926 }
19927 
19928 /++
19929 	$(PITFALL This is not yet stable and may break in future versions without notice.)
19930 
19931 	History:
19932 		Added February 19, 2021
19933 +/
19934 /// Group: drag_and_drop
19935 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
19936 in {
19937 	assert(window !is null);
19938 	assert(handler !is null);
19939 }
19940 do
19941 {
19942 	version(X11) {
19943 		auto sh = cast(X11SetSelectionHandler) handler;
19944 		if(sh is null) {
19945 			// gotta make my own adapter.
19946 			sh = new class X11SetSelectionHandler {
19947 				mixin X11SetSelectionHandler_Basics;
19948 
19949 				Atom[] availableFormats() { return handler.availableFormats(); }
19950 				ubyte[] getData(Atom format, return scope ubyte[] data) {
19951 					return handler.getData(format, data);
19952 				}
19953 
19954 				// since the drop selection is only ever used once it isn't important
19955 				// to reset it.
19956 				void done() {}
19957 			};
19958 		}
19959 		return doDragDropX11(window, sh, action);
19960 	} else version(Windows) {
19961 		return doDragDropWindows(window, handler, action);
19962 	} else throw new NotYetImplementedException();
19963 }
19964 
19965 version(Windows)
19966 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
19967 	IDataObject obj = new class IDataObject {
19968 		ULONG refCount;
19969 		ULONG AddRef() {
19970 			return ++refCount;
19971 		}
19972 		ULONG Release() {
19973 			return --refCount;
19974 		}
19975 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
19976 			if (IID_IUnknown == *riid) {
19977 				*ppv = cast(void*) cast(IUnknown) this;
19978 			}
19979 			else if (IID_IDataObject == *riid) {
19980 				*ppv = cast(void*) cast(IDataObject) this;
19981 			}
19982 			else {
19983 				*ppv = null;
19984 				return E_NOINTERFACE;
19985 			}
19986 
19987 			AddRef();
19988 			return NOERROR;
19989 		}
19990 
19991 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
19992 			// import std.stdio; writeln("Advise");
19993 			return E_NOTIMPL;
19994 		}
19995 		HRESULT DUnadvise(DWORD dwConnection) {
19996 			return E_NOTIMPL;
19997 		}
19998 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
19999 			// import std.stdio; writeln("EnumDAdvise");
20000 			return OLE_E_ADVISENOTSUPPORTED;
20001 		}
20002 		// tell what formats it supports
20003 
20004 		FORMATETC[] types;
20005 		this() {
20006 			FORMATETC t;
20007 			foreach(ty; handler.availableFormats()) {
20008 				assert(ty <= ushort.max && ty >= 0);
20009 				t.cfFormat = cast(ushort) ty;
20010 				t.lindex = -1;
20011 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
20012 				t.tymed = TYMED.TYMED_HGLOBAL;
20013 			}
20014 			types ~= t;
20015 		}
20016 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
20017 			if(dwDirection == DATADIR.DATADIR_GET) {
20018 				*ppenumFormatEtc = new class IEnumFORMATETC {
20019 					ULONG refCount;
20020 					ULONG AddRef() {
20021 						return ++refCount;
20022 					}
20023 					ULONG Release() {
20024 						return --refCount;
20025 					}
20026 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
20027 						if (IID_IUnknown == *riid) {
20028 							*ppv = cast(void*) cast(IUnknown) this;
20029 						}
20030 						else if (IID_IEnumFORMATETC == *riid) {
20031 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
20032 						}
20033 						else {
20034 							*ppv = null;
20035 							return E_NOINTERFACE;
20036 						}
20037 
20038 						AddRef();
20039 						return NOERROR;
20040 					}
20041 
20042 
20043 					int pos;
20044 					this() {
20045 						pos = 0;
20046 					}
20047 
20048 					HRESULT Clone(IEnumFORMATETC* ppenum) {
20049 						// import std.stdio; writeln("clone");
20050 						return E_NOTIMPL; // FIXME
20051 					}
20052 
20053 					// Caller is responsible for freeing memory
20054 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
20055 						// fetched may be null if celt is one
20056 						if(celt != 1)
20057 							return E_NOTIMPL; // FIXME
20058 
20059 						if(celt + pos > types.length)
20060 							return S_FALSE;
20061 
20062 						*rgelt = types[pos++];
20063 
20064 						if(pceltFetched !is null)
20065 							*pceltFetched = 1;
20066 
20067 						// import std.stdio; writeln("ok celt ", celt);
20068 						return S_OK;
20069 					}
20070 
20071 					HRESULT Reset() {
20072 						pos = 0;
20073 						return S_OK;
20074 					}
20075 
20076 					HRESULT Skip(ULONG celt) {
20077 						if(celt + pos <= types.length) {
20078 							pos += celt;
20079 							return S_OK;
20080 						}
20081 						return S_FALSE;
20082 					}
20083 				};
20084 
20085 				return S_OK;
20086 			} else
20087 				return E_NOTIMPL;
20088 		}
20089 		// given a format, return the format you'd prefer to use cuz it is identical
20090 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
20091 			// FIXME: prolly could be better but meh
20092 			// import std.stdio; writeln("gcf: ", *pformatectIn);
20093 			*pformatetcOut = *pformatectIn;
20094 			return S_OK;
20095 		}
20096 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
20097 			foreach(ty; types) {
20098 				if(ty == *pformatetcIn) {
20099 					auto format = ty.cfFormat;
20100 					// import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty);
20101 					STGMEDIUM medium;
20102 					medium.tymed = TYMED.TYMED_HGLOBAL;
20103 
20104 					auto sz = handler.dataLength(format);
20105 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
20106 					if(handle is null) throw new Exception("GlobalAlloc");
20107 					if(auto data = cast(wchar*) GlobalLock(handle)) {
20108 						auto slice = data[0 .. sz];
20109 						scope(exit)
20110 							GlobalUnlock(handle);
20111 
20112 						handler.getData(format, cast(ubyte[]) slice[]);
20113 					}
20114 
20115 
20116 					medium.hGlobal = handle; // FIXME
20117 					*pmedium = medium;
20118 					return S_OK;
20119 				}
20120 			}
20121 			return DV_E_FORMATETC;
20122 		}
20123 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
20124 			// import std.stdio; writeln("GDH: ", *pformatetcIn);
20125 			return E_NOTIMPL; // FIXME
20126 		}
20127 		HRESULT QueryGetData(FORMATETC* pformatetc) {
20128 			auto search = *pformatetc;
20129 			search.tymed &= TYMED.TYMED_HGLOBAL;
20130 			foreach(ty; types)
20131 				if(ty == search) {
20132 					// import std.stdio; writeln("QueryGetData ", search, " ", types[0]);
20133 					return S_OK;
20134 				}
20135 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
20136 				//import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]);
20137 			}
20138 			return S_FALSE;
20139 		}
20140 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
20141 			// import std.stdio; writeln("SetData: ");
20142 			return E_NOTIMPL;
20143 		}
20144 	};
20145 
20146 
20147 	IDropSource src = new class IDropSource {
20148 		ULONG refCount;
20149 		ULONG AddRef() {
20150 			return ++refCount;
20151 		}
20152 		ULONG Release() {
20153 			return --refCount;
20154 		}
20155 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
20156 			if (IID_IUnknown == *riid) {
20157 				*ppv = cast(void*) cast(IUnknown) this;
20158 			}
20159 			else if (IID_IDropSource == *riid) {
20160 				*ppv = cast(void*) cast(IDropSource) this;
20161 			}
20162 			else {
20163 				*ppv = null;
20164 				return E_NOINTERFACE;
20165 			}
20166 
20167 			AddRef();
20168 			return NOERROR;
20169 		}
20170 
20171 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
20172 			if(fEscapePressed)
20173 				return DRAGDROP_S_CANCEL;
20174 			if(!(grfKeyState & MK_LBUTTON))
20175 				return DRAGDROP_S_DROP;
20176 			return S_OK;
20177 		}
20178 
20179 		int GiveFeedback(uint dwEffect) {
20180 			return DRAGDROP_S_USEDEFAULTCURSORS;
20181 		}
20182 	};
20183 
20184 	DWORD effect;
20185 
20186 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
20187 
20188 	DROPEFFECT de = win32DragAndDropAction(action);
20189 
20190 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
20191 	// but still prolly a FIXME
20192 
20193 	auto ret = DoDragDrop(obj, src, de, &effect);
20194 	/+
20195 	import std.stdio;
20196 	if(ret == DRAGDROP_S_DROP)
20197 		writeln("drop ", effect);
20198 	else if(ret == DRAGDROP_S_CANCEL)
20199 		writeln("cancel");
20200 	else if(ret == S_OK)
20201 		writeln("ok");
20202 	else writeln(ret);
20203 	+/
20204 
20205 	return ret;
20206 }
20207 
20208 version(Windows)
20209 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
20210 	DROPEFFECT de;
20211 
20212 	with(DragAndDropAction)
20213 	with(DROPEFFECT)
20214 	final switch(action) {
20215 		case none: de = DROPEFFECT_NONE; break;
20216 		case copy: de = DROPEFFECT_COPY; break;
20217 		case move: de = DROPEFFECT_MOVE; break;
20218 		case link: de = DROPEFFECT_LINK; break;
20219 		case ask: throw new Exception("ask not implemented yet");
20220 		case custom: throw new Exception("custom not implemented yet");
20221 	}
20222 
20223 	return de;
20224 }
20225 
20226 
20227 /++
20228 	History:
20229 		Added February 19, 2021
20230 +/
20231 /// Group: drag_and_drop
20232 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
20233 	version(X11) {
20234 		auto display = XDisplayConnection.get;
20235 
20236 		Atom atom = 5; // right???
20237 
20238 		XChangeProperty(
20239 			display,
20240 			window.impl.window,
20241 			GetAtom!"XdndAware"(display),
20242 			XA_ATOM,
20243 			32 /* bits */,
20244 			PropModeReplace,
20245 			&atom,
20246 			1);
20247 
20248 		window.dropHandler = handler;
20249 	} else version(Windows) {
20250 
20251 		initDnd();
20252 
20253 		auto dropTarget = new class (handler) IDropTarget {
20254 			DropHandler handler;
20255 			this(DropHandler handler) {
20256 				this.handler = handler;
20257 			}
20258 			ULONG refCount;
20259 			ULONG AddRef() {
20260 				return ++refCount;
20261 			}
20262 			ULONG Release() {
20263 				return --refCount;
20264 			}
20265 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
20266 				if (IID_IUnknown == *riid) {
20267 					*ppv = cast(void*) cast(IUnknown) this;
20268 				}
20269 				else if (IID_IDropTarget == *riid) {
20270 					*ppv = cast(void*) cast(IDropTarget) this;
20271 				}
20272 				else {
20273 					*ppv = null;
20274 					return E_NOINTERFACE;
20275 				}
20276 
20277 				AddRef();
20278 				return NOERROR;
20279 			}
20280 
20281 
20282 			// ///////////////////
20283 
20284 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
20285 				DropPackage dropPackage = DropPackage(pDataObj);
20286 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
20287 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
20288 			}
20289 
20290 			HRESULT DragLeave() {
20291 				handler.dragLeave();
20292 				// release the IDataObject if needed
20293 				return S_OK;
20294 			}
20295 
20296 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
20297 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
20298 
20299 				*pdwEffect = win32DragAndDropAction(res.action);
20300 				// same as DragEnter basically
20301 				return S_OK;
20302 			}
20303 
20304 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
20305 				DropPackage pkg = DropPackage(pDataObj);
20306 				handler.drop(&pkg);
20307 
20308 				return S_OK;
20309 			}
20310 		};
20311 		// Windows can hold on to the handler and try to call it
20312 		// during which time the GC can't see it. so important to
20313 		// manually manage this. At some point i'll FIXME and make
20314 		// all my com instances manually managed since they supposed
20315 		// to respect the refcount.
20316 		import core.memory;
20317 		GC.addRoot(cast(void*) dropTarget);
20318 
20319 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
20320 			throw new Exception("register");
20321 
20322 		window.dropHandler = handler;
20323 	} else throw new NotYetImplementedException();
20324 }
20325 
20326 
20327 
20328 static if(UsingSimpledisplayX11) {
20329 
20330 enum _NET_WM_STATE_ADD = 1;
20331 enum _NET_WM_STATE_REMOVE = 0;
20332 enum _NET_WM_STATE_TOGGLE = 2;
20333 
20334 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases.
20335 void demandAttention(SimpleWindow window, bool needs = true) {
20336 	demandAttention(window.impl.window, needs);
20337 }
20338 
20339 /// ditto
20340 void demandAttention(Window window, bool needs = true) {
20341 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
20342 }
20343 
20344 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
20345 	auto display = XDisplayConnection.get();
20346 	if(atom == None)
20347 		return; // non-failure error
20348 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
20349 
20350 	XClientMessageEvent xclient;
20351 
20352 	xclient.type = EventType.ClientMessage;
20353 	xclient.window = window;
20354 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
20355 	xclient.format = 32;
20356 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
20357 	xclient.data.l[1] = atom;
20358 	xclient.data.l[2] = atom2;
20359 	xclient.data.l[3] = 1;
20360 	// [3] == source. 0 == unknown, 1 == app, 2 == else
20361 
20362 	XSendEvent(
20363 		display,
20364 		RootWindow(display, DefaultScreen(display)),
20365 		false,
20366 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
20367 		cast(XEvent*) &xclient
20368 	);
20369 
20370 	/+
20371 	XChangeProperty(
20372 		display,
20373 		window.impl.window,
20374 		GetAtom!"_NET_WM_STATE"(display),
20375 		XA_ATOM,
20376 		32 /* bits */,
20377 		PropModeAppend,
20378 		&atom,
20379 		1);
20380 	+/
20381 }
20382 
20383 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
20384 	Atom actionAtom;
20385 	with(DragAndDropAction)
20386 	final switch(action) {
20387 		case none: actionAtom = None; break;
20388 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
20389 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
20390 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
20391 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
20392 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
20393 	}
20394 
20395 	return actionAtom;
20396 }
20397 
20398 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
20399 	// FIXME: I need to show user feedback somehow.
20400 	auto display = XDisplayConnection.get;
20401 
20402 	auto actionAtom = dndActionAtom(display, action);
20403 	assert(actionAtom, "Don't use action none to accept a drop");
20404 
20405 	setX11Selection!"XdndSelection"(window, handler, null);
20406 
20407 	auto oldKeyHandler = window.handleKeyEvent;
20408 	scope(exit) window.handleKeyEvent = oldKeyHandler;
20409 
20410 	auto oldCharHandler = window.handleCharEvent;
20411 	scope(exit) window.handleCharEvent = oldCharHandler;
20412 
20413 	auto oldMouseHandler = window.handleMouseEvent;
20414 	scope(exit) window.handleMouseEvent = oldMouseHandler;
20415 
20416 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
20417 
20418 	import core.sys.posix.sys.time;
20419 	timeval tv;
20420 	gettimeofday(&tv, null);
20421 
20422 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
20423 
20424 	Time lastMouseTimestamp;
20425 
20426 	bool dnding = true;
20427 	Window lastIn = None;
20428 
20429 	void leave() {
20430 		if(lastIn == None)
20431 			return;
20432 
20433 		XEvent ev;
20434 		ev.xclient.type = EventType.ClientMessage;
20435 		ev.xclient.window = lastIn;
20436 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
20437 		ev.xclient.format = 32;
20438 		ev.xclient.data.l[0] = window.impl.window;
20439 
20440 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
20441 		XFlush(display);
20442 
20443 		lastIn = None;
20444 	}
20445 
20446 	void enter(Window w) {
20447 		assert(lastIn == None);
20448 
20449 		lastIn = w;
20450 
20451 		XEvent ev;
20452 		ev.xclient.type = EventType.ClientMessage;
20453 		ev.xclient.window = lastIn;
20454 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
20455 		ev.xclient.format = 32;
20456 		ev.xclient.data.l[0] = window.impl.window;
20457 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
20458 
20459 		auto types = handler.availableFormats();
20460 		assert(types.length > 0);
20461 
20462 		ev.xclient.data.l[2] = types[0];
20463 		if(types.length > 1)
20464 			ev.xclient.data.l[3] = types[1];
20465 		if(types.length > 2)
20466 			ev.xclient.data.l[4] = types[2];
20467 
20468 		// FIXME: other types?!?!? and make sure we skip TARGETS
20469 
20470 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
20471 		XFlush(display);
20472 	}
20473 
20474 	void position(int rootX, int rootY) {
20475 		assert(lastIn != None);
20476 
20477 		XEvent ev;
20478 		ev.xclient.type = EventType.ClientMessage;
20479 		ev.xclient.window = lastIn;
20480 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
20481 		ev.xclient.format = 32;
20482 		ev.xclient.data.l[0] = window.impl.window;
20483 		ev.xclient.data.l[1] = 0; // reserved
20484 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
20485 		ev.xclient.data.l[3] = dataTimestamp;
20486 		ev.xclient.data.l[4] = actionAtom;
20487 
20488 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
20489 		XFlush(display);
20490 
20491 	}
20492 
20493 	void drop() {
20494 		XEvent ev;
20495 		ev.xclient.type = EventType.ClientMessage;
20496 		ev.xclient.window = lastIn;
20497 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
20498 		ev.xclient.format = 32;
20499 		ev.xclient.data.l[0] = window.impl.window;
20500 		ev.xclient.data.l[1] = 0; // reserved
20501 		ev.xclient.data.l[2] = dataTimestamp;
20502 
20503 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
20504 		XFlush(display);
20505 
20506 		lastIn = None;
20507 		dnding = false;
20508 	}
20509 
20510 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
20511 	// but idk if i should...
20512 
20513 	window.setEventHandlers(
20514 		delegate(KeyEvent ev) {
20515 			if(ev.pressed == true && ev.key == Key.Escape) {
20516 				// cancel
20517 				dnding = false;
20518 			}
20519 		},
20520 		delegate(MouseEvent ev) {
20521 			if(ev.timestamp < lastMouseTimestamp)
20522 				return;
20523 
20524 			lastMouseTimestamp = ev.timestamp;
20525 
20526 			if(ev.type == MouseEventType.motion) {
20527 				auto display = XDisplayConnection.get;
20528 				auto root = RootWindow(display, DefaultScreen(display));
20529 
20530 				Window topWindow;
20531 				int rootX, rootY;
20532 
20533 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
20534 
20535 				if(topWindow == None)
20536 					return;
20537 
20538 				top:
20539 				if(auto result = topWindow in eligibility) {
20540 					auto dropWindow = *result;
20541 					if(dropWindow == None) {
20542 						leave();
20543 						return;
20544 					}
20545 
20546 					if(dropWindow != lastIn) {
20547 						leave();
20548 						enter(dropWindow);
20549 						position(rootX, rootY);
20550 					} else {
20551 						position(rootX, rootY);
20552 					}
20553 				} else {
20554 					// determine eligibility
20555 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
20556 					if(data.length == 1) {
20557 						// in case there is no WM or it isn't reparenting
20558 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
20559 					} else {
20560 
20561 						Window tryScanChildren(Window search, int maxRecurse) {
20562 							// could be reparenting window manager, so gotta check the next few children too
20563 							Window child;
20564 							int x;
20565 							int y;
20566 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
20567 
20568 							if(child == None)
20569 								return None;
20570 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
20571 							if(data.length == 1) {
20572 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
20573 							} else {
20574 								if(maxRecurse)
20575 									return tryScanChildren(child, maxRecurse - 1);
20576 								else
20577 									return None;
20578 							}
20579 
20580 						}
20581 
20582 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
20583 						auto topResult = tryScanChildren(topWindow, 3);
20584 						// it is easy to have a false negative due to the mouse going over a WM
20585 						// child window like the close button if separate from the frame... so I
20586 						// can't really cache negatives, :(
20587 						if(topResult != None) {
20588 							eligibility[topWindow] = topResult;
20589 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
20590 						}
20591 					}
20592 
20593 				}
20594 
20595 			} else if(ev.type == MouseEventType.buttonReleased) {
20596 				drop();
20597 				dnding = false;
20598 			}
20599 		}
20600 	);
20601 
20602 	window.grabInput();
20603 	scope(exit)
20604 		window.releaseInputGrab();
20605 
20606 
20607 	EventLoop.get.run(() => dnding);
20608 
20609 	return 0;
20610 }
20611 
20612 /// X-specific
20613 TrueColorImage getWindowNetWmIcon(Window window) {
20614 	try {
20615 		auto display = XDisplayConnection.get;
20616 
20617 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
20618 
20619 		if (data.length > arch_ulong.sizeof * 2) {
20620 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
20621 			// these are an array of rgba images that we have to convert into pixmaps ourself
20622 
20623 			int width = cast(int) meta[0];
20624 			int height = cast(int) meta[1];
20625 
20626 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
20627 
20628 			static if(arch_ulong.sizeof == 4) {
20629 				bytes = bytes[0 .. width * height * 4];
20630 				alias imageData = bytes;
20631 			} else static if(arch_ulong.sizeof == 8) {
20632 				bytes = bytes[0 .. width * height * 8];
20633 				auto imageData = new ubyte[](4 * width * height);
20634 			} else static assert(0);
20635 
20636 
20637 
20638 			// this returns ARGB. Remember it is little-endian so
20639 			//                                         we have BGRA
20640 			// our thing uses RGBA, which in little endian, is ABGR
20641 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
20642 				auto r = bytes[idx + 2];
20643 				auto g = bytes[idx + 1];
20644 				auto b = bytes[idx + 0];
20645 				auto a = bytes[idx + 3];
20646 
20647 				imageData[idx2 + 0] = r;
20648 				imageData[idx2 + 1] = g;
20649 				imageData[idx2 + 2] = b;
20650 				imageData[idx2 + 3] = a;
20651 			}
20652 
20653 			return new TrueColorImage(width, height, imageData);
20654 		}
20655 
20656 		return null;
20657 	} catch(Exception e) {
20658 		return null;
20659 	}
20660 }
20661 
20662 } /* UsingSimpledisplayX11 */
20663 
20664 
20665 void loadBinNameToWindowClassName () {
20666 	import core.stdc.stdlib : realloc;
20667 	version(linux) {
20668 		// args[0] MAY be empty, so we'll just use this
20669 		import core.sys.posix.unistd : readlink;
20670 		char[1024] ebuf = void; // 1KB should be enough for everyone!
20671 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
20672 		if (len < 1) return;
20673 	} else /*version(Windows)*/ {
20674 		import core.runtime : Runtime;
20675 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
20676 		auto ebuf = Runtime.args[0];
20677 		auto len = ebuf.length;
20678 	}
20679 	auto pos = len;
20680 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
20681 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
20682 	if (sdpyWindowClassStr is null) return; // oops
20683 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
20684 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
20685 }
20686 
20687 /++
20688 	An interface representing a font.
20689 
20690 	This is still MAJOR work in progress.
20691 +/
20692 interface DrawableFont {
20693 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
20694 }
20695 
20696 /++
20697 	Loads a true type font using [arsd.ttf]. That module must be compiled
20698 	in if you choose to use this function.
20699 
20700 	Be warned: this can be slow and memory hungry, especially on remote connections
20701 	to the X server.
20702 
20703 	This is still MAJOR work in progress.
20704 +/
20705 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
20706 	import arsd.ttf;
20707 	static class ArsdTtfFont : DrawableFont {
20708 		TtfFont font;
20709 		int size;
20710 		this(in ubyte[] data, int size) {
20711 			font = TtfFont(data);
20712 			this.size = size;
20713 		}
20714 
20715 		Sprite[string] cache;
20716 
20717 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
20718 			Sprite sprite = (text in cache) ? *(text in cache) : null;
20719 
20720 			auto fg = painter.impl._outlineColor;
20721 			auto bg = painter.impl._fillColor;
20722 
20723 			if(sprite is null) {
20724 				int width, height;
20725 				auto data = font.renderString(text, size, width, height);
20726 				auto image = new TrueColorImage(width, height);
20727 				int pos = 0;
20728 				foreach(y; 0 .. height)
20729 				foreach(x; 0 .. width) {
20730 					fg.a = data[0];
20731 					bg.a = 255;
20732 					auto color = alphaBlend(fg, bg);
20733 					image.imageData.bytes[pos++] = color.r;
20734 					image.imageData.bytes[pos++] = color.g;
20735 					image.imageData.bytes[pos++] = color.b;
20736 					image.imageData.bytes[pos++] = data[0];
20737 					data = data[1 .. $];
20738 				}
20739 				assert(data.length == 0);
20740 
20741 				sprite = new Sprite(painter.window, Image.fromMemoryImage(image));
20742 				cache[text.idup] = sprite;
20743 			}
20744 
20745 			sprite.drawAt(painter, upperLeft);
20746 		}
20747 	}
20748 
20749 	return new ArsdTtfFont(data, size);
20750 }
20751 
20752 class NotYetImplementedException : Exception {
20753 	this(string file = __FILE__, size_t line = __LINE__) {
20754 		super("Not yet implemented", file, line);
20755 	}
20756 }
20757 
20758 ///
20759 __gshared bool librariesSuccessfullyLoaded = true;
20760 ///
20761 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
20762 
20763 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
20764 	mixin(staticForeachReplacement!Iface);
20765 
20766 	void loadDynamicLibrary() @nogc {
20767 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
20768 	}
20769 
20770         void loadDynamicLibraryForReal() {
20771                 foreach(name; __traits(derivedMembers, Iface)) {
20772                         mixin("alias tmp = " ~ name ~ ";");
20773                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
20774                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
20775                 }
20776         }
20777 }
20778 
20779 private const(char)[] staticForeachReplacement(Iface)() pure {
20780 /*
20781 	// just this for gdc 9....
20782 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
20783 
20784         static foreach(name; __traits(derivedMembers, Iface))
20785                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
20786 */
20787 
20788 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
20789 	size_t pos;
20790 
20791 	void append(in char[] what) {
20792 		if(pos + what.length > code.length)
20793 			code.length = (code.length * 3) / 2;
20794 		code[pos .. pos + what.length] = what[];
20795 		pos += what.length;
20796 	}
20797 
20798         foreach(name; __traits(derivedMembers, Iface)) {
20799                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
20800 		append(name);
20801 		append(`")) `);
20802 		append(name);
20803 		append(";");
20804 	}
20805 
20806 	return code[0 .. pos];
20807 }
20808 
20809 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) {
20810 	mixin(staticForeachReplacement!Iface);
20811 
20812 	private __gshared void* libHandle;
20813 	private __gshared bool attempted;
20814 
20815         void loadDynamicLibrary() @nogc {
20816 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
20817 	}
20818 
20819 	bool loadAttempted() {
20820 		return attempted;
20821 	}
20822 	bool loadSuccessful() {
20823 		return libHandle !is null;
20824 	}
20825 
20826         void loadDynamicLibraryForReal() {
20827 		attempted = true;
20828                 version(Posix) {
20829                         import core.sys.posix.dlfcn;
20830 			version(OSX) {
20831 				version(X11)
20832                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
20833 				else
20834                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
20835 			} else {
20836                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
20837 				if(libHandle is null)
20838                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
20839 			}
20840 
20841 			static void* loadsym(void* l, const char* name) {
20842 				import core.stdc.stdlib;
20843 				if(l is null)
20844 					return &abort;
20845 				return dlsym(l, name);
20846 			}
20847                 } else version(Windows) {
20848                         import core.sys.windows.windows;
20849                         libHandle = LoadLibrary(library ~ ".dll");
20850 			static void* loadsym(void* l, const char* name) {
20851 				import core.stdc.stdlib;
20852 				if(l is null)
20853 					return &abort;
20854 				return GetProcAddress(l, name);
20855 			}
20856                 }
20857                 if(libHandle is null) {
20858 			success = false;
20859                         //throw new Exception("load failure of library " ~ library);
20860 		}
20861                 foreach(name; __traits(derivedMembers, Iface)) {
20862                         mixin("alias tmp = " ~ name ~ ";");
20863                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
20864                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
20865                 }
20866         }
20867 
20868         void unloadDynamicLibrary() {
20869                 version(Posix) {
20870                         import core.sys.posix.dlfcn;
20871                         dlclose(libHandle);
20872                 } else version(Windows) {
20873                         import core.sys.windows.windows;
20874                         FreeLibrary(libHandle);
20875                 }
20876                 foreach(name; __traits(derivedMembers, Iface))
20877                         mixin(name ~ " = null;");
20878         }
20879 }
20880 
20881 void guiAbortProcess(string msg) {
20882 	import core.stdc.stdlib;
20883 	version(Windows) {
20884 		WCharzBuffer t = WCharzBuffer(msg);
20885 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
20886 	} else {
20887 		import std.stdio;
20888 		stderr.writeln(msg);
20889 		stderr.flush();
20890 	}
20891 
20892 	abort();
20893 }
20894 
20895 private int minInternal(int a, int b) {
20896 	return (a < b) ? a : b;
20897 }
20898 
20899 private alias scriptable = arsd_jsvar_compatible;