1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 
4 // on Mac with X11: -L-L/usr/X11/lib 
5 
6 /*
7 	Event Loop would be nices:
8 
9 	* add on idle - runs when nothing else happens
10 	* send messages without a recipient window
11 	* setTimeout
12 	* setInterval
13 */
14 
15 /*
16 	Classic games I want to add:
17 		* my tetris clone
18 		* pac man
19 */
20 
21 /*
22 	Text layout needs a lot of work. Plain drawText is useful but too
23 	limited. It will need some kind of text context thing which it will
24 	update and you can pass it on and get more details out of it.
25 
26 	It will need a bounding box, a current cursor location that is updated
27 	as drawing continues, and various changable facts (which can also be
28 	changed on the painter i guess) like font, color, size, background,
29 	etc.
30 
31 	We can also fetch the caret location from it somehow.
32 
33 	Should prolly be an overload of drawText
34 
35 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
36 
37 		WS_EX_NOACTIVATE
38 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
39 		full screen windows. Can just set the atom on X. Windows will be harder.
40 
41 		moving windows. resizing windows.
42 
43 		hide cursor, capture cursor, change cursor.
44 
45 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
46 	sure the pieces are there to do its job easily and make other jobs possible.
47 */
48 
49 /++
50 	simpledisplay.d provides basic cross-platform GUI-related functionality,
51 	including creating windows, drawing on them, working with the clipboard,
52 	timers, OpenGL, and more. However, it does NOT provide high level GUI
53 	widgets. See my minigui.d, an extension to this module, for that
54 	functionality.
55 
56 	simpledisplay provides cross-platform wrapping for Windows and Linux
57 	(and perhaps other OSes that use X11), but also does not prevent you
58 	from using the underlying facilities if you need them. It has a goal
59 	of working efficiently over a remote X link (at least as far as Xlib
60 	reasonably allows.)
61 
62 	simpledisplay depends on [arsd.color|color.d], which should be available from the
63 	same place where you got this file. Other than that, however, it has
64 	very few dependencies and ones that don't come with the OS and/or the
65 	compiler are all opt-in.
66 
67 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
68 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
69 
70 	simpledisplay is basically stable. I plan to refactor the internals,
71 	and may add new features and fix bugs, but It do not expect to
72 	significantly change the API. It has been stable a few years already now.
73 
74 	Installation_instructions:
75 
76 	`simpledisplay.d` does not have any dependencies outside the
77 	operating system and `color.d`, so it should just work most the
78 	time, but there are a few caveats on some systems:
79 
80 	Please note when compiling on Win64, you need to explicitly list
81 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
82 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
83 
84 	If using ldc instead of dmd, use `-L/entry:wmainCRTstartup` instead of `mainCRTStartup`;
85 	note the "w".
86 
87 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
88 	console to be automatically allocated.
89 
90 	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.
91 
92 	On Ubuntu, you might need to install X11 development libraries to
93 	successfully link.
94 
95 	$(CONSOLE
96 		$ sudo apt-get install libglc-dev
97 		$ sudo apt-get install libx11-dev
98 	)
99 
100 
101 	Jump_list:
102 
103 	Don't worry, you don't have to read this whole documentation file!
104 
105 	Check out the [#Event-example] and [#Pong-example] to get started quickly.
106 
107 	The main classes you may want to create are [SimpleWindow], [Timer],
108 	[Image], and [Sprite].
109 
110 	The main functions you'll want are [setClipboardText] and [getClipboardText].
111 
112 	There are also platform-specific functions available such as [XDisplayConnection]
113 	and [GetAtom] for X11, among others.
114 
115 	See the examples and topics list below to learn more.
116 
117 	$(WARNING
118 		There should only be one GUI thread per application,
119 		and all windows should be created in it and your
120 		event loop should run there.
121 
122 		To do otherwise is undefined behavior and has no
123 		cross platform guarantees.
124 	)
125 
126 	$(H2 About this documentation)
127 
128 	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.
129 
130 	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!
131 
132 	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.
133 
134 	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.
135 
136 	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.
137 
138 	At points, I will talk about implementation details in the documentation. These are sometimes
139 	subject to change, but nevertheless useful to understand what is really going on. You can learn
140 	more about some of the referenced things by searching the web for info about using them from C.
141 	You can always look at the source of simpledisplay.d too for the most authoritative source on
142 	its specific implementation. If you disagree with how I did something, please contact me so we
143 	can discuss it!
144 
145 	Examples:
146 
147 	$(H3 Event-example)
148 	This program creates a window and draws events inside them as they
149 	happen, scrolling the text in the window as needed. Run this program
150 	and experiment to get a feel for where basic input events take place
151 	in the library.
152 
153 	---
154 	// dmd example.d simpledisplay.d color.d
155 	import arsd.simpledisplay;
156 	import std.conv;
157 
158 	void main() {
159 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
160 
161 		int y = 0;
162 
163 		void addLine(string text) {
164 			auto painter = window.draw();
165 
166 			if(y + painter.fontHeight >= window.height) {
167 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
168 				y -= painter.fontHeight;
169 			}
170 
171 			painter.outlineColor = Color.red;
172 			painter.fillColor = Color.black;
173 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
174 
175 			painter.outlineColor = Color.white;
176 
177 			painter.drawText(Point(10, y), text);
178 
179 			y += painter.fontHeight;
180 		}
181 
182 		window.eventLoop(1000,
183 		  () {
184 			addLine("Timer went off!");
185 		  },
186 		  (KeyEvent event) {
187 			addLine(to!string(event));
188 		  },
189 		  (MouseEvent event) {
190 			addLine(to!string(event));
191 		  },
192 		  (dchar ch) {
193 			addLine(to!string(ch));
194 		  }
195 		);
196 	}
197 	---
198 
199 	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.
200 
201 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
202 
203 	---
204 
205 	---
206 
207 	$(H2 Topics)
208 
209 	$(H3 $(ID topic-windows) Windows)
210 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
211 		window on the user's screen.
212 
213 		You may create multiple windows, if the underlying platform supports it. You may check
214 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
215 		SimpleWindow's constructor at runtime to handle those cases.
216 
217 		A single running event loop will handle as many windows as needed.
218 
219 		setEventHandlers function
220 		eventLoop function
221 		draw function
222 		title property
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 [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 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
249 		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.
250 
251 	$(H3 $(ID topic-input-handling) Input handling)
252 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
253 
254 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
255 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
256 
257 		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:
258 
259 		---
260 		// dmd example.d simpledisplay.d color.d
261 		import arsd.simpledisplay;
262 		void main() {
263 			auto window = new SimpleWindow(200, 200);
264 			{ // introduce sub-scope
265 				auto painter = window.draw(); // begin drawing
266 				/* draw here */
267 				painter.outlineColor = Color.red;
268 				painter.fillColor = Color.black;
269 				painter.drawRectangle(Point(0, 0), 200, 200);
270 			} // end scope, calling `painter`'s destructor, drawing to the screen.
271 			window.eventLoop(0); // handle events
272 		}
273 		---
274 
275 		Painting is done based on two color properties, a pen and a brush.
276 
277 		At this time, the 2d drawing does not support alpha blending. If you need that, use a 2d OpenGL context instead.
278 		FIXME add example of 2d opengl drawing here
279 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
280 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
281 
282 		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.
283 
284 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
285 
286 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
287 
288 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlSceneNow()].
289 
290 		Please note that my experience with OpenGL is very out-of-date, and the bindings in simpledisplay reflect that. If you want to use more modern functions, you may have to define the bindings yourself, or import them from another module. However, the OpenGL context creation done in simpledisplay will work for any version.
291 
292 		This example program will draw a rectangle on your window:
293 
294 		---
295 		// dmd example.d simpledisplay.d color.d
296 		import arsd.simpledisplay;
297 
298 		void main() {
299 
300 		}
301 		---
302 
303 	$(H3 $(ID topic-images) Displaying images)
304 		You can also load PNG images using [arsd.png].
305 
306 		---
307 		// dmd example.d simpledisplay.d color.d png.d
308 		import arsd.simpledisplay;
309 		import arsd.png;
310 
311 		void main() {
312 			auto image = Image.fromMemoryImage(readPng("image.png"));
313 			displayImage(image);
314 		}
315 		---
316 
317 		Compile with `dmd example.d simpledisplay.d png.d`.
318 
319 		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.
320 
321 	$(H3 $(ID topic-sprites) Sprites)
322 		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.
323 
324 	$(H3 $(ID topic-clipboard) Clipboard)
325 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
326 
327 		It also has helpers for handling X-specific events.
328 
329 	$(H3 $(ID topic-timers) Timers)
330 		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].
331 
332 		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.
333 
334 		---
335 			import arsd.simpledisplay;
336 
337 			void main() {
338 				auto window = new SimpleWindow(400, 400);
339 				// every 100 ms, it will draw a random line
340 				// on the window.
341 				window.eventLoop(100, {
342 					auto painter = window.draw();
343 
344 					import std.random;
345 					// random color
346 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
347 					// random line
348 					painter.drawLine(
349 						Point(uniform(0, window.width), uniform(0, window.height)),
350 						Point(uniform(0, window.width), uniform(0, window.height)));
351 
352 				});
353 			}
354 		---
355 
356 		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.
357 
358 		The pulse timer and instances of the [Timer] class may be combined at will.
359 
360 		---
361 			import arsd.simpledisplay;
362 
363 			void main() {
364 				auto window = new SimpleWindow(400, 400);
365 				auto timer = new Timer(1000, delegate {
366 					auto painter = window.draw();
367 					painter.clear();
368 				});
369 
370 				window.eventLoop(0);
371 			}
372 		---
373 
374 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
375 
376 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
377 		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.
378 
379 		See also: `xwindows.d` from my github.
380 
381 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
382 		`handleNativeEvent` and `handleNativeGlobalEvent`.
383 
384 	$(H3 $(ID topic-integration) Integration with other libraries)
385 		Integration with a third-party event loop is possible.
386 
387 		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.
388 
389 	$(H3 $(ID topic-guis) GUI widgets)
390 		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!
391 
392 		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.
393 
394 		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.)
395 
396 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
397 
398 	$(H2 Platform-specific tips and tricks)
399 
400 	Windows_tips:
401 
402 	You can add icons or manifest files to your exe using a resource file.
403 
404 	To create a Windows .ico file, use the gimp or something. I'll write a helper
405 	program later.
406 
407 	Create `yourapp.rc`:
408 
409 	```rc
410 		1 ICON filename.ico
411 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
412 	```
413 
414 	And `yourapp.exe.manifest`:
415 
416 	```xml
417 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
418 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
419 		<assemblyIdentity
420 		    version="1.0.0.0"
421 		    processorArchitecture="*"
422 		    name="CompanyName.ProductName.YourApplication"
423 		    type="win32"
424 		/>
425 		<description>Your application description here.</description>
426 		<dependency>
427 		    <dependentAssembly>
428 			<assemblyIdentity
429 			    type="win32"
430 			    name="Microsoft.Windows.Common-Controls"
431 			    version="6.0.0.0"
432 			    processorArchitecture="*"
433 			    publicKeyToken="6595b64144ccf1df"
434 			    language="*"
435 			/>
436 		    </dependentAssembly>
437 		</dependency>
438 		</assembly>
439 	```
440 
441 
442 	$(H2 $(ID developer-notes) Developer notes)
443 
444 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
445 	implementation though.
446 
447 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
448 	suck. If I was rewriting it, I wouldn't do it that way again.
449 
450 	This file must not have any more required dependencies. If you need bindings, add
451 	them right to this file. Once it gets into druntime and is there for a while, remove
452 	bindings from here to avoid conflicts (or put them in an appropriate version block
453 	so it continues to just work on old dmd), but wait a couple releases before making the
454 	transition so this module remains usable with older versions of dmd.
455 
456 	You may have optional dependencies if needed by putting them in version blocks or
457 	template functions. You may also extend the module with other modules with UFCS without
458 	actually editing this - that is nice to do if you can.
459 
460 	Try to make functions work the same way across operating systems. I typically make
461 	it thinly wrap Windows, then emulate that on Linux.
462 
463 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
464 	Phobos! So try to avoid it.
465 
466 	See more comments throughout the source.
467 
468 	I realize this file is fairly large, but over half that is just bindings at the bottom
469 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
470 	to understand. I suggest you jump around the source by looking for a particular
471 	declaration you're interested in, like `class SimpleWindow` using your editor's search
472 	function, then look at one piece at a time.
473 
474 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
475 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode. I go by
476 	Destructionator or adam_d_ruppe, depending on which computer I'm logged into.
477 
478 	I live in the eastern United States, so I will most likely not be around at night in
479 	that US east timezone.
480 
481 	License: Copyright Adam D. Ruppe, 2011-2020. Released under the Boost Software License.
482 
483 	Building documentation: You may wish to use the `arsd.ddoc` file from my github with
484 	building the documentation for simpledisplay yourself. It will give it a bit more style.
485 	Simply download the arsd.ddoc file and add it to your compile command when building docs.
486 	`dmd -c simpledisplay.d color.d -D arsd.ddoc`
487 +/
488 module arsd.simpledisplay;
489 
490 // FIXME: tetris demo
491 // FIXME: space invaders demo
492 // FIXME: asteroids demo
493 
494 /++ $(ID Pong-example)
495 	$(H3 Pong)
496 
497 	This program creates a little Pong-like game. Player one is controlled
498 	with the keyboard.  Player two is controlled with the mouse. It demos
499 	the pulse timer, event handling, and some basic drawing.
500 +/
501 version(demos)
502 unittest {
503 	// dmd example.d simpledisplay.d color.d
504 	import arsd.simpledisplay;
505 
506 	enum paddleMovementSpeed = 8;
507 	enum paddleHeight = 48;
508 
509 	void main() {
510 		auto window = new SimpleWindow(600, 400, "Pong game!");
511 
512 		int playerOnePosition, playerTwoPosition;
513 		int playerOneMovement, playerTwoMovement;
514 		int playerOneScore, playerTwoScore;
515 
516 		int ballX, ballY;
517 		int ballDx, ballDy;
518 
519 		void serve() {
520 			import std.random;
521 
522 			ballX = window.width / 2;
523 			ballY = window.height / 2;
524 			ballDx = uniform(-4, 4) * 3;
525 			ballDy = uniform(-4, 4) * 3;
526 			if(ballDx == 0)
527 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
528 		}
529 
530 		serve();
531 
532 		window.eventLoop(50, // set a 50 ms timer pulls
533 			// This runs once per timer pulse
534 			delegate () {
535 				auto painter = window.draw();
536 
537 				painter.clear();
538 
539 				// Update everyone's motion
540 				playerOnePosition += playerOneMovement;
541 				playerTwoPosition += playerTwoMovement;
542 
543 				ballX += ballDx;
544 				ballY += ballDy;
545 
546 				// Bounce off the top and bottom edges of the window
547 				if(ballY + 7 >= window.height)
548 					ballDy = -ballDy;
549 				if(ballY - 8 <= 0)
550 					ballDy = -ballDy;
551 
552 				// Bounce off the paddle, if it is in position
553 				if(ballX - 8 <= 16) {
554 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
555 						ballDx = -ballDx + 1; // add some speed to keep it interesting
556 						ballDy += playerOneMovement; // and y movement based on your controls too
557 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
558 					} else {
559 						// Missed it
560 						playerTwoScore ++;
561 						serve();
562 					}
563 				}
564 
565 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
566 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
567 						ballDx = -ballDx - 1;
568 						ballDy += playerTwoMovement;
569 						ballX = window.width - 24;
570 					} else {
571 						// Missed it
572 						playerOneScore ++;
573 						serve();
574 					}
575 				}
576 
577 				// Draw the paddles
578 				painter.outlineColor = Color.black;
579 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
580 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
581 
582 				// Draw the ball
583 				painter.fillColor = Color.red;
584 				painter.outlineColor = Color.yellow;
585 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
586 
587 				// Draw the score
588 				painter.outlineColor = Color.blue;
589 				import std.conv;
590 				painter.drawText(Point(64, 4), to!string(playerOneScore));
591 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
592 
593 			},
594 			delegate (KeyEvent event) {
595 				// Player 1's controls are the arrow keys on the keyboard
596 				if(event.key == Key.Down)
597 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
598 				if(event.key == Key.Up)
599 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
600 
601 			},
602 			delegate (MouseEvent event) {
603 				// Player 2's controls are mouse movement while the left button is held down
604 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
605 					if(event.dy > 0)
606 						playerTwoMovement = paddleMovementSpeed;
607 					else if(event.dy < 0)
608 						playerTwoMovement = -paddleMovementSpeed;
609 				} else {
610 					playerTwoMovement = 0;
611 				}
612 			}
613 		);
614 	}
615 }
616 
617 /++ $(ID example-minesweeper)
618 
619 	This minesweeper demo shows how we can implement another classic
620 	game with simpledisplay and shows some mouse input and basic output
621 	code.
622 +/
623 version(demos)
624 unittest {
625 	import arsd.simpledisplay;
626 
627 	enum GameSquare {
628 		mine = 0,
629 		clear,
630 		m1, m2, m3, m4, m5, m6, m7, m8
631 	}
632 
633 	enum UserSquare {
634 		unknown,
635 		revealed,
636 		flagged,
637 		questioned
638 	}
639 
640 	enum GameState {
641 		inProgress,
642 		lose,
643 		win
644 	}
645 
646 	GameSquare[] board;
647 	UserSquare[] userState;
648 	GameState gameState;
649 	int boardWidth;
650 	int boardHeight;
651 
652 	bool isMine(int x, int y) {
653 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
654 			return false;
655 		return board[y * boardWidth + x] == GameSquare.mine;
656 	}
657 
658 	GameState reveal(int x, int y) {
659 		if(board[y * boardWidth + x] == GameSquare.clear) {
660 			floodFill(userState, boardWidth, boardHeight,
661 				UserSquare.unknown, UserSquare.revealed,
662 				x, y,
663 				(x, y) {
664 					if(board[y * boardWidth + x] == GameSquare.clear)
665 						return true;
666 					else {
667 						userState[y * boardWidth + x] = UserSquare.revealed;
668 						return false;
669 					}
670 				});
671 		} else {
672 			userState[y * boardWidth + x] = UserSquare.revealed;
673 			if(isMine(x, y))
674 				return GameState.lose;
675 		}
676 
677 		foreach(state; userState) {
678 			if(state == UserSquare.unknown || state == UserSquare.questioned)
679 				return GameState.inProgress;
680 		}
681 
682 		return GameState.win;
683 	}
684 
685 	void initializeBoard(int width, int height, int numberOfMines) {
686 		boardWidth = width;
687 		boardHeight = height;
688 		board.length = width * height;
689 
690 		userState.length = width * height;
691 		userState[] = UserSquare.unknown; 
692 
693 		import std.algorithm, std.random, std.range;
694 
695 		board[] = GameSquare.clear;
696 
697 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
698 			board[minePosition] = GameSquare.mine;
699 
700 		int x;
701 		int y;
702 		foreach(idx, ref square; board) {
703 			if(square == GameSquare.clear) {
704 				int danger = 0;
705 				danger += isMine(x-1, y-1)?1:0;
706 				danger += isMine(x-1, y)?1:0;
707 				danger += isMine(x-1, y+1)?1:0;
708 				danger += isMine(x, y-1)?1:0;
709 				danger += isMine(x, y+1)?1:0;
710 				danger += isMine(x+1, y-1)?1:0;
711 				danger += isMine(x+1, y)?1:0;
712 				danger += isMine(x+1, y+1)?1:0;
713 
714 				square = cast(GameSquare) (danger + 1);
715 			}
716 
717 			x++;
718 			if(x == width) {
719 				x = 0;
720 				y++;
721 			}
722 		}
723 	}
724 
725 	void redraw(SimpleWindow window) {
726 		import std.conv;
727 
728 		auto painter = window.draw();
729 
730 		painter.clear();
731 
732 		final switch(gameState) with(GameState) {
733 			case inProgress:
734 				break;
735 			case win:
736 				painter.fillColor = Color.green;
737 				painter.drawRectangle(Point(0, 0), window.width, window.height);
738 				return;
739 			case lose:
740 				painter.fillColor = Color.red;
741 				painter.drawRectangle(Point(0, 0), window.width, window.height);
742 				return;
743 		}
744 
745 		int x = 0;
746 		int y = 0;
747 
748 		foreach(idx, square; board) {
749 			auto state = userState[idx];
750 
751 			final switch(state) with(UserSquare) {
752 				case unknown:
753 					painter.outlineColor = Color.black;
754 					painter.fillColor = Color(128,128,128);
755 
756 					painter.drawRectangle(
757 						Point(x * 20, y * 20),
758 						20, 20
759 					);
760 				break;
761 				case revealed:
762 					if(square == GameSquare.clear) {
763 						painter.outlineColor = Color.white;
764 						painter.fillColor = Color.white;
765 
766 						painter.drawRectangle(
767 							Point(x * 20, y * 20),
768 							20, 20
769 						);
770 					} else {
771 						painter.outlineColor = Color.black;
772 						painter.fillColor = Color.white;
773 
774 						painter.drawText(
775 							Point(x * 20, y * 20),
776 							to!string(square)[1..2],
777 							Point(x * 20 + 20, y * 20 + 20),
778 							TextAlignment.Center | TextAlignment.VerticalCenter);
779 					}
780 				break;
781 				case flagged:
782 					painter.outlineColor = Color.black;
783 					painter.fillColor = Color.red;
784 					painter.drawRectangle(
785 						Point(x * 20, y * 20),
786 						20, 20
787 					);
788 				break;
789 				case questioned:
790 					painter.outlineColor = Color.black;
791 					painter.fillColor = Color.yellow;
792 					painter.drawRectangle(
793 						Point(x * 20, y * 20),
794 						20, 20
795 					);
796 				break;
797 			}
798 
799 			x++;
800 			if(x == boardWidth) {
801 				x = 0;
802 				y++;
803 			}
804 		}
805 
806 	}
807 
808 	void main() {
809 		auto window = new SimpleWindow(200, 200);
810 
811 		initializeBoard(10, 10, 10);
812 
813 		redraw(window);
814 		window.eventLoop(0,
815 			delegate (MouseEvent me) {
816 				if(me.type != MouseEventType.buttonPressed)
817 					return;
818 				auto x = me.x / 20;
819 				auto y = me.y / 20;
820 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
821 					if(me.button == MouseButton.left) {
822 						gameState = reveal(x, y);
823 					} else {
824 						userState[y*boardWidth+x] = UserSquare.flagged;
825 					}
826 					redraw(window);
827 				}
828 			}
829 		);
830 	}
831 }
832 
833 /*
834 version(OSX) {
835 	version=without_opengl;
836 	version=allow_unimplemented_features;
837 	version=OSXCocoa;
838 	pragma(linkerDirective, "-framework Cocoa");
839 }
840 */
841 
842 version(without_opengl) {
843 	enum SdpyIsUsingIVGLBinds = false;
844 } else /*version(Posix)*/ {
845 	static if (__traits(compiles, (){import iv.glbinds;})) {
846 		enum SdpyIsUsingIVGLBinds = true;
847 		public import iv.glbinds;
848 		//pragma(msg, "SDPY: using iv.glbinds");
849 	} else {
850 		enum SdpyIsUsingIVGLBinds = false;
851 	}
852 //} else {
853 //	enum SdpyIsUsingIVGLBinds = false;
854 }
855 
856 
857 version(Windows) {
858 	//import core.sys.windows.windows;
859 	import core.sys.windows.winnls;
860 	import core.sys.windows.windef;
861 	import core.sys.windows.basetyps;
862 	import core.sys.windows.winbase;
863 	import core.sys.windows.winuser;
864 	import core.sys.windows.shellapi;
865 	import core.sys.windows.wingdi;
866 	static import gdi = core.sys.windows.wingdi; // so i
867 
868 	pragma(lib, "gdi32");
869 	pragma(lib, "user32");
870 } else version (linux) {
871 	//k8: this is hack for rdmd. sorry.
872 	static import core.sys.linux.epoll;
873 	static import core.sys.linux.timerfd;
874 }
875 
876 
877 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
878 
879 // http://wiki.dlang.org/Simpledisplay.d
880 
881 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
882 
883 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
884 // but can i control the scroll lock led
885 
886 
887 // Note: if you are using Image on X, you might want to do:
888 /*
889 	static if(UsingSimpledisplayX11) {
890 		if(!Image.impl.xshmAvailable) {
891 			// the images will use the slower XPutImage, you might
892 			// want to consider an alternative method to get better speed
893 		}
894 	}
895 
896 	If the shared memory extension is available though, simpledisplay uses it
897 	for a significant speed boost whenever you draw large Images.
898 */
899 
900 // 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.
901 
902 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
903 
904 /*
905 	Biggest FIXME:
906 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
907 
908 		clean up opengl contexts when their windows close
909 
910 		fix resizing the bitmaps/pixmaps
911 */
912 
913 // BTW on Windows:
914 // -L/SUBSYSTEM:WINDOWS:5.0
915 // to dmd will make a nice windows binary w/o a console if you want that.
916 
917 /*
918 	Stuff to add:
919 
920 	use multibyte functions everywhere we can
921 
922 	OpenGL windows
923 	more event stuff
924 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
925 
926 
927 	resizeEvent
928 		and make the windows non-resizable by default,
929 		or perhaps stretched (if I can find something in X like StretchBlt)
930 
931 	take a screenshot function!
932 
933 	Pens and brushes?
934 	Maybe a global event loop?
935 
936 	Mouse deltas
937 	Key items
938 */
939 
940 /*
941 From MSDN:
942 
943 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
944 
945 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.
946 
947 */
948 
949 version(linux) {
950 	version = X11;
951 	version(without_libnotify) {
952 		// we cool
953 	}
954 	else
955 		version = libnotify;
956 }
957 
958 version(libnotify) {
959 	pragma(lib, "dl");
960 	import core.sys.posix.dlfcn;
961 
962 	void delegate()[int] libnotify_action_delegates;
963 	int libnotify_action_delegates_count;
964 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
965 		auto idx = cast(int) user_data;
966 		if(auto dgptr = idx in libnotify_action_delegates) {
967 			(*dgptr)();
968 			libnotify_action_delegates.remove(idx);
969 		}
970 	}
971 
972 	struct C_DynamicLibrary {
973 		void* handle;
974 		this(string name) {
975 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
976 			if(handle is null)
977 				throw new Exception("dlopen");
978 		}
979 
980 		void close() {
981 			dlclose(handle);
982 		}
983 
984 		~this() {
985 			// close
986 		}
987 
988 		// FIXME: this looks up by name every time.... 
989 		template call(string func, Ret, Args...) {
990 			extern(C) Ret function(Args) fptr;
991 			typeof(fptr) call() {
992 				fptr = cast(typeof(fptr)) dlsym(handle, func);
993 				return fptr;
994 			}
995 		}
996 	}
997 
998 	C_DynamicLibrary* libnotify;
999 }
1000 
1001 version(OSX) {
1002 	version(OSXCocoa) {}
1003 	else { version = X11; }
1004 }
1005 	//version = OSXCocoa; // this was written by KennyTM
1006 version(FreeBSD)
1007 	version = X11;
1008 version(Solaris)
1009 	version = X11;
1010 
1011 
1012 void featureNotImplemented()() {
1013 	version(allow_unimplemented_features)
1014 		throw new NotYetImplementedException();
1015 	else
1016 		static assert(0);
1017 }
1018 
1019 // these are so the static asserts don't trigger unless you want to
1020 // add support to it for an OS
1021 version(Windows)
1022 	version = with_timer;
1023 version(linux)
1024 	version = with_timer;
1025 
1026 version(with_timer)
1027 	enum bool SimpledisplayTimerAvailable = true;
1028 else
1029 	enum bool SimpledisplayTimerAvailable = false;
1030 
1031 /// 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.
1032 version(Windows)
1033 	enum bool UsingSimpledisplayWindows = true;
1034 else
1035 	enum bool UsingSimpledisplayWindows = false;
1036 
1037 /// 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.
1038 version(X11)
1039 	enum bool UsingSimpledisplayX11 = true;
1040 else
1041 	enum bool UsingSimpledisplayX11 = false;
1042 
1043 /// 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.
1044 version(OSXCocoa)
1045 	enum bool UsingSimpledisplayCocoa = true;
1046 else
1047 	enum bool UsingSimpledisplayCocoa = false;
1048 
1049 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1050 version(Windows)
1051 	enum multipleWindowsSupported = true;
1052 else version(X11)
1053 	enum multipleWindowsSupported = true;
1054 else version(OSXCocoa)
1055 	enum multipleWindowsSupported = true;
1056 else
1057 	static assert(0);
1058 
1059 version(without_opengl)
1060 	enum bool OpenGlEnabled = false;
1061 else
1062 	enum bool OpenGlEnabled = true;
1063 
1064 
1065 /++
1066 	After selecting a type from [WindowTypes], you may further customize
1067 	its behavior by setting one or more of these flags.
1068 
1069 
1070 	The different window types have different meanings of `normal`. If the
1071 	window type already is a good match for what you want to do, you should
1072 	just use [WindowFlags.normal], the default, which will do the right thing
1073 	for your users.
1074 
1075 	The window flags will not always be honored by the operating system
1076 	and window managers; they are hints, not commands.
1077 +/
1078 enum WindowFlags : int {
1079 	normal = 0, ///
1080 	skipTaskbar = 1, ///
1081 	alwaysOnTop = 2, ///
1082 	alwaysOnBottom = 4, ///
1083 	cannotBeActivated = 8, ///
1084 	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.
1085 	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.
1086 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1087 }
1088 
1089 /++
1090 	When creating a window, you can pass a type to SimpleWindow's constructor,
1091 	then further customize the window by changing `WindowFlags`.
1092 
1093 
1094 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1095 	use. The others are there to build a foundation for a higher level GUI toolkit,
1096 	but are themselves not as high level as you might think from their names.
1097 
1098 	This list is based on the EMWH spec for X11.
1099 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1100 +/
1101 enum WindowTypes : int {
1102 	/// An ordinary application window.
1103 	normal,
1104 	/// 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.
1105 	undecorated,
1106 	/// 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.
1107 	eventOnly,
1108 	/// A drop down menu, such as from a menu bar
1109 	dropdownMenu,
1110 	/// A popup menu, such as from a right click
1111 	popupMenu,
1112 	/// A popup bubble notification
1113 	notification,
1114 	/*
1115 	menu, /// a tearable menu bar
1116 	splashScreen, /// a loading splash screen for your application
1117 	tooltip, /// A tiny window showing temporary help text or something.
1118 	comboBoxDropdown,
1119 	dialog,
1120 	toolbar
1121 	*/
1122 	/// a child nested inside the parent. You must pass a parent window to the ctor
1123 	nestedChild,
1124 }
1125 
1126 
1127 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1128 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1129 private __gshared char* sdpyWindowClassStr = null;
1130 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1131 
1132 /**
1133 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1134 	You may want to change context version if you want to use advanced shaders or
1135 	other modern OpenGL techinques. This setting doesn't affect already created
1136 	windows. You may use version 2.1 as your default, which should be supported
1137 	by any box since 2006, so seems to be a reasonable choice.
1138 
1139 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1140 	old context creation code without any version specified. This is the safest
1141 	way to init OpenGL, but it may not give you access to advanced features.
1142 
1143 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1144 */
1145 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1146 
1147 /**
1148 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1149 	pipeline functions, and without "compatible" mode you won't be able to use
1150 	your old non-shader-based code with such contexts. By default SimpleDisplay
1151 	creates compatible context, so you can gradually upgrade your OpenGL code if
1152 	you want to (or leave it as is, as it should "just work").
1153 */
1154 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1155 
1156 /**
1157 	Set to `true` to allow creating OpenGL context with lower version than requested
1158 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1159 	`openGLContextFallbackActivated()` will return `true`.
1160 	*/
1161 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1162 
1163 /**
1164 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1165 	*/
1166 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1167 
1168 
1169 /**
1170 	Set window class name for all following `new SimpleWindow()` calls.
1171 
1172 	WARNING! For Windows, you should set your class name before creating any
1173 	window, and NEVER change it after that!
1174 */
1175 void sdpyWindowClass (const(char)[] v) {
1176 	import core.stdc.stdlib : realloc;
1177 	if (v.length == 0) v = "SimpleDisplayWindow";
1178 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1179 	if (sdpyWindowClassStr is null) return; // oops
1180 	sdpyWindowClassStr[0..v.length+1] = 0;
1181 	sdpyWindowClassStr[0..v.length] = v[];
1182 }
1183 
1184 /**
1185 	Get current window class name.
1186 */
1187 string sdpyWindowClass () {
1188 	if (sdpyWindowClassStr is null) return null;
1189 	foreach (immutable idx; 0..size_t.max-1) {
1190 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1191 	}
1192 	return null;
1193 }
1194 
1195 /++
1196 	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.
1197 +/
1198 float[2] getDpi() {
1199 	float[2] dpi;
1200 	version(Windows) {
1201 		HDC screen = GetDC(null);
1202 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1203 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1204 	} else version(X11) {
1205 		auto display = XDisplayConnection.get;
1206 		auto screen = DefaultScreen(display);
1207 
1208 		void fallback() {
1209 			// 25.4 millimeters in an inch...
1210 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1211 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1212 		}
1213 
1214 		char* resourceString = XResourceManagerString(display);
1215 		XrmInitialize();
1216 
1217 		auto db = XrmGetStringDatabase(resourceString);
1218 
1219 		if (resourceString) {
1220 			XrmValue value;
1221 			char* type;
1222 			if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1223 				if (value.addr) {
1224 					import core.stdc.stdlib;
1225 					dpi[0] = atof(cast(char*) value.addr);
1226 					dpi[1] = dpi[0];
1227 				} else {
1228 					fallback();
1229 				}
1230 			} else {
1231 				fallback();
1232 			}
1233 		} else {
1234 			fallback();
1235 		}
1236 	}
1237 
1238 	return dpi;
1239 }
1240 
1241 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width, int height) {
1242 	throw new Exception("not implemented");
1243 	version(none) {
1244 	version(X11) {
1245 		auto display = XDisplayConnection.get;
1246 		auto image = XGetImage(display, handle, 0, 0, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ZPixmap);
1247 
1248 		// https://github.com/adamdruppe/arsd/issues/98
1249 
1250 		// FIXME: copy that shit
1251 
1252 		XDestroyImage(image);
1253 	} else version(Windows) {
1254 		// I just need to BitBlt that shit... BUT WAIT IT IS ALREADY IN A DIB!!!!!!!
1255 
1256 	} else featureNotImplemented();
1257 
1258 	return null;
1259 	}
1260 }
1261 
1262 /++
1263 	The flagship window class.
1264 
1265 
1266 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1267 	out of more advanced or complex features of the underlying windowing system.
1268 
1269 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1270 	and get a suitable window to work with.
1271 
1272 	From there, you can opt into additional features, like custom resizability and OpenGL support
1273 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1274 	and customization flags with the final two constructor arguments.
1275 
1276 	If none of that works for you, you can also create a window using native function calls, then
1277 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1278 	though, if you do this, managing the window is still your own responsibility! Notably, you
1279 	will need to destroy it yourself.
1280 +/
1281 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1282 
1283 	/// Be warned: this can be a very slow operation
1284 	/// FIXME NOT IMPLEMENTED
1285 	TrueColorImage takeScreenshot() {
1286 		version(Windows)
1287 			return trueColorImageFromNativeHandle(impl.hwnd, width, height);
1288 		else version(OSXCocoa)
1289 			throw new NotYetImplementedException();
1290 		else
1291 			return trueColorImageFromNativeHandle(impl.window, width, height);
1292 	}
1293 
1294 	version(X11) {
1295 		void recreateAfterDisconnect() {
1296 			if(!stateDiscarded) return;
1297 
1298 			if(_parent !is null && _parent.stateDiscarded)
1299 				_parent.recreateAfterDisconnect();
1300 
1301 			bool wasHidden = hidden;
1302 
1303 			activeScreenPainter = null; // should already be done but just to confirm
1304 
1305 			impl.createWindow(_width, _height, _title, openglMode, _parent);
1306 
1307 			if(recreateAdditionalConnectionState)
1308 				recreateAdditionalConnectionState();
1309 
1310 			hidden = wasHidden;
1311 			stateDiscarded = false;
1312 		}
1313 
1314 		bool stateDiscarded;
1315 		void discardConnectionState() {
1316 			if(XDisplayConnection.display)
1317 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
1318 			if(discardAdditionalConnectionState)
1319 				discardAdditionalConnectionState();
1320 			stateDiscarded = true;
1321 		}
1322 
1323 		void delegate() discardAdditionalConnectionState;
1324 		void delegate() recreateAdditionalConnectionState;
1325 	}
1326 
1327 
1328 	SimpleWindow _parent;
1329 	bool beingOpenKeepsAppOpen = true;
1330 	/++
1331 		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.
1332 
1333 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
1334 
1335 		Params:
1336 
1337 		width = the width of the window's client area, in pixels
1338 		height = the height of the window's client area, in pixels
1339 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
1340 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
1341 		resizable = [Resizability] has three options:
1342 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
1343 			$(P `fixedSize` will not allow the user to resize the window.)
1344 			$(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.)
1345 		windowType = The type of window you want to make.
1346 		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.
1347 		parent = the parent window, if applicable
1348 	+/
1349 	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) {
1350 		claimGuiThread();
1351 		version(sdpy_thread_checks) assert(thisIsGuiThread);
1352 		this._width = width;
1353 		this._height = height;
1354 		this.openglMode = opengl;
1355 		this.resizability = resizable;
1356 		this.windowType = windowType;
1357 		this.customizationFlags = customizationFlags;
1358 		this._title = (title is null ? "D Application" : title);
1359 		this._parent = parent;
1360 		impl.createWindow(width, height, this._title, opengl, parent);
1361 
1362 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild)
1363 			beingOpenKeepsAppOpen = false;
1364 	}
1365 
1366 	/// Same as above, except using the `Size` struct instead of separate width and height.
1367 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
1368 		this(size.width, size.height, title, opengl, resizable);
1369 	}
1370 
1371 
1372 	/++
1373 		Creates a window based on the given [Image]. It's client area
1374 		width and height is equal to the image. (A window's client area
1375 		is the drawable space inside; it excludes the title bar, etc.)
1376 
1377 		Windows based on images will not be resizable and do not use OpenGL.
1378 
1379 		It will draw the image in upon creation, but this will be overwritten
1380 		upon any draws, including the initial window visible event.
1381 
1382 		You probably do not want to use this and it may be removed from
1383 		the library eventually, or I might change it to be a "permanent"
1384 		background image; one that is automatically drawn on it before any
1385 		other drawing event. idk.
1386 	+/
1387 	this(Image image, string title = null) {
1388 		this(image.width, image.height, title);
1389 		this.image = image;
1390 	}
1391 
1392 	/++
1393 		Wraps a native window handle with very little additional processing - notably no destruction
1394 		this is incomplete so don't use it for much right now. The purpose of this is to make native
1395 		windows created through the low level API (so you can use platform-specific options and
1396 		other details SimpleWindow does not expose) available to the event loop wrappers.
1397 	+/
1398 	this(NativeWindowHandle nativeWindow) {
1399 		version(Windows)
1400 			impl.hwnd = nativeWindow;
1401 		else version(X11) {
1402 			impl.window = nativeWindow;
1403 			display = XDisplayConnection.get(); // get initial display to not segfault
1404 		} else version(OSXCocoa)
1405 			throw new NotYetImplementedException();
1406 		else featureNotImplemented();
1407 		// FIXME: set the size correctly
1408 		_width = 1;
1409 		_height = 1;
1410 		nativeMapping[nativeWindow] = this;
1411 
1412 		beingOpenKeepsAppOpen = false;
1413 
1414 		CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
1415 		_suppressDestruction = true; // so it doesn't try to close
1416 	}
1417 
1418 	/// Experimental, do not use yet
1419 	/++
1420 		Grabs exclusive input from the user until you release it with
1421 		[releaseInputGrab].
1422 
1423 
1424 		Note: it is extremely rude to do this without good reason.
1425 		Reasons may include doing some kind of mouse drag operation
1426 		or popping up a temporary menu that should get events and will
1427 		be dismissed at ease by the user clicking away.
1428 
1429 		Params:
1430 			keyboard = do you want to grab keyboard input?
1431 			mouse = grab mouse input?
1432 			confine = confine the mouse cursor to inside this window?
1433 	+/
1434 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
1435 		static if(UsingSimpledisplayX11) {
1436 			XSync(XDisplayConnection.get, 0);
1437 			if(keyboard)
1438 				XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
1439 			if(mouse) {
1440 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 
1441 				EventMask.PointerMotionMask // FIXME: not efficient
1442 				| EventMask.ButtonPressMask
1443 				| EventMask.ButtonReleaseMask
1444 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
1445 				)
1446 			{
1447 				XSync(XDisplayConnection.get, 0);
1448 				import core.stdc.stdio;
1449 				printf("Grab input failed %d\n", res);
1450 				//throw new Exception("Grab input failed");
1451 			} else {
1452 				// cool
1453 			}
1454 			}
1455 
1456 		} else version(Windows) {
1457 			// FIXME: keyboard?
1458 			SetCapture(impl.hwnd);
1459 			if(confine) {
1460 				RECT rcClip;
1461 				//RECT rcOldClip;
1462 				//GetClipCursor(&rcOldClip); 
1463 				GetWindowRect(hwnd, &rcClip); 
1464 				ClipCursor(&rcClip); 
1465 			}
1466 		} else version(OSXCocoa) {
1467 			throw new NotYetImplementedException();
1468 		} else static assert(0);
1469 	}
1470 
1471 	/++
1472 		Releases the grab acquired by [grabInput].
1473 	+/
1474 	void releaseInputGrab() {
1475 		static if(UsingSimpledisplayX11) {
1476 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
1477 		} else version(Windows) {
1478 			ReleaseCapture();
1479 			ClipCursor(null); 
1480 		} else version(OSXCocoa) {
1481 			throw new NotYetImplementedException();
1482 		} else static assert(0);
1483 	}
1484 
1485 	/++
1486 		Sets the input focus to this window.
1487 
1488 		You shouldn't call this very often - please let the user control the input focus.
1489 	+/
1490 	void focus() {
1491 		static if(UsingSimpledisplayX11) {
1492 			XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
1493 		} else version(Windows) {
1494 			SetFocus(this.impl.hwnd);
1495 		} else version(OSXCocoa) {
1496 			throw new NotYetImplementedException();
1497 		} else static assert(0);
1498 	}
1499 
1500 	/++
1501 		Requests attention from the user for this window.
1502 
1503 
1504 		The typical result of this function is to change the color
1505 		of the taskbar icon, though it may be tweaked on specific
1506 		platforms.
1507 
1508 		It is meant to unobtrusively tell the user that something
1509 		relevant to them happened in the background and they should
1510 		check the window when they get a chance. Upon receiving the
1511 		keyboard focus, the window will automatically return to its
1512 		natural state.
1513 
1514 		If the window already has the keyboard focus, this function
1515 		may do nothing, because the user is presumed to already be
1516 		giving the window attention.
1517 
1518 		Implementation_note:
1519 
1520 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
1521 		atom on X11 and the FlashWindow function on Windows.
1522 	+/
1523 	void requestAttention() {
1524 		if(_focused)
1525 			return;
1526 
1527 		version(Windows) {
1528 			FLASHWINFO info;
1529 			info.cbSize = info.sizeof;
1530 			info.hwnd = impl.hwnd;
1531 			info.dwFlags = FLASHW_TRAY;
1532 			info.uCount = 1;
1533 
1534 			FlashWindowEx(&info);
1535 
1536 		} else version(X11) {
1537 			demandingAttention = true;
1538 			demandAttention(this, true);
1539 		} else version(OSXCocoa) {
1540 			throw new NotYetImplementedException();
1541 		} else static assert(0);
1542 	}
1543 
1544 	private bool _focused;
1545 
1546 	version(X11) private bool demandingAttention;
1547 
1548 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
1549 	/// You'll have to call `close()` manually if you set this delegate.
1550 	void delegate () closeQuery;
1551 
1552 	/// This will be called when window visibility was changed.
1553 	void delegate (bool becomesVisible) visibilityChanged;
1554 
1555 	/// This will be called when window becomes visible for the first time.
1556 	/// You can do OpenGL initialization here. Note that in X11 you can't call
1557 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
1558 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
1559 	private bool _visibleForTheFirstTimeCalled;
1560 	void delegate () visibleForTheFirstTime;
1561 
1562 	/// Returns true if the window has been closed.
1563 	final @property bool closed() { return _closed; }
1564 
1565 	/// Returns true if the window is focused.
1566 	final @property bool focused() { return _focused; }
1567 
1568 	private bool _visible;
1569 	/// Returns true if the window is visible (mapped).
1570 	final @property bool visible() { return _visible; }
1571 
1572 	/// Closes the window. If there are no more open windows, the event loop will terminate.
1573 	void close() {
1574 		if (!_closed) {
1575 			if (onClosing !is null) onClosing();
1576 			impl.closeWindow();
1577 			_closed = true;
1578 		}
1579 	}
1580 
1581 	/// Alias for `hidden = false`
1582 	void show() {
1583 		hidden = false;
1584 	}
1585 
1586 	/// Alias for `hidden = true`
1587 	void hide() {
1588 		hidden = true;
1589 	}
1590 
1591 	/// Hide cursor when it enters the window.
1592 	void hideCursor() {
1593 		version(OSXCocoa) throw new NotYetImplementedException(); else
1594 		if (!_closed) impl.hideCursor();
1595 	}
1596 
1597 	/// Don't hide cursor when it enters the window.
1598 	void showCursor() {
1599 		version(OSXCocoa) throw new NotYetImplementedException(); else
1600 		if (!_closed) impl.showCursor();
1601 	}
1602 
1603 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
1604 	 *
1605 	 * Currently only supported on X11, so Windows implementation will return `false`.
1606 	 *
1607 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
1608 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
1609 	 *       receive "mouse moved here" event.
1610 	 */
1611 	bool warpMouse (int x, int y) {
1612 		version(X11) {
1613 			if (!_closed) { impl.warpMouse(x, y); return true; }
1614 		}
1615 		return false;
1616 	}
1617 
1618 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
1619 	void sendDummyEvent () {
1620 		version(X11) {
1621 			if (!_closed) { impl.sendDummyEvent(); }
1622 		}
1623 	}
1624 
1625 	/// Set window minimal size.
1626 	void setMinSize (int minwidth, int minheight) {
1627 		version(OSXCocoa) throw new NotYetImplementedException(); else
1628 		if (!_closed) impl.setMinSize(minwidth, minheight);
1629 	}
1630 
1631 	/// Set window maximal size.
1632 	void setMaxSize (int maxwidth, int maxheight) {
1633 		version(OSXCocoa) throw new NotYetImplementedException(); else
1634 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
1635 	}
1636 
1637 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
1638 	/// Currently only supported on X11.
1639 	void setResizeGranularity (int granx, int grany) {
1640 		version(OSXCocoa) throw new NotYetImplementedException(); else
1641 		if (!_closed) impl.setResizeGranularity(granx, grany);
1642 	}
1643 
1644 	/// Move window.
1645 	void move(int x, int y) {
1646 		version(OSXCocoa) throw new NotYetImplementedException(); else
1647 		if (!_closed) impl.move(x, y);
1648 	}
1649 
1650 	/// ditto
1651 	void move(Point p) {
1652 		version(OSXCocoa) throw new NotYetImplementedException(); else
1653 		if (!_closed) impl.move(p.x, p.y);
1654 	}
1655 
1656 	/++
1657 		Resize window.
1658 
1659 		Note that the width and height of the window are NOT instantly
1660 		updated - it waits for the window manager to approve the resize
1661 		request, which means you must return to the event loop before the
1662 		width and height are actually changed.
1663 	+/
1664 	void resize(int w, int h) {
1665 		version(OSXCocoa) throw new NotYetImplementedException(); else
1666 		if (!_closed) impl.resize(w, h);
1667 	}
1668 
1669 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
1670 	void moveResize (int x, int y, int w, int h) {
1671 		version(OSXCocoa) throw new NotYetImplementedException(); else
1672 		if (!_closed) impl.moveResize(x, y, w, h);
1673 	}
1674 
1675 	private bool _hidden;
1676 
1677 	/// Returns true if the window is hidden.
1678 	final @property bool hidden() {
1679 		return _hidden;
1680 	}
1681 
1682 	/// Shows or hides the window based on the bool argument.
1683 	final @property void hidden(bool b) {
1684 		_hidden = b;
1685 		version(Windows) {
1686 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
1687 		} else version(X11) {
1688 			if(b)
1689 				//XUnmapWindow(impl.display, impl.window);
1690 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
1691 			else
1692 				XMapWindow(impl.display, impl.window);
1693 		} else version(OSXCocoa) {
1694 			throw new NotYetImplementedException();
1695 		} else static assert(0);
1696 	}
1697 
1698 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
1699 	void opacity(double opacity) @property
1700 	in {
1701 		assert(opacity >= 0 && opacity <= 1);
1702 	} body {
1703 		version (Windows) {
1704 			impl.setOpacity(cast(ubyte)(255 * opacity));
1705 		} else version (X11) {
1706 			impl.setOpacity(cast(uint)(uint.max * opacity));
1707 		} else throw new NotYetImplementedException();
1708 	}
1709 
1710 	/++
1711 		Sets your event handlers, without entering the event loop. Useful if you
1712 		have multiple windows - set the handlers on each window, then only do eventLoop on your main window.
1713 	+/
1714 	void setEventHandlers(T...)(T eventHandlers) {
1715 		// FIXME: add more events
1716 		foreach(handler; eventHandlers) {
1717 			static if(__traits(compiles, handleKeyEvent = handler)) {
1718 				handleKeyEvent = handler;
1719 			} else static if(__traits(compiles, handleCharEvent = handler)) {
1720 				handleCharEvent = handler;
1721 			} else static if(__traits(compiles, handlePulse = handler)) {
1722 				handlePulse = handler;
1723 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
1724 				handleMouseEvent = handler;
1725 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
1726 		}
1727 	}
1728 
1729 	/// The event loop automatically returns when the window is closed
1730 	/// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
1731 	/// pulse timer is created. The event loop will block until an event
1732 	/// arrives or the pulse timer goes off.
1733 	final int eventLoop(T...)(
1734 		long pulseTimeout,    /// set to zero if you don't want a pulse.
1735 		T eventHandlers) /// delegate list like std.concurrency.receive
1736 	{
1737 		setEventHandlers(eventHandlers);
1738 
1739 		version(with_eventloop) {
1740 			// delegates event loop to my other module
1741 			version(X11)
1742 				XFlush(display);
1743 
1744 			import arsd.eventloop;
1745 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
1746 			scope(exit) clearInterval(handle);
1747 
1748 			loop();
1749 			return 0;
1750 		} else version(OSXCocoa) {
1751 			// FIXME
1752 			if (handlePulse !is null && pulseTimeout != 0) {
1753 				timer = scheduledTimer(pulseTimeout*1e-3,
1754 					view, sel_registerName("simpledisplay_pulse"),
1755 					null, true);
1756 			}
1757 
1758             		setNeedsDisplay(view, true);
1759             		run(NSApp);
1760             		return 0;
1761         	} else {
1762 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
1763 			return el.run();
1764 		}
1765 	}
1766 
1767 	/++
1768 		This lets you draw on the window (or its backing buffer) using basic
1769 		2D primitives.
1770 
1771 		Be sure to call this in a limited scope because your changes will not
1772 		actually appear on the window until ScreenPainter's destructor runs.
1773 
1774 		Returns: an instance of [ScreenPainter], which has the drawing methods
1775 		on it to draw on this window.
1776 	+/
1777 	ScreenPainter draw() {
1778 		return impl.getPainter();
1779 	}
1780 
1781 	// This is here to implement the interface we use for various native handlers.
1782 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
1783 
1784 	// maps native window handles to SimpleWindow instances, if there are any
1785 	// you shouldn't need this, but it is public in case you do in a native event handler or something
1786 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
1787 
1788 	/// Width of the window's drawable client area, in pixels.
1789 	@scriptable
1790 	final @property int width() { return _width; }
1791 
1792 	/// Height of the window's drawable client area, in pixels.
1793 	@scriptable
1794 	final @property int height() { return _height; }
1795 
1796 	private int _width;
1797 	private int _height;
1798 
1799 	// HACK: making the best of some copy constructor woes with refcounting
1800 	private ScreenPainterImplementation* activeScreenPainter_;
1801 
1802 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
1803 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
1804 
1805 	private OpenGlOptions openglMode;
1806 	private Resizability resizability;
1807 	private WindowTypes windowType;
1808 	private int customizationFlags;
1809 
1810 	/// `true` if OpenGL was initialized for this window.
1811 	@property bool isOpenGL () const pure nothrow @safe @nogc {
1812 		version(without_opengl)
1813 			return false;
1814 		else
1815 			return (openglMode == OpenGlOptions.yes);
1816 	}
1817 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
1818 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
1819 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
1820 
1821 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
1822 	/// to call this, as it's not recommended to share window between threads.
1823 	void mtLock () {
1824 		version(X11) {
1825 			XLockDisplay(this.display);
1826 		}
1827 	}
1828 
1829 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
1830 	/// to call this, as it's not recommended to share window between threads.
1831 	void mtUnlock () {
1832 		version(X11) {
1833 			XUnlockDisplay(this.display);
1834 		}
1835 	}
1836 
1837 	/// Emit a beep to get user's attention.
1838 	void beep () {
1839 		version(X11) {
1840 			XBell(this.display, 100);
1841 		} else version(Windows) {
1842 			MessageBeep(0xFFFFFFFF);
1843 		}
1844 	}
1845 
1846 
1847 
1848 	version(without_opengl) {} else {
1849 
1850 		/// 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`.
1851 		void delegate() redrawOpenGlScene;
1852 
1853 		/// This will allow you to change OpenGL vsync state.
1854 		final @property void vsync (bool wait) {
1855 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
1856 		  version(X11) {
1857 		    setAsCurrentOpenGlContext();
1858 		    glxSetVSync(display, impl.window, wait);
1859 		  }
1860 		}
1861 
1862 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
1863 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
1864 		/// enough without waiting 'em to finish their frame bussiness.
1865 		bool useGLFinish = true;
1866 
1867 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
1868 		/// 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.
1869 		void redrawOpenGlSceneNow() {
1870 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
1871 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
1872 			if(redrawOpenGlScene is null)
1873 				return;
1874 
1875 			this.mtLock();
1876 			scope(exit) this.mtUnlock();
1877 
1878 			this.setAsCurrentOpenGlContext();
1879 
1880 			redrawOpenGlScene();
1881 
1882 			this.swapOpenGlBuffers();
1883 			// 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.
1884 			if (useGLFinish) glFinish();
1885 		}
1886 
1887 
1888 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
1889 		void setAsCurrentOpenGlContext() {
1890 			assert(openglMode == OpenGlOptions.yes);
1891 			version(X11) {
1892 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
1893 					throw new Exception("glXMakeCurrent");
1894 			} else version(Windows) {
1895 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
1896 				if (!wglMakeCurrent(ghDC, ghRC))
1897 					throw new Exception("wglMakeCurrent"); // let windows users suffer too
1898 			}
1899 		}
1900 
1901 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
1902 		/// This doesn't throw, returning success flag instead.
1903 		bool setAsCurrentOpenGlContextNT() nothrow {
1904 			assert(openglMode == OpenGlOptions.yes);
1905 			version(X11) {
1906 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
1907 			} else version(Windows) {
1908 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
1909 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
1910 			}
1911 		}
1912 
1913 		/// 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.
1914 		/// This doesn't throw, returning success flag instead.
1915 		bool releaseCurrentOpenGlContext() nothrow {
1916 			assert(openglMode == OpenGlOptions.yes);
1917 			version(X11) {
1918 				return (glXMakeCurrent(display, 0, null) != 0);
1919 			} else version(Windows) {
1920 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
1921 				return wglMakeCurrent(ghDC, null) ? true : false;
1922 			}
1923 		}
1924 
1925 		/++
1926 			simpledisplay always uses double buffering, usually automatically. This
1927 			manually swaps the OpenGL buffers.
1928 
1929 
1930 			You should not need to call this yourself because simpledisplay will do it
1931 			for you after calling your `redrawOpenGlScene`.
1932 
1933 			Remember that this may throw an exception, which you can catch in a multithreaded
1934 			application to keep your thread from dying from an unhandled exception.
1935 		+/
1936 		void swapOpenGlBuffers() {
1937 			assert(openglMode == OpenGlOptions.yes);
1938 			version(X11) {
1939 				if (!this._visible) return; // no need to do this if window is invisible
1940 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
1941 				glXSwapBuffers(display, impl.window);
1942 			} else version(Windows) {
1943 				SwapBuffers(ghDC);
1944 			}
1945 		}
1946 	}
1947 
1948 	/++
1949 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
1950 
1951 
1952 		---
1953 			auto window = new SimpleWindow(100, 100, "First title");
1954 			window.title = "A new title";
1955 		---
1956 
1957 		You may call this function at any time.
1958 	+/
1959 	@property void title(string title) {
1960 		_title = title;
1961 		version(OSXCocoa) throw new NotYetImplementedException(); else
1962 		impl.setTitle(title);
1963 	}
1964 
1965 	private string _title;
1966 
1967 	/// Gets the title
1968 	@property string title() {
1969 		if(_title is null)
1970 			_title = getRealTitle();
1971 		return _title;
1972 	}
1973 
1974 	/++
1975 		Get the title as set by the window manager.
1976 		May not match what you attempted to set.
1977 	+/
1978 	string getRealTitle() {
1979 		static if(is(typeof(impl.getTitle())))
1980 			return impl.getTitle();
1981 		else
1982 			return null;
1983 	}
1984 
1985 	// don't use this generally it is not yet really released
1986 	version(X11)
1987 	@property Image secret_icon() {
1988 		return secret_icon_inner;
1989 	}
1990 	private Image secret_icon_inner;
1991 
1992 
1993 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user.
1994 	@property void icon(MemoryImage icon) {
1995 		auto tci = icon.getAsTrueColorImage();
1996 		version(Windows) {
1997 			winIcon = new WindowsIcon(icon);
1998 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
1999 		} else version(X11) {
2000 			secret_icon_inner = Image.fromMemoryImage(icon);
2001 			// FIXME: ensure this is correct
2002 			auto display = XDisplayConnection.get;
2003 			arch_ulong[] buffer;
2004 			buffer ~= icon.width;
2005 			buffer ~= icon.height;
2006 			foreach(c; tci.imageData.colors) {
2007 				arch_ulong b;
2008 				b |= c.a << 24;
2009 				b |= c.r << 16;
2010 				b |= c.g << 8;
2011 				b |= c.b;
2012 				buffer ~= b;
2013 			}
2014 
2015 			XChangeProperty(
2016 				display,
2017 				impl.window,
2018 				GetAtom!("_NET_WM_ICON", true)(display),
2019 				GetAtom!"CARDINAL"(display),
2020 				32 /* bits */,
2021 				0 /*PropModeReplace*/,
2022 				buffer.ptr,
2023 				cast(int) buffer.length);
2024 		} else version(OSXCocoa) {
2025 			throw new NotYetImplementedException();
2026 		} else static assert(0);
2027 	}
2028 
2029 	version(Windows)
2030 		private WindowsIcon winIcon;
2031 
2032 	bool _suppressDestruction;
2033 
2034 	~this() {
2035 		if(_suppressDestruction)
2036 			return;
2037 		impl.dispose();
2038 	}
2039 
2040 	private bool _closed;
2041 
2042 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
2043 	/*
2044 	ScreenPainter drawTransiently() {
2045 		return impl.getPainter();
2046 	}
2047 	*/
2048 
2049 	/// Draws an image on the window. This is meant to provide quick look
2050 	/// of a static image generated elsewhere.
2051 	@property void image(Image i) {
2052 		version(Windows) {
2053 			BITMAP bm;
2054 			HDC hdc = GetDC(hwnd);
2055 			HDC hdcMem = CreateCompatibleDC(hdc);
2056 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
2057 
2058 			GetObject(i.handle, bm.sizeof, &bm);
2059 
2060 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
2061 
2062 			SelectObject(hdcMem, hbmOld);
2063 			DeleteDC(hdcMem);
2064 			DeleteDC(hwnd);
2065 
2066 			/*
2067 			RECT r;
2068 			r.right = i.width;
2069 			r.bottom = i.height;
2070 			InvalidateRect(hwnd, &r, false);
2071 			*/
2072 		} else
2073 		version(X11) {
2074 			if(!destroyed) {
2075 				if(i.usingXshm)
2076 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
2077 				else
2078 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
2079 			}
2080 		} else
2081 		version(OSXCocoa) {
2082 			draw().drawImage(Point(0, 0), i);
2083 			setNeedsDisplay(view, true);
2084 		} else static assert(0);
2085 	}
2086 
2087 	/++
2088 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
2089 
2090 		---
2091 		window.cursor = GenericCursor.Help;
2092 		// now the window mouse cursor is set to a generic help
2093 		---
2094 
2095 	+/
2096 	@property void cursor(MouseCursor cursor) {
2097 		version(OSXCocoa)
2098 			featureNotImplemented();
2099 		else
2100 		if(this.impl.curHidden <= 0) {
2101 			static if(UsingSimpledisplayX11) {
2102 				auto ch = cursor.cursorHandle;
2103 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
2104 			} else version(Windows) {
2105 				auto ch = cursor.cursorHandle;
2106 				impl.currentCursor = ch;
2107 				SetCursor(ch); // redraw without waiting for mouse movement to update
2108 			} else featureNotImplemented();
2109 		}
2110 
2111 	}
2112 
2113 	/// What follows are the event handlers. These are set automatically
2114 	/// by the eventLoop function, but are still public so you can change
2115 	/// them later. wasPressed == true means key down. false == key up.
2116 
2117 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
2118 	void delegate(KeyEvent ke) handleKeyEvent;
2119 
2120 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
2121 	void delegate(dchar c) handleCharEvent;
2122 
2123 	/// Handles a timer pulse. Settable through setEventHandlers.
2124 	void delegate() handlePulse;
2125 
2126 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
2127 	void delegate(bool) onFocusChange;
2128 
2129 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
2130 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
2131 	void delegate() onClosing;
2132 
2133 	/** Called when we received destroy notification. At this stage we cannot do much with our window
2134 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
2135 	 * last minute cleanup. */
2136 	void delegate() onDestroyed;
2137 
2138 	static if (UsingSimpledisplayX11)
2139 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
2140 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
2141 	 * You will probably never need to setup this handler, it is for very low-level stuff.
2142 	 *
2143 	 * WARNING! Xlib is multithread-locked when this handles is called! */
2144 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
2145 
2146 	//version(Windows)
2147 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
2148 
2149 	private {
2150 		int lastMouseX = int.min;
2151 		int lastMouseY = int.min;
2152 		void mdx(ref MouseEvent ev) {
2153 			if(lastMouseX == int.min || lastMouseY == int.min) {
2154 				ev.dx = 0;
2155 				ev.dy = 0;
2156 			} else {
2157 				ev.dx = ev.x - lastMouseX;
2158 				ev.dy = ev.y - lastMouseY;
2159 			}
2160 
2161 			lastMouseX = ev.x;
2162 			lastMouseY = ev.y;
2163 		}
2164 	}
2165 
2166 	/// Mouse event handler. Settable through setEventHandlers.
2167 	void delegate(MouseEvent) handleMouseEvent;
2168 
2169 	/// use to redraw child widgets if you use system apis to add stuff
2170 	void delegate() paintingFinished;
2171 
2172 	void delegate() paintingFinishedDg() {
2173 		return paintingFinished;
2174 	}
2175 
2176 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
2177 	/// for this to ever happen.
2178 	void delegate(int width, int height) windowResized;
2179 
2180 	/** Platform specific - handle any native messages this window gets.
2181 	  *
2182 	  * Note: this is called *in addition to* other event handlers, unless you return zero indicating that you handled it.
2183 
2184 	  * On Windows, it takes the form of int delegate(HWND,UINT, WPARAM, LPARAM).
2185 
2186 	  * On X11, it takes the form of int delegate(XEvent).
2187 
2188 	  * IMPORTANT: it used to be static in old versions of simpledisplay.d, but I always used
2189 	  * it as if it wasn't static... so now I just fixed it so it isn't anymore.
2190 	**/
2191 	NativeEventHandler handleNativeEvent;
2192 
2193 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
2194 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
2195 	/// this instead and it will work the same way.
2196 	__gshared NativeEventHandler handleNativeGlobalEvent;
2197 
2198 //  private:
2199 	/// The native implementation is available, but you shouldn't use it unless you are
2200 	/// familiar with the underlying operating system, don't mind depending on it, and
2201 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
2202 	/// do what you need to do with handleNativeEvent instead.
2203 	///
2204 	/// This is likely to eventually change to be just a struct holding platform-specific
2205 	/// handles instead of a template mixin at some point because I'm not happy with the
2206 	/// code duplication here (ironically).
2207 	mixin NativeSimpleWindowImplementation!() impl;
2208 
2209 	/**
2210 		This is in-process one-way (from anything to window) event sending mechanics.
2211 		It is thread-safe, so it can be used in multi-threaded applications to send,
2212 		for example, "wake up and repaint" events when thread completed some operation.
2213 		This will allow to avoid using timer pulse to check events with synchronization,
2214 		'cause event handler will be called in UI thread. You can stop guessing which
2215 		pulse frequency will be enough for your app.
2216 		Note that events handlers may be called in arbitrary order, i.e. last registered
2217 		handler can be called first, and vice versa.
2218 	*/
2219 public:
2220 	/** Is our custom event queue empty? Can be used in simple cases to prevent
2221 	 * "spamming" window with events it can't cope with.
2222 	 * It is safe to call this from non-UI threads.
2223 	 */
2224 	@property bool eventQueueEmpty() () {
2225 		synchronized(this) {
2226 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
2227 		}
2228 		return true;
2229 	}
2230 
2231 	/** Does our custom event queue contains at least one with the given type?
2232 	 * Can be used in simple cases to prevent "spamming" window with events
2233 	 * it can't cope with.
2234 	 * It is safe to call this from non-UI threads.
2235 	 */
2236 	@property bool eventQueued(ET:Object) () {
2237 		synchronized(this) {
2238 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
2239 				if (!o.doProcess) {
2240 					if (cast(ET)(o.evt)) return true;
2241 				}
2242 			}
2243 		}
2244 		return false;
2245 	}
2246 
2247 	/** Add listener for custom event. Can be used like this:
2248 	 *
2249 	 * ---------------------
2250 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
2251 	 *   ...
2252 	 *   win.removeEventListener(eid);
2253 	 * ---------------------
2254 	 *
2255 	 * Returns: 0 on failure (should never happen, so ignore it)
2256 	 *
2257 	 * $(WARNING Don't use this method in object destructors!)
2258 	 *
2259 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
2260 	 *           'cause if event handler id counter will overflow, you won't be able
2261 	 *           to register any more events.)
2262 	 */
2263 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
2264 		if (dg is null) return 0; // ignore empty handlers
2265 		synchronized(this) {
2266 			//FIXME: abort on overflow?
2267 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
2268 			EventHandlerEntry e;
2269 			e.dg = delegate (Object o) {
2270 				if (auto co = cast(ET)o) {
2271 					try {
2272 						dg(co);
2273 					} catch (Exception) {
2274 						// sorry!
2275 					}
2276 					return true;
2277 				}
2278 				return false;
2279 			};
2280 			e.id = lastUsedHandlerId;
2281 			auto optr = eventHandlers.ptr;
2282 			eventHandlers ~= e;
2283 			if (eventHandlers.ptr !is optr) {
2284 				import core.memory : GC;
2285 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
2286 			}
2287 			return lastUsedHandlerId;
2288 		}
2289 	}
2290 
2291 	/// Remove event listener. It is safe to pass invalid event id here.
2292 	/// $(WARNING Don't use this method in object destructors!)
2293 	void removeEventListener() (uint id) {
2294 		if (id == 0 || id > lastUsedHandlerId) return;
2295 		synchronized(this) {
2296 			foreach (immutable idx; 0..eventHandlers.length) {
2297 				if (eventHandlers[idx].id == id) {
2298 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
2299 					eventHandlers[$-1].dg = null;
2300 					eventHandlers.length -= 1;
2301 					eventHandlers.assumeSafeAppend;
2302 					return;
2303 				}
2304 			}
2305 		}
2306 	}
2307 
2308 	/// Post event to queue. It is safe to call this from non-UI threads.
2309 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
2310 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
2311 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
2312 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
2313 		if (this.closed) return false; // closed windows can't handle events
2314 
2315 		// remove all events of type `ET`
2316 		void removeAllET () {
2317 			uint eidx = 0, ec = eventQueueUsed;
2318 			auto eptr = eventQueue.ptr;
2319 			while (eidx < ec) {
2320 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
2321 				if (cast(ET)eptr.evt !is null) {
2322 					// i found her!
2323 					if (inCustomEventProcessor) {
2324 						// if we're in custom event processing loop, processor will clear it for us
2325 						eptr.evt = null;
2326 						++eidx;
2327 						++eptr;
2328 					} else {
2329 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2330 						ec = --eventQueueUsed;
2331 						// clear last event (it is already copied)
2332 						eventQueue.ptr[ec].evt = null;
2333 					}
2334 				} else {
2335 					++eidx;
2336 					++eptr;
2337 				}
2338 			}
2339 		}
2340 
2341 		if (evt is null) {
2342 			if (replace) { synchronized(this) removeAllET(); }
2343 			// ignore empty events, they can't be handled anyway
2344 			return false;
2345 		}
2346 
2347 		// add events even if no event FD/event object created yet
2348 		synchronized(this) {
2349 			if (replace) removeAllET();
2350 			if (eventQueueUsed == uint.max) return false; // just in case
2351 			if (eventQueueUsed < eventQueue.length) {
2352 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
2353 			} else {
2354 				if (eventQueue.capacity == eventQueue.length) {
2355 					// need to reallocate; do a trick to ensure that old array is cleared
2356 					auto oarr = eventQueue;
2357 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
2358 					// just in case, do yet another check
2359 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
2360 					import core.memory : GC;
2361 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
2362 				} else {
2363 					auto optr = eventQueue.ptr;
2364 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
2365 					assert(eventQueue.ptr is optr);
2366 				}
2367 				++eventQueueUsed;
2368 				assert(eventQueueUsed == eventQueue.length);
2369 			}
2370 			if (!eventWakeUp()) {
2371 				// can't wake up event processor, so there is no reason to keep the event
2372 				assert(eventQueueUsed > 0);
2373 				eventQueue[--eventQueueUsed].evt = null;
2374 				return false;
2375 			}
2376 			return true;
2377 		}
2378 	}
2379 
2380 	/// Post event to queue. It is safe to call this from non-UI threads.
2381 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
2382 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
2383 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
2384 		return postTimeout!ET(evt, 0, replace);
2385 	}
2386 
2387 private:
2388 	private import core.time : MonoTime;
2389 
2390 	version(Posix) {
2391 		__gshared int customEventFDRead = -1;
2392 		__gshared int customEventFDWrite = -1;
2393 		__gshared int customSignalFD = -1;
2394 	} else version(Windows) {
2395 		__gshared HANDLE customEventH = null;
2396 	}
2397 
2398 	// wake up event processor
2399 	static bool eventWakeUp () {
2400 		version(X11) {
2401 			import core.sys.posix.unistd : write;
2402 			ulong n = 1;
2403 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
2404 			return true;
2405 		} else version(Windows) {
2406 			if (customEventH !is null) SetEvent(customEventH);
2407 			return true;
2408 		} else {
2409 			// not implemented for other OSes
2410 			return false;
2411 		}
2412 	}
2413 
2414 	static struct QueuedEvent {
2415 		Object evt;
2416 		bool timed = false;
2417 		MonoTime hittime = MonoTime.zero;
2418 		bool doProcess = false; // process event at the current iteration (internal flag)
2419 
2420 		this (Object aevt, uint toutmsecs) {
2421 			evt = aevt;
2422 			if (toutmsecs > 0) {
2423 				import core.time : msecs;
2424 				timed = true;
2425 				hittime = MonoTime.currTime+toutmsecs.msecs;
2426 			}
2427 		}
2428 	}
2429 
2430 	alias CustomEventHandler = bool delegate (Object o) nothrow;
2431 	static struct EventHandlerEntry {
2432 		CustomEventHandler dg;
2433 		uint id;
2434 	}
2435 
2436 	uint lastUsedHandlerId;
2437 	EventHandlerEntry[] eventHandlers;
2438 	QueuedEvent[] eventQueue = null;
2439 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
2440 	bool inCustomEventProcessor = false; // required to properly remove events
2441 
2442 	// process queued events and call custom event handlers
2443 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
2444 	void processCustomEvents () {
2445 		bool hasSomethingToDo = false;
2446 		uint ecount;
2447 		bool ocep;
2448 		synchronized(this) {
2449 			ocep = inCustomEventProcessor;
2450 			inCustomEventProcessor = true;
2451 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
2452 			auto ctt = MonoTime.currTime;
2453 			bool hasEmpty = false;
2454 			// mark events to process (this is required for `eventQueued()`)
2455 			foreach (ref qe; eventQueue[0..ecount]) {
2456 				if (qe.evt is null) { hasEmpty = true; continue; }
2457 				if (qe.timed) {
2458 					qe.doProcess = (qe.hittime <= ctt);
2459 				} else {
2460 					qe.doProcess = true;
2461 				}
2462 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
2463 			}
2464 			if (!hasSomethingToDo) {
2465 				// remove empty events
2466 				if (hasEmpty) {
2467 					uint eidx = 0, ec = eventQueueUsed;
2468 					auto eptr = eventQueue.ptr;
2469 					while (eidx < ec) {
2470 						if (eptr.evt is null) {
2471 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2472 							ec = --eventQueueUsed;
2473 							eventQueue.ptr[ec].evt = null; // make GC life easier
2474 						} else {
2475 							++eidx;
2476 							++eptr;
2477 						}
2478 					}
2479 				}
2480 				inCustomEventProcessor = ocep;
2481 				return;
2482 			}
2483 		}
2484 		// process marked events
2485 		uint efree = 0; // non-processed events will be put at this index
2486 		EventHandlerEntry[] eh;
2487 		Object evt;
2488 		foreach (immutable eidx; 0..ecount) {
2489 			synchronized(this) {
2490 				if (!eventQueue[eidx].doProcess) {
2491 					// skip this event
2492 					assert(efree <= eidx);
2493 					if (efree != eidx) {
2494 						// copy this event to queue start
2495 						eventQueue[efree] = eventQueue[eidx];
2496 						eventQueue[eidx].evt = null; // just in case
2497 					}
2498 					++efree;
2499 					continue;
2500 				}
2501 				evt = eventQueue[eidx].evt;
2502 				eventQueue[eidx].evt = null; // in case event handler will hit GC
2503 				if (evt is null) continue; // just in case
2504 				// try all handlers; this can be slow, but meh...
2505 				eh = eventHandlers;
2506 			}
2507 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
2508 			evt = null;
2509 			eh = null;
2510 		}
2511 		synchronized(this) {
2512 			// move all unprocessed events to queue top; efree holds first "free index"
2513 			foreach (immutable eidx; ecount..eventQueueUsed) {
2514 				assert(efree <= eidx);
2515 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
2516 				++efree;
2517 			}
2518 			eventQueueUsed = efree;
2519 			// wake up event processor on next event loop iteration if we have more queued events
2520 			// also, remove empty events
2521 			bool awaken = false;
2522 			uint eidx = 0, ec = eventQueueUsed;
2523 			auto eptr = eventQueue.ptr;
2524 			while (eidx < ec) {
2525 				if (eptr.evt is null) {
2526 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2527 					ec = --eventQueueUsed;
2528 					eventQueue.ptr[ec].evt = null; // make GC life easier
2529 				} else {
2530 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
2531 					++eidx;
2532 					++eptr;
2533 				}
2534 			}
2535 			inCustomEventProcessor = ocep;
2536 		}
2537 	}
2538 
2539 	// for all windows in nativeMapping
2540 	static void processAllCustomEvents () {
2541 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
2542 			if (sw is null || sw.closed) continue;
2543 			sw.processCustomEvents();
2544 		}
2545 
2546 		// run pending [runInGuiThread] delegates
2547 		more:
2548 		RunQueueMember* next;
2549 		synchronized(runInGuiThreadLock) {
2550 			if(runInGuiThreadQueue.length) {
2551 				next = runInGuiThreadQueue[0];
2552 				runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
2553 			} else {
2554 				next = null;
2555 			}
2556 		}
2557 
2558 		if(next) {
2559 			try {
2560 				next.dg();
2561 				next.thrown = null;
2562 			} catch(Throwable t) {
2563 				next.thrown = t;
2564 			}
2565 
2566 			next.signal.notify();
2567 
2568 			goto more;
2569 		}
2570 	}
2571 
2572 	// 0: infinite (i.e. no scheduled events in queue)
2573 	uint eventQueueTimeoutMSecs () {
2574 		synchronized(this) {
2575 			if (eventQueueUsed == 0) return 0;
2576 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
2577 			uint res = int.max;
2578 			auto ctt = MonoTime.currTime;
2579 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
2580 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
2581 				if (qe.doProcess) continue; // just in case
2582 				if (!qe.timed) return 1; // minimal
2583 				if (qe.hittime <= ctt) return 1; // minimal
2584 				auto tms = (qe.hittime-ctt).total!"msecs";
2585 				if (tms < 1) tms = 1; // safety net
2586 				if (tms >= int.max) tms = int.max-1; // and another safety net
2587 				if (res > tms) res = cast(uint)tms;
2588 			}
2589 			return (res >= int.max ? 0 : res);
2590 		}
2591 	}
2592 
2593 	// for all windows in nativeMapping
2594 	static uint eventAllQueueTimeoutMSecs () {
2595 		uint res = uint.max;
2596 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
2597 			if (sw is null || sw.closed) continue;
2598 			uint to = sw.eventQueueTimeoutMSecs();
2599 			if (to && to < res) {
2600 				res = to;
2601 				if (to == 1) break; // can't have less than this
2602 			}
2603 		}
2604 		return (res >= int.max ? 0 : res);
2605 	}
2606 }
2607 
2608 
2609 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
2610 /// See [GenericCursor]
2611 class MouseCursor {
2612 	int osId;
2613 	bool isStockCursor;
2614 	private this(int osId) {
2615 		this.osId = osId;
2616 		this.isStockCursor = true;
2617 	}
2618 
2619 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
2620 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
2621 
2622 	version(Windows) {
2623 		HCURSOR cursor_;
2624 		HCURSOR cursorHandle() {
2625 			if(cursor_ is null)
2626 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
2627 			return cursor_;
2628 		}
2629 
2630 	} else static if(UsingSimpledisplayX11) {
2631 		Cursor cursor_ = None;
2632 		int xDisplaySequence;
2633 
2634 		Cursor cursorHandle() {
2635 			if(this.osId == None)
2636 				return None;
2637 
2638 			// we need to reload if we on a new X connection
2639 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
2640 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
2641 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
2642 			}
2643 			return cursor_;
2644 		}
2645 	}
2646 }
2647 
2648 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
2649 // https://tronche.com/gui/x/xlib/appendix/b/
2650 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
2651 /// 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
2652 enum GenericCursorType {
2653 	Default, /// The default arrow pointer.
2654 	Wait, /// A cursor indicating something is loading and the user must wait.
2655 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
2656 	Help, /// A cursor indicating the user can get help about the pointer location.
2657 	Cross, /// A crosshair.
2658 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
2659 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll
2660 	UpArrow, /// An arrow pointing straight up.
2661 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
2662 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
2663 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator)
2664 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator)
2665 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator)
2666 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator)
2667 
2668 }
2669 
2670 /*
2671 	X_plus == css cell == Windows ?
2672 */
2673 
2674 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
2675 static struct GenericCursor {
2676 	static:
2677 	///
2678 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
2679 		static MouseCursor mc;
2680 
2681 		auto type = __traits(getMember, GenericCursorType, str);
2682 
2683 		if(mc is null) {
2684 
2685 			version(Windows) {
2686 				int osId;
2687 				final switch(type) {
2688 					case GenericCursorType.Default: osId = IDC_ARROW; break;
2689 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
2690 					case GenericCursorType.Hand: osId = IDC_HAND; break;
2691 					case GenericCursorType.Help: osId = IDC_HELP; break;
2692 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
2693 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
2694 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
2695 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
2696 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
2697 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
2698 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
2699 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
2700 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
2701 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
2702 				}
2703 			} else static if(UsingSimpledisplayX11) {
2704 				int osId;
2705 				final switch(type) {
2706 					case GenericCursorType.Default: osId = None; break;
2707 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
2708 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
2709 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
2710 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
2711 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
2712 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
2713 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
2714 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
2715 
2716 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
2717 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
2718 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
2719 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
2720 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
2721 				}
2722 
2723 			} else featureNotImplemented();
2724 
2725 			mc = new MouseCursor(osId);
2726 		}
2727 		return mc;
2728 	}
2729 }
2730 
2731 
2732 /++
2733 	If you want to get more control over the event loop, you can use this.
2734 
2735 	Typically though, you can just call [SimpleWindow.eventLoop].
2736 +/
2737 struct EventLoop {
2738 	@disable this();
2739 
2740 	/// Gets a reference to an existing event loop
2741 	static EventLoop get() {
2742 		return EventLoop(0, null);
2743 	}
2744 
2745 	__gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
2746 
2747 	/// Construct an application-global event loop for yourself
2748 	/// See_Also: [SimpleWindow.setEventHandlers]
2749 	this(long pulseTimeout, void delegate() handlePulse) {
2750 		synchronized(monitor) {
2751 			if(impl is null) {
2752 				claimGuiThread();
2753 				version(sdpy_thread_checks) assert(thisIsGuiThread);
2754 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
2755 			} else {
2756 				if(pulseTimeout) {
2757 					impl.pulseTimeout = pulseTimeout;
2758 					impl.handlePulse = handlePulse;
2759 				}
2760 			}
2761 			impl.refcount++;
2762 		}
2763 	}
2764 
2765 	~this() {
2766 		if(impl is null)
2767 			return;
2768 		impl.refcount--;
2769 		if(impl.refcount == 0)
2770 			impl.dispose();
2771 
2772 	}
2773 
2774 	this(this) {
2775 		if(impl is null)
2776 			return;
2777 		impl.refcount++;
2778 	}
2779 
2780 	/// Runs the event loop until the whileCondition, if present, returns false
2781 	int run(bool delegate() whileCondition = null) {
2782 		assert(impl !is null);
2783 		impl.notExited = true;
2784 		return impl.run(whileCondition);
2785 	}
2786 
2787 	/// Exits the event loop
2788 	void exit() {
2789 		assert(impl !is null);
2790 		impl.notExited = false;
2791 	}
2792 
2793 	version(linux)
2794 	ref void delegate(int) signalHandler() {
2795 		assert(impl !is null);
2796 		return impl.signalHandler;
2797 	}
2798 
2799 	__gshared static EventLoopImpl* impl;
2800 }
2801 
2802 version(linux)
2803 	void delegate(int, int) globalHupHandler;
2804 
2805 version(Posix)
2806 	void makeNonBlocking(int fd) {
2807 		import fcntl = core.sys.posix.fcntl;
2808 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
2809 		if(flags == -1)
2810 			throw new Exception("fcntl get");
2811 		flags |= fcntl.O_NONBLOCK;
2812 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
2813 		if(s == -1)
2814 			throw new Exception("fcntl set");
2815 	}
2816 
2817 struct EventLoopImpl {
2818 	int refcount;
2819 
2820 	bool notExited = true;
2821 
2822 	version(linux) {
2823 		static import ep = core.sys.linux.epoll;
2824 		static import unix = core.sys.posix.unistd;
2825 		static import err = core.stdc.errno;
2826 		import core.sys.linux.timerfd;
2827 
2828 		void delegate(int) signalHandler;
2829 	}
2830 
2831 	version(X11) {
2832 		int pulseFd = -1;
2833 		version(linux) ep.epoll_event[16] events = void;
2834 	} else version(Windows) {
2835 		Timer pulser;
2836 		HANDLE[] handles;
2837 	}
2838 
2839 
2840 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2841 	/// to call this, as it's not recommended to share window between threads.
2842 	void mtLock () {
2843 		version(X11) {
2844 			XLockDisplay(this.display);
2845 		}
2846 	}
2847 
2848 	version(X11)
2849 	auto display() { return XDisplayConnection.get; }
2850 
2851 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2852 	/// to call this, as it's not recommended to share window between threads.
2853 	void mtUnlock () {
2854 		version(X11) {
2855 			XUnlockDisplay(this.display);
2856 		}
2857 	}
2858 
2859 	version(with_eventloop)
2860 	void initialize(long pulseTimeout) {}
2861 	else
2862 	void initialize(long pulseTimeout) {
2863 		version(Windows) {
2864 			if(pulseTimeout)
2865 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
2866 
2867 			if (customEventH is null) {
2868 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
2869 				if (customEventH !is null) {
2870 					handles ~= customEventH;
2871 				} else {
2872 					// this is something that should not be; better be safe than sorry
2873 					throw new Exception("can't create eventfd for custom event processing");
2874 				}
2875 			}
2876 
2877 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
2878 		}
2879 
2880 		version(linux) {
2881 			prepareEventLoop();
2882 			{
2883 				auto display = XDisplayConnection.get;
2884 				// adding Xlib file
2885 				ep.epoll_event ev = void;
2886 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
2887 				ev.events = ep.EPOLLIN;
2888 				ev.data.fd = display.fd;
2889 				//import std.conv;
2890 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
2891 					throw new Exception("add x fd");// ~ to!string(epollFd));
2892 				displayFd = display.fd;
2893 			}
2894 
2895 			if(pulseTimeout) {
2896 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
2897 				if(pulseFd == -1)
2898 					throw new Exception("pulse timer create failed");
2899 
2900 				itimerspec value;
2901 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
2902 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
2903 
2904 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
2905 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
2906 
2907 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
2908 					throw new Exception("couldn't make pulse timer");
2909 
2910 				ep.epoll_event ev = void;
2911 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
2912 				ev.events = ep.EPOLLIN;
2913 				ev.data.fd = pulseFd;
2914 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
2915 			}
2916 
2917 			// eventfd for custom events
2918 			if (customEventFDWrite == -1) {
2919 				customEventFDWrite = eventfd(0, 0);
2920 				customEventFDRead = customEventFDWrite;
2921 				if (customEventFDRead >= 0) {
2922 					ep.epoll_event ev = void;
2923 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
2924 					ev.events = ep.EPOLLIN;
2925 					ev.data.fd = customEventFDRead;
2926 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
2927 				} else {
2928 					// this is something that should not be; better be safe than sorry
2929 					throw new Exception("can't create eventfd for custom event processing");
2930 				}
2931 			}
2932 
2933 			if (customSignalFD == -1) {
2934 				import core.sys.linux.sys.signalfd;
2935 
2936 				sigset_t sigset;
2937 				auto err = sigemptyset(&sigset);
2938 				assert(!err);
2939 				err = sigaddset(&sigset, SIGINT);
2940 				assert(!err);
2941 				err = sigaddset(&sigset, SIGHUP);
2942 				assert(!err);
2943 				err = sigprocmask(SIG_BLOCK, &sigset, null);
2944 				assert(!err);
2945 
2946 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
2947 				assert(customSignalFD != -1);
2948 
2949 				ep.epoll_event ev = void;
2950 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
2951 				ev.events = ep.EPOLLIN;
2952 				ev.data.fd = customSignalFD;
2953 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
2954 			}
2955 		} else version(Posix) {
2956 			prepareEventLoop();
2957 			if (customEventFDRead == -1) {
2958 				int[2] bfr;
2959 				import core.sys.posix.unistd;
2960 				auto ret = pipe(bfr);
2961 				if(ret == -1) throw new Exception("pipe");
2962 				customEventFDRead = bfr[0];
2963 				customEventFDWrite = bfr[1];
2964 			}
2965 
2966 		}
2967 
2968 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
2969 
2970 		version(linux) {
2971 			this.mtLock();
2972 			scope(exit) this.mtUnlock();
2973 			XPending(display); // no, really
2974 		}
2975 
2976 		disposed = false;
2977 	}
2978 
2979 	bool disposed = true;
2980 	version(X11)
2981 		int displayFd = -1;
2982 
2983 	version(with_eventloop)
2984 	void dispose() {}
2985 	else
2986 	void dispose() {
2987 		disposed = true;
2988 		version(X11) {
2989 			if(pulseFd != -1) {
2990 				import unix = core.sys.posix.unistd;
2991 				unix.close(pulseFd);
2992 				pulseFd = -1;
2993 			}
2994 
2995 				version(linux)
2996 				if(displayFd != -1) {
2997 					// 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
2998 					ep.epoll_event ev = void;
2999 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3000 					ev.events = ep.EPOLLIN;
3001 					ev.data.fd = displayFd;
3002 					//import std.conv;
3003 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
3004 					displayFd = -1;
3005 				}
3006 
3007 		} else version(Windows) {
3008 			if(pulser !is null) {
3009 				pulser.destroy();
3010 				pulser = null;
3011 			}
3012 			if (customEventH !is null) {
3013 				CloseHandle(customEventH);
3014 				customEventH = null;
3015 			}
3016 		}
3017 	}
3018 
3019 	this(long pulseTimeout, void delegate() handlePulse) {
3020 		this.pulseTimeout = pulseTimeout;
3021 		this.handlePulse = handlePulse;
3022 		initialize(pulseTimeout);
3023 	}
3024 
3025 	private long pulseTimeout;
3026 	void delegate() handlePulse;
3027 
3028 	~this() {
3029 		dispose();
3030 	}
3031 
3032 	version(Posix)
3033 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
3034 	version(Posix)
3035 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
3036 	version(linux)
3037 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
3038 	version(Windows)
3039 	ref auto customEventH() { return SimpleWindow.customEventH; }
3040 
3041 	version(with_eventloop) {
3042 		int loopHelper(bool delegate() whileCondition) {
3043 			// FIXME: whileCondition
3044 			import arsd.eventloop;
3045 			loop();
3046 			return 0;
3047 		}
3048 	} else
3049 	int loopHelper(bool delegate() whileCondition) {
3050 		version(X11) {
3051 			bool done = false;
3052 
3053 			XFlush(display);
3054 			insideXEventLoop = true;
3055 			scope(exit) insideXEventLoop = false;
3056 
3057 			version(linux) {
3058 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
3059 					bool forceXPending = false;
3060 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
3061 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
3062 					{
3063 						this.mtLock();
3064 						scope(exit) this.mtUnlock();
3065 						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
3066 					}
3067 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
3068 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
3069 					if(nfds == -1) {
3070 						if(err.errno == err.EINTR) {
3071 							continue; // interrupted by signal, just try again
3072 						}
3073 						throw new Exception("epoll wait failure");
3074 					}
3075 
3076 					SimpleWindow.processAllCustomEvents(); // anyway
3077 					//version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
3078 					foreach(idx; 0 .. nfds) {
3079 						if(done) break;
3080 						auto fd = events[idx].data.fd;
3081 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
3082 						auto flags = events[idx].events;
3083 						if(flags & ep.EPOLLIN) {
3084 							if (fd == customSignalFD) {
3085 								version(linux) {
3086 									import core.sys.linux.sys.signalfd;
3087 									import core.sys.posix.unistd : read;
3088 									signalfd_siginfo info;
3089 									read(customSignalFD, &info, info.sizeof);
3090 
3091 									auto sig = info.ssi_signo;
3092 
3093 									if(EventLoop.get.signalHandler !is null) {
3094 										EventLoop.get.signalHandler()(sig);
3095 									} else {
3096 										EventLoop.get.exit();
3097 									}
3098 								}
3099 							} else if(fd == display.fd) {
3100 								version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
3101 								this.mtLock();
3102 								scope(exit) this.mtUnlock();
3103 								while(!done && XPending(display)) {
3104 									done = doXNextEvent(this.display);
3105 								}
3106 								forceXPending = false;
3107 							} else if(fd == pulseFd) {
3108 								long expirationCount;
3109 								// 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...
3110 
3111 								handlePulse();
3112 
3113 								// read just to clear the buffer so poll doesn't trigger again
3114 								// BTW I read AFTER the pulse because if the pulse handler takes
3115 								// a lot of time to execute, we don't want the app to get stuck
3116 								// in a loop of timer hits without a chance to do anything else
3117 								//
3118 								// IOW handlePulse happens at most once per pulse interval.
3119 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
3120 							} else if (fd == customEventFDRead) {
3121 								// we have some custom events; process 'em
3122 								import core.sys.posix.unistd : read;
3123 								ulong n;
3124 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
3125 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
3126 								//SimpleWindow.processAllCustomEvents();
3127 							} else {
3128 								// some other timer
3129 								version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); }
3130 
3131 								if(Timer* t = fd in Timer.mapping)
3132 									(*t).trigger();
3133 
3134 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3135 									(*pfr).ready(flags);
3136 
3137 								// or i might add support for other FDs too
3138 								// but for now it is just timer
3139 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
3140 							}
3141 						}
3142 						if(flags & ep.EPOLLHUP) {
3143 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3144 								(*pfr).hup(flags);
3145 							if(globalHupHandler)
3146 								globalHupHandler(fd, flags);
3147 						}
3148 						/+
3149 						} else {
3150 							// not interested in OUT, we are just reading here.
3151 							//
3152 							// error or hup might also be reported
3153 							// but it shouldn't here since we are only
3154 							// using a few types of FD and Xlib will report
3155 							// if it dies.
3156 							// so instead of thoughtfully handling it, I'll
3157 							// just throw. for now at least
3158 
3159 							throw new Exception("epoll did something else");
3160 						}
3161 						+/
3162 					}
3163 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
3164 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
3165 					if (!done && forceXPending) {
3166 						this.mtLock();
3167 						scope(exit) this.mtUnlock();
3168 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
3169 						while(!done && XPending(display)) {
3170 							done = doXNextEvent(this.display);
3171 						}
3172 					}
3173 				}
3174 			} else {
3175 				// Generic fallback: yes to simple pulse support,
3176 				// but NO timer support!
3177 
3178 				// FIXME: we could probably support the POSIX timer_create
3179 				// signal-based option, but I'm in no rush to write it since
3180 				// I prefer the fd-based functions.
3181 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
3182 
3183 					import core.sys.posix.poll;
3184 
3185 					pollfd[] pfds;
3186 					pollfd[32] pfdsBuffer;
3187 					auto len = PosixFdReader.mapping.length + 2;
3188 					// FIXME: i should just reuse the buffer
3189 					if(len < pfdsBuffer.length)
3190 						pfds = pfdsBuffer[0 .. len];
3191 					else
3192 						pfds = new pollfd[](len);
3193 
3194 					pfds[0].fd = display.fd;
3195 					pfds[0].events = POLLIN;
3196 					pfds[0].revents = 0;
3197 
3198 					int slot = 1;
3199 
3200 					if(customEventFDRead != -1) {
3201 						pfds[slot].fd = customEventFDRead;
3202 						pfds[slot].events = POLLIN;
3203 						pfds[slot].revents = 0;
3204 
3205 						slot++;
3206 					}
3207 
3208 					foreach(fd, obj; PosixFdReader.mapping) {
3209 						if(!obj.enabled) continue;
3210 						pfds[slot].fd = fd;
3211 						pfds[slot].events = POLLIN;
3212 						pfds[slot].revents = 0;
3213 
3214 						slot++;
3215 					}
3216 
3217 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
3218 					if(ret == -1) throw new Exception("poll");
3219 
3220 					if(ret == 0) {
3221 						// FIXME it may not necessarily time out if events keep coming
3222 						if(handlePulse !is null)
3223 							handlePulse();
3224 					} else {
3225 						foreach(s; 0 .. slot) {
3226 							if(pfds[s].revents == 0) continue;
3227 
3228 							if(pfds[s].fd == display.fd) {
3229 								while(!done && XPending(display)) {
3230 									this.mtLock();
3231 									scope(exit) this.mtUnlock();
3232 									done = doXNextEvent(this.display);
3233 								}
3234 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
3235 
3236 								import core.sys.posix.unistd : read;
3237 								ulong n;
3238 								read(customEventFDRead, &n, n.sizeof);
3239 								SimpleWindow.processAllCustomEvents();
3240 							} else {
3241 								auto obj = PosixFdReader.mapping[pfds[s].fd];
3242 								if(pfds[s].revents & POLLNVAL) {
3243 									obj.dispose();
3244 								} else {
3245 									obj.ready(pfds[s].revents);
3246 								}
3247 							}
3248 
3249 							ret--;
3250 							if(ret == 0) break;
3251 						}
3252 					}
3253 				}
3254 			}
3255 		}
3256 		
3257 		version(Windows) {
3258 			int ret = -1;
3259 			MSG message;
3260 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
3261 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
3262 				auto waitResult = MsgWaitForMultipleObjectsEx(
3263 					cast(int) handles.length, handles.ptr,
3264 					(wto == 0 ? INFINITE : wto), /* timeout */
3265 					0x04FF, /* QS_ALLINPUT */
3266 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
3267 
3268 				SimpleWindow.processAllCustomEvents(); // anyway
3269 				enum WAIT_OBJECT_0 = 0;
3270 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
3271 					auto h = handles[waitResult - WAIT_OBJECT_0];
3272 					if(auto e = h in WindowsHandleReader.mapping) {
3273 						(*e).ready();
3274 					}
3275 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
3276 					// message ready
3277 					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
3278 						ret = GetMessage(&message, null, 0, 0);
3279 						if(ret == -1)
3280 							throw new Exception("GetMessage failed");
3281 						TranslateMessage(&message);
3282 						DispatchMessage(&message);
3283 
3284 						if(ret == 0) // WM_QUIT
3285 							break;
3286 					}
3287 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
3288 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
3289 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
3290 					// timeout, should never happen since we aren't using it
3291 				} else if(waitResult == 0xFFFFFFFF) {
3292 						// failed
3293 						throw new Exception("MsgWaitForMultipleObjectsEx failed");
3294 				} else {
3295 					// idk....
3296 				}
3297 			}
3298 
3299 			// return message.wParam;
3300 			return 0;
3301 		} else {
3302 			return 0;
3303 		}
3304 	}
3305 
3306 	int run(bool delegate() whileCondition = null) {
3307 		if(disposed)
3308 			initialize(this.pulseTimeout);
3309 
3310 		version(X11) {
3311 			try {
3312 				return loopHelper(whileCondition);
3313 			} catch(XDisconnectException e) {
3314 				if(e.userRequested) {
3315 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
3316 						item.discardConnectionState();
3317 					XCloseDisplay(XDisplayConnection.display);
3318 				}
3319 
3320 				XDisplayConnection.display = null;
3321 
3322 				this.dispose();
3323 
3324 				throw e;
3325 			}
3326 		} else {
3327 			return loopHelper(whileCondition);
3328 		}
3329 	}
3330 }
3331 
3332 
3333 /++
3334 	Provides an icon on the system notification area (also known as the system tray).
3335 
3336 
3337 	If a notification area is not available with the NotificationIcon object is created,
3338 	it will silently succeed and simply attempt to create one when an area becomes available.
3339 
3340 
3341 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
3342 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
3343 	use the older version.
3344 +/
3345 version(OSXCocoa) {} else // NotYetImplementedException
3346 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
3347 
3348 	version(X11) {
3349 		void recreateAfterDisconnect() {
3350 			stateDiscarded = false;
3351 			clippixmap = None;
3352 			throw new Exception("NOT IMPLEMENTED");
3353 		}
3354 
3355 		bool stateDiscarded;
3356 		void discardConnectionState() {
3357 			stateDiscarded = true;
3358 		}
3359 	}
3360 
3361 
3362 	version(X11) {
3363 		Image img;
3364 
3365 		NativeEventHandler getNativeEventHandler() {
3366 			return delegate int(XEvent e) {
3367 				switch(e.type) {
3368 					case EventType.Expose:
3369 					//case EventType.VisibilityNotify:
3370 						redraw();
3371 					break;
3372 					case EventType.ClientMessage:
3373 						version(sddddd) {
3374 						import std.stdio;
3375 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
3376 						writeln("\t", e.xclient.format);
3377 						writeln("\t", e.xclient.data.l);
3378 						}
3379 					break;
3380 					case EventType.ButtonPress:
3381 						auto event = e.xbutton;
3382 						if (onClick !is null || onClickEx !is null) {
3383 							MouseButton mb = cast(MouseButton)0;
3384 							switch (event.button) {
3385 								case 1: mb = MouseButton.left; break; // left
3386 								case 2: mb = MouseButton.middle; break; // middle
3387 								case 3: mb = MouseButton.right; break; // right
3388 								case 4: mb = MouseButton.wheelUp; break; // scroll up
3389 								case 5: mb = MouseButton.wheelDown; break; // scroll down
3390 								case 6: break; // idk
3391 								case 7: break; // idk
3392 								case 8: mb = MouseButton.backButton; break;
3393 								case 9: mb = MouseButton.forwardButton; break;
3394 								default:
3395 							}
3396 							if (mb) {
3397 								try { onClick()(mb); } catch (Exception) {}
3398 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
3399 							}
3400 						}
3401 					break;
3402 					case EventType.EnterNotify:
3403 						if (onEnter !is null) {
3404 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
3405 						}
3406 						break;
3407 					case EventType.LeaveNotify:
3408 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
3409 						break;
3410 					case EventType.DestroyNotify:
3411 						active = false;
3412 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
3413 					break;
3414 					case EventType.ConfigureNotify:
3415 						auto event = e.xconfigure;
3416 						this.width = event.width;
3417 						this.height = event.height;
3418 						//import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y);
3419 						redraw();
3420 					break;
3421 					default: return 1;
3422 				}
3423 				return 1;
3424 			};
3425 		}
3426 
3427 		/* private */ void hideBalloon() {
3428 			balloon.close();
3429 			version(with_timer)
3430 				timer.destroy();
3431 			balloon = null;
3432 			version(with_timer)
3433 				timer = null;
3434 		}
3435 
3436 		void redraw() {
3437 			if (!active) return;
3438 
3439 			auto display = XDisplayConnection.get;
3440 			auto gc = DefaultGC(display, DefaultScreen(display));
3441 			XClearWindow(display, nativeHandle);
3442 
3443 			XSetClipMask(display, gc, clippixmap);
3444 
3445 			XSetForeground(display, gc,
3446 				cast(uint) 0 << 16 |
3447 				cast(uint) 0 << 8 |
3448 				cast(uint) 0);
3449 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
3450 
3451 			if (img is null) {
3452 				XSetForeground(display, gc,
3453 					cast(uint) 0 << 16 |
3454 					cast(uint) 127 << 8 |
3455 					cast(uint) 0);
3456 				XFillArc(display, nativeHandle,
3457 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
3458 			} else {
3459 				int dx = 0;
3460 				int dy = 0;
3461 				if(width > img.width)
3462 					dx = (width - img.width) / 2;
3463 				if(height > img.height)
3464 					dy = (height - img.height) / 2;
3465 				XSetClipOrigin(display, gc, dx, dy);
3466 
3467 				if (img.usingXshm)
3468 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
3469 				else
3470 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
3471 			}
3472 			XSetClipMask(display, gc, None);
3473 			flushGui();
3474 		}
3475 
3476 		static Window getTrayOwner() {
3477 			auto display = XDisplayConnection.get;
3478 			auto i = cast(int) DefaultScreen(display);
3479 			if(i < 10 && i >= 0) {
3480 				static Atom atom;
3481 				if(atom == None)
3482 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
3483 				return XGetSelectionOwner(display, atom);
3484 			}
3485 			return None;
3486 		}
3487 
3488 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
3489 			auto to = getTrayOwner();
3490 			auto display = XDisplayConnection.get;
3491 			XEvent ev;
3492 			ev.xclient.type = EventType.ClientMessage;
3493 			ev.xclient.window = to;
3494 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
3495 			ev.xclient.format = 32;
3496 			ev.xclient.data.l[0] = CurrentTime;
3497 			ev.xclient.data.l[1] = message;
3498 			ev.xclient.data.l[2] = d1;
3499 			ev.xclient.data.l[3] = d2;
3500 			ev.xclient.data.l[4] = d3;
3501 
3502 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
3503 		}
3504 
3505 		private static NotificationAreaIcon[] activeIcons;
3506 
3507 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
3508 		private void newManager() {
3509 			close();
3510 			createXWin();
3511 
3512 			if(this.clippixmap)
3513 				XFreePixmap(XDisplayConnection.get, clippixmap);
3514 			if(this.originalMemoryImage)
3515 				this.icon = this.originalMemoryImage;
3516 			else if(this.img)
3517 				this.icon = this.img;
3518 		}
3519 
3520 		private void createXWin () {
3521 			// create window
3522 			auto display = XDisplayConnection.get;
3523 
3524 			// to check for MANAGER on root window to catch new/changed tray owners
3525 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
3526 			// so if a thing does appear, we can handle it
3527 			foreach(ai; activeIcons)
3528 				if(ai is this)
3529 					goto alreadythere;
3530 			activeIcons ~= this;
3531 			alreadythere:
3532 
3533 			// and check for an existing tray
3534 			auto trayOwner = getTrayOwner();
3535 			if(trayOwner == None)
3536 				return;
3537 				//throw new Exception("No notification area found");
3538 
3539 			Visual* v = cast(Visual*) CopyFromParent;
3540 			/+
3541 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
3542 			if(visualProp !is null) {
3543 				c_ulong[] info = cast(c_ulong[]) visualProp;
3544 				if(info.length == 1) {
3545 					auto vid = info[0];
3546 					int returned;
3547 					XVisualInfo t;
3548 					t.visualid = vid;
3549 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
3550 					if(got !is null) {
3551 						if(returned == 1) {
3552 							v = got.visual;
3553 							import std.stdio;
3554 							writeln("using special visual ", *got);
3555 						}
3556 						XFree(got);
3557 					}
3558 				}
3559 			}
3560 			+/
3561 
3562 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
3563 			assert(nativeWindow);
3564 
3565 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
3566 
3567 			nativeHandle = nativeWindow;
3568 
3569 			///+
3570 			arch_ulong[2] info;
3571 			info[0] = 0;
3572 			info[1] = 1;
3573 
3574 			string title = this.name is null ? "simpledisplay.d program" : this.name;
3575 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
3576 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
3577 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
3578 
3579 			XChangeProperty(
3580 				display,
3581 				nativeWindow,
3582 				GetAtom!("_XEMBED_INFO", true)(display),
3583 				GetAtom!("_XEMBED_INFO", true)(display),
3584 				32 /* bits */,
3585 				0 /*PropModeReplace*/,
3586 				info.ptr,
3587 				2);
3588 
3589 			import core.sys.posix.unistd;
3590 			arch_ulong pid = getpid();
3591 
3592 			XChangeProperty(
3593 				display,
3594 				nativeWindow,
3595 				GetAtom!("_NET_WM_PID", true)(display),
3596 				XA_CARDINAL,
3597 				32 /* bits */,
3598 				0 /*PropModeReplace*/,
3599 				&pid,
3600 				1);
3601 
3602 			updateNetWmIcon();
3603 
3604 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
3605 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
3606 				XClassHint klass;
3607 				XWMHints wh;
3608 				XSizeHints size;
3609 				klass.res_name = sdpyWindowClassStr;
3610 				klass.res_class = sdpyWindowClassStr;
3611 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
3612 			}
3613 
3614 				// believe it or not, THIS is what xfce needed for the 9999 issue
3615 				XSizeHints sh;
3616 					c_long spr;
3617 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
3618 					sh.flags |= PMaxSize | PMinSize;
3619 				// FIXME maybe nicer resizing
3620 				sh.min_width = 16;
3621 				sh.min_height = 16;
3622 				sh.max_width = 16;
3623 				sh.max_height = 16;
3624 				XSetWMNormalHints(display, nativeWindow, &sh);
3625 
3626 
3627 			//+/
3628 
3629 
3630 			XSelectInput(display, nativeWindow,
3631 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
3632 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
3633 
3634 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
3635 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
3636 			active = true;
3637 		}
3638 
3639 		void updateNetWmIcon() {
3640 			if(img is null) return;
3641 			auto display = XDisplayConnection.get;
3642 			// FIXME: ensure this is correct
3643 			arch_ulong[] buffer;
3644 			auto imgMi = img.toTrueColorImage;
3645 			buffer ~= imgMi.width;
3646 			buffer ~= imgMi.height;
3647 			foreach(c; imgMi.imageData.colors) {
3648 				arch_ulong b;
3649 				b |= c.a << 24;
3650 				b |= c.r << 16;
3651 				b |= c.g << 8;
3652 				b |= c.b;
3653 				buffer ~= b;
3654 			}
3655 
3656 			XChangeProperty(
3657 				display,
3658 				nativeHandle,
3659 				GetAtom!"_NET_WM_ICON"(display),
3660 				GetAtom!"CARDINAL"(display),
3661 				32 /* bits */,
3662 				0 /*PropModeReplace*/,
3663 				buffer.ptr,
3664 				cast(int) buffer.length);
3665 		}
3666 
3667 
3668 
3669 		private SimpleWindow balloon;
3670 		version(with_timer)
3671 		private Timer timer;
3672 
3673 		private Window nativeHandle;
3674 		private Pixmap clippixmap = None;
3675 		private int width = 16;
3676 		private int height = 16;
3677 		private bool active = false;
3678 
3679 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
3680 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
3681 		void delegate () onLeave; /// X11 only.
3682 
3683 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
3684 
3685 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
3686 		void getWindowRect (out int x, out int y, out int width, out int height) {
3687 			if (!active) { width = 1; height = 1; return; } // 1: just in case
3688 			Window dummyw;
3689 			auto dpy = XDisplayConnection.get;
3690 			//XWindowAttributes xwa;
3691 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
3692 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
3693 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
3694 			width = this.width;
3695 			height = this.height;
3696 		}
3697 	}
3698 
3699 	/+
3700 		What I actually want from this:
3701 
3702 		* set / change: icon, tooltip
3703 		* handle: mouse click, right click
3704 		* show: notification bubble.
3705 	+/
3706 
3707 	version(Windows) {
3708 		WindowsIcon win32Icon;
3709 		HWND hwnd;
3710 
3711 		NOTIFYICONDATAW data;
3712 
3713 		NativeEventHandler getNativeEventHandler() {
3714 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
3715 				if(msg == WM_USER) {
3716 					auto event = LOWORD(lParam);
3717 					auto iconId = HIWORD(lParam);
3718 					//auto x = GET_X_LPARAM(wParam);
3719 					//auto y = GET_Y_LPARAM(wParam);
3720 					switch(event) {
3721 						case WM_LBUTTONDOWN:
3722 							onClick()(MouseButton.left);
3723 						break;
3724 						case WM_RBUTTONDOWN:
3725 							onClick()(MouseButton.right);
3726 						break;
3727 						case WM_MBUTTONDOWN:
3728 							onClick()(MouseButton.middle);
3729 						break;
3730 						case WM_MOUSEMOVE:
3731 							// sent, we could use it.
3732 						break;
3733 						case WM_MOUSEWHEEL:
3734 							// NOT SENT
3735 						break;
3736 						//case NIN_KEYSELECT:
3737 						//case NIN_SELECT:
3738 						//break;
3739 						default: {}
3740 					}
3741 				}
3742 				return 0;
3743 			};
3744 		}
3745 
3746 		enum NIF_SHOWTIP = 0x00000080;
3747 
3748 		private static struct NOTIFYICONDATAW {
3749 			DWORD cbSize;
3750 			HWND  hWnd;
3751 			UINT  uID;
3752 			UINT  uFlags;
3753 			UINT  uCallbackMessage;
3754 			HICON hIcon;
3755 			WCHAR[128] szTip;
3756 			DWORD dwState;
3757 			DWORD dwStateMask;
3758 			WCHAR[256] szInfo;
3759 			union {
3760 				UINT uTimeout;
3761 				UINT uVersion;
3762 			}
3763 			WCHAR[64] szInfoTitle;
3764 			DWORD dwInfoFlags;
3765 			GUID  guidItem;
3766 			HICON hBalloonIcon;
3767 		}
3768 
3769 	}
3770 
3771 	/++
3772 		Note that on Windows, only left, right, and middle buttons are sent.
3773 		Mouse wheel buttons are NOT set, so don't rely on those events if your
3774 		program is meant to be used on Windows too.
3775 	+/
3776 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
3777 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
3778 		// but on X, we need an Image, so its canonical ctor is there. They should
3779 		// forward to each other though.
3780 		version(X11) {
3781 			this.name = name;
3782 			this.onClick = onClick;
3783 			createXWin();
3784 			this.icon = icon;
3785 		} else version(Windows) {
3786 			this.onClick = onClick;
3787 			this.win32Icon = new WindowsIcon(icon);
3788 
3789 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
3790 
3791 			static bool registered = false;
3792 			if(!registered) {
3793 				WNDCLASSEX wc;
3794 				wc.cbSize = wc.sizeof;
3795 				wc.hInstance = hInstance;
3796 				wc.lpfnWndProc = &WndProc;
3797 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
3798 				if(!RegisterClassExW(&wc))
3799 					throw new WindowsApiException("RegisterClass");
3800 				registered = true;
3801 			}
3802 
3803 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
3804 			if(hwnd is null)
3805 				throw new Exception("CreateWindow");
3806 
3807 			data.cbSize = data.sizeof;
3808 			data.hWnd = hwnd;
3809 			data.uID = cast(uint) cast(void*) this;
3810 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
3811 				// NIF_INFO means show balloon
3812 			data.uCallbackMessage = WM_USER;
3813 			data.hIcon = this.win32Icon.hIcon;
3814 			data.szTip = ""; // FIXME
3815 			data.dwState = 0; // NIS_HIDDEN; // windows vista
3816 			data.dwStateMask = NIS_HIDDEN; // windows vista
3817 
3818 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
3819 
3820 
3821 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
3822 
3823 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
3824 		} else version(OSXCocoa) {
3825 			throw new NotYetImplementedException();
3826 		} else static assert(0);
3827 	}
3828 
3829 	/// ditto
3830 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
3831 		version(X11) {
3832 			this.onClick = onClick;
3833 			this.name = name;
3834 			createXWin();
3835 			this.icon = icon;
3836 		} else version(Windows) {
3837 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
3838 		} else version(OSXCocoa) {
3839 			throw new NotYetImplementedException();
3840 		} else static assert(0);
3841 	}
3842 
3843 	version(X11) {
3844 		/++
3845 			X-specific extension (for now at least)
3846 		+/
3847 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
3848 			this.onClickEx = onClickEx;
3849 			createXWin();
3850 			if (icon !is null) this.icon = icon;
3851 		}
3852 
3853 		/// ditto
3854 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
3855 			this.onClickEx = onClickEx;
3856 			createXWin();
3857 			this.icon = icon;
3858 		}
3859 	}
3860 
3861 	private void delegate (MouseButton button) onClick_;
3862 
3863 	///
3864 	@property final void delegate(MouseButton) onClick() {
3865 		if(onClick_ is null)
3866 			onClick_ = delegate void(MouseButton) {};
3867 		return onClick_;
3868 	}
3869 
3870 	/// ditto
3871 	@property final void onClick(void delegate(MouseButton) handler) {
3872 		// I made this a property setter so we can wrap smaller arg
3873 		// delegates and just forward all to onClickEx or something.
3874 		onClick_ = handler;
3875 	}
3876 
3877 
3878 	string name_;
3879 	@property void name(string n) {
3880 		name_ = n;
3881 	}
3882 
3883 	@property string name() {
3884 		return name_;
3885 	}
3886 
3887 	private MemoryImage originalMemoryImage;
3888 
3889 	///
3890 	@property void icon(MemoryImage i) {
3891 		version(X11) {
3892 			this.originalMemoryImage = i;
3893 			if (!active) return;
3894 			if (i !is null) {
3895 				this.img = Image.fromMemoryImage(i);
3896 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
3897 				//import std.stdio; writeln("using pixmap ", clippixmap);
3898 				updateNetWmIcon();
3899 				redraw();
3900 			} else {
3901 				if (this.img !is null) {
3902 					this.img = null;
3903 					redraw();
3904 				}
3905 			}
3906 		} else version(Windows) {
3907 			this.win32Icon = new WindowsIcon(i);
3908 
3909 			data.uFlags = NIF_ICON;
3910 			data.hIcon = this.win32Icon.hIcon;
3911 
3912 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
3913 		} else version(OSXCocoa) {
3914 			throw new NotYetImplementedException();
3915 		} else static assert(0);
3916 	}
3917 
3918 	/// ditto
3919 	@property void icon (Image i) {
3920 		version(X11) {
3921 			if (!active) return;
3922 			if (i !is img) {
3923 				originalMemoryImage = null;
3924 				img = i;
3925 				redraw();
3926 			}
3927 		} else version(Windows) {
3928 			this.icon(i is null ? null : i.toTrueColorImage());
3929 		} else version(OSXCocoa) {
3930 			throw new NotYetImplementedException();
3931 		} else static assert(0);
3932 	}
3933 
3934 	/++
3935 		Shows a balloon notification. You can only show one balloon at a time, if you call
3936 		it twice while one is already up, the first balloon will be replaced.
3937 		
3938 		
3939 		The user is free to block notifications and they will automatically disappear after
3940 		a timeout period.
3941 
3942 		Params:
3943 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
3944 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
3945 			icon = the icon to display with the notification. If null, it uses your existing icon.
3946 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
3947 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
3948 	+/
3949 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
3950 		bool useCustom = true;
3951 		version(libnotify) {
3952 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
3953 			try {
3954 				if(!active) return;
3955 
3956 				if(libnotify is null) {
3957 					libnotify = new C_DynamicLibrary("libnotify.so");
3958 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
3959 				}
3960 
3961 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
3962 
3963 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
3964 
3965 				if(onclick) {
3966 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
3967 					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);
3968 					libnotify_action_delegates_count++;
3969 				}
3970 
3971 				// FIXME icon
3972 
3973 				// set hint image-data
3974 				// set default action for onclick
3975 
3976 				void* error;
3977 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
3978 
3979 				useCustom = false;
3980 			} catch(Exception e) {
3981 
3982 			}
3983 		}
3984 		
3985 		version(X11) {
3986 		if(useCustom) {
3987 			if(!active) return;
3988 			if(balloon) {
3989 				hideBalloon();
3990 			}
3991 			// I know there are two specs for this, but one is never
3992 			// implemented by any window manager I have ever seen, and
3993 			// the other is a bloated mess and too complicated for simpledisplay...
3994 			// so doing my own little window instead.
3995 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
3996 
3997 			int x, y, width, height;
3998 			getWindowRect(x, y, width, height);
3999 
4000 			int bx = x - balloon.width;
4001 			int by = y - balloon.height;
4002 			if(bx < 0)
4003 				bx = x + width + balloon.width;
4004 			if(by < 0)
4005 				by = y + height;
4006 
4007 			// just in case, make sure it is actually on scren
4008 			if(bx < 0)
4009 				bx = 0;
4010 			if(by < 0)
4011 				by = 0;
4012 
4013 			balloon.move(bx, by);
4014 			auto painter = balloon.draw();
4015 			painter.fillColor = Color(220, 220, 220);
4016 			painter.outlineColor = Color.black;
4017 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
4018 			auto iconWidth = icon is null ? 0 : icon.width;
4019 			if(icon)
4020 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
4021 			iconWidth += 6; // margin around the icon
4022 
4023 			// draw a close button
4024 			painter.outlineColor = Color(44, 44, 44);
4025 			painter.fillColor = Color(255, 255, 255);
4026 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
4027 			painter.pen = Pen(Color.black, 3);
4028 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
4029 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
4030 			painter.pen = Pen(Color.black, 1);
4031 			painter.fillColor = Color(220, 220, 220);
4032 
4033 			// Draw the title and message
4034 			painter.drawText(Point(4 + iconWidth, 4), title);
4035 			painter.drawLine(
4036 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
4037 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
4038 			);
4039 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
4040 
4041 			balloon.setEventHandlers(
4042 				(MouseEvent ev) {
4043 					if(ev.type == MouseEventType.buttonPressed) {
4044 						if(ev.x > balloon.width - 16 && ev.y < 16)
4045 							hideBalloon();
4046 						else if(onclick)
4047 							onclick();
4048 					}
4049 				}
4050 			);
4051 			balloon.show();
4052 
4053 			version(with_timer)
4054 			timer = new Timer(timeout, &hideBalloon);
4055 			else {} // FIXME
4056 		}
4057 		} else version(Windows) {
4058 			enum NIF_INFO = 0x00000010;
4059 
4060 			data.uFlags = NIF_INFO;
4061 
4062 			// FIXME: go back to the last valid unicode code point
4063 			if(title.length > 40)
4064 				title = title[0 .. 40];
4065 			if(message.length > 220)
4066 				message = message[0 .. 220];
4067 
4068 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
4069 			enum NIIF_LARGE_ICON  = 0x00000020;
4070 			enum NIIF_NOSOUND = 0x00000010;
4071 			enum NIIF_USER = 0x00000004;
4072 			enum NIIF_ERROR = 0x00000003;
4073 			enum NIIF_WARNING = 0x00000002;
4074 			enum NIIF_INFO = 0x00000001;
4075 			enum NIIF_NONE = 0;
4076 
4077 			WCharzBuffer t = WCharzBuffer(title);
4078 			WCharzBuffer m = WCharzBuffer(message);
4079 
4080 			t.copyInto(data.szInfoTitle);
4081 			m.copyInto(data.szInfo);
4082 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
4083 
4084 			if(icon !is null) {
4085 				auto i = new WindowsIcon(icon);
4086 				data.hBalloonIcon = i.hIcon;
4087 				data.dwInfoFlags |= NIIF_USER;
4088 			}
4089 
4090 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4091 		} else version(OSXCocoa) {
4092 			throw new NotYetImplementedException();
4093 		} else static assert(0);
4094 	}
4095 
4096 	///
4097 	//version(Windows)
4098 	void show() {
4099 		version(X11) {
4100 			if(!hidden)
4101 				return;
4102 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
4103 			hidden = false;
4104 		} else version(Windows) {
4105 			data.uFlags = NIF_STATE;
4106 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4107 			data.dwStateMask = NIS_HIDDEN; // windows vista
4108 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4109 		} else version(OSXCocoa) {
4110 			throw new NotYetImplementedException();
4111 		} else static assert(0);
4112 	}
4113 
4114 	version(X11)
4115 		bool hidden = false;
4116 
4117 	///
4118 	//version(Windows)
4119 	void hide() {
4120 		version(X11) {
4121 			if(hidden)
4122 				return;
4123 			hidden = true;
4124 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
4125 		} else version(Windows) {
4126 			data.uFlags = NIF_STATE;
4127 			data.dwState = NIS_HIDDEN; // windows vista
4128 			data.dwStateMask = NIS_HIDDEN; // windows vista
4129 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4130 		} else version(OSXCocoa) {
4131 			throw new NotYetImplementedException();
4132 		} else static assert(0);
4133 	}
4134 
4135 	///
4136 	void close () {
4137 		version(X11) {
4138 			if (active) {
4139 				active = false; // event handler will set this too, but meh
4140 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
4141 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
4142 				flushGui();
4143 			}
4144 		} else version(Windows) {
4145 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
4146 		} else version(OSXCocoa) {
4147 			throw new NotYetImplementedException();
4148 		} else static assert(0);
4149 	}
4150 
4151 	~this() {
4152 		version(X11)
4153 			if(clippixmap != None)
4154 				XFreePixmap(XDisplayConnection.get, clippixmap);
4155 		close();
4156 	}
4157 }
4158 
4159 version(X11)
4160 /// call XFreePixmap on the return value
4161 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
4162 	char[] data = new char[](i.width * i.height / 8 + 2);
4163 	data[] = 0;
4164 
4165 	int bitOffset = 0;
4166 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
4167 		ubyte v = c.a > 128 ? 1 : 0;
4168 		data[bitOffset / 8] |= v << (bitOffset%8);
4169 		bitOffset++;
4170 	}
4171 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
4172 	return handle;
4173 }
4174 
4175 
4176 // basic functions to make timers
4177 /**
4178 	A timer that will trigger your function on a given interval.
4179 
4180 
4181 	You create a timer with an interval and a callback. It will continue
4182 	to fire on the interval until it is destroyed.
4183 
4184 	There are currently no one-off timers (instead, just create one and
4185 	destroy it when it is triggered) nor are there pause/resume functions -
4186 	the timer must again be destroyed and recreated if you want to pause it.
4187 
4188 	auto timer = new Timer(50, { it happened!; });
4189 	timer.destroy();
4190 
4191 	Timers can only be expected to fire when the event loop is running.
4192 */
4193 version(with_timer) {
4194 class Timer {
4195 // FIXME: needs pause and unpause
4196 	// FIXME: I might add overloads for ones that take a count of
4197 	// how many elapsed since last time (on Windows, it will divide
4198 	// the ticks thing given, on Linux it is just available) and
4199 	// maybe one that takes an instance of the Timer itself too
4200 	/// Create a timer with a callback when it triggers.
4201 	this(int intervalInMilliseconds, void delegate() onPulse) {
4202 		assert(onPulse !is null);
4203 
4204 		this.onPulse = onPulse;
4205 
4206 		version(Windows) {
4207 			/*
4208 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
4209 			if(handle == 0)
4210 				throw new Exception("SetTimer fail");
4211 			*/
4212 
4213 			// thanks to Archival 998 for the WaitableTimer blocks
4214 			handle = CreateWaitableTimer(null, false, null);
4215 			long initialTime = 0;
4216 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
4217 				throw new Exception("SetWaitableTimer Failed");
4218 
4219 			mapping[handle] = this;
4220 
4221 		} else version(linux) {
4222 			static import ep = core.sys.linux.epoll;
4223 
4224 			import core.sys.linux.timerfd;
4225 
4226 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
4227 			if(fd == -1)
4228 				throw new Exception("timer create failed");
4229 
4230 			mapping[fd] = this;
4231 
4232 			itimerspec value;
4233 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
4234 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
4235 
4236 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
4237 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
4238 
4239 			if(timerfd_settime(fd, 0, &value, null) == -1)
4240 				throw new Exception("couldn't make pulse timer");
4241 
4242 			version(with_eventloop) {
4243 				import arsd.eventloop;
4244 				addFileEventListeners(fd, &trigger, null, null);
4245 			} else {
4246 				prepareEventLoop();
4247 
4248 				ep.epoll_event ev = void;
4249 				ev.events = ep.EPOLLIN;
4250 				ev.data.fd = fd;
4251 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
4252 			}
4253 		} else featureNotImplemented();
4254 	}
4255 
4256 	/// Stop and destroy the timer object.
4257 	void destroy() {
4258 		version(Windows) {
4259 			if(handle) {
4260 				// KillTimer(null, handle);
4261 				CancelWaitableTimer(cast(void*)handle);
4262 				mapping.remove(handle);
4263 				CloseHandle(handle);
4264 				handle = null;
4265 			}
4266 		} else version(linux) {
4267 			if(fd != -1) {
4268 				import unix = core.sys.posix.unistd;
4269 				static import ep = core.sys.linux.epoll;
4270 
4271 				version(with_eventloop) {
4272 					import arsd.eventloop;
4273 					removeFileEventListeners(fd);
4274 				} else {
4275 					ep.epoll_event ev = void;
4276 					ev.events = ep.EPOLLIN;
4277 					ev.data.fd = fd;
4278 
4279 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
4280 				}
4281 				unix.close(fd);
4282 				mapping.remove(fd);
4283 				fd = -1;
4284 			}
4285 		} else featureNotImplemented();
4286 	}
4287 
4288 	~this() {
4289 		destroy();
4290 	}
4291 
4292 
4293 	void changeTime(int intervalInMilliseconds)
4294 	{
4295 		version(Windows)
4296 		{
4297 			if(handle)
4298 			{
4299 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
4300 				long initialTime = 0;
4301 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
4302 					throw new Exception("couldn't change pulse timer");
4303 			}
4304 		}
4305 	}
4306 
4307 
4308 	private:
4309 
4310 	void delegate() onPulse;
4311 
4312 	void trigger() {
4313 		version(linux) {
4314 			import unix = core.sys.posix.unistd;
4315 			long val;
4316 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
4317 		} else version(Windows) {
4318 
4319 		} else featureNotImplemented();
4320 
4321 		onPulse();
4322 	}
4323 
4324 	version(Windows)
4325 		extern(Windows)
4326 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
4327 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
4328 			if(Timer* t = timer in mapping) {
4329 				try
4330 				(*t).trigger();
4331 				catch(Exception e) { throw new Error(e.msg, e.file, e.line); }
4332 			}
4333 		}
4334 
4335 	version(Windows) {
4336 		//UINT_PTR handle;
4337 		//static Timer[UINT_PTR] mapping;
4338 		HANDLE handle;
4339 		__gshared Timer[HANDLE] mapping;
4340 	} else version(linux) {
4341 		int fd = -1;
4342 		__gshared Timer[int] mapping;
4343 	} else static assert(0, "timer not supported");
4344 }
4345 }
4346 
4347 version(Windows)
4348 /// 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
4349 class WindowsHandleReader {
4350 	///
4351 	this(void delegate() onReady, HANDLE handle) {
4352 		this.onReady = onReady;
4353 		this.handle = handle;
4354 
4355 		mapping[handle] = this;
4356 
4357 		enable();
4358 	}
4359 
4360 	///
4361 	void enable() {
4362 		auto el = EventLoop.get().impl;
4363 		el.handles ~= handle;
4364 	}
4365 
4366 	///
4367 	void disable() {
4368 		auto el = EventLoop.get().impl;
4369 		for(int i = 0; i < el.handles.length; i++) {
4370 			if(el.handles[i] is handle) {
4371 				el.handles[i] = el.handles[$-1];
4372 				el.handles = el.handles[0 .. $-1];
4373 				return;
4374 			}
4375 		}
4376 	}
4377 
4378 	void dispose() {
4379 		disable();
4380 		mapping.remove(handle);
4381 		handle = null;
4382 	}
4383 
4384 	void ready() {
4385 		if(onReady)
4386 			onReady();
4387 	}
4388 
4389 	HANDLE handle;
4390 	void delegate() onReady;
4391 
4392 	__gshared WindowsHandleReader[HANDLE] mapping;
4393 }
4394 
4395 version(Posix)
4396 /// Lets you add files to the event loop for reading. Use at your own risk.
4397 class PosixFdReader {
4398 	///
4399 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4400 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
4401 	}
4402 
4403 	///
4404 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4405 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
4406 	}
4407 
4408 	///
4409 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4410 		this.onReady = onReady;
4411 		this.fd = fd;
4412 		this.captureWrites = captureWrites;
4413 		this.captureReads = captureReads;
4414 
4415 		mapping[fd] = this;
4416 
4417 		version(with_eventloop) {
4418 			import arsd.eventloop;
4419 			addFileEventListeners(fd, &readyel);
4420 		} else {
4421 			enable();
4422 		}
4423 	}
4424 
4425 	bool captureReads;
4426 	bool captureWrites;
4427 
4428 	version(with_eventloop) {} else
4429 	///
4430 	void enable() {
4431 		prepareEventLoop();
4432 
4433 		enabled = true;
4434 
4435 		version(linux) {
4436 			static import ep = core.sys.linux.epoll;
4437 			ep.epoll_event ev = void;
4438 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
4439 			//import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites);
4440 			ev.data.fd = fd;
4441 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
4442 		} else {
4443 
4444 		}
4445 	}
4446 
4447 	version(with_eventloop) {} else
4448 	///
4449 	void disable() {
4450 		prepareEventLoop();
4451 
4452 		enabled = false;
4453 
4454 		version(linux) {
4455 			static import ep = core.sys.linux.epoll;
4456 			ep.epoll_event ev = void;
4457 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
4458 			//import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites);
4459 			ev.data.fd = fd;
4460 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
4461 		}
4462 	}
4463 
4464 	version(with_eventloop) {} else
4465 	///
4466 	void dispose() {
4467 		disable();
4468 		mapping.remove(fd);
4469 		fd = -1;
4470 	}
4471 
4472 	void delegate(int, bool, bool) onReady;
4473 
4474 	version(with_eventloop)
4475 	void readyel() {
4476 		onReady(fd, true, true);
4477 	}
4478 
4479 	void ready(uint flags) {
4480 		version(linux) {
4481 			static import ep = core.sys.linux.epoll;
4482 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
4483 		} else {
4484 			import core.sys.posix.poll;
4485 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
4486 		}
4487 	}
4488 
4489 	void hup(uint flags) {
4490 		if(onHup)
4491 			onHup();
4492 	}
4493 
4494 	void delegate() onHup;
4495 
4496 	int fd = -1;
4497 	private bool enabled;
4498 	__gshared PosixFdReader[int] mapping;
4499 }
4500 
4501 // basic functions to access the clipboard
4502 /+
4503 
4504 
4505 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
4506 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
4507 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
4508 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
4509 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
4510 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
4511 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
4512 
4513 +/
4514 
4515 /++
4516 	this does a delegate because it is actually an async call on X...
4517 	the receiver may never be called if the clipboard is empty or unavailable
4518 	gets plain text from the clipboard
4519 +/
4520 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
4521 	version(Windows) {
4522 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
4523 		if(OpenClipboard(hwndOwner) == 0)
4524 			throw new Exception("OpenClipboard");
4525 		scope(exit)
4526 			CloseClipboard();
4527 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
4528 
4529 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
4530 				scope(exit)
4531 					GlobalUnlock(dataHandle);
4532 
4533 				// FIXME: CR/LF conversions
4534 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
4535 				int len = 0;
4536 				auto d = data;
4537 				while(*d) {
4538 					d++;
4539 					len++;
4540 				}
4541 				string s;
4542 				s.reserve(len);
4543 				foreach(dchar ch; data[0 .. len]) {
4544 					s ~= ch;
4545 				}
4546 				receiver(s);
4547 			}
4548 		}
4549 	} else version(X11) {
4550 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
4551 	} else version(OSXCocoa) {
4552 		throw new NotYetImplementedException();
4553 	} else static assert(0);
4554 }
4555 
4556 version(Windows)
4557 struct WCharzBuffer {
4558 	wchar[256] staticBuffer;
4559 	wchar[] buffer;
4560 
4561 	size_t length() {
4562 		return buffer.length;
4563 	}
4564 
4565 	wchar* ptr() {
4566 		return buffer.ptr;
4567 	}
4568 
4569 	wchar[] slice() {
4570 		return buffer;
4571 	}
4572 
4573 	void copyInto(R)(ref R r) {
4574 		static if(is(R == wchar[N], size_t N)) {
4575 			r[0 .. this.length] = slice[];
4576 			r[this.length] = 0;
4577 		} else static assert(0, "can only copy into wchar[n], not " ~ R.stringof);
4578 	}
4579 
4580 	/++
4581 		conversionFlags = [WindowsStringConversionFlags]
4582 	+/
4583 	this(in char[] data, int conversionFlags = 0) {
4584 		conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name
4585 		auto sz = sizeOfConvertedWstring(data, conversionFlags);
4586 		if(sz > staticBuffer.length)
4587 			buffer = new wchar[](sz);
4588 		else
4589 			buffer = staticBuffer[];
4590 
4591 		buffer = makeWindowsString(data, buffer, conversionFlags);
4592 	}
4593 }
4594 
4595 version(Windows)
4596 int sizeOfConvertedWstring(in char[] s, int conversionFlags) {
4597 	int size = 0;
4598 
4599 	if(conversionFlags & WindowsStringConversionFlags.convertNewLines) {
4600 		// need to convert line endings, which means the length will get bigger.
4601 
4602 		// BTW I betcha this could be faster with some simd stuff.
4603 		char last;
4604 		foreach(char ch; s) {
4605 			if(ch == 10 && last != 13)
4606 				size++; // will add a 13 before it...
4607 			size++;
4608 			last = ch;
4609 		}
4610 	} else {
4611 		// no conversion necessary, just estimate based on length
4612 		/*
4613 			I don't think there's any string with a longer length
4614 			in code units when encoded in UTF-16 than it has in UTF-8.
4615 			This will probably over allocate, but that's OK.
4616 		*/
4617 		size = cast(int) s.length;
4618 	}
4619 
4620 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate)
4621 		size++;
4622 
4623 	return size;
4624 }
4625 
4626 version(Windows)
4627 enum WindowsStringConversionFlags : int {
4628 	zeroTerminate = 1,
4629 	convertNewLines = 2,
4630 }
4631 
4632 version(Windows)
4633 class WindowsApiException : Exception {
4634 	char[256] buffer;
4635 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
4636 		assert(msg.length < 100);
4637 
4638 		auto error = GetLastError();
4639 		buffer[0 .. msg.length] = msg;
4640 		buffer[msg.length] = ' ';
4641 
4642 		int pos = cast(int) msg.length + 1;
4643 
4644 		if(error == 0)
4645 			buffer[pos++] = '0';
4646 		else {
4647 			auto init = pos;
4648 			while(error) {
4649 				buffer[pos++] = (error % 10) + '0';
4650 				error /= 10;
4651 			}
4652 			for(int i = 0; i < (pos - init) / 2; i++) {
4653 				char c = buffer[i + init];
4654 				buffer[i + init] = buffer[pos - (i + init) - 1];
4655 				buffer[pos - (i + init) - 1] = c;
4656 			}
4657 		}
4658 
4659 
4660 		super(cast(string) buffer[0 .. pos], file, line, next);
4661 	}
4662 }
4663 
4664 class ErrnoApiException : Exception {
4665 	char[256] buffer;
4666 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
4667 		assert(msg.length < 100);
4668 
4669 		import core.stdc.errno;
4670 		auto error = errno;
4671 		buffer[0 .. msg.length] = msg;
4672 		buffer[msg.length] = ' ';
4673 
4674 		int pos = cast(int) msg.length + 1;
4675 
4676 		if(error == 0)
4677 			buffer[pos++] = '0';
4678 		else {
4679 			auto init = pos;
4680 			while(error) {
4681 				buffer[pos++] = (error % 10) + '0';
4682 				error /= 10;
4683 			}
4684 			for(int i = 0; i < (pos - init) / 2; i++) {
4685 				char c = buffer[i + init];
4686 				buffer[i + init] = buffer[pos - (i + init) - 1];
4687 				buffer[pos - (i + init) - 1] = c;
4688 			}
4689 		}
4690 
4691 
4692 		super(cast(string) buffer[0 .. pos], file, line, next);
4693 	}
4694 
4695 }
4696 
4697 version(Windows)
4698 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) {
4699 	if(str.length == 0)
4700 		return null;
4701 
4702 	int pos = 0;
4703 	dchar last;
4704 	foreach(dchar c; str) {
4705 		if(c <= 0xFFFF) {
4706 			if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13)
4707 				buffer[pos++] = 13;
4708 			buffer[pos++] = cast(wchar) c;
4709 		} else if(c <= 0x10FFFF) {
4710 			buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
4711 			buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
4712 		}
4713 
4714 		last = c;
4715 	}
4716 
4717 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) {
4718 		buffer[pos] = 0;
4719 	}
4720 
4721 	return buffer[0 .. pos];
4722 }
4723 
4724 version(Windows)
4725 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
4726 	if(str.length == 0)
4727 		return null;
4728 
4729 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null);
4730 	if(got == 0) {
4731 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
4732 			throw new Exception("not enough buffer");
4733 		else
4734 			throw new Exception("conversion"); // FIXME: GetLastError
4735 	}
4736 	return buffer[0 .. got];
4737 }
4738 
4739 version(Windows)
4740 string makeUtf8StringFromWindowsString(in wchar[] str) {
4741 	char[] buffer;
4742 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null);
4743 	buffer.length = got;
4744 
4745 	// it is unique because we just allocated it above!
4746 	return cast(string) makeUtf8StringFromWindowsString(str, buffer);
4747 }
4748 
4749 version(Windows)
4750 string makeUtf8StringFromWindowsString(wchar* str) {
4751 	char[] buffer;
4752 	auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null);
4753 	buffer.length = got;
4754 
4755 	got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null);
4756 	if(got == 0) {
4757 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
4758 			throw new Exception("not enough buffer");
4759 		else
4760 			throw new Exception("conversion"); // FIXME: GetLastError
4761 	}
4762 	return cast(string) buffer[0 .. got];
4763 }
4764 
4765 int findIndexOfZero(in wchar[] str) {
4766 	foreach(idx, wchar ch; str)
4767 		if(ch == 0)
4768 			return cast(int) idx;
4769 	return cast(int) str.length;
4770 }
4771 int findIndexOfZero(in char[] str) {
4772 	foreach(idx, char ch; str)
4773 		if(ch == 0)
4774 			return cast(int) idx;
4775 	return cast(int) str.length;
4776 }
4777 
4778 /// copies some text to the clipboard
4779 void setClipboardText(SimpleWindow clipboardOwner, string text) {
4780 	assert(clipboardOwner !is null);
4781 	version(Windows) {
4782 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
4783 			throw new Exception("OpenClipboard");
4784 		scope(exit)
4785 			CloseClipboard();
4786 		EmptyClipboard();
4787 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
4788 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
4789 		if(handle is null) throw new Exception("GlobalAlloc");
4790 		if(auto data = cast(wchar*) GlobalLock(handle)) {
4791 			auto slice = data[0 .. sz];
4792 			scope(failure)
4793 				GlobalUnlock(handle);
4794 
4795 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
4796 
4797 			GlobalUnlock(handle);
4798 			SetClipboardData(CF_UNICODETEXT, handle);
4799 		}
4800 	} else version(X11) {
4801 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
4802 	} else version(OSXCocoa) {
4803 		throw new NotYetImplementedException();
4804 	} else static assert(0);
4805 }
4806 
4807 // FIXME: functions for doing images would be nice too - CF_DIB and whatever it is on X would be ok if we took the MemoryImage from color.d, or an Image from here. hell it might even be a variadic template that sets all the formats in one call. that might be cool.
4808 
4809 version(X11) {
4810 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
4811 
4812 	private Atom*[] interredAtoms; // for discardAndRecreate
4813 
4814 	/// Platform specific for X11
4815 	@property Atom GetAtom(string name, bool create = false)(Display* display) {
4816 		static Atom a;
4817 		if(!a) {
4818 			a = XInternAtom(display, name, !create);
4819 			interredAtoms ~= &a;
4820 		}
4821 		if(a == None)
4822 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
4823 		return a;
4824 	}
4825 
4826 	/// Platform specific for X11 - gets atom names as a string
4827 	string getAtomName(Atom atom, Display* display) {
4828 		auto got = XGetAtomName(display, atom);
4829 		scope(exit) XFree(got);
4830 		import core.stdc.string;
4831 		string s = got[0 .. strlen(got)].idup;
4832 		return s;
4833 	}
4834 
4835 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later
4836 	void setPrimarySelection(SimpleWindow window, string text) {
4837 		setX11Selection!"PRIMARY"(window, text);
4838 	}
4839 
4840 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later
4841 	void setSecondarySelection(SimpleWindow window, string text) {
4842 		setX11Selection!"SECONDARY"(window, text);
4843 	}
4844 
4845 	/// The `after` delegate is called after a client requests the UTF8_STRING thing. it is a mega-hack right now!
4846 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
4847 		assert(window !is null);
4848 
4849 		auto display = XDisplayConnection.get();
4850 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
4851 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
4852 		else Atom a = GetAtom!atomName(display);
4853 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
4854 		window.impl.setSelectionHandler = (XEvent ev) {
4855 			XSelectionRequestEvent* event = &ev.xselectionrequest;
4856 			XSelectionEvent selectionEvent;
4857 			selectionEvent.type = EventType.SelectionNotify;
4858 			selectionEvent.display = event.display;
4859 			selectionEvent.requestor = event.requestor;
4860 			selectionEvent.selection = event.selection;
4861 			selectionEvent.time = event.time;
4862 			selectionEvent.target = event.target;
4863 
4864 			if(event.property == None)
4865 				selectionEvent.property = event.target;
4866 			if(event.target == GetAtom!"TARGETS"(display)) {
4867 				/* respond with the supported types */
4868 				Atom[3] tlist;// = [XA_UTF8, XA_STRING, XA_TARGETS];
4869 				tlist[0] = GetAtom!"UTF8_STRING"(display);
4870 				tlist[1] = XA_STRING;
4871 				tlist[2] = GetAtom!"TARGETS"(display);
4872 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, 3);
4873 				selectionEvent.property = event.property;
4874 			} else if(event.target == XA_STRING) {
4875 				selectionEvent.property = event.property;
4876 				XChangeProperty (display,
4877 					selectionEvent.requestor,
4878 					selectionEvent.property,
4879 					event.target,
4880 					8 /* bits */, 0 /* PropModeReplace */,
4881 					text.ptr, cast(int) text.length);
4882 			} else if(event.target == GetAtom!"UTF8_STRING"(display)) {
4883 				selectionEvent.property = event.property;
4884 				XChangeProperty (display,
4885 					selectionEvent.requestor,
4886 					selectionEvent.property,
4887 					event.target,
4888 					8 /* bits */, 0 /* PropModeReplace */,
4889 					text.ptr, cast(int) text.length);
4890 
4891 				if(after)
4892 					after();
4893 			} else {
4894 				selectionEvent.property = None; // I don't know how to handle this type...
4895 			}
4896 
4897 			XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
4898 		};
4899 	}
4900 
4901 	///
4902 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
4903 		getX11Selection!"PRIMARY"(window, handler);
4904 	}
4905 
4906 	///
4907 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler) {
4908 		assert(window !is null);
4909 
4910 		auto display = XDisplayConnection.get();
4911 		auto atom = GetAtom!atomName(display);
4912 
4913 		window.impl.getSelectionHandler = handler;
4914 
4915 		auto target = GetAtom!"TARGETS"(display);
4916 
4917 		// SDD_DATA is "simpledisplay.d data"
4918 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
4919 	}
4920 
4921 	///
4922 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
4923 		Atom actualType;
4924 		int actualFormat;
4925 		arch_ulong actualItems;
4926 		arch_ulong bytesRemaining;
4927 		void* data;
4928 
4929 		auto display = XDisplayConnection.get();
4930 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
4931 			if(actualFormat == 0)
4932 				return null;
4933 			else {
4934 				int byteLength;
4935 				if(actualFormat == 32) {
4936 					// 32 means it is a C long... which is variable length
4937 					actualFormat = cast(int) arch_long.sizeof * 8;
4938 				}
4939 
4940 				// then it is just a bit count
4941 				byteLength = cast(int) (actualItems * actualFormat / 8);
4942 
4943 				auto d = new ubyte[](byteLength);
4944 				d[] = cast(ubyte[]) data[0 .. byteLength];
4945 				XFree(data);
4946 				return d;
4947 			}
4948 		}
4949 		return null;
4950 	}
4951 
4952 	/* defined in the systray spec */
4953 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
4954 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
4955 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
4956 
4957 
4958 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
4959 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
4960 	public class GlobalHotkey {
4961 		KeyEvent key;
4962 		void delegate () handler;
4963 
4964 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
4965 
4966 		/// Create from initialzed KeyEvent object
4967 		this (KeyEvent akey, void delegate () ahandler=null) {
4968 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
4969 			key = akey;
4970 			handler = ahandler;
4971 		}
4972 
4973 		/// Create from emacs-like key name ("C-M-Y", etc.)
4974 		this (const(char)[] akey, void delegate () ahandler=null) {
4975 			key = KeyEvent.parse(akey);
4976 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
4977 			handler = ahandler;
4978 		}
4979 
4980 	}
4981 
4982 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
4983 		//conwriteln("failed to grab key");
4984 		GlobalHotkeyManager.ghfailed = true;
4985 		return 0;
4986 	}
4987 
4988 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
4989 		import core.stdc.stdio;
4990 		char[265] buffer;
4991 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
4992 		printf("ERROR: %s\n", buffer.ptr);
4993 		return 0;
4994 	}
4995 
4996 	/++
4997 		Global hotkey manager. It contains static methods to manage global hotkeys.
4998 
4999 		---
5000 		 try {
5001 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
5002 		} catch (Exception e) {
5003 			conwriteln("ERROR registering hotkey!");
5004 		}
5005 		---
5006 
5007 		The key strings are based on Emacs. In practical terms,
5008 		`M` means `alt` and `H` means the Windows logo key. `C`
5009 		is `ctrl`.
5010 
5011 		$(WARNING
5012 			This is X-specific right now. If you are on
5013 			Windows, try [registerHotKey] instead.
5014 
5015 			We will probably merge these into a single
5016 			interface later.
5017 		)
5018 	+/
5019 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
5020 		version(X11) {
5021 			void recreateAfterDisconnect() {
5022 				throw new Exception("NOT IMPLEMENTED");
5023 			}
5024 			void discardConnectionState() {
5025 				throw new Exception("NOT IMPLEMENTED");
5026 			}
5027 		}
5028 
5029 		private static immutable uint[8] masklist = [ 0,
5030 			KeyOrButtonMask.LockMask,
5031 			KeyOrButtonMask.Mod2Mask,
5032 			KeyOrButtonMask.Mod3Mask,
5033 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
5034 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
5035 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
5036 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
5037 		];
5038 		private __gshared GlobalHotkeyManager ghmanager;
5039 		private __gshared bool ghfailed = false;
5040 
5041 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
5042 			if (modmask == 0) return false;
5043 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
5044 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
5045 			return true;
5046 		}
5047 
5048 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
5049 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
5050 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
5051 			return modmask;
5052 		}
5053 
5054 		private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) {
5055 			uint keycode = cast(uint)ke.key;
5056 			auto dpy = XDisplayConnection.get;
5057 			return XKeysymToKeycode(dpy, keycode);
5058 		}
5059 
5060 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
5061 
5062 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
5063 
5064 		NativeEventHandler getNativeEventHandler () {
5065 			return delegate int (XEvent e) {
5066 				if (e.type != EventType.KeyPress) return 1;
5067 				auto kev = cast(const(XKeyEvent)*)&e;
5068 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
5069 				if (auto ghkp = hash in globalHotkeyList) {
5070 					try {
5071 						ghkp.doHandle();
5072 					} catch (Exception e) {
5073 						import core.stdc.stdio : stderr, fprintf;
5074 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
5075 					}
5076 				}
5077 				return 1;
5078 			};
5079 		}
5080 
5081 		private this () {
5082 			auto dpy = XDisplayConnection.get;
5083 			auto root = RootWindow(dpy, DefaultScreen(dpy));
5084 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
5085 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
5086 		}
5087 
5088 		/// Register new global hotkey with initialized `GlobalHotkey` object.
5089 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
5090 		static void register (GlobalHotkey gh) {
5091 			if (gh is null) return;
5092 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
5093 
5094 			auto dpy = XDisplayConnection.get;
5095 			immutable keycode = keyEvent2KeyCode(gh.key);
5096 
5097 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
5098 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
5099 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
5100 			XSync(dpy, 0/*False*/);
5101 
5102 			Window root = RootWindow(dpy, DefaultScreen(dpy));
5103 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
5104 			ghfailed = false;
5105 			foreach (immutable uint ormask; masklist[]) {
5106 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
5107 			}
5108 			XSync(dpy, 0/*False*/);
5109 			XSetErrorHandler(savedErrorHandler);
5110 
5111 			if (ghfailed) {
5112 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
5113 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
5114 				XSync(dpy, 0/*False*/);
5115 				XSetErrorHandler(savedErrorHandler);
5116 				throw new Exception("cannot register global hotkey");
5117 			}
5118 
5119 			globalHotkeyList[hash] = gh;
5120 		}
5121 
5122 		/// Ditto
5123 		static void register (const(char)[] akey, void delegate () ahandler) {
5124 			register(new GlobalHotkey(akey, ahandler));
5125 		}
5126 
5127 		private static void removeByHash (ulong hash) {
5128 			if (auto ghp = hash in globalHotkeyList) {
5129 				auto dpy = XDisplayConnection.get;
5130 				immutable keycode = keyEvent2KeyCode(ghp.key);
5131 				Window root = RootWindow(dpy, DefaultScreen(dpy));
5132 				XSync(dpy, 0/*False*/);
5133 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
5134 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
5135 				XSync(dpy, 0/*False*/);
5136 				XSetErrorHandler(savedErrorHandler);
5137 				globalHotkeyList.remove(hash);
5138 			}
5139 		}
5140 
5141 		/// Register new global hotkey with previously used `GlobalHotkey` object.
5142 		/// It is safe to unregister unknown or invalid hotkey.
5143 		static void unregister (GlobalHotkey gh) {
5144 			//TODO: add second AA for faster search? prolly doesn't worth it.
5145 			if (gh is null) return;
5146 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
5147 				if (kv.value is gh) {
5148 					removeByHash(kv.key);
5149 					return;
5150 				}
5151 			}
5152 		}
5153 
5154 		/// Ditto.
5155 		static void unregister (const(char)[] key) {
5156 			auto kev = KeyEvent.parse(key);
5157 			immutable keycode = keyEvent2KeyCode(kev);
5158 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
5159 		}
5160 	}
5161 }
5162 
5163 version(Windows) {
5164 	/// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application)
5165 	void sendSyntheticInput(wstring s) {
5166 		INPUT[] inputs;
5167 		inputs.reserve(s.length * 2);
5168 
5169 		foreach(wchar c; s) {
5170 			INPUT input;
5171 			input.type = INPUT_KEYBOARD;
5172 			input.ki.wScan = c;
5173 			input.ki.dwFlags = KEYEVENTF_UNICODE;
5174 			inputs ~= input;
5175 
5176 			input.ki.dwFlags |= KEYEVENTF_KEYUP;
5177 			inputs ~= input;
5178 		}
5179 
5180 		if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
5181 			throw new Exception("SendInput failed");
5182 		}
5183 	}
5184 
5185 
5186 	// global hotkey helper function
5187 
5188 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID.
5189 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
5190 		__gshared int hotkeyId = 0;
5191 		int id = ++hotkeyId;
5192 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
5193 			throw new Exception("RegisterHotKey failed");
5194 
5195 		__gshared void delegate()[WPARAM][HWND] handlers;
5196 
5197 		handlers[window.impl.hwnd][id] = handler;
5198 
5199 		int delegate(HWND, UINT, WPARAM, LPARAM) oldHandler;
5200 
5201 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5202 			switch(msg) {
5203 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
5204 				case WM_HOTKEY:
5205 					if(auto list = hwnd in handlers) {
5206 						if(auto h = wParam in *list) {
5207 							(*h)();
5208 							return 0;
5209 						}
5210 					}
5211 				goto default;
5212 				default:
5213 			}
5214 			if(oldHandler)
5215 				return oldHandler(hwnd, msg, wParam, lParam);
5216 			return 1; // pass it on
5217 		};
5218 
5219 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
5220 			oldHandler = window.handleNativeEvent;
5221 			window.handleNativeEvent = nativeEventHandler;
5222 		}
5223 
5224 		return id;
5225 	}
5226 
5227 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by registerHotKey.
5228 	void unregisterHotKey(SimpleWindow window, int id) {
5229 		if(!UnregisterHotKey(window.impl.hwnd, id))
5230 			throw new Exception("UnregisterHotKey");
5231 	}
5232 }
5233 
5234 version (X11) {
5235 	pragma(lib, "dl");
5236 	import core.sys.posix.dlfcn;
5237 
5238 	/++
5239 		Allows for sending synthetic input to the X server via the Xtst
5240 		extension.
5241 
5242 		Please remember user input is meant to be user - don't use this
5243 		if you have some other alternative!
5244 
5245 		If you need this on Windows btw, the top-level [sendSyntheticInput] shows
5246 		the Win32 api to start it, but I only did basics there, PR welcome if you like,
5247 		it is an easy enough function to use.
5248 
5249 		History: Added May 17, 2020.
5250 	+/
5251 	struct SyntheticInput {
5252 		@disable this();
5253 
5254 		private void* lib;
5255 		private int* refcount;
5256 
5257 		private extern(C) {
5258 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
5259 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
5260 		}
5261 
5262 		/// The dummy param must be 0.
5263 		this(int dummy) {
5264 			lib = dlopen("libXtst.so", RTLD_NOW);
5265 			if(lib is null)
5266 				throw new Exception("cannot load xtest lib extension");
5267 			scope(failure)
5268 				dlclose(lib);
5269 
5270 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
5271 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
5272 
5273 			if(XTestFakeKeyEvent is null)
5274 				throw new Exception("No XTestFakeKeyEvent");
5275 			if(XTestFakeButtonEvent is null)
5276 				throw new Exception("No XTestFakeButtonEvent");
5277 
5278 			refcount = new int;
5279 			*refcount = 1;
5280 		}
5281 
5282 		this(this) {
5283 			if(refcount)
5284 				*refcount += 1;
5285 		}
5286 
5287 		~this() {
5288 			if(refcount) {
5289 				*refcount -= 1;
5290 				if(*refcount == 0)
5291 					// I commented this because if I close the lib before
5292 					// XCloseDisplay, it is liable to segfault... so just
5293 					// gonna keep it loaded if it is loaded, no big deal
5294 					// anyway.
5295 					{} // dlclose(lib);
5296 			}
5297 		}
5298 
5299 		/// This ONLY works with basic ascii!
5300 		void sendSyntheticInput(string s) {
5301 			int delay = 0;
5302 			foreach(ch; s) {
5303 				pressKey(cast(Key) ch, true, delay);
5304 				pressKey(cast(Key) ch, false, delay);
5305 				delay += 5;
5306 			}
5307 		}
5308 
5309 		///
5310 		void pressKey(Key key, bool pressed, int delay = 0) {
5311 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
5312 		}
5313 
5314 		///
5315 		void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
5316 			int btn;
5317 
5318 			switch(button) {
5319 				case MouseButton.left: btn = 1; break;
5320 				case MouseButton.middle: btn = 2; break;
5321 				case MouseButton.right: btn = 3; break;
5322 				case MouseButton.wheelUp: btn = 4; break;
5323 				case MouseButton.wheelDown: btn = 5; break;
5324 				case MouseButton.backButton: btn = 8; break;
5325 				case MouseButton.forwardButton: btn = 9; break;
5326 				default:
5327 			}
5328 
5329 			assert(btn);
5330 
5331 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
5332 		}
5333 
5334 		///
5335 		static void moveMouseArrowBy(int dx, int dy) {
5336 			auto disp = XDisplayConnection.get();
5337 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
5338 			XFlush(disp);
5339 		}
5340 
5341 		///
5342 		static void moveMouseArrowTo(int x, int y) {
5343 			auto disp = XDisplayConnection.get();
5344 			auto root = RootWindow(disp, DefaultScreen(disp));
5345 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
5346 			XFlush(disp);
5347 		}
5348 	}
5349 }
5350 
5351 
5352 
5353 /++
5354 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
5355 
5356 	See_Also:
5357 	$(LIST
5358 		*[ScreenPainter]
5359 		*[ScreenPainter.rasterOp]
5360 	)
5361 +/
5362 enum RasterOp {
5363 	normal, /// Replaces the pixel.
5364 	xor, /// Uses bitwise xor to draw.
5365 }
5366 
5367 // being phobos-free keeps the size WAY down
5368 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
5369 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
5370 package(arsd) const(wchar)* toWStringz(string s) {
5371 	wstring r;
5372 	foreach(dchar c; s)
5373 		r ~= c;
5374 	r ~= '\0';
5375 	return r.ptr;
5376 }
5377 private string[] split(in void[] a, char c) {
5378 		string[] ret;
5379 		size_t previous = 0;
5380 		foreach(i, char ch; cast(ubyte[]) a) {
5381 			if(ch == c) {
5382 				ret ~= cast(string) a[previous .. i];
5383 				previous = i + 1;
5384 			}
5385 		}
5386 		if(previous != a.length)
5387 			ret ~= cast(string) a[previous .. $];
5388 		return ret;
5389 	}
5390 
5391 version(without_opengl) {
5392 	enum OpenGlOptions {
5393 		no,
5394 	}
5395 } else {
5396 	/++
5397 		Determines if you want an OpenGL context created on the new window.
5398 
5399 
5400 		See more: [#topics-3d|in the 3d topic].
5401 
5402 		---
5403 		import arsd.simpledisplay;
5404 		void main() {
5405 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
5406 
5407 			// Set up the matrix
5408 			window.setAsCurrentOpenGlContext(); // make this window active
5409 
5410 			// This is called on each frame, we will draw our scene
5411 			window.redrawOpenGlScene = delegate() {
5412 
5413 			};
5414 
5415 			window.eventLoop(0);
5416 		}
5417 		---
5418 	+/
5419 	enum OpenGlOptions {
5420 		no, /// No OpenGL context is created
5421 		yes, /// Yes, create an OpenGL context
5422 	}
5423 
5424 	version(X11) {
5425 		static if (!SdpyIsUsingIVGLBinds) {
5426 
5427 
5428 			struct __GLXFBConfigRec {}
5429 			alias GLXFBConfig = __GLXFBConfigRec*;
5430 
5431 			//pragma(lib, "GL");
5432 			//pragma(lib, "GLU");
5433 			interface GLX {
5434 			extern(C) nothrow @nogc {
5435 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
5436 						const int *attrib_list);
5437 
5438 				 void glXCopyContext(Display *dpy, GLXContext src,
5439 						GLXContext dst, arch_ulong mask);
5440 
5441 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
5442 						GLXContext share_list, Bool direct);
5443 
5444 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
5445 						Pixmap pixmap);
5446 
5447 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
5448 
5449 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
5450 
5451 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
5452 						int attrib, int *value);
5453 
5454 				 GLXContext glXGetCurrentContext();
5455 
5456 				 GLXDrawable glXGetCurrentDrawable();
5457 
5458 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
5459 
5460 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
5461 						GLXContext ctx);
5462 
5463 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
5464 
5465 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
5466 
5467 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
5468 
5469 				 void glXUseXFont(Font font, int first, int count, int list_base);
5470 
5471 				 void glXWaitGL();
5472 
5473 				 void glXWaitX();
5474 
5475 
5476 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
5477 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
5478 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
5479 
5480 				char* glXQueryExtensionsString (Display*, int);
5481 				void* glXGetProcAddress (const(char)*);
5482 
5483 			}
5484 			}
5485 
5486 			version(OSX)
5487 			mixin DynamicLoad!(GLX, "GL", true) glx;
5488 			else
5489 			mixin DynamicLoad!(GLX, "GLX", true) glx;
5490 			shared static this() {
5491 				glx.loadDynamicLibrary();
5492 			}
5493 
5494 			alias glbindGetProcAddress = glXGetProcAddress;
5495 		}
5496 	} else version(Windows) {
5497 		/* it is done below by interface GL */
5498 	} else
5499 		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.");
5500 }
5501 
5502 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
5503 alias Resizablity = Resizability;
5504 
5505 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
5506 enum Resizability {
5507 	fixedSize, /// the window cannot be resized
5508 	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.
5509 	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.
5510 
5511 	// FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events
5512 }
5513 
5514 
5515 /++
5516 	Alignment for $(ScreenPainter.drawText). Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
5517 +/
5518 enum TextAlignment : uint {
5519 	Left = 0, ///
5520 	Center = 1, ///
5521 	Right = 2, ///
5522 
5523 	VerticalTop = 0, ///
5524 	VerticalCenter = 4, ///
5525 	VerticalBottom = 8, ///
5526 }
5527 
5528 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
5529 alias Rectangle = arsd.color.Rectangle;
5530 
5531 
5532 /++
5533 	Keyboard press and release events
5534 +/
5535 struct KeyEvent {
5536 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
5537 	Key key;
5538 	ubyte hardwareCode; /// A platform and hardware specific code for the key
5539 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
5540 
5541 	dchar character; ///
5542 
5543 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
5544 
5545 	SimpleWindow window; /// associated Window
5546 
5547 	// convert key event to simplified string representation a-la emacs
5548 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
5549 		uint dpos = 0;
5550 		void put (const(char)[] s...) nothrow @trusted {
5551 			static if (growdest) {
5552 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
5553 			} else {
5554 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
5555 			}
5556 		}
5557 
5558 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
5559 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
5560 		}
5561 
5562 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
5563 
5564 		// put modifiers
5565 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
5566 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
5567 		putMod(ModifierState.alt, Key.Alt, "Alt+");
5568 		putMod(ModifierState.windows, Key.Shift, "Windows+");
5569 		putMod(ModifierState.shift, Key.Shift, "Shift+");
5570 
5571 		if (this.key) {
5572 			foreach (string kn; __traits(allMembers, Key)) {
5573 				if (this.key == __traits(getMember, Key, kn)) {
5574 					// HACK!
5575 					static if (kn == "N0") put("0");
5576 					else static if (kn == "N1") put("1");
5577 					else static if (kn == "N2") put("2");
5578 					else static if (kn == "N3") put("3");
5579 					else static if (kn == "N4") put("4");
5580 					else static if (kn == "N5") put("5");
5581 					else static if (kn == "N6") put("6");
5582 					else static if (kn == "N7") put("7");
5583 					else static if (kn == "N8") put("8");
5584 					else static if (kn == "N9") put("9");
5585 					else put(kn);
5586 					return dest[0..dpos];
5587 				}
5588 			}
5589 			put("Unknown");
5590 		} else {
5591 			if (dpos && dest[dpos-1] == '+') --dpos;
5592 		}
5593 		return dest[0..dpos];
5594 	}
5595 
5596 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
5597 
5598 	/** Parse string into key name with modifiers. It accepts things like:
5599 	 *
5600 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
5601 	 *
5602 	 * Ctrl+Win+1 -- windows style
5603 	 *
5604 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
5605 	 *
5606 	 * Ctrl Win 1 -- and space
5607 	 *
5608 	 * and even "Win + 1 + Ctrl".
5609 	 */
5610 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
5611 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
5612 
5613 		// remove trailing spaces
5614 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
5615 
5616 		// tokens delimited by blank, '+', or '-'
5617 		// null on eol
5618 		const(char)[] getToken () nothrow @trusted @nogc {
5619 			// remove leading spaces and delimiters
5620 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
5621 			if (name.length == 0) return null; // oops, no more tokens
5622 			// get token
5623 			size_t epos = 0;
5624 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
5625 			assert(epos > 0 && epos <= name.length);
5626 			auto res = name[0..epos];
5627 			name = name[epos..$];
5628 			return res;
5629 		}
5630 
5631 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
5632 			if (s0.length != s1.length) return false;
5633 			foreach (immutable ci, char c0; s0) {
5634 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
5635 				char c1 = s1[ci];
5636 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
5637 				if (c0 != c1) return false;
5638 			}
5639 			return true;
5640 		}
5641 
5642 		if (ignoreModsOut !is null) *ignoreModsOut = false;
5643 		if (updown !is null) *updown = -1;
5644 		KeyEvent res;
5645 		res.key = cast(Key)0; // just in case
5646 		const(char)[] tk, tkn; // last token
5647 		bool allowEmascStyle = true;
5648 		bool ignoreModifiers = false;
5649 		tokenloop: for (;;) {
5650 			tk = tkn;
5651 			tkn = getToken();
5652 			//k8: yay, i took "Bloody Mess" trait from Fallout!
5653 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
5654 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
5655 			if (allowEmascStyle && tkn.length != 0) {
5656 				if (tk.length == 1) {
5657 					char mdc = tk[0];
5658 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
5659 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
5660 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
5661 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
5662 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
5663 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
5664 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
5665 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
5666 				}
5667 			}
5668 			allowEmascStyle = false;
5669 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
5670 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
5671 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
5672 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
5673 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
5674 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
5675 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
5676 			if (tk.length == 0) continue;
5677 			// try key name
5678 			if (res.key == 0) {
5679 				// little hack
5680 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
5681 					final switch (tk[0]) {
5682 						case '0': tk = "N0"; break;
5683 						case '1': tk = "N1"; break;
5684 						case '2': tk = "N2"; break;
5685 						case '3': tk = "N3"; break;
5686 						case '4': tk = "N4"; break;
5687 						case '5': tk = "N5"; break;
5688 						case '6': tk = "N6"; break;
5689 						case '7': tk = "N7"; break;
5690 						case '8': tk = "N8"; break;
5691 						case '9': tk = "N9"; break;
5692 					}
5693 				}
5694 				foreach (string kn; __traits(allMembers, Key)) {
5695 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
5696 				}
5697 			}
5698 			// unknown or duplicate key name, get out of here
5699 			break;
5700 		}
5701 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
5702 		return res; // something
5703 	}
5704 
5705 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
5706 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
5707 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
5708 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
5709 		}
5710 		bool ignoreMods;
5711 		int updown;
5712 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
5713 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
5714 		if (this.key != ke.key) {
5715 			// things like "ctrl+alt" are complicated
5716 			uint tkm = this.modifierState&modmask;
5717 			uint kkm = ke.modifierState&modmask;
5718 			Key tk = this.key;
5719 			// ke
5720 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
5721 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
5722 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
5723 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
5724 			// this
5725 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
5726 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
5727 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
5728 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
5729 			return (tk == ke.key && tkm == kkm);
5730 		}
5731 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
5732 	}
5733 }
5734 
5735 /// sets the application name.
5736 @property string ApplicationName(string name) {
5737 	return _applicationName = name;
5738 }
5739 
5740 string _applicationName;
5741 
5742 /// ditto
5743 @property string ApplicationName() {
5744 	if(_applicationName is null) {
5745 		import core.runtime;
5746 		return Runtime.args[0];
5747 	}
5748 	return _applicationName;
5749 }
5750 
5751 
5752 /// Type of a [MouseEvent]
5753 enum MouseEventType : int {
5754 	motion = 0, /// The mouse moved inside the window
5755 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
5756 	buttonReleased = 2, /// A mouse button was released
5757 }
5758 
5759 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
5760 /++
5761 	Listen for this on your event listeners if you are interested in mouse action.
5762 
5763 	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.
5764 
5765 	Examples:
5766 
5767 	This will draw boxes on the window with the mouse as you hold the left button.
5768 	---
5769 	import arsd.simpledisplay;
5770 
5771 	void main() {
5772 		auto window = new SimpleWindow();
5773 
5774 		window.eventLoop(0,
5775 			(MouseEvent ev) {
5776 				if(ev.modifierState & ModifierState.leftButtonDown) {
5777 					auto painter = window.draw();
5778 					painter.fillColor = Color.red;
5779 					painter.outlineColor = Color.black;
5780 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
5781 				}
5782 			}
5783 		);
5784 	}
5785 	---
5786 +/
5787 struct MouseEvent {
5788 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
5789 
5790 	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.
5791 	int y; /// Current Y position of the cursor when the event fired.
5792 
5793 	int dx; /// Change in X position since last report
5794 	int dy; /// Change in Y position since last report
5795 
5796 	MouseButton button; /// See [MouseButton]
5797 	int modifierState; /// See [ModifierState]
5798 
5799 	/// Returns a linear representation of mouse button,
5800 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
5801 	///
5802 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
5803 	@property ubyte buttonLinear() const {
5804 		import core.bitop;
5805 		if(button == 0)
5806 			return 0;
5807 		return (bsf(button) + 1) & 0b1111;
5808 	}
5809 
5810 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
5811 
5812 	SimpleWindow window; /// The window in which the event happened.
5813 
5814 	Point globalCoordinates() {
5815 		Point p;
5816 		if(window is null)
5817 			throw new Exception("wtf");
5818 		static if(UsingSimpledisplayX11) {
5819 			Window child;
5820 			XTranslateCoordinates(
5821 				XDisplayConnection.get,
5822 				window.impl.window,
5823 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
5824 				x, y, &p.x, &p.y, &child);
5825 			return p;
5826 		} else version(Windows) {
5827 			POINT[1] points;
5828 			points[0].x = x;
5829 			points[0].y = y;
5830 			MapWindowPoints(
5831 				window.impl.hwnd,
5832 				null,
5833 				points.ptr,
5834 				points.length
5835 			);
5836 			p.x = points[0].x;
5837 			p.y = points[0].y;
5838 
5839 			return p;
5840 		} else version(OSXCocoa) {
5841 			throw new NotYetImplementedException();
5842 		} else static assert(0);
5843 	}
5844 
5845 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
5846 
5847 	/**
5848 	can contain emacs-like modifier prefix
5849 	case-insensitive names:
5850 		lmbX/leftX
5851 		rmbX/rightX
5852 		mmbX/middleX
5853 		wheelX
5854 		motion (no prefix allowed)
5855 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
5856 	*/
5857 	static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
5858 		if (str.length == 0) return false; // just in case
5859 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
5860 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
5861 		auto anchor = str;
5862 		uint mods = 0; // uint.max == any
5863 		// interesting bits in kmod
5864 		uint kmodmask =
5865 			ModifierState.shift|
5866 			ModifierState.ctrl|
5867 			ModifierState.alt|
5868 			ModifierState.windows|
5869 			ModifierState.leftButtonDown|
5870 			ModifierState.middleButtonDown|
5871 			ModifierState.rightButtonDown|
5872 			0;
5873 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
5874 		bool wasButtons = false;
5875 		while (str.length) {
5876 			if (str.ptr[0] <= ' ') {
5877 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
5878 				continue;
5879 			}
5880 			// one-letter modifier?
5881 			if (str.length >= 2 && str.ptr[1] == '-') {
5882 				switch (str.ptr[0]) {
5883 					case '*': // "any" modifier (cannot be undone)
5884 						mods = mods.max;
5885 						break;
5886 					case 'C': case 'c': // emacs "ctrl"
5887 						if (mods != mods.max) mods |= ModifierState.ctrl;
5888 						break;
5889 					case 'M': case 'm': // emacs "meta"
5890 						if (mods != mods.max) mods |= ModifierState.alt;
5891 						break;
5892 					case 'S': case 's': // emacs "shift"
5893 						if (mods != mods.max) mods |= ModifierState.shift;
5894 						break;
5895 					case 'H': case 'h': // emacs "hyper" (aka winkey)
5896 						if (mods != mods.max) mods |= ModifierState.windows;
5897 						break;
5898 					default:
5899 						return false; // unknown modifier
5900 				}
5901 				str = str[2..$];
5902 				continue;
5903 			}
5904 			// word
5905 			char[16] buf = void; // locased
5906 			auto wep = 0;
5907 			while (str.length) {
5908 				immutable char ch = str.ptr[0];
5909 				if (ch <= ' ' || ch == '-') break;
5910 				str = str[1..$];
5911 				if (wep > buf.length) return false; // too long
5912 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
5913 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
5914 				else return false; // invalid char
5915 			}
5916 			if (wep == 0) return false; // just in case
5917 			uint bnum;
5918 			enum UpDown { None = -1, Up, Down, Any }
5919 			auto updown = UpDown.None; // 0: up; 1: down
5920 			switch (buf[0..wep]) {
5921 				// left button
5922 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
5923 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
5924 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
5925 				case "lmb": case "left": bnum = 0; break;
5926 				// middle button
5927 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
5928 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
5929 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
5930 				case "mmb": case "middle": bnum = 1; break;
5931 				// right button
5932 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
5933 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
5934 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
5935 				case "rmb": case "right": bnum = 2; break;
5936 				// wheel
5937 				case "wheelup": updown = UpDown.Up; goto case "wheel";
5938 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
5939 				case "wheelany": updown = UpDown.Any; goto case "wheel";
5940 				case "wheel": bnum = 3; break;
5941 				// motion
5942 				case "motion": bnum = 7; break;
5943 				// unknown
5944 				default: return false;
5945 			}
5946 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
5947 			// parse possible "-up" or "-down"
5948 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
5949 				wep = 0;
5950 				foreach (immutable idx, immutable char ch; str[1..$]) {
5951 					if (ch <= ' ' || ch == '-') break;
5952 					assert(idx == wep); // for now; trick
5953 					if (wep > buf.length) { wep = 0; break; } // too long
5954 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
5955 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
5956 					else { wep = 0; break; } // invalid char
5957 				}
5958 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
5959 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
5960 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
5961 				// remove parsed part
5962 				if (updown != UpDown.None) str = str[wep+1..$];
5963 			}
5964 			if (updown == UpDown.None) {
5965 				updown = UpDown.Down;
5966 			}
5967 			wasButtons = wasButtons || (bnum <= 2);
5968 			//assert(updown != UpDown.None);
5969 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
5970 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
5971 			if (lastButt != lastButt.max) {
5972 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
5973 				if (mods != mods.max) {
5974 					uint butbit = 0;
5975 					final switch (lastButt&0x03) {
5976 						case 0: butbit = ModifierState.leftButtonDown; break;
5977 						case 1: butbit = ModifierState.middleButtonDown; break;
5978 						case 2: butbit = ModifierState.rightButtonDown; break;
5979 					}
5980 					     if (lastButt&Flag.Down) mods |= butbit;
5981 					else if (lastButt&Flag.Up) mods &= ~butbit;
5982 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
5983 				}
5984 			}
5985 			// remember last button
5986 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
5987 		}
5988 		// no button -- nothing to do
5989 		if (lastButt == lastButt.max) return false;
5990 		// done parsing, check if something's left
5991 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
5992 		// remove action button from mask
5993 		if ((lastButt&0xff) < 3) {
5994 			final switch (lastButt&0x03) {
5995 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
5996 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
5997 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
5998 			}
5999 		}
6000 		// special case: "Motion" means "ignore buttons"
6001 		if ((lastButt&0xff) == 7 && !wasButtons) {
6002 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
6003 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
6004 		}
6005 		uint kmod = event.modifierState&kmodmask;
6006 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
6007 		// check modifier state
6008 		if (mods != mods.max) {
6009 			if (kmod != mods) return false;
6010 		}
6011 		// now check type
6012 		if ((lastButt&0xff) == 7) {
6013 			// motion
6014 			if (event.type != MouseEventType.motion) return false;
6015 		} else if ((lastButt&0xff) == 3) {
6016 			// wheel
6017 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
6018 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
6019 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
6020 			return false;
6021 		} else {
6022 			// buttons
6023 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
6024 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
6025 			{
6026 				return false;
6027 			}
6028 			// button number
6029 			switch (lastButt&0x03) {
6030 				case 0: if (event.button != MouseButton.left) return false; break;
6031 				case 1: if (event.button != MouseButton.middle) return false; break;
6032 				case 2: if (event.button != MouseButton.right) return false; break;
6033 				default: return false;
6034 			}
6035 		}
6036 		return true;
6037 	}
6038 }
6039 
6040 version(arsd_mevent_strcmp_test) unittest {
6041 	MouseEvent event;
6042 	event.type = MouseEventType.buttonPressed;
6043 	event.button = MouseButton.left;
6044 	event.modifierState = ModifierState.ctrl;
6045 	assert(event == "C-LMB");
6046 	assert(event != "C-LMBUP");
6047 	assert(event != "C-LMB-UP");
6048 	assert(event != "C-S-LMB");
6049 	assert(event == "*-LMB");
6050 	assert(event != "*-LMB-UP");
6051 
6052 	event.type = MouseEventType.buttonReleased;
6053 	assert(event != "C-LMB");
6054 	assert(event == "C-LMBUP");
6055 	assert(event == "C-LMB-UP");
6056 	assert(event != "C-S-LMB");
6057 	assert(event != "*-LMB");
6058 	assert(event == "*-LMB-UP");
6059 
6060 	event.button = MouseButton.right;
6061 	event.modifierState |= ModifierState.shift;
6062 	event.type = MouseEventType.buttonPressed;
6063 	assert(event != "C-LMB");
6064 	assert(event != "C-LMBUP");
6065 	assert(event != "C-LMB-UP");
6066 	assert(event != "C-S-LMB");
6067 	assert(event != "*-LMB");
6068 	assert(event != "*-LMB-UP");
6069 
6070 	assert(event != "C-RMB");
6071 	assert(event != "C-RMBUP");
6072 	assert(event != "C-RMB-UP");
6073 	assert(event == "C-S-RMB");
6074 	assert(event == "*-RMB");
6075 	assert(event != "*-RMB-UP");
6076 }
6077 
6078 /// This gives a few more options to drawing lines and such
6079 struct Pen {
6080 	Color color; /// the foreground color
6081 	int width = 1; /// width of the line
6082 	Style style; /// See [Style]
6083 /+
6084 // From X.h
6085 
6086 #define LineSolid		0
6087 #define LineOnOffDash		1
6088 #define LineDoubleDash		2
6089        LineDou-        The full path of the line is drawn, but the
6090        bleDash         even dashes are filled differently from the
6091                        odd dashes (see fill-style) with CapButt
6092                        style used where even and odd dashes meet.
6093 
6094 
6095 
6096 /* capStyle */
6097 
6098 #define CapNotLast		0
6099 #define CapButt			1
6100 #define CapRound		2
6101 #define CapProjecting		3
6102 
6103 /* joinStyle */
6104 
6105 #define JoinMiter		0
6106 #define JoinRound		1
6107 #define JoinBevel		2
6108 
6109 /* fillStyle */
6110 
6111 #define FillSolid		0
6112 #define FillTiled		1
6113 #define FillStippled		2
6114 #define FillOpaqueStippled	3
6115 
6116 
6117 +/
6118 	/// Style of lines drawn
6119 	enum Style {
6120 		Solid, /// a solid line
6121 		Dashed, /// a dashed line
6122 		Dotted, /// a dotted line
6123 	}
6124 }
6125 
6126 
6127 /++
6128 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
6129 
6130 
6131 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
6132 
6133 	$(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.)
6134 
6135 	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.
6136 
6137 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
6138 
6139 	$(IMPORTANT `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.
6140 
6141 	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!
6142 
6143 	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!)
6144 
6145 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
6146 
6147 	---
6148 		auto image = new Image(256, 256);
6149 		scope(exit) destroy(image);
6150 	---
6151 
6152 	As long as you don't hold on to it outside the scope.
6153 
6154 	I might change it to be an owned pointer at some point in the future.
6155 
6156 	)
6157 
6158 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
6159 	you can also often get a fair amount of speedup by getting the raw data format and
6160 	writing some custom code.
6161 
6162 	FIXME INSERT EXAMPLES HERE
6163 
6164 
6165 +/
6166 final class Image {
6167 	///
6168 	this(int width, int height, bool forcexshm=false) {
6169 		this.width = width;
6170 		this.height = height;
6171 
6172 		impl.createImage(width, height, forcexshm);
6173 	}
6174 
6175 	///
6176 	this(Size size, bool forcexshm=false) {
6177 		this(size.width, size.height, forcexshm);
6178 	}
6179 
6180 	~this() {
6181 		impl.dispose();
6182 	}
6183 
6184 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
6185 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
6186 	pure const @system nothrow {
6187 		/*
6188 			To use these to draw a blue rectangle with size WxH at position X,Y...
6189 
6190 			// make certain that it will fit before we proceed
6191 			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!
6192 
6193 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
6194 			// (though calculating them isn't really that expensive).
6195 			auto nextLineAdjustment = img.adjustmentForNextLine();
6196 			auto offR = img.redByteOffset();
6197 			auto offB = img.blueByteOffset();
6198 			auto offG = img.greenByteOffset();
6199 			auto bpp = img.bytesPerPixel();
6200 
6201 			auto data = img.getDataPointer();
6202 
6203 			// figure out the starting byte offset
6204 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
6205 
6206 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
6207 
6208 			// and now our drawing loop for the rectangle
6209 			foreach(y; 0 .. H) {
6210 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
6211 				foreach(x; 0 .. W) {
6212 					// write our color
6213 					data[offR] = 0;
6214 					data[offG] = 0;
6215 					data[offB] = 255;
6216 
6217 					data += bpp; // moving to the next pixel is just an addition...
6218 				}
6219 				startOfLine += nextLineAdjustment;
6220 			}
6221 
6222 
6223 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
6224 
6225 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
6226 			can be made into a bitmask or something so we can write them as *uint...
6227 		*/
6228 
6229 		///
6230 		int offsetForTopLeftPixel() {
6231 			version(X11) {
6232 				return 0;
6233 			} else version(Windows) {
6234 				return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
6235 			} else version(OSXCocoa) {
6236 				return 0 ; //throw new NotYetImplementedException();
6237 			} else static assert(0, "fill in this info for other OSes");
6238 		}
6239 
6240 		///
6241 		int offsetForPixel(int x, int y) {
6242 			version(X11) {
6243 				auto offset = (y * width + x) * 4;
6244 				return offset;
6245 			} else version(Windows) {
6246 				auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
6247 				// remember, bmps are upside down
6248 				auto offset = itemsPerLine * (height - y - 1) + x * 3;
6249 				return offset;
6250 			} else version(OSXCocoa) {
6251 				return 0 ; //throw new NotYetImplementedException();
6252 			} else static assert(0, "fill in this info for other OSes");
6253 		}
6254 
6255 		///
6256 		int adjustmentForNextLine() {
6257 			version(X11) {
6258 				return width * 4;
6259 			} else version(Windows) {
6260 				// windows bmps are upside down, so the adjustment is actually negative
6261 				return -((cast(int) width * 3 + 3) / 4) * 4;
6262 			} else version(OSXCocoa) {
6263 				return 0 ; //throw new NotYetImplementedException();
6264 			} else static assert(0, "fill in this info for other OSes");
6265 		}
6266 
6267 		/// once you have the position of a pixel, use these to get to the proper color
6268 		int redByteOffset() {
6269 			version(X11) {
6270 				return 2;
6271 			} else version(Windows) {
6272 				return 2;
6273 			} else version(OSXCocoa) {
6274 				return 0 ; //throw new NotYetImplementedException();
6275 			} else static assert(0, "fill in this info for other OSes");
6276 		}
6277 
6278 		///
6279 		int greenByteOffset() {
6280 			version(X11) {
6281 				return 1;
6282 			} else version(Windows) {
6283 				return 1;
6284 			} else version(OSXCocoa) {
6285 				return 0 ; //throw new NotYetImplementedException();
6286 			} else static assert(0, "fill in this info for other OSes");
6287 		}
6288 
6289 		///
6290 		int blueByteOffset() {
6291 			version(X11) {
6292 				return 0;
6293 			} else version(Windows) {
6294 				return 0;
6295 			} else version(OSXCocoa) {
6296 				return 0 ; //throw new NotYetImplementedException();
6297 			} else static assert(0, "fill in this info for other OSes");
6298 		}
6299 	}
6300 
6301 	///
6302 	final void putPixel(int x, int y, Color c) {
6303 		if(x < 0 || x >= width)
6304 			return;
6305 		if(y < 0 || y >= height)
6306 			return;
6307 
6308 		impl.setPixel(x, y, c);
6309 	}
6310 
6311 	///
6312 	final Color getPixel(int x, int y) {
6313 		if(x < 0 || x >= width)
6314 			return Color.transparent;
6315 		if(y < 0 || y >= height)
6316 			return Color.transparent;
6317 
6318 		version(OSXCocoa) throw new NotYetImplementedException(); else
6319 		return impl.getPixel(x, y);
6320 	}
6321 
6322 	///
6323 	final void opIndexAssign(Color c, int x, int y) {
6324 		putPixel(x, y, c);
6325 	}
6326 
6327 	///
6328 	TrueColorImage toTrueColorImage() {
6329 		auto tci = new TrueColorImage(width, height);
6330 		convertToRgbaBytes(tci.imageData.bytes);
6331 		return tci;
6332 	}
6333 
6334 	///
6335 	static Image fromMemoryImage(MemoryImage i) {
6336 		auto tci = i.getAsTrueColorImage();
6337 		auto img = new Image(tci.width, tci.height);
6338 		img.setRgbaBytes(tci.imageData.bytes);
6339 		return img;
6340 	}
6341 
6342 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
6343 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
6344 	/// if you pass null, it will allocate a new one.
6345 	ubyte[] getRgbaBytes(ubyte[] where = null) {
6346 		if(where is null)
6347 			where = new ubyte[this.width*this.height*4];
6348 		convertToRgbaBytes(where);
6349 		return where;
6350 	}
6351 
6352 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
6353 	void setRgbaBytes(in ubyte[] from ) {
6354 		assert(from.length == this.width * this.height * 4);
6355 		setFromRgbaBytes(from);
6356 	}
6357 
6358 	// FIXME: make properly cross platform by getting rgba right
6359 
6360 	/// warning: this is not portable across platforms because the data format can change
6361 	ubyte* getDataPointer() {
6362 		return impl.rawData;
6363 	}
6364 
6365 	/// for use with getDataPointer
6366 	final int bytesPerLine() const pure @safe nothrow {
6367 		version(Windows)
6368 			return ((cast(int) width * 3 + 3) / 4) * 4;
6369 		else version(X11)
6370 			return 4 * width;
6371 		else version(OSXCocoa)
6372 			return 4 * width;
6373 		else static assert(0);
6374 	}
6375 
6376 	/// for use with getDataPointer
6377 	final int bytesPerPixel() const pure @safe nothrow {
6378 		version(Windows)
6379 			return 3;
6380 		else version(X11)
6381 			return 4;
6382 		else version(OSXCocoa)
6383 			return 4;
6384 		else static assert(0);
6385 	}
6386 
6387 	///
6388 	immutable int width;
6389 
6390 	///
6391 	immutable int height;
6392     //private:
6393 	mixin NativeImageImplementation!() impl;
6394 }
6395 
6396 /// A convenience function to pop up a window displaying the image.
6397 /// If you pass a win, it will draw the image in it. Otherwise, it will
6398 /// create a window with the size of the image and run its event loop, closing
6399 /// when a key is pressed.
6400 void displayImage(Image image, SimpleWindow win = null) {
6401 	if(win is null) {
6402 		win = new SimpleWindow(image);
6403 		{
6404 			auto p = win.draw;
6405 			p.drawImage(Point(0, 0), image);
6406 		}
6407 		win.eventLoop(0,
6408 			(KeyEvent ev) {
6409 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
6410 			} );
6411 	} else {
6412 		win.image = image;
6413 	}
6414 }
6415 
6416 enum FontWeight : int {
6417 	dontcare = 0,
6418 	thin = 100,
6419 	extralight = 200,
6420 	light = 300,
6421 	regular = 400,
6422 	medium = 500,
6423 	semibold = 600,
6424 	bold = 700,
6425 	extrabold = 800,
6426 	heavy = 900
6427 }
6428 
6429 /++
6430 	Represents a font loaded off the operating system or the X server.
6431 
6432 
6433 	While the api here is unified cross platform, the fonts are not necessarily
6434 	available, even across machines of the same platform, so be sure to always check
6435 	for null (using [isNull]) and have a fallback plan.
6436 
6437 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
6438 
6439 	Worst case, a null font will automatically fall back to the default font loaded
6440 	for your system.
6441 +/
6442 class OperatingSystemFont {
6443 
6444 	version(X11) {
6445 		XFontStruct* font;
6446 		XFontSet fontset;
6447 	} else version(Windows) {
6448 		HFONT font;
6449 		int width_;
6450 		int height_;
6451 	} else version(OSXCocoa) {
6452 		// FIXME
6453 	} else static assert(0);
6454 
6455 	///
6456 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
6457 		load(name, size, weight, italic);
6458 	}
6459 
6460 	///
6461 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
6462 		unload();
6463 		version(X11) {
6464 			string weightstr;
6465 			with(FontWeight)
6466 			final switch(weight) {
6467 				case dontcare: weightstr = "*"; break;
6468 				case thin: weightstr = "extralight"; break;
6469 				case extralight: weightstr = "extralight"; break;
6470 				case light: weightstr = "light"; break;
6471 				case regular: weightstr = "regular"; break;
6472 				case medium: weightstr = "medium"; break;
6473 				case semibold: weightstr = "demibold"; break;
6474 				case bold: weightstr = "bold"; break;
6475 				case extrabold: weightstr = "demibold"; break;
6476 				case heavy: weightstr = "black"; break;
6477 			}
6478 			string sizestr;
6479 			if(size == 0)
6480 				sizestr = "*";
6481 			else if(size < 10)
6482 				sizestr = "" ~ cast(char)(size % 10 + '0');
6483 			else
6484 				sizestr = "" ~ cast(char)(size / 10 + '0') ~ cast(char)(size % 10 + '0');
6485 			auto xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
6486 
6487 			//import std.stdio; writeln(xfontstr);
6488 
6489 			auto display = XDisplayConnection.get;
6490 
6491 			font = XLoadQueryFont(display, xfontstr.ptr);
6492 			if(font is null)
6493 				return false;
6494 
6495 			char** lol;
6496 			int lol2;
6497 			char* lol3;
6498 			fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
6499 		} else version(Windows) {
6500 			WCharzBuffer buffer = WCharzBuffer(name);
6501 			font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
6502 
6503 			TEXTMETRIC tm;
6504 			auto dc = GetDC(null);
6505 			SelectObject(dc, font);
6506 			GetTextMetrics(dc, &tm);
6507 			ReleaseDC(null, dc);
6508 
6509 			width_ = tm.tmAveCharWidth;
6510 			height_ = tm.tmHeight;
6511 		} else version(OSXCocoa) {
6512 			// FIXME
6513 		} else static assert(0);
6514 
6515 		return !isNull();
6516 	}
6517 
6518 	///
6519 	void unload() {
6520 		if(isNull())
6521 			return;
6522 
6523 		version(X11) {
6524 			auto display = XDisplayConnection.display;
6525 
6526 			if(display is null)
6527 				return;
6528 
6529 			if(font)
6530 				XFreeFont(display, font);
6531 			if(fontset)
6532 				XFreeFontSet(display, fontset);
6533 
6534 			font = null;
6535 			fontset = null;
6536 		} else version(Windows) {
6537 			DeleteObject(font);
6538 			font = null;
6539 		} else version(OSXCocoa) {
6540 			// FIXME
6541 		} else static assert(0);
6542 	}
6543 
6544 	// Assuming monospace!!!!!
6545 	// added March 26, 2020
6546 	int averageWidth() {
6547 		version(X11)
6548 			return font.max_bounds.width;
6549 		else version(Windows)
6550 			return width_;
6551 		else assert(0);
6552 	}
6553 
6554 	// Assuming monospace!!!!!
6555 	// added March 26, 2020
6556 	int height() {
6557 		version(X11)
6558 			return font.max_bounds.ascent + font.max_bounds.descent;
6559 		else version(Windows)
6560 			return height_;
6561 		else assert(0);
6562 	}
6563 
6564 	/// FIXME not implemented
6565 	void loadDefault() {
6566 
6567 	}
6568 
6569 	///
6570 	bool isNull() {
6571 		version(OSXCocoa) throw new NotYetImplementedException(); else
6572 		return font is null;
6573 	}
6574 
6575 	/* Metrics */
6576 	/+
6577 		GetABCWidth
6578 		GetKerningPairs
6579 
6580 		if I do it right, I can size it all here, and match
6581 		what happens when I draw the full string with the OS functions.
6582 
6583 		subclasses might do the same thing while getting the glyphs on images
6584 	+/
6585 	struct GlyphInfo {
6586 		int glyph;
6587 
6588 		size_t stringIdxStart;
6589 		size_t stringIdxEnd;
6590 
6591 		Rectangle boundingBox;
6592 	}
6593 	GlyphInfo[] getCharBoxes() {
6594 		return null;
6595 
6596 	}
6597 
6598 	~this() {
6599 		unload();
6600 	}
6601 }
6602 
6603 /**
6604 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
6605 	than constructing it directly. Then, it is reference counted so you can pass it
6606 	at around and when the last ref goes out of scope, the buffered drawing activities
6607 	are all carried out.
6608 
6609 
6610 	Most functions use the outlineColor instead of taking a color themselves.
6611 	ScreenPainter is reference counted and draws its buffer to the screen when its
6612 	final reference goes out of scope.
6613 */
6614 struct ScreenPainter {
6615 	CapableOfBeingDrawnUpon window;
6616 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) {
6617 		this.window = window;
6618 		if(window.closed)
6619 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
6620 		currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
6621 		if(window.activeScreenPainter !is null) {
6622 			impl = window.activeScreenPainter;
6623 			if(impl.referenceCount == 0) {
6624 				impl.window = window;
6625 				impl.create(handle);
6626 			}
6627 			impl.referenceCount++;
6628 		//	writeln("refcount ++ ", impl.referenceCount);
6629 		} else {
6630 			impl = new ScreenPainterImplementation;
6631 			impl.window = window;
6632 			impl.create(handle);
6633 			impl.referenceCount = 1;
6634 			window.activeScreenPainter = impl;
6635 		//	writeln("constructed");
6636 		}
6637 
6638 		copyActiveOriginals();
6639 	}
6640 
6641 	private Pen originalPen;
6642 	private Color originalFillColor;
6643 	private arsd.color.Rectangle originalClipRectangle;
6644 	void copyActiveOriginals() {
6645 		if(impl is null) return;
6646 		originalPen = impl._activePen;
6647 		originalFillColor = impl._fillColor;
6648 		originalClipRectangle = impl._clipRectangle;
6649 	}
6650 
6651 	~this() {
6652 		if(impl is null) return;
6653 		impl.referenceCount--;
6654 		//writeln("refcount -- ", impl.referenceCount);
6655 		if(impl.referenceCount == 0) {
6656 			//writeln("destructed");
6657 			impl.dispose();
6658 			*window.activeScreenPainter = ScreenPainterImplementation.init;
6659 			//import std.stdio; writeln("paint finished");
6660 		} else {
6661 			// there is still an active reference, reset stuff so the
6662 			// next user doesn't get weirdness via the reference
6663 			this.rasterOp = RasterOp.normal;
6664 			pen = originalPen;
6665 			fillColor = originalFillColor;
6666 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
6667 		}
6668 	}
6669 
6670 	this(this) {
6671 		if(impl is null) return;
6672 		impl.referenceCount++;
6673 		//writeln("refcount ++ ", impl.referenceCount);
6674 
6675 		copyActiveOriginals();
6676 	}
6677 
6678 	private int _originX;
6679 	private int _originY;
6680 	@property int originX() { return _originX; }
6681 	@property int originY() { return _originY; }
6682 	@property int originX(int a) {
6683 		//currentClipRectangle.left += a - _originX;
6684 		//currentClipRectangle.right += a - _originX;
6685 		_originX = a;
6686 		return _originX;
6687 	}
6688 	@property int originY(int a) {
6689 		//currentClipRectangle.top += a - _originY;
6690 		//currentClipRectangle.bottom += a - _originY;
6691 		_originY = a;
6692 		return _originY;
6693 	}
6694 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
6695 	private void transform(ref Point p) {
6696 		if(impl is null) return;
6697 		p.x += _originX;
6698 		p.y += _originY;
6699 	}
6700 
6701 	// this needs to be checked BEFORE the originX/Y transformation
6702 	private bool isClipped(Point p) {
6703 		return !currentClipRectangle.contains(p);
6704 	}
6705 	private bool isClipped(Point p, int width, int height) {
6706 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
6707 	}
6708 	private bool isClipped(Point p, Size s) {
6709 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
6710 	}
6711 	private bool isClipped(Point p, Point p2) {
6712 		// need to ensure the end points are actually included inside, so the +1 does that
6713 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
6714 	}
6715 
6716 
6717 	/// Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
6718 	void setClipRectangle(Point pt, int width, int height) {
6719 		if(impl is null) return;
6720 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
6721 			return; // no need to do anything
6722 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
6723 		transform(pt);
6724 
6725 		impl.setClipRectangle(pt.x, pt.y, width, height);
6726 	}
6727 
6728 	/// ditto
6729 	void setClipRectangle(arsd.color.Rectangle rect) {
6730 		if(impl is null) return;
6731 		setClipRectangle(rect.upperLeft, rect.width, rect.height);
6732 	}
6733 
6734 	///
6735 	void setFont(OperatingSystemFont font) {
6736 		if(impl is null) return;
6737 		impl.setFont(font);
6738 	}
6739 
6740 	///
6741 	int fontHeight() {
6742 		if(impl is null) return 0;
6743 		return impl.fontHeight();
6744 	}
6745 
6746 	private Pen activePen;
6747 
6748 	///
6749 	@property void pen(Pen p) {
6750 		if(impl is null) return;
6751 		activePen = p;
6752 		impl.pen(p);
6753 	}
6754 
6755 	///
6756 	@scriptable
6757 	@property void outlineColor(Color c) {
6758 		if(impl is null) return;
6759 		if(activePen.color == c)
6760 			return;
6761 		activePen.color = c;
6762 		impl.pen(activePen);
6763 	}
6764 
6765 	///
6766 	@scriptable
6767 	@property void fillColor(Color c) {
6768 		if(impl is null) return;
6769 		impl.fillColor(c);
6770 	}
6771 
6772 	///
6773 	@property void rasterOp(RasterOp op) {
6774 		if(impl is null) return;
6775 		impl.rasterOp(op);
6776 	}
6777 
6778 
6779 	void updateDisplay() {
6780 		// FIXME this should do what the dtor does
6781 	}
6782 
6783 	/// 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)
6784 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
6785 		if(impl is null) return;
6786 		if(isClipped(upperLeft, width, height)) return;
6787 		transform(upperLeft);
6788 		version(Windows) {
6789 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
6790 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
6791 			RECT clip = scroll;
6792 			RECT uncovered;
6793 			HRGN hrgn;
6794 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
6795 				throw new Exception("ScrollDC");
6796 
6797 		} else version(X11) {
6798 			// FIXME: clip stuff outside this rectangle
6799 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
6800 		} else version(OSXCocoa) {
6801 			throw new NotYetImplementedException();
6802 		} else static assert(0);
6803 	}
6804 
6805 	///
6806 	void clear(Color color = Color.white()) {
6807 		if(impl is null) return;
6808 		fillColor = color;
6809 		outlineColor = color;
6810 		drawRectangle(Point(0, 0), window.width, window.height);
6811 	}
6812 
6813 	///
6814 	version(OSXCocoa) {} else // NotYetImplementedException
6815 	void drawPixmap(Sprite s, Point upperLeft) {
6816 		if(impl is null) return;
6817 		if(isClipped(upperLeft, s.width, s.height)) return;
6818 		transform(upperLeft);
6819 		impl.drawPixmap(s, upperLeft.x, upperLeft.y);
6820 	}
6821 
6822 	///
6823 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
6824 		if(impl is null) return;
6825 		//if(isClipped(upperLeft, w, h)) return; // FIXME
6826 		transform(upperLeft);
6827 		if(w == 0 || w > i.width)
6828 			w = i.width;
6829 		if(h == 0 || h > i.height)
6830 			h = i.height;
6831 		if(upperLeftOfImage.x < 0)
6832 			upperLeftOfImage.x = 0;
6833 		if(upperLeftOfImage.y < 0)
6834 			upperLeftOfImage.y = 0;
6835 
6836 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
6837 	}
6838 
6839 	///
6840 	Size textSize(in char[] text) {
6841 		if(impl is null) return Size(0, 0);
6842 		return impl.textSize(text);
6843 	}
6844 
6845 	///
6846 	@scriptable
6847 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
6848 		if(impl is null) return;
6849 		if(lowerRight.x != 0 || lowerRight.y != 0) {
6850 			if(isClipped(upperLeft, lowerRight)) return;
6851 			transform(lowerRight);
6852 		} else {
6853 			if(isClipped(upperLeft, textSize(text))) return;
6854 		}
6855 		transform(upperLeft);
6856 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
6857 	}
6858 
6859 	/++
6860 		Draws text using a custom font.
6861 
6862 		This is still MAJOR work in progress.
6863 
6864 		Creating a [DrawableFont] can be tricky and require additional dependencies.
6865 	+/
6866 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
6867 		if(impl is null) return;
6868 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
6869 		transform(upperLeft);
6870 		font.drawString(this, upperLeft, text);
6871 	}
6872 
6873 	static struct TextDrawingContext {
6874 		Point boundingBoxUpperLeft;
6875 		Point boundingBoxLowerRight;
6876 
6877 		Point currentLocation;
6878 
6879 		Point lastDrewUpperLeft;
6880 		Point lastDrewLowerRight;
6881 
6882 		// how do i do right aligned rich text?
6883 		// i kinda want to do a pre-made drawing then right align
6884 		// draw the whole block.
6885 		//
6886 		// That's exactly the diff: inline vs block stuff.
6887 
6888 		// I need to get coordinates of an inline section out too,
6889 		// not just a bounding box, but a series of bounding boxes
6890 		// should be ok. Consider what's needed to detect a click
6891 		// on a link in the middle of a paragraph breaking a line.
6892 		//
6893 		// Generally, we should be able to get the rectangles of
6894 		// any portion we draw.
6895 		//
6896 		// It also needs to tell what text is left if it overflows
6897 		// out of the box, so we can do stuff like float images around
6898 		// it. It should not attempt to draw a letter that would be
6899 		// clipped.
6900 		//
6901 		// I might also turn off word wrap stuff.
6902 	}
6903 
6904 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
6905 		if(impl is null) return;
6906 		// FIXME
6907 	}
6908 
6909 	/// Drawing an individual pixel is slow. Avoid it if possible.
6910 	void drawPixel(Point where) {
6911 		if(impl is null) return;
6912 		if(isClipped(where)) return;
6913 		transform(where);
6914 		impl.drawPixel(where.x, where.y);
6915 	}
6916 
6917 
6918 	/// Draws a pen using the current pen / outlineColor
6919 	@scriptable
6920 	void drawLine(Point starting, Point ending) {
6921 		if(impl is null) return;
6922 		if(isClipped(starting, ending)) return;
6923 		transform(starting);
6924 		transform(ending);
6925 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
6926 	}
6927 
6928 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
6929 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
6930 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
6931 	@scriptable
6932 	void drawRectangle(Point upperLeft, int width, int height) {
6933 		if(impl is null) return;
6934 		if(isClipped(upperLeft, width, height)) return;
6935 		transform(upperLeft);
6936 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
6937 	}
6938 
6939 	/// ditto
6940 	void drawRectangle(Point upperLeft, Size size) {
6941 		if(impl is null) return;
6942 		if(isClipped(upperLeft, size.width, size.height)) return;
6943 		transform(upperLeft);
6944 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
6945 	}
6946 
6947 	/// ditto
6948 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
6949 		if(impl is null) return;
6950 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
6951 		transform(upperLeft);
6952 		transform(lowerRightInclusive);
6953 		impl.drawRectangle(upperLeft.x, upperLeft.y,
6954 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
6955 	}
6956 
6957 	/// Arguments are the points of the bounding rectangle
6958 	void drawEllipse(Point upperLeft, Point lowerRight) {
6959 		if(impl is null) return;
6960 		if(isClipped(upperLeft, lowerRight)) return;
6961 		transform(upperLeft);
6962 		transform(lowerRight);
6963 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
6964 	}
6965 
6966 	/++
6967 		start and finish are units of degrees * 64
6968 	+/
6969 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
6970 		if(impl is null) return;
6971 		// FIXME: not actually implemented
6972 		if(isClipped(upperLeft, width, height)) return;
6973 		transform(upperLeft);
6974 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
6975 	}
6976 
6977 	//this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
6978 	void drawCircle(Point upperLeft, int diameter) {
6979 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
6980 	}
6981 
6982 	/// .
6983 	void drawPolygon(Point[] vertexes) {
6984 		if(impl is null) return;
6985 		assert(vertexes.length);
6986 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
6987 		foreach(ref vertex; vertexes) {
6988 			if(vertex.x < minX)
6989 				minX = vertex.x;
6990 			if(vertex.y < minY)
6991 				minY = vertex.y;
6992 			if(vertex.x > maxX)
6993 				maxX = vertex.x;
6994 			if(vertex.y > maxY)
6995 				maxY = vertex.y;
6996 			transform(vertex);
6997 		}
6998 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
6999 		impl.drawPolygon(vertexes);
7000 	}
7001 
7002 	/// ditto
7003 	void drawPolygon(Point[] vertexes...) {
7004 		if(impl is null) return;
7005 		drawPolygon(vertexes);
7006 	}
7007 
7008 
7009 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
7010 
7011 	//mixin NativeScreenPainterImplementation!() impl;
7012 
7013 
7014 	// HACK: if I mixin the impl directly, it won't let me override the copy
7015 	// constructor! The linker complains about there being multiple definitions.
7016 	// I'll make the best of it and reference count it though.
7017 	ScreenPainterImplementation* impl;
7018 }
7019 
7020 	// HACK: I need a pointer to the implementation so it's separate
7021 	struct ScreenPainterImplementation {
7022 		CapableOfBeingDrawnUpon window;
7023 		int referenceCount;
7024 		mixin NativeScreenPainterImplementation!();
7025 	}
7026 
7027 // FIXME: i haven't actually tested the sprite class on MS Windows
7028 
7029 /**
7030 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
7031 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
7032 
7033 
7034 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
7035 	though I'm not sure that's ideal and the implementation might change.
7036 
7037 	You create one by giving a window and an image. It optimizes for that window,
7038 	and copies the image into it to use as the initial picture. Creating a sprite
7039 	can be quite slow (especially over a network connection) so you should do it
7040 	as little as possible and just hold on to your sprite handles after making them.
7041 	simpledisplay does try to do its best though, using the XSHM extension if available,
7042 	but you should still write your code as if it will always be slow.
7043 
7044 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
7045 	a fast operation - much faster than drawing the Image itself every time.
7046 
7047 	`Sprite` represents a scarce resource which should be freed when you
7048 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
7049 	after it has been disposed. If you are unsure about this, don't take chances,
7050 	just let the garbage collector do it for you. But ideally, you can manage its
7051 	lifetime more efficiently.
7052 
7053 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
7054 	support alpha blending in its drawing at this time. That might change in the
7055 	future, but if you need alpha blending right now, use OpenGL instead. See
7056 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
7057 
7058 	FIXME: you are supposed to be able to draw on these similarly to on windows.
7059 	ScreenPainter needs to be refactored to allow that though. So until that is
7060 	done, consider a `Sprite` to have const contents.
7061 */
7062 version(OSXCocoa) {} else // NotYetImplementedException
7063 class Sprite : CapableOfBeingDrawnUpon {
7064 
7065 	///
7066 	ScreenPainter draw() {
7067 		return ScreenPainter(this, handle);
7068 	}
7069 
7070 	/// Be warned: this can be a very slow operation
7071 	/// FIXME NOT IMPLEMENTED
7072 	TrueColorImage takeScreenshot() {
7073 		return trueColorImageFromNativeHandle(handle, width, height);
7074 	}
7075 
7076 	void delegate() paintingFinishedDg() { return null; }
7077 	bool closed() { return false; }
7078 	ScreenPainterImplementation* activeScreenPainter_;
7079 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
7080 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
7081 
7082 	version(Windows)
7083 		private ubyte* rawData;
7084 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
7085 
7086 	this(SimpleWindow win, int width, int height) {
7087 		this._width = width;
7088 		this._height = height;
7089 
7090 		version(X11) {
7091 			auto display = XDisplayConnection.get();
7092 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
7093 		} else version(Windows) {
7094 			BITMAPINFO infoheader;
7095 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
7096 			infoheader.bmiHeader.biWidth = width;
7097 			infoheader.bmiHeader.biHeight = height;
7098 			infoheader.bmiHeader.biPlanes = 1;
7099 			infoheader.bmiHeader.biBitCount = 24;
7100 			infoheader.bmiHeader.biCompression = BI_RGB;
7101 
7102 			// FIXME: this should prolly be a device dependent bitmap...
7103 			handle = CreateDIBSection(
7104 				null,
7105 				&infoheader,
7106 				DIB_RGB_COLORS,
7107 				cast(void**) &rawData,
7108 				null,
7109 				0);
7110 
7111 			if(handle is null)
7112 				throw new Exception("couldn't create pixmap");
7113 		}
7114 	}
7115 
7116 	/// Makes a sprite based on the image with the initial contents from the Image
7117 	this(SimpleWindow win, Image i) {
7118 		this(win, i.width, i.height);
7119 
7120 		version(X11) {
7121 			auto display = XDisplayConnection.get();
7122 			if(i.usingXshm)
7123 				XShmPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height, false);
7124 			else
7125 				XPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height);
7126 		} else version(Windows) {
7127 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
7128 			auto arrLength = itemsPerLine * height;
7129 			rawData[0..arrLength] = i.rawData[0..arrLength];
7130 		} else version(OSXCocoa) {
7131 			// FIXME: I have no idea if this is even any good
7132 			ubyte* rawData;
7133 
7134 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
7135 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
7136 				colorSpace,
7137 				kCGImageAlphaPremultipliedLast
7138 				|kCGBitmapByteOrder32Big);
7139 			CGColorSpaceRelease(colorSpace);
7140 			rawData = CGBitmapContextGetData(context);
7141 
7142 			auto rdl = (width * height * 4);
7143 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
7144 		} else static assert(0);
7145 	}
7146 
7147 	/++
7148 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
7149 	+/
7150 	void drawAt(ScreenPainter painter, Point where) {
7151 		painter.drawPixmap(this, where);
7152 	}
7153 
7154 
7155 	/// Call this when you're ready to get rid of it
7156 	void dispose() {
7157 		version(X11) {
7158 			if(handle)
7159 				XFreePixmap(XDisplayConnection.get(), handle);
7160 			handle = None;
7161 		} else version(Windows) {
7162 			if(handle)
7163 				DeleteObject(handle);
7164 			handle = null;
7165 		} else version(OSXCocoa) {
7166 			if(context)
7167 				CGContextRelease(context);
7168 			context = null;
7169 		} else static assert(0);
7170 
7171 	}
7172 
7173 	~this() {
7174 		dispose();
7175 	}
7176 
7177 	///
7178 	final @property int width() { return _width; }
7179 
7180 	///
7181 	final @property int height() { return _height; }
7182 
7183 	private:
7184 
7185 	int _width;
7186 	int _height;
7187 	version(X11)
7188 		Pixmap handle;
7189 	else version(Windows)
7190 		HBITMAP handle;
7191 	else version(OSXCocoa)
7192 		CGContextRef context;
7193 	else static assert(0);
7194 }
7195 
7196 ///
7197 interface CapableOfBeingDrawnUpon {
7198 	///
7199 	ScreenPainter draw();
7200 	///
7201 	int width();
7202 	///
7203 	int height();
7204 	protected ScreenPainterImplementation* activeScreenPainter();
7205 	protected void activeScreenPainter(ScreenPainterImplementation*);
7206 	bool closed();
7207 
7208 	void delegate() paintingFinishedDg();
7209 
7210 	/// Be warned: this can be a very slow operation
7211 	TrueColorImage takeScreenshot();
7212 }
7213 
7214 /// Flushes any pending gui buffers. Necessary if you are using with_eventloop with X - flush after you create your windows but before you call loop()
7215 void flushGui() {
7216 	version(X11) {
7217 		auto dpy = XDisplayConnection.get();
7218 		XLockDisplay(dpy);
7219 		scope(exit) XUnlockDisplay(dpy);
7220 		XFlush(dpy);
7221 	}
7222 }
7223 
7224 /++
7225 	Runs the given code in the GUI thread when its event loop
7226 	is available, blocking until it completes. This allows you
7227 	to create and manipulate windows from another thread without
7228 	invoking undefined behavior.
7229 
7230 	If this is the gui thread, it runs the code immediately.
7231 
7232 	If no gui thread exists yet, the current thread is assumed
7233 	to be it. Attempting to create windows or run the event loop
7234 	in any other thread will cause an assertion failure.
7235 
7236 
7237 	$(TIP
7238 		Did you know you can use UFCS on delegate literals?
7239 
7240 		() {
7241 			// code here
7242 		}.runInGuiThread;
7243 	)
7244 
7245 	History:
7246 		Added April 10, 2020 (v7.2.0)
7247 +/
7248 void runInGuiThread(scope void delegate() dg) @trusted {
7249 	claimGuiThread();
7250 
7251 	if(thisIsGuiThread) {
7252 		dg();
7253 		return;
7254 	}
7255 
7256 	import core.sync.semaphore;
7257 	static Semaphore sc;
7258 	if(sc is null)
7259 		sc = new Semaphore();
7260 
7261 	static RunQueueMember* rqm;
7262 	if(rqm is null)
7263 		rqm = new RunQueueMember;
7264 	rqm.dg = cast(typeof(rqm.dg)) dg;
7265 	rqm.signal = sc;
7266 	rqm.thrown = null;
7267 
7268 	synchronized(runInGuiThreadLock) {
7269 		runInGuiThreadQueue ~= rqm;
7270 	}
7271 
7272 	if(!SimpleWindow.eventWakeUp())
7273 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
7274 
7275 	rqm.signal.wait();
7276 
7277 	if(rqm.thrown)
7278 		throw rqm.thrown;
7279 }
7280 
7281 private void claimGuiThread() {
7282 	import core.atomic;
7283 	if(cas(&guiThreadExists, false, true))
7284 		thisIsGuiThread = true;
7285 }
7286 
7287 private struct RunQueueMember {
7288 	void delegate() dg;
7289 	import core.sync.semaphore;
7290 	Semaphore signal;
7291 	Throwable thrown;
7292 }
7293 
7294 private __gshared RunQueueMember*[] runInGuiThreadQueue;
7295 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
7296 private bool thisIsGuiThread = false;
7297 private shared bool guiThreadExists = false;
7298 
7299 /// Used internal to dispatch events to various classes.
7300 interface CapableOfHandlingNativeEvent {
7301 	NativeEventHandler getNativeEventHandler();
7302 
7303 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
7304 
7305 	version(X11) {
7306 		// if this is impossible, you are allowed to just throw from it
7307 		// Note: if you call it from another object, set a flag cuz the manger will call you again
7308 		void recreateAfterDisconnect();
7309 		// discard any *connection specific* state, but keep enough that you
7310 		// can be recreated if possible. discardConnectionState() is always called immediately
7311 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
7312 		// you need initialization order
7313 		void discardConnectionState();
7314 	}
7315 }
7316 
7317 version(X11)
7318 /++
7319 	State of keys on mouse events, especially motion.
7320 
7321 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
7322 +/
7323 enum ModifierState : uint {
7324 	shift = 1, ///
7325 	capsLock = 2, ///
7326 	ctrl = 4, ///
7327 	alt = 8, /// Not always available on Windows
7328 	windows = 64, /// ditto
7329 	numLock = 16, ///
7330 
7331 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
7332 	middleButtonDown = 512, /// ditto
7333 	rightButtonDown = 1024, /// ditto
7334 }
7335 else version(Windows)
7336 /// ditto
7337 enum ModifierState : uint {
7338 	shift = 4, ///
7339 	ctrl = 8, ///
7340 
7341 	// i'm not sure if the next two are available
7342 	alt = 256, /// not always available on Windows
7343 	windows = 512, /// ditto
7344 
7345 	capsLock = 1024, ///
7346 	numLock = 2048, ///
7347 
7348 	leftButtonDown = 1, /// not available on key events
7349 	middleButtonDown = 16, /// ditto
7350 	rightButtonDown = 2, /// ditto
7351 
7352 	backButtonDown = 0x20, /// not available on X
7353 	forwardButtonDown = 0x40, /// ditto
7354 }
7355 else version(OSXCocoa)
7356 // FIXME FIXME NotYetImplementedException
7357 enum ModifierState : uint {
7358 	shift = 1, ///
7359 	capsLock = 2, ///
7360 	ctrl = 4, ///
7361 	alt = 8, /// Not always available on Windows
7362 	windows = 64, /// ditto
7363 	numLock = 16, ///
7364 
7365 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
7366 	middleButtonDown = 512, /// ditto
7367 	rightButtonDown = 1024, /// ditto
7368 }
7369 
7370 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them
7371 enum MouseButton : int {
7372 	none = 0,
7373 	left = 1, ///
7374 	right = 2, ///
7375 	middle = 4, ///
7376 	wheelUp = 8, ///
7377 	wheelDown = 16, ///
7378 	backButton = 32, /// often found on the thumb and used for back in browsers
7379 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
7380 }
7381 
7382 version(X11) {
7383 	// FIXME: match ASCII whenever we can. Most of it is already there,
7384 	// but there's a few exceptions and mismatches with Windows
7385 
7386 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
7387 	enum Key {
7388 		Escape = 0xff1b, ///
7389 		F1 = 0xffbe, ///
7390 		F2 = 0xffbf, ///
7391 		F3 = 0xffc0, ///
7392 		F4 = 0xffc1, ///
7393 		F5 = 0xffc2, ///
7394 		F6 = 0xffc3, ///
7395 		F7 = 0xffc4, ///
7396 		F8 = 0xffc5, ///
7397 		F9 = 0xffc6, ///
7398 		F10 = 0xffc7, ///
7399 		F11 = 0xffc8, ///
7400 		F12 = 0xffc9, ///
7401 		PrintScreen = 0xff61, ///
7402 		ScrollLock = 0xff14, ///
7403 		Pause = 0xff13, ///
7404 		Grave = 0x60, /// The $(BACKTICK) ~ key
7405 		// number keys across the top of the keyboard
7406 		N1 = 0x31, /// Number key atop the keyboard
7407 		N2 = 0x32, ///
7408 		N3 = 0x33, ///
7409 		N4 = 0x34, ///
7410 		N5 = 0x35, ///
7411 		N6 = 0x36, ///
7412 		N7 = 0x37, ///
7413 		N8 = 0x38, ///
7414 		N9 = 0x39, ///
7415 		N0 = 0x30, ///
7416 		Dash = 0x2d, ///
7417 		Equals = 0x3d, ///
7418 		Backslash = 0x5c, /// The \ | key
7419 		Backspace = 0xff08, ///
7420 		Insert = 0xff63, ///
7421 		Home = 0xff50, ///
7422 		PageUp = 0xff55, ///
7423 		Delete = 0xffff, ///
7424 		End = 0xff57, ///
7425 		PageDown = 0xff56, ///
7426 		Up = 0xff52, ///
7427 		Down = 0xff54, ///
7428 		Left = 0xff51, ///
7429 		Right = 0xff53, ///
7430 
7431 		Tab = 0xff09, ///
7432 		Q = 0x71, ///
7433 		W = 0x77, ///
7434 		E = 0x65, ///
7435 		R = 0x72, ///
7436 		T = 0x74, ///
7437 		Y = 0x79, ///
7438 		U = 0x75, ///
7439 		I = 0x69, ///
7440 		O = 0x6f, ///
7441 		P = 0x70, ///
7442 		LeftBracket = 0x5b, /// the [ { key
7443 		RightBracket = 0x5d, /// the ] } key
7444 		CapsLock = 0xffe5, ///
7445 		A = 0x61, ///
7446 		S = 0x73, ///
7447 		D = 0x64, ///
7448 		F = 0x66, ///
7449 		G = 0x67, ///
7450 		H = 0x68, ///
7451 		J = 0x6a, ///
7452 		K = 0x6b, ///
7453 		L = 0x6c, ///
7454 		Semicolon = 0x3b, ///
7455 		Apostrophe = 0x27, ///
7456 		Enter = 0xff0d, ///
7457 		Shift = 0xffe1, ///
7458 		Z = 0x7a, ///
7459 		X = 0x78, ///
7460 		C = 0x63, ///
7461 		V = 0x76, ///
7462 		B = 0x62, ///
7463 		N = 0x6e, ///
7464 		M = 0x6d, ///
7465 		Comma = 0x2c, ///
7466 		Period = 0x2e, ///
7467 		Slash = 0x2f, /// the / ? key
7468 		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
7469 		Ctrl = 0xffe3, ///
7470 		Windows = 0xffeb, ///
7471 		Alt = 0xffe9, ///
7472 		Space = 0x20, ///
7473 		Alt_r = 0xffea, /// ditto of shift_r
7474 		Windows_r = 0xffec, ///
7475 		Menu = 0xff67, ///
7476 		Ctrl_r = 0xffe4, ///
7477 
7478 		NumLock = 0xff7f, ///
7479 		Divide = 0xffaf, /// The / key on the number pad
7480 		Multiply = 0xffaa, /// The * key on the number pad
7481 		Minus = 0xffad, /// The - key on the number pad
7482 		Plus = 0xffab, /// The + key on the number pad
7483 		PadEnter = 0xff8d, /// Numberpad enter key
7484 		Pad1 = 0xff9c, /// Numberpad keys
7485 		Pad2 = 0xff99, ///
7486 		Pad3 = 0xff9b, ///
7487 		Pad4 = 0xff96, ///
7488 		Pad5 = 0xff9d, ///
7489 		Pad6 = 0xff98, ///
7490 		Pad7 = 0xff95, ///
7491 		Pad8 = 0xff97, ///
7492 		Pad9 = 0xff9a, ///
7493 		Pad0 = 0xff9e, ///
7494 		PadDot = 0xff9f, ///
7495 	}
7496 } else version(Windows) {
7497 	// the character here is for en-us layouts and for illustration only
7498 	// if you actually want to get characters, wait for character events
7499 	// (the argument to your event handler is simply a dchar)
7500 	// those will be converted by the OS for the right locale.
7501 
7502 	enum Key {
7503 		Escape = 0x1b,
7504 		F1 = 0x70,
7505 		F2 = 0x71,
7506 		F3 = 0x72,
7507 		F4 = 0x73,
7508 		F5 = 0x74,
7509 		F6 = 0x75,
7510 		F7 = 0x76,
7511 		F8 = 0x77,
7512 		F9 = 0x78,
7513 		F10 = 0x79,
7514 		F11 = 0x7a,
7515 		F12 = 0x7b,
7516 		PrintScreen = 0x2c,
7517 		ScrollLock = 0x91,
7518 		Pause = 0x13,
7519 		Grave = 0xc0,
7520 		// number keys across the top of the keyboard
7521 		N1 = 0x31,
7522 		N2 = 0x32,
7523 		N3 = 0x33,
7524 		N4 = 0x34,
7525 		N5 = 0x35,
7526 		N6 = 0x36,
7527 		N7 = 0x37,
7528 		N8 = 0x38,
7529 		N9 = 0x39,
7530 		N0 = 0x30,
7531 		Dash = 0xbd,
7532 		Equals = 0xbb,
7533 		Backslash = 0xdc,
7534 		Backspace = 0x08,
7535 		Insert = 0x2d,
7536 		Home = 0x24,
7537 		PageUp = 0x21,
7538 		Delete = 0x2e,
7539 		End = 0x23,
7540 		PageDown = 0x22,
7541 		Up = 0x26,
7542 		Down = 0x28,
7543 		Left = 0x25,
7544 		Right = 0x27,
7545 
7546 		Tab = 0x09,
7547 		Q = 0x51,
7548 		W = 0x57,
7549 		E = 0x45,
7550 		R = 0x52,
7551 		T = 0x54,
7552 		Y = 0x59,
7553 		U = 0x55,
7554 		I = 0x49,
7555 		O = 0x4f,
7556 		P = 0x50,
7557 		LeftBracket = 0xdb,
7558 		RightBracket = 0xdd,
7559 		CapsLock = 0x14,
7560 		A = 0x41,
7561 		S = 0x53,
7562 		D = 0x44,
7563 		F = 0x46,
7564 		G = 0x47,
7565 		H = 0x48,
7566 		J = 0x4a,
7567 		K = 0x4b,
7568 		L = 0x4c,
7569 		Semicolon = 0xba,
7570 		Apostrophe = 0xde,
7571 		Enter = 0x0d,
7572 		Shift = 0x10,
7573 		Z = 0x5a,
7574 		X = 0x58,
7575 		C = 0x43,
7576 		V = 0x56,
7577 		B = 0x42,
7578 		N = 0x4e,
7579 		M = 0x4d,
7580 		Comma = 0xbc,
7581 		Period = 0xbe,
7582 		Slash = 0xbf,
7583 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
7584 		Ctrl = 0x11,
7585 		Windows = 0x5b,
7586 		Alt = -5, // FIXME
7587 		Space = 0x20,
7588 		Alt_r = 0xffea, // ditto of shift_r
7589 		Windows_r = 0x5c, // ditto of shift_r
7590 		Menu = 0x5d,
7591 		Ctrl_r = 0xa3, // ditto of shift_r
7592 
7593 		NumLock = 0x90,
7594 		Divide = 0x6f,
7595 		Multiply = 0x6a,
7596 		Minus = 0x6d,
7597 		Plus = 0x6b,
7598 		PadEnter = -8, // FIXME
7599 		Pad1 = 0x61,
7600 		Pad2 = 0x62,
7601 		Pad3 = 0x63,
7602 		Pad4 = 0x64,
7603 		Pad5 = 0x65,
7604 		Pad6 = 0x66,
7605 		Pad7 = 0x67,
7606 		Pad8 = 0x68,
7607 		Pad9 = 0x69,
7608 		Pad0 = 0x60,
7609 		PadDot = 0x6e,
7610 	}
7611 
7612 	// I'm keeping this around for reference purposes
7613 	// ideally all these buttons will be listed for all platforms,
7614 	// but now now I'm just focusing on my US keyboard
7615 	version(none)
7616 	enum Key {
7617 		LBUTTON = 0x01,
7618 		RBUTTON = 0x02,
7619 		CANCEL = 0x03,
7620 		MBUTTON = 0x04,
7621 		//static if (_WIN32_WINNT > =  0x500) {
7622 		XBUTTON1 = 0x05,
7623 		XBUTTON2 = 0x06,
7624 		//}
7625 		BACK = 0x08,
7626 		TAB = 0x09,
7627 		CLEAR = 0x0C,
7628 		RETURN = 0x0D,
7629 		SHIFT = 0x10,
7630 		CONTROL = 0x11,
7631 		MENU = 0x12,
7632 		PAUSE = 0x13,
7633 		CAPITAL = 0x14,
7634 		KANA = 0x15,
7635 		HANGEUL = 0x15,
7636 		HANGUL = 0x15,
7637 		JUNJA = 0x17,
7638 		FINAL = 0x18,
7639 		HANJA = 0x19,
7640 		KANJI = 0x19,
7641 		ESCAPE = 0x1B,
7642 		CONVERT = 0x1C,
7643 		NONCONVERT = 0x1D,
7644 		ACCEPT = 0x1E,
7645 		MODECHANGE = 0x1F,
7646 		SPACE = 0x20,
7647 		PRIOR = 0x21,
7648 		NEXT = 0x22,
7649 		END = 0x23,
7650 		HOME = 0x24,
7651 		LEFT = 0x25,
7652 		UP = 0x26,
7653 		RIGHT = 0x27,
7654 		DOWN = 0x28,
7655 		SELECT = 0x29,
7656 		PRINT = 0x2A,
7657 		EXECUTE = 0x2B,
7658 		SNAPSHOT = 0x2C,
7659 		INSERT = 0x2D,
7660 		DELETE = 0x2E,
7661 		HELP = 0x2F,
7662 		LWIN = 0x5B,
7663 		RWIN = 0x5C,
7664 		APPS = 0x5D,
7665 		SLEEP = 0x5F,
7666 		NUMPAD0 = 0x60,
7667 		NUMPAD1 = 0x61,
7668 		NUMPAD2 = 0x62,
7669 		NUMPAD3 = 0x63,
7670 		NUMPAD4 = 0x64,
7671 		NUMPAD5 = 0x65,
7672 		NUMPAD6 = 0x66,
7673 		NUMPAD7 = 0x67,
7674 		NUMPAD8 = 0x68,
7675 		NUMPAD9 = 0x69,
7676 		MULTIPLY = 0x6A,
7677 		ADD = 0x6B,
7678 		SEPARATOR = 0x6C,
7679 		SUBTRACT = 0x6D,
7680 		DECIMAL = 0x6E,
7681 		DIVIDE = 0x6F,
7682 		F1 = 0x70,
7683 		F2 = 0x71,
7684 		F3 = 0x72,
7685 		F4 = 0x73,
7686 		F5 = 0x74,
7687 		F6 = 0x75,
7688 		F7 = 0x76,
7689 		F8 = 0x77,
7690 		F9 = 0x78,
7691 		F10 = 0x79,
7692 		F11 = 0x7A,
7693 		F12 = 0x7B,
7694 		F13 = 0x7C,
7695 		F14 = 0x7D,
7696 		F15 = 0x7E,
7697 		F16 = 0x7F,
7698 		F17 = 0x80,
7699 		F18 = 0x81,
7700 		F19 = 0x82,
7701 		F20 = 0x83,
7702 		F21 = 0x84,
7703 		F22 = 0x85,
7704 		F23 = 0x86,
7705 		F24 = 0x87,
7706 		NUMLOCK = 0x90,
7707 		SCROLL = 0x91,
7708 		LSHIFT = 0xA0,
7709 		RSHIFT = 0xA1,
7710 		LCONTROL = 0xA2,
7711 		RCONTROL = 0xA3,
7712 		LMENU = 0xA4,
7713 		RMENU = 0xA5,
7714 		//static if (_WIN32_WINNT > =  0x500) {
7715 		BROWSER_BACK = 0xA6,
7716 		BROWSER_FORWARD = 0xA7,
7717 		BROWSER_REFRESH = 0xA8,
7718 		BROWSER_STOP = 0xA9,
7719 		BROWSER_SEARCH = 0xAA,
7720 		BROWSER_FAVORITES = 0xAB,
7721 		BROWSER_HOME = 0xAC,
7722 		VOLUME_MUTE = 0xAD,
7723 		VOLUME_DOWN = 0xAE,
7724 		VOLUME_UP = 0xAF,
7725 		MEDIA_NEXT_TRACK = 0xB0,
7726 		MEDIA_PREV_TRACK = 0xB1,
7727 		MEDIA_STOP = 0xB2,
7728 		MEDIA_PLAY_PAUSE = 0xB3,
7729 		LAUNCH_MAIL = 0xB4,
7730 		LAUNCH_MEDIA_SELECT = 0xB5,
7731 		LAUNCH_APP1 = 0xB6,
7732 		LAUNCH_APP2 = 0xB7,
7733 		//}
7734 		OEM_1 = 0xBA,
7735 		//static if (_WIN32_WINNT > =  0x500) {
7736 		OEM_PLUS = 0xBB,
7737 		OEM_COMMA = 0xBC,
7738 		OEM_MINUS = 0xBD,
7739 		OEM_PERIOD = 0xBE,
7740 		//}
7741 		OEM_2 = 0xBF,
7742 		OEM_3 = 0xC0,
7743 		OEM_4 = 0xDB,
7744 		OEM_5 = 0xDC,
7745 		OEM_6 = 0xDD,
7746 		OEM_7 = 0xDE,
7747 		OEM_8 = 0xDF,
7748 		//static if (_WIN32_WINNT > =  0x500) {
7749 		OEM_102 = 0xE2,
7750 		//}
7751 		PROCESSKEY = 0xE5,
7752 		//static if (_WIN32_WINNT > =  0x500) {
7753 		PACKET = 0xE7,
7754 		//}
7755 		ATTN = 0xF6,
7756 		CRSEL = 0xF7,
7757 		EXSEL = 0xF8,
7758 		EREOF = 0xF9,
7759 		PLAY = 0xFA,
7760 		ZOOM = 0xFB,
7761 		NONAME = 0xFC,
7762 		PA1 = 0xFD,
7763 		OEM_CLEAR = 0xFE,
7764 	}
7765 
7766 } else version(OSXCocoa) {
7767 	// FIXME
7768 	enum Key {
7769 		Escape = 0x1b,
7770 		F1 = 0x70,
7771 		F2 = 0x71,
7772 		F3 = 0x72,
7773 		F4 = 0x73,
7774 		F5 = 0x74,
7775 		F6 = 0x75,
7776 		F7 = 0x76,
7777 		F8 = 0x77,
7778 		F9 = 0x78,
7779 		F10 = 0x79,
7780 		F11 = 0x7a,
7781 		F12 = 0x7b,
7782 		PrintScreen = 0x2c,
7783 		ScrollLock = -2, // FIXME
7784 		Pause = -3, // FIXME
7785 		Grave = 0xc0,
7786 		// number keys across the top of the keyboard
7787 		N1 = 0x31,
7788 		N2 = 0x32,
7789 		N3 = 0x33,
7790 		N4 = 0x34,
7791 		N5 = 0x35,
7792 		N6 = 0x36,
7793 		N7 = 0x37,
7794 		N8 = 0x38,
7795 		N9 = 0x39,
7796 		N0 = 0x30,
7797 		Dash = 0xbd,
7798 		Equals = 0xbb,
7799 		Backslash = 0xdc,
7800 		Backspace = 0x08,
7801 		Insert = 0x2d,
7802 		Home = 0x24,
7803 		PageUp = 0x21,
7804 		Delete = 0x2e,
7805 		End = 0x23,
7806 		PageDown = 0x22,
7807 		Up = 0x26,
7808 		Down = 0x28,
7809 		Left = 0x25,
7810 		Right = 0x27,
7811 
7812 		Tab = 0x09,
7813 		Q = 0x51,
7814 		W = 0x57,
7815 		E = 0x45,
7816 		R = 0x52,
7817 		T = 0x54,
7818 		Y = 0x59,
7819 		U = 0x55,
7820 		I = 0x49,
7821 		O = 0x4f,
7822 		P = 0x50,
7823 		LeftBracket = 0xdb,
7824 		RightBracket = 0xdd,
7825 		CapsLock = 0x14,
7826 		A = 0x41,
7827 		S = 0x53,
7828 		D = 0x44,
7829 		F = 0x46,
7830 		G = 0x47,
7831 		H = 0x48,
7832 		J = 0x4a,
7833 		K = 0x4b,
7834 		L = 0x4c,
7835 		Semicolon = 0xba,
7836 		Apostrophe = 0xde,
7837 		Enter = 0x0d,
7838 		Shift = 0x10,
7839 		Z = 0x5a,
7840 		X = 0x58,
7841 		C = 0x43,
7842 		V = 0x56,
7843 		B = 0x42,
7844 		N = 0x4e,
7845 		M = 0x4d,
7846 		Comma = 0xbc,
7847 		Period = 0xbe,
7848 		Slash = 0xbf,
7849 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
7850 		Ctrl = 0x11,
7851 		Windows = 0x5b,
7852 		Alt = -5, // FIXME
7853 		Space = 0x20,
7854 		Alt_r = 0xffea, // ditto of shift_r
7855 		Windows_r = -6, // FIXME
7856 		Menu = 0x5d,
7857 		Ctrl_r = -7, // FIXME
7858 
7859 		NumLock = 0x90,
7860 		Divide = 0x6f,
7861 		Multiply = 0x6a,
7862 		Minus = 0x6d,
7863 		Plus = 0x6b,
7864 		PadEnter = -8, // FIXME
7865 		// FIXME for the rest of these:
7866 		Pad1 = 0xff9c,
7867 		Pad2 = 0xff99,
7868 		Pad3 = 0xff9b,
7869 		Pad4 = 0xff96,
7870 		Pad5 = 0xff9d,
7871 		Pad6 = 0xff98,
7872 		Pad7 = 0xff95,
7873 		Pad8 = 0xff97,
7874 		Pad9 = 0xff9a,
7875 		Pad0 = 0xff9e,
7876 		PadDot = 0xff9f,
7877 	}
7878 
7879 }
7880 
7881 /* Additional utilities */
7882 
7883 
7884 Color fromHsl(real h, real s, real l) {
7885 	return arsd.color.fromHsl([h,s,l]);
7886 }
7887 
7888 
7889 
7890 /* ********** What follows is the system-specific implementations *********/
7891 version(Windows) {
7892 
7893 
7894 	// helpers for making HICONs from MemoryImages
7895 	class WindowsIcon {
7896 		struct Win32Icon(int colorCount) {
7897 		align(1):
7898 			uint biSize;
7899 			int biWidth;
7900 			int biHeight;
7901 			ushort biPlanes;
7902 			ushort biBitCount;
7903 			uint biCompression;
7904 			uint biSizeImage;
7905 			int biXPelsPerMeter;
7906 			int biYPelsPerMeter;
7907 			uint biClrUsed;
7908 			uint biClrImportant;
7909 			RGBQUAD[colorCount] biColors;
7910 			/* Pixels:
7911 			Uint8 pixels[]
7912 			*/
7913 			/* Mask:
7914 			Uint8 mask[]
7915 			*/
7916 
7917 			ubyte[4096] data;
7918 
7919 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
7920 				width = mi.width;
7921 				height = mi.height;
7922 
7923 				auto indexedImage = cast(IndexedImage) mi;
7924 				if(indexedImage is null)
7925 					indexedImage = quantize(mi.getAsTrueColorImage());
7926 
7927 				assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
7928 				assert(height %4 == 0);
7929 
7930 				int icon_plen = height*((width+3)&~3);
7931 				int icon_mlen = height*((((width+7)/8)+3)&~3);
7932 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
7933 
7934 				biSize = 40;
7935 				biWidth = width;
7936 				biHeight = height*2;
7937 				biPlanes = 1;
7938 				biBitCount = 8;
7939 				biSizeImage = icon_plen+icon_mlen;
7940 
7941 				int offset = 0;
7942 				int andOff = icon_plen * 8; // the and offset is in bits
7943 				for(int y = height - 1; y >= 0; y--) {
7944 					int off2 = y * width;
7945 					foreach(x; 0 .. width) {
7946 						const b = indexedImage.data[off2 + x];
7947 						data[offset] = b;
7948 						offset++;
7949 
7950 						const andBit = andOff % 8;
7951 						const andIdx = andOff / 8;
7952 						assert(b < indexedImage.palette.length);
7953 						// this is anded to the destination, since and 0 means erase,
7954 						// we want that to  be opaque, and 1 for transparent
7955 						auto transparent = (indexedImage.palette[b].a <= 127);
7956 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
7957 
7958 						andOff++;
7959 					}
7960 
7961 					andOff += andOff % 32;
7962 				}
7963 
7964 				foreach(idx, entry; indexedImage.palette) {
7965 					if(entry.a > 127) {
7966 						biColors[idx].rgbBlue = entry.b;
7967 						biColors[idx].rgbGreen = entry.g;
7968 						biColors[idx].rgbRed = entry.r;
7969 					} else {
7970 						biColors[idx].rgbBlue = 255;
7971 						biColors[idx].rgbGreen = 255;
7972 						biColors[idx].rgbRed = 255;
7973 					}
7974 				}
7975 
7976 				/*
7977 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
7978 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
7979 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
7980 				auto pngMap = fetchPaletteWin32(png);
7981 				biColors[0..pngMap.length] = pngMap[];
7982 				*/
7983 			}
7984 		}
7985 
7986 
7987 		Win32Icon!(256) icon_win32;
7988 
7989 
7990 		this(MemoryImage mi) {
7991 			int icon_len, width, height;
7992 
7993 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
7994 
7995 			/*
7996 			PNG* png = readPnpngData);
7997 			PNGHeader pngh = getHeader(png);
7998 			void* icon_win32;
7999 			if(pngh.depth == 4) {
8000 				auto i = new Win32Icon!(16);
8001 				i.fromPNG(png, pngh, icon_len, width, height);
8002 				icon_win32 = i;
8003 			}
8004 			else if(pngh.depth == 8) {
8005 				auto i = new Win32Icon!(256);
8006 				i.fromPNG(png, pngh, icon_len, width, height);
8007 				icon_win32 = i;
8008 			} else assert(0);
8009 			*/
8010 
8011 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
8012 
8013 			if(hIcon is null) throw new Exception("CreateIconFromResourceEx");
8014 		}
8015 
8016 		~this() {
8017 			DestroyIcon(hIcon);
8018 		}
8019 
8020 		HICON hIcon;
8021 	}
8022 
8023 
8024 
8025 
8026 
8027 
8028 	alias int delegate(HWND, UINT, WPARAM, LPARAM) NativeEventHandler;
8029 	alias HWND NativeWindowHandle;
8030 
8031 	extern(Windows)
8032 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
8033 		try {
8034 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
8035 				// it returns zero if the message is handled, so we won't do anything more there
8036 				// do I like that though?
8037 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam);
8038 				if(ret == 0)
8039 					return ret;
8040 			}
8041 
8042 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
8043 				if(window.getNativeEventHandler !is null) {
8044 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam);
8045 					if(ret == 0)
8046 						return ret;
8047 				}
8048 				if(auto w = cast(SimpleWindow) (*window))
8049 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
8050 				else
8051 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
8052 			} else {
8053 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
8054 			}
8055 		} catch (Exception e) {
8056 			assert(false, "Exception caught in WndProc " ~ e.toString());
8057 		}
8058 	}
8059 
8060 	mixin template NativeScreenPainterImplementation() {
8061 		HDC hdc;
8062 		HWND hwnd;
8063 		//HDC windowHdc;
8064 		HBITMAP oldBmp;
8065 
8066 		void create(NativeWindowHandle window) {
8067 			hwnd = window;
8068 
8069 			if(auto sw = cast(SimpleWindow) this.window) {
8070 				// drawing on a window, double buffer
8071 				auto windowHdc = GetDC(hwnd);
8072 
8073 				auto buffer = sw.impl.buffer;
8074 				hdc = CreateCompatibleDC(windowHdc);
8075 
8076 				ReleaseDC(hwnd, windowHdc);
8077 
8078 				oldBmp = SelectObject(hdc, buffer);
8079 			} else {
8080 				// drawing on something else, draw directly
8081 				hdc = CreateCompatibleDC(null);
8082 				SelectObject(hdc, window);
8083 
8084 			}
8085 
8086 			// X doesn't draw a text background, so neither should we
8087 			SetBkMode(hdc, TRANSPARENT);
8088 
8089 
8090 			static bool triedDefaultGuiFont = false;
8091 			if(!triedDefaultGuiFont) {
8092 				NONCLIENTMETRICS params;
8093 				params.cbSize = params.sizeof;
8094 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
8095 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
8096 				}
8097 				triedDefaultGuiFont = true;
8098 			}
8099 
8100 			if(defaultGuiFont) {
8101 				SelectObject(hdc, defaultGuiFont);
8102 				// DeleteObject(defaultGuiFont);
8103 			}
8104 		}
8105 
8106 		static HFONT defaultGuiFont;
8107 
8108 		void setFont(OperatingSystemFont font) {
8109 			if(font && font.font) {
8110 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
8111 					// error... how to handle tho?
8112 				}
8113 			}
8114 			else if(defaultGuiFont)
8115 				SelectObject(hdc, defaultGuiFont);
8116 		}
8117 
8118 		arsd.color.Rectangle _clipRectangle;
8119 
8120 		void setClipRectangle(int x, int y, int width, int height) {
8121 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
8122 
8123 			if(width == 0 || height == 0) {
8124 				SelectClipRgn(hdc, null);
8125 			} else {
8126 				auto region = CreateRectRgn(x, y, x + width, y + height);
8127 				SelectClipRgn(hdc, region);
8128 				DeleteObject(region);
8129 			}
8130 		}
8131 
8132 
8133 		// just because we can on Windows...
8134 		//void create(Image image);
8135 
8136 		void dispose() {
8137 			// FIXME: this.window.width/height is probably wrong
8138 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
8139 			// ReleaseDC(hwnd, windowHdc);
8140 
8141 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
8142 			if(cast(SimpleWindow) this.window)
8143 			InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
8144 
8145 			if(originalPen !is null)
8146 				SelectObject(hdc, originalPen);
8147 			if(currentPen !is null)
8148 				DeleteObject(currentPen);
8149 			if(originalBrush !is null)
8150 				SelectObject(hdc, originalBrush);
8151 			if(currentBrush !is null)
8152 				DeleteObject(currentBrush);
8153 
8154 			SelectObject(hdc, oldBmp);
8155 
8156 			DeleteDC(hdc);
8157 
8158 			if(window.paintingFinishedDg !is null)
8159 				window.paintingFinishedDg();
8160 		}
8161 
8162 		HPEN originalPen;
8163 		HPEN currentPen;
8164 
8165 		Pen _activePen;
8166 
8167 		@property void pen(Pen p) {
8168 			_activePen = p;
8169 
8170 			HPEN pen;
8171 			if(p.color.a == 0) {
8172 				pen = GetStockObject(NULL_PEN);
8173 			} else {
8174 				int style = PS_SOLID;
8175 				final switch(p.style) {
8176 					case Pen.Style.Solid:
8177 						style = PS_SOLID;
8178 					break;
8179 					case Pen.Style.Dashed:
8180 						style = PS_DASH;
8181 					break;
8182 					case Pen.Style.Dotted:
8183 						style = PS_DOT;
8184 					break;
8185 				}
8186 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
8187 			}
8188 			auto orig = SelectObject(hdc, pen);
8189 			if(originalPen is null)
8190 				originalPen = orig;
8191 
8192 			if(currentPen !is null)
8193 				DeleteObject(currentPen);
8194 
8195 			currentPen = pen;
8196 
8197 			// the outline is like a foreground since it's done that way on X
8198 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
8199 
8200 		}
8201 
8202 		@property void rasterOp(RasterOp op) {
8203 			int mode;
8204 			final switch(op) {
8205 				case RasterOp.normal:
8206 					mode = R2_COPYPEN;
8207 				break;
8208 				case RasterOp.xor:
8209 					mode = R2_XORPEN;
8210 				break;
8211 			}
8212 			SetROP2(hdc, mode);
8213 		}
8214 
8215 		HBRUSH originalBrush;
8216 		HBRUSH currentBrush;
8217 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
8218 		@property void fillColor(Color c) {
8219 			if(c == _fillColor)
8220 				return;
8221 			_fillColor = c;
8222 			HBRUSH brush;
8223 			if(c.a == 0) {
8224 				brush = GetStockObject(HOLLOW_BRUSH);
8225 			} else {
8226 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
8227 			}
8228 			auto orig = SelectObject(hdc, brush);
8229 			if(originalBrush is null)
8230 				originalBrush = orig;
8231 
8232 			if(currentBrush !is null)
8233 				DeleteObject(currentBrush);
8234 
8235 			currentBrush = brush;
8236 
8237 			// background color is NOT set because X doesn't draw text backgrounds
8238 			//   SetBkColor(hdc, RGB(255, 255, 255));
8239 		}
8240 
8241 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
8242 			BITMAP bm;
8243 
8244 			HDC hdcMem = CreateCompatibleDC(hdc);
8245 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
8246 
8247 			GetObject(i.handle, bm.sizeof, &bm);
8248 
8249 			// or should I AlphaBlend!??!?!
8250 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
8251 
8252 			SelectObject(hdcMem, hbmOld);
8253 			DeleteDC(hdcMem);
8254 		}
8255 
8256 		void drawPixmap(Sprite s, int x, int y) {
8257 			BITMAP bm;
8258 
8259 			HDC hdcMem = CreateCompatibleDC(hdc);
8260 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
8261 
8262 			GetObject(s.handle, bm.sizeof, &bm);
8263 
8264 			// or should I AlphaBlend!??!?!
8265 			BitBlt(hdc, x, y, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
8266 
8267 			SelectObject(hdcMem, hbmOld);
8268 			DeleteDC(hdcMem);
8269 		}
8270 
8271 		Size textSize(scope const(char)[] text) {
8272 			bool dummyX;
8273 			if(text.length == 0) {
8274 				text = " ";
8275 				dummyX = true;
8276 			}
8277 			RECT rect;
8278 			WCharzBuffer buffer = WCharzBuffer(text);
8279 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT);
8280 			return Size(dummyX ? 0 : rect.right, rect.bottom);
8281 		}
8282 
8283 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
8284 			if(text.length && text[$-1] == '\n')
8285 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
8286 
8287 			WCharzBuffer buffer = WCharzBuffer(text);
8288 			if(x2 == 0 && y2 == 0)
8289 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
8290 			else {
8291 				RECT rect;
8292 				rect.left = x;
8293 				rect.top = y;
8294 				rect.right = x2;
8295 				rect.bottom = y2;
8296 
8297 				uint mode = DT_LEFT;
8298 				if(alignment & TextAlignment.Right)
8299 					mode = DT_RIGHT;
8300 				else if(alignment & TextAlignment.Center)
8301 					mode = DT_CENTER;
8302 
8303 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
8304 				if(alignment & TextAlignment.VerticalCenter)
8305 					mode |= DT_VCENTER | DT_SINGLELINE;
8306 
8307 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode);
8308 			}
8309 
8310 			/*
8311 			uint mode;
8312 
8313 			if(alignment & TextAlignment.Center)
8314 				mode = TA_CENTER;
8315 
8316 			SetTextAlign(hdc, mode);
8317 			*/
8318 		}
8319 
8320 		int fontHeight() {
8321 			TEXTMETRIC metric;
8322 			if(GetTextMetricsW(hdc, &metric)) {
8323 				return metric.tmHeight;
8324 			}
8325 
8326 			return 16; // idk just guessing here, maybe we should throw
8327 		}
8328 
8329 		void drawPixel(int x, int y) {
8330 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
8331 		}
8332 
8333 		// The basic shapes, outlined
8334 
8335 		void drawLine(int x1, int y1, int x2, int y2) {
8336 			MoveToEx(hdc, x1, y1, null);
8337 			LineTo(hdc, x2, y2);
8338 		}
8339 
8340 		void drawRectangle(int x, int y, int width, int height) {
8341 			gdi.Rectangle(hdc, x, y, x + width, y + height);
8342 		}
8343 
8344 		/// Arguments are the points of the bounding rectangle
8345 		void drawEllipse(int x1, int y1, int x2, int y2) {
8346 			Ellipse(hdc, x1, y1, x2, y2);
8347 		}
8348 
8349 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
8350 			if((start % (360*64)) == (finish % (360*64)))
8351 				drawEllipse(x1, y1, x1 + width, y1 + height);
8352 			else {
8353 				import core.stdc.math;
8354 				float startAngle = start * 64 * 180 / 3.14159265;
8355 				float endAngle = finish * 64 * 180 / 3.14159265;
8356 				Arc(hdc, x1, y1, x1 + width, y1 + height,
8357 					cast(int)(cos(startAngle) * width / 2 + x1),
8358 					cast(int)(sin(startAngle) * height / 2 + y1),
8359 					cast(int)(cos(endAngle) * width / 2 + x1),
8360 					cast(int)(sin(endAngle) * height / 2 + y1),
8361 				);
8362 			}
8363 		}
8364 
8365 		void drawPolygon(Point[] vertexes) {
8366 			POINT[] points;
8367 			points.length = vertexes.length;
8368 
8369 			foreach(i, p; vertexes) {
8370 				points[i].x = p.x;
8371 				points[i].y = p.y;
8372 			}
8373 
8374 			Polygon(hdc, points.ptr, cast(int) points.length);
8375 		}
8376 	}
8377 
8378 
8379 	// Mix this into the SimpleWindow class
8380 	mixin template NativeSimpleWindowImplementation() {
8381 		int curHidden = 0; // counter
8382 		__gshared static bool[string] knownWinClasses;
8383 		static bool altPressed = false;
8384 
8385 		HANDLE oldCursor;
8386 
8387 		void hideCursor () {
8388 			if(curHidden == 0)
8389 				oldCursor = SetCursor(null);
8390 			++curHidden;
8391 		}
8392 
8393 		void showCursor () {
8394 			--curHidden;
8395 			if(curHidden == 0) {
8396 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
8397 			}
8398 		}
8399 
8400 
8401 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
8402 
8403 		void setMinSize (int minwidth, int minheight) {
8404 			minWidth = minwidth;
8405 			minHeight = minheight;
8406 		}
8407 		void setMaxSize (int maxwidth, int maxheight) {
8408 			maxWidth = maxwidth;
8409 			maxHeight = maxheight;
8410 		}
8411 
8412 		// FIXME i'm not sure that Windows has this functionality
8413 		// though it is nonessential anyway.
8414 		void setResizeGranularity (int granx, int grany) {}
8415 
8416 		ScreenPainter getPainter() {
8417 			return ScreenPainter(this, hwnd);
8418 		}
8419 
8420 		HBITMAP buffer;
8421 
8422 		void setTitle(string title) {
8423 			WCharzBuffer bfr = WCharzBuffer(title);
8424 			SetWindowTextW(hwnd, bfr.ptr);
8425 		}
8426 
8427 		string getTitle() {
8428 			auto len = GetWindowTextLengthW(hwnd);
8429 			if (!len)
8430 				return null;
8431 			wchar[256] tmpBuffer;
8432 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
8433 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
8434 			auto str = buffer[0 .. len2];
8435 			return makeUtf8StringFromWindowsString(str);
8436 		}
8437 
8438 		void move(int x, int y) {
8439 			RECT rect;
8440 			GetWindowRect(hwnd, &rect);
8441 			// move it while maintaining the same size...
8442 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
8443 		}
8444 
8445 		void resize(int w, int h) {
8446 			RECT rect;
8447 			GetWindowRect(hwnd, &rect);
8448 
8449 			RECT client;
8450 			GetClientRect(hwnd, &client);
8451 
8452 			rect.right = rect.right - client.right + w;
8453 			rect.bottom = rect.bottom - client.bottom + h;
8454 
8455 			// same position, new size for the client rectangle
8456 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
8457 
8458 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
8459 		}
8460 
8461 		void moveResize (int x, int y, int w, int h) {
8462 			// what's given is the client rectangle, we need to adjust
8463 
8464 			RECT rect;
8465 			rect.left = x;
8466 			rect.top = y;
8467 			rect.right = w + x;
8468 			rect.bottom = h + y;
8469 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
8470 				throw new Exception("AdjustWindowRect");
8471 
8472 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
8473 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
8474 			if (windowResized !is null) windowResized(w, h);
8475 		}
8476 
8477 		version(without_opengl) {} else {
8478 			HGLRC ghRC;
8479 			HDC ghDC;
8480 		}
8481 
8482 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
8483 			string cnamec;
8484 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
8485 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
8486 				cnamec = "DSimpleWindow";
8487 			} else {
8488 				cnamec = sdpyWindowClass;
8489 			}
8490 
8491 			WCharzBuffer cn = WCharzBuffer(cnamec);
8492 
8493 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
8494 
8495 			if(cnamec !in knownWinClasses) {
8496 				WNDCLASSEX wc;
8497 
8498 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
8499 				// to the object. Maybe.
8500 				wc.cbSize = wc.sizeof;
8501 				wc.cbClsExtra = 0;
8502 				wc.cbWndExtra = 0;
8503 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
8504 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
8505 				wc.hIcon = LoadIcon(hInstance, null);
8506 				wc.hInstance = hInstance;
8507 				wc.lpfnWndProc = &WndProc;
8508 				wc.lpszClassName = cn.ptr;
8509 				wc.hIconSm = null;
8510 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
8511 				if(!RegisterClassExW(&wc))
8512 					throw new WindowsApiException("RegisterClassExW");
8513 				knownWinClasses[cnamec] = true;
8514 			}
8515 
8516 			int style;
8517 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
8518 
8519 			// FIXME: windowType and customizationFlags
8520 			final switch(windowType) {
8521 				case WindowTypes.normal:
8522 					style = WS_OVERLAPPEDWINDOW;
8523 				break;
8524 				case WindowTypes.undecorated:
8525 					style = WS_POPUP | WS_SYSMENU;
8526 				break;
8527 				case WindowTypes.eventOnly:
8528 					_hidden = true;
8529 				break;
8530 				case WindowTypes.dropdownMenu:
8531 				case WindowTypes.popupMenu:
8532 				case WindowTypes.notification:
8533 					style = WS_POPUP;
8534 					flags |= WS_EX_NOACTIVATE;
8535 				break;
8536 				case WindowTypes.nestedChild:
8537 					style = WS_CHILD;
8538 				break;
8539 			}
8540 
8541 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
8542 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
8543 
8544 			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
8545 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
8546 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
8547 
8548 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
8549 				setOpacity(255);
8550 
8551 			SimpleWindow.nativeMapping[hwnd] = this;
8552 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
8553 
8554 			if(windowType == WindowTypes.eventOnly)
8555 				return;
8556 
8557 			HDC hdc = GetDC(hwnd);
8558 
8559 
8560 			version(without_opengl) {}
8561 			else {
8562 				if(opengl == OpenGlOptions.yes) {
8563 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
8564 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
8565 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
8566 					ghDC = hdc;
8567 					PIXELFORMATDESCRIPTOR pfd;
8568 
8569 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
8570 					pfd.nVersion = 1;
8571 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
8572 					pfd.dwLayerMask = PFD_MAIN_PLANE;
8573 					pfd.iPixelType = PFD_TYPE_RGBA;
8574 					pfd.cColorBits = 24;
8575 					pfd.cDepthBits = 24;
8576 					pfd.cAccumBits = 0;
8577 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
8578 
8579 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
8580 
8581 					if (pixelformat == 0)
8582 						throw new WindowsApiException("ChoosePixelFormat");
8583 
8584 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
8585 						throw new WindowsApiException("SetPixelFormat");
8586 
8587 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
8588 						// windoze is idiotic: we have to have OpenGL context to get function addresses
8589 						// so we will create fake context to get that stupid address
8590 						auto tmpcc = wglCreateContext(ghDC);
8591 						if (tmpcc !is null) {
8592 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
8593 							wglMakeCurrent(ghDC, tmpcc);
8594 							wglInitOtherFunctions();
8595 						}
8596 					}
8597 
8598 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
8599 						int[9] contextAttribs = [
8600 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
8601 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
8602 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
8603 							// for modern context, set "forward compatibility" flag too
8604 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
8605 							0/*None*/,
8606 						];
8607 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
8608 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
8609 							// activate fallback mode
8610 							// 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;
8611 							ghRC = wglCreateContext(ghDC);
8612 						}
8613 						if (ghRC is null)
8614 							throw new WindowsApiException("wglCreateContextAttribsARB");
8615 					} else {
8616 						// try to do at least something
8617 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
8618 							sdpyOpenGLContextVersion = 0;
8619 							ghRC = wglCreateContext(ghDC);
8620 						}
8621 						if (ghRC is null)
8622 							throw new WindowsApiException("wglCreateContext");
8623 					}
8624 				}
8625 			}
8626 
8627 			if(opengl == OpenGlOptions.no) {
8628 				buffer = CreateCompatibleBitmap(hdc, width, height);
8629 
8630 				auto hdcBmp = CreateCompatibleDC(hdc);
8631 				// make sure it's filled with a blank slate
8632 				auto oldBmp = SelectObject(hdcBmp, buffer);
8633 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
8634 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
8635 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
8636 				SelectObject(hdcBmp, oldBmp);
8637 				SelectObject(hdcBmp, oldBrush);
8638 				SelectObject(hdcBmp, oldPen);
8639 				DeleteDC(hdcBmp);
8640 
8641 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
8642 			}
8643 
8644 			// We want the window's client area to match the image size
8645 			RECT rcClient, rcWindow;
8646 			POINT ptDiff;
8647 			GetClientRect(hwnd, &rcClient);
8648 			GetWindowRect(hwnd, &rcWindow);
8649 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
8650 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
8651 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
8652 
8653 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
8654 				ShowWindow(hwnd, SW_SHOWNORMAL);
8655 			} else {
8656 				_hidden = true;
8657 			}
8658 			this._visibleForTheFirstTimeCalled = false; // hack!
8659 		}
8660 
8661 
8662 		void dispose() {
8663 			if(buffer)
8664 				DeleteObject(buffer);
8665 		}
8666 
8667 		void closeWindow() {
8668 			DestroyWindow(hwnd);
8669 		}
8670 
8671 		bool setOpacity(ubyte alpha) {
8672 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
8673 		}
8674 
8675 		HANDLE currentCursor;
8676 
8677 		// returns zero if it recognized the event
8678 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
8679 			MouseEvent mouse;
8680 
8681 			void mouseEvent(bool isScreen, ulong mods) {
8682 				auto x = LOWORD(lParam);
8683 				auto y = HIWORD(lParam);
8684 				if(isScreen) {
8685 					POINT p;
8686 					p.x = x;
8687 					p.y = y;
8688 					ScreenToClient(hwnd, &p);
8689 					x = cast(ushort) p.x;
8690 					y = cast(ushort) p.y;
8691 				}
8692 				mouse.x = x + offsetX;
8693 				mouse.y = y + offsetY;
8694 
8695 				wind.mdx(mouse);
8696 				mouse.modifierState = cast(int) mods;
8697 				mouse.window = wind;
8698 
8699 				if(wind.handleMouseEvent)
8700 					wind.handleMouseEvent(mouse);
8701 			}
8702 
8703 			switch(msg) {
8704 				case WM_GETMINMAXINFO:
8705 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
8706 
8707 					if(wind.minWidth > 0) {
8708 						RECT rect;
8709 						rect.left = 100;
8710 						rect.top = 100;
8711 						rect.right = wind.minWidth + 100;
8712 						rect.bottom = wind.minHeight + 100;
8713 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
8714 							throw new WindowsApiException("AdjustWindowRect");
8715 
8716 						mmi.ptMinTrackSize.x = rect.right - rect.left;
8717 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
8718 					}
8719 
8720 					if(wind.maxWidth < int.max) {
8721 						RECT rect;
8722 						rect.left = 100;
8723 						rect.top = 100;
8724 						rect.right = wind.maxWidth + 100;
8725 						rect.bottom = wind.maxHeight + 100;
8726 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
8727 							throw new WindowsApiException("AdjustWindowRect");
8728 
8729 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
8730 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
8731 					}
8732 				break;
8733 				case WM_CHAR:
8734 					wchar c = cast(wchar) wParam;
8735 					if(wind.handleCharEvent)
8736 						wind.handleCharEvent(cast(dchar) c);
8737 				break;
8738 				  case WM_SETFOCUS:
8739 				  case WM_KILLFOCUS:
8740 					wind._focused = (msg == WM_SETFOCUS);
8741 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
8742 					if(wind.onFocusChange)
8743 						wind.onFocusChange(msg == WM_SETFOCUS);
8744 				  break;
8745 				case WM_SYSKEYDOWN:
8746 				case WM_SYSKEYUP:
8747 				case WM_KEYDOWN:
8748 				case WM_KEYUP:
8749 					KeyEvent ev;
8750 					ev.key = cast(Key) wParam;
8751 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
8752 					if ((msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP) && wParam == 0x12) ev.key = Key.Alt; // windows does it this way
8753 
8754 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
8755 
8756 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
8757 						ev.modifierState |= ModifierState.shift;
8758 					//k8: this doesn't work; thanks for nothing, windows
8759 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
8760 						ev.modifierState |= ModifierState.alt;*/
8761 					if ((msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP) && wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN);
8762 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
8763 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
8764 						ev.modifierState |= ModifierState.ctrl;
8765 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
8766 						ev.modifierState |= ModifierState.windows;
8767 					if(GetKeyState(Key.NumLock))
8768 						ev.modifierState |= ModifierState.numLock;
8769 					if(GetKeyState(Key.CapsLock))
8770 						ev.modifierState |= ModifierState.capsLock;
8771 
8772 					/+
8773 					// we always want to send the character too, so let's convert it
8774 					ubyte[256] state;
8775 					wchar[16] buffer;
8776 					GetKeyboardState(state.ptr);
8777 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
8778 
8779 					foreach(dchar d; buffer) {
8780 						ev.character = d;
8781 						break;
8782 					}
8783 					+/
8784 
8785 					ev.window = wind;
8786 					if(wind.handleKeyEvent)
8787 						wind.handleKeyEvent(ev);
8788 				break;
8789 				case 0x020a /*WM_MOUSEWHEEL*/:
8790 					// send click
8791 					mouse.type = cast(MouseEventType) 1;
8792 					mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp);
8793 					mouseEvent(true, LOWORD(wParam));
8794 
8795 					// also send release
8796 					mouse.type = cast(MouseEventType) 2;
8797 					mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp);
8798 					mouseEvent(true, LOWORD(wParam));
8799 				break;
8800 				case WM_MOUSEMOVE:
8801 					mouse.type = cast(MouseEventType) 0;
8802 					mouseEvent(false, wParam);
8803 				break;
8804 				case WM_LBUTTONDOWN:
8805 				case WM_LBUTTONDBLCLK:
8806 					mouse.type = cast(MouseEventType) 1;
8807 					mouse.button = MouseButton.left;
8808 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
8809 					mouseEvent(false, wParam);
8810 				break;
8811 				case WM_LBUTTONUP:
8812 					mouse.type = cast(MouseEventType) 2;
8813 					mouse.button = MouseButton.left;
8814 					mouseEvent(false, wParam);
8815 				break;
8816 				case WM_RBUTTONDOWN:
8817 				case WM_RBUTTONDBLCLK:
8818 					mouse.type = cast(MouseEventType) 1;
8819 					mouse.button = MouseButton.right;
8820 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
8821 					mouseEvent(false, wParam);
8822 				break;
8823 				case WM_RBUTTONUP:
8824 					mouse.type = cast(MouseEventType) 2;
8825 					mouse.button = MouseButton.right;
8826 					mouseEvent(false, wParam);
8827 				break;
8828 				case WM_MBUTTONDOWN:
8829 				case WM_MBUTTONDBLCLK:
8830 					mouse.type = cast(MouseEventType) 1;
8831 					mouse.button = MouseButton.middle;
8832 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
8833 					mouseEvent(false, wParam);
8834 				break;
8835 				case WM_MBUTTONUP:
8836 					mouse.type = cast(MouseEventType) 2;
8837 					mouse.button = MouseButton.middle;
8838 					mouseEvent(false, wParam);
8839 				break;
8840 				case WM_XBUTTONDOWN:
8841 				case WM_XBUTTONDBLCLK:
8842 					mouse.type = cast(MouseEventType) 1;
8843 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
8844 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
8845 					mouseEvent(false, wParam);
8846 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
8847 				case WM_XBUTTONUP:
8848 					mouse.type = cast(MouseEventType) 2;
8849 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
8850 					mouseEvent(false, wParam);
8851 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
8852 
8853 				default: return 1;
8854 			}
8855 			return 0;
8856 		}
8857 
8858 		HWND hwnd;
8859 		int oldWidth;
8860 		int oldHeight;
8861 		bool inSizeMove;
8862 
8863 		// the extern(Windows) wndproc should just forward to this
8864 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
8865 			assert(hwnd is this.hwnd);
8866 
8867 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
8868 			switch(msg) {
8869 				case WM_SETCURSOR:
8870 					if(cast(HWND) wParam !is hwnd)
8871 						return 0; // further processing elsewhere
8872 
8873 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
8874 						SetCursor(this.curHidden > 0 ? null : currentCursor);
8875 						return 1;
8876 					} else {
8877 						return DefWindowProc(hwnd, msg, wParam, lParam);
8878 					}
8879 				//break;
8880 
8881 				case WM_CLOSE:
8882 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
8883 				break;
8884 				case WM_DESTROY:
8885 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
8886 					SimpleWindow.nativeMapping.remove(hwnd);
8887 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
8888 
8889 					bool anyImportant = false;
8890 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
8891 						if(w.beingOpenKeepsAppOpen) {
8892 							anyImportant = true;
8893 							break;
8894 						}
8895 					if(!anyImportant) {
8896 						PostQuitMessage(0);
8897 					}
8898 				break;
8899 				case WM_SIZE:
8900 					if(wParam == 1 /* SIZE_MINIMIZED */)
8901 						break;
8902 					_width = LOWORD(lParam);
8903 					_height = HIWORD(lParam);
8904 
8905 					// I want to avoid tearing in the windows (my code is inefficient
8906 					// so this is a hack around that) so while sizing, we don't trigger,
8907 					// but we do want to trigger on events like mazimize.
8908 					if(!inSizeMove)
8909 						goto size_changed;
8910 				break;
8911 				// I don't like the tearing I get when redrawing on WM_SIZE
8912 				// (I know there's other ways to fix that but I don't like that behavior anyway)
8913 				// so instead it is going to redraw only at the end of a size.
8914 				case 0x0231: /* WM_ENTERSIZEMOVE */
8915 					oldWidth = this.width;
8916 					oldHeight = this.height;
8917 					inSizeMove = true;
8918 				break;
8919 				case 0x0232: /* WM_EXITSIZEMOVE */
8920 					inSizeMove = false;
8921 					// nothing relevant changed, don't bother redrawing
8922 					if(oldWidth == width && oldHeight == height)
8923 						break;
8924 
8925 					size_changed:
8926 
8927 					// note: OpenGL windows don't use a backing bmp, so no need to change them
8928 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
8929 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
8930 						// gotta get the double buffer bmp to match the window
8931 					// FIXME: could this be more efficient? It isn't really necessary to make
8932 					// a new buffer if we're sizing down at least.
8933 						auto hdc = GetDC(hwnd);
8934 						auto oldBuffer = buffer;
8935 						buffer = CreateCompatibleBitmap(hdc, width, height);
8936 
8937 						auto hdcBmp = CreateCompatibleDC(hdc);
8938 						auto oldBmp = SelectObject(hdcBmp, buffer);
8939 
8940 						auto hdcOldBmp = CreateCompatibleDC(hdc);
8941 						auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp);
8942 
8943 						BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY);
8944 
8945 						SelectObject(hdcOldBmp, oldOldBmp);
8946 						DeleteDC(hdcOldBmp);
8947 
8948 						SelectObject(hdcBmp, oldBmp);
8949 						DeleteDC(hdcBmp);
8950 
8951 						ReleaseDC(hwnd, hdc);
8952 
8953 						DeleteObject(oldBuffer);
8954 					}
8955 
8956 					version(without_opengl) {} else
8957 					if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
8958 						glViewport(0, 0, width, height);
8959 					}
8960 
8961 					if(windowResized !is null)
8962 						windowResized(width, height);
8963 				break;
8964 				case WM_ERASEBKGND:
8965 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
8966 					if (!this._visibleForTheFirstTimeCalled) {
8967 						this._visibleForTheFirstTimeCalled = true;
8968 						if (this.visibleForTheFirstTime !is null) {
8969 							version(without_opengl) {} else {
8970 								if(openglMode == OpenGlOptions.yes) {
8971 									this.setAsCurrentOpenGlContextNT();
8972 									glViewport(0, 0, width, height);
8973 								}
8974 							}
8975 							this.visibleForTheFirstTime();
8976 						}
8977 					}
8978 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
8979 					version(without_opengl) {} else {
8980 						if (openglMode == OpenGlOptions.yes) return 1;
8981 					}
8982 					// call windows default handler, so it can paint standard controls
8983 					goto default;
8984 				case WM_CTLCOLORBTN:
8985 				case WM_CTLCOLORSTATIC:
8986 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
8987 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
8988 					GetSysColorBrush(COLOR_3DFACE);
8989 				//break;
8990 				case WM_SHOWWINDOW:
8991 					this._visible = (wParam != 0);
8992 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
8993 						this._visibleForTheFirstTimeCalled = true;
8994 						if (this.visibleForTheFirstTime !is null) {
8995 							version(without_opengl) {} else {
8996 								if(openglMode == OpenGlOptions.yes) {
8997 									this.setAsCurrentOpenGlContextNT();
8998 									glViewport(0, 0, width, height);
8999 								}
9000 							}
9001 							this.visibleForTheFirstTime();
9002 						}
9003 					}
9004 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
9005 					break;
9006 				case WM_PAINT: {
9007 					if (!this._visibleForTheFirstTimeCalled) {
9008 						this._visibleForTheFirstTimeCalled = true;
9009 						if (this.visibleForTheFirstTime !is null) {
9010 							version(without_opengl) {} else {
9011 								if(openglMode == OpenGlOptions.yes) {
9012 									this.setAsCurrentOpenGlContextNT();
9013 									glViewport(0, 0, width, height);
9014 								}
9015 							}
9016 							this.visibleForTheFirstTime();
9017 						}
9018 					}
9019 
9020 					BITMAP bm;
9021 					PAINTSTRUCT ps;
9022 
9023 					HDC hdc = BeginPaint(hwnd, &ps);
9024 
9025 					if(openglMode == OpenGlOptions.no) {
9026 
9027 						HDC hdcMem = CreateCompatibleDC(hdc);
9028 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
9029 
9030 						GetObject(buffer, bm.sizeof, &bm);
9031 
9032 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
9033 						if(resizability == Resizability.automaticallyScaleIfPossible)
9034 						StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
9035 						else
9036 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
9037 
9038 						SelectObject(hdcMem, hbmOld);
9039 						DeleteDC(hdcMem);
9040 						EndPaint(hwnd, &ps);
9041 					} else {
9042 						EndPaint(hwnd, &ps);
9043 						version(without_opengl) {} else
9044 							redrawOpenGlSceneNow();
9045 					}
9046 				} break;
9047 				  default:
9048 					return DefWindowProc(hwnd, msg, wParam, lParam);
9049 			}
9050 			 return 0;
9051 
9052 		}
9053 	}
9054 
9055 	mixin template NativeImageImplementation() {
9056 		HBITMAP handle;
9057 		ubyte* rawData;
9058 
9059 	final:
9060 
9061 		Color getPixel(int x, int y) {
9062 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
9063 			// remember, bmps are upside down
9064 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
9065 
9066 			Color c;
9067 			c.a = 255;
9068 			c.b = rawData[offset + 0];
9069 			c.g = rawData[offset + 1];
9070 			c.r = rawData[offset + 2];
9071 			return c;
9072 		}
9073 
9074 		void setPixel(int x, int y, Color c) {
9075 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
9076 			// remember, bmps are upside down
9077 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
9078 
9079 			rawData[offset + 0] = c.b;
9080 			rawData[offset + 1] = c.g;
9081 			rawData[offset + 2] = c.r;
9082 		}
9083 
9084 		void convertToRgbaBytes(ubyte[] where) {
9085 			assert(where.length == this.width * this.height * 4);
9086 
9087 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
9088 			int idx = 0;
9089 			int offset = itemsPerLine * (height - 1);
9090 			// remember, bmps are upside down
9091 			for(int y = height - 1; y >= 0; y--) {
9092 				auto offsetStart = offset;
9093 				for(int x = 0; x < width; x++) {
9094 					where[idx + 0] = rawData[offset + 2]; // r
9095 					where[idx + 1] = rawData[offset + 1]; // g
9096 					where[idx + 2] = rawData[offset + 0]; // b
9097 					where[idx + 3] = 255; // a
9098 					idx += 4;
9099 					offset += 3;
9100 				}
9101 
9102 				offset = offsetStart - itemsPerLine;
9103 			}
9104 		}
9105 
9106 		void setFromRgbaBytes(in ubyte[] what) {
9107 			assert(what.length == this.width * this.height * 4);
9108 
9109 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
9110 			int idx = 0;
9111 			int offset = itemsPerLine * (height - 1);
9112 			// remember, bmps are upside down
9113 			for(int y = height - 1; y >= 0; y--) {
9114 				auto offsetStart = offset;
9115 				for(int x = 0; x < width; x++) {
9116 					rawData[offset + 2] = what[idx + 0]; // r
9117 					rawData[offset + 1] = what[idx + 1]; // g
9118 					rawData[offset + 0] = what[idx + 2]; // b
9119 					//where[idx + 3] = 255; // a
9120 					idx += 4;
9121 					offset += 3;
9122 				}
9123 
9124 				offset = offsetStart - itemsPerLine;
9125 			}
9126 		}
9127 
9128 
9129 		void createImage(int width, int height, bool forcexshm=false) {
9130 			BITMAPINFO infoheader;
9131 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
9132 			infoheader.bmiHeader.biWidth = width;
9133 			infoheader.bmiHeader.biHeight = height;
9134 			infoheader.bmiHeader.biPlanes = 1;
9135 			infoheader.bmiHeader.biBitCount = 24;
9136 			infoheader.bmiHeader.biCompression = BI_RGB;
9137 
9138 			handle = CreateDIBSection(
9139 				null,
9140 				&infoheader,
9141 				DIB_RGB_COLORS,
9142 				cast(void**) &rawData,
9143 				null,
9144 				0);
9145 			if(handle is null)
9146 				throw new WindowsApiException("create image failed");
9147 
9148 		}
9149 
9150 		void dispose() {
9151 			DeleteObject(handle);
9152 		}
9153 	}
9154 
9155 	enum KEY_ESCAPE = 27;
9156 }
9157 version(X11) {
9158 	/// This is the default font used. You might change this before doing anything else with
9159 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
9160 	/// for cross-platform compatibility.
9161 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
9162 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
9163 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
9164 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
9165 
9166 	alias int delegate(XEvent) NativeEventHandler;
9167 	alias Window NativeWindowHandle;
9168 
9169 	enum KEY_ESCAPE = 9;
9170 
9171 	mixin template NativeScreenPainterImplementation() {
9172 		Display* display;
9173 		Drawable d;
9174 		Drawable destiny;
9175 
9176 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
9177 		GC gc;
9178 
9179 		__gshared bool fontAttempted;
9180 
9181 		__gshared XFontStruct* defaultfont;
9182 		__gshared XFontSet defaultfontset;
9183 
9184 		XFontStruct* font;
9185 		XFontSet fontset;
9186 
9187 		void create(NativeWindowHandle window) {
9188 			this.display = XDisplayConnection.get();
9189 
9190 			Drawable buffer = None;
9191 			if(auto sw = cast(SimpleWindow) this.window) {
9192 				buffer = sw.impl.buffer;
9193 				this.destiny = cast(Drawable) window;
9194 			} else {
9195 				buffer = cast(Drawable) window;
9196 				this.destiny = None;
9197 			}
9198 
9199 			this.d = cast(Drawable) buffer;
9200 
9201 			auto dgc = DefaultGC(display, DefaultScreen(display));
9202 
9203 			this.gc = XCreateGC(display, d, 0, null);
9204 
9205 			XCopyGC(display, dgc, 0xffffffff, this.gc);
9206 
9207 			if(!fontAttempted) {
9208 				font = XLoadQueryFont(display, xfontstr.ptr);
9209 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
9210 				if(font is null)
9211 					font = XLoadQueryFont(display, "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*".ptr);
9212 
9213 				char** lol;
9214 				int lol2;
9215 				char* lol3;
9216 				fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
9217 
9218 				fontAttempted = true;
9219 
9220 				defaultfont = font;
9221 				defaultfontset = fontset;
9222 			}
9223 
9224 			font = defaultfont;
9225 			fontset = defaultfontset;
9226 
9227 			if(font) {
9228 				XSetFont(display, gc, font.fid);
9229 			}
9230 		}
9231 
9232 		arsd.color.Rectangle _clipRectangle;
9233 		void setClipRectangle(int x, int y, int width, int height) {
9234 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
9235 			if(width == 0 || height == 0)
9236 				XSetClipMask(display, gc, None);
9237 			else {
9238 				XRectangle[1] rects;
9239 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
9240 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
9241 			}
9242 		}
9243 
9244 
9245 		void setFont(OperatingSystemFont font) {
9246 			if(font && font.font) {
9247 				this.font = font.font;
9248 				this.fontset = font.fontset;
9249 				XSetFont(display, gc, font.font.fid);
9250 			} else {
9251 				this.font = defaultfont;
9252 				this.fontset = defaultfontset;
9253 			}
9254 
9255 		}
9256 
9257 		void dispose() {
9258 			this.rasterOp = RasterOp.normal;
9259 
9260 			// FIXME: this.window.width/height is probably wrong
9261 
9262 			// src x,y     then dest x, y
9263 			if(destiny != None) {
9264 				XSetClipMask(display, gc, None);
9265 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
9266 			}
9267 
9268 			XFreeGC(display, gc);
9269 
9270 			version(none) // we don't want to free it because we can use it later
9271 			if(font)
9272 				XFreeFont(display, font);
9273 			version(none) // we don't want to free it because we can use it later
9274 			if(fontset)
9275 				XFreeFontSet(display, fontset);
9276 			XFlush(display);
9277 
9278 			if(window.paintingFinishedDg !is null)
9279 				window.paintingFinishedDg();
9280 		}
9281 
9282 		bool backgroundIsNotTransparent = true;
9283 		bool foregroundIsNotTransparent = true;
9284 
9285 		bool _penInitialized = false;
9286 		Pen _activePen;
9287 
9288 		Color _outlineColor;
9289 		Color _fillColor;
9290 
9291 		@property void pen(Pen p) {
9292 			if(_penInitialized && p == _activePen) {
9293 				return;
9294 			}
9295 			_penInitialized = true;
9296 			_activePen = p;
9297 			_outlineColor = p.color;
9298 
9299 			int style;
9300 
9301 			byte dashLength;
9302 
9303 			final switch(p.style) {
9304 				case Pen.Style.Solid:
9305 					style = 0 /*LineSolid*/;
9306 				break;
9307 				case Pen.Style.Dashed:
9308 					style = 1 /*LineOnOffDash*/;
9309 					dashLength = 4;
9310 				break;
9311 				case Pen.Style.Dotted:
9312 					style = 1 /*LineOnOffDash*/;
9313 					dashLength = 1;
9314 				break;
9315 			}
9316 
9317 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
9318 			if(dashLength)
9319 				XSetDashes(display, gc, 0, &dashLength, 1);
9320 
9321 			if(p.color.a == 0) {
9322 				foregroundIsNotTransparent = false;
9323 				return;
9324 			}
9325 
9326 			foregroundIsNotTransparent = true;
9327 
9328 			XSetForeground(display, gc, colorToX(p.color, display));
9329 		}
9330 
9331 		RasterOp _currentRasterOp;
9332 		bool _currentRasterOpInitialized = false;
9333 		@property void rasterOp(RasterOp op) {
9334 			if(_currentRasterOpInitialized && _currentRasterOp == op)
9335 				return;
9336 			_currentRasterOp = op;
9337 			_currentRasterOpInitialized = true;
9338 			int mode;
9339 			final switch(op) {
9340 				case RasterOp.normal:
9341 					mode = GXcopy;
9342 				break;
9343 				case RasterOp.xor:
9344 					mode = GXxor;
9345 				break;
9346 			}
9347 			XSetFunction(display, gc, mode);
9348 		}
9349 
9350 
9351 		bool _fillColorInitialized = false;
9352 
9353 		@property void fillColor(Color c) {
9354 			if(_fillColorInitialized && _fillColor == c)
9355 				return; // already good, no need to waste time calling it
9356 			_fillColor = c;
9357 			_fillColorInitialized = true;
9358 			if(c.a == 0) {
9359 				backgroundIsNotTransparent = false;
9360 				return;
9361 			}
9362 
9363 			backgroundIsNotTransparent = true;
9364 
9365 			XSetBackground(display, gc, colorToX(c, display));
9366 
9367 		}
9368 
9369 		void swapColors() {
9370 			auto tmp = _fillColor;
9371 			fillColor = _outlineColor;
9372 			auto newPen = _activePen;
9373 			newPen.color = tmp;
9374 			pen(newPen);
9375 		}
9376 
9377 		uint colorToX(Color c, Display* display) {
9378 			auto visual = DefaultVisual(display, DefaultScreen(display));
9379 			import core.bitop;
9380 			uint color = 0;
9381 			{
9382 			auto startBit = bsf(visual.red_mask);
9383 			auto lastBit = bsr(visual.red_mask);
9384 			auto r = cast(uint) c.r;
9385 			r >>= 7 - (lastBit - startBit);
9386 			r <<= startBit;
9387 			color |= r;
9388 			}
9389 			{
9390 			auto startBit = bsf(visual.green_mask);
9391 			auto lastBit = bsr(visual.green_mask);
9392 			auto g = cast(uint) c.g;
9393 			g >>= 7 - (lastBit - startBit);
9394 			g <<= startBit;
9395 			color |= g;
9396 			}
9397 			{
9398 			auto startBit = bsf(visual.blue_mask);
9399 			auto lastBit = bsr(visual.blue_mask);
9400 			auto b = cast(uint) c.b;
9401 			b >>= 7 - (lastBit - startBit);
9402 			b <<= startBit;
9403 			color |= b;
9404 			}
9405 
9406 
9407 
9408 			return color;
9409 		}
9410 
9411 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
9412 			// source x, source y
9413 			if(ix >= i.width) return;
9414 			if(iy >= i.height) return;
9415 			if(ix + w > i.width) w = i.width - ix;
9416 			if(iy + h > i.height) h = i.height - iy;
9417 			if(i.usingXshm)
9418 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
9419 			else
9420 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
9421 		}
9422 
9423 		void drawPixmap(Sprite s, int x, int y) {
9424 			XCopyArea(display, s.handle, d, gc, 0, 0, s.width, s.height, x, y);
9425 		}
9426 
9427 		int fontHeight() {
9428 			if(font)
9429 				return font.max_bounds.ascent + font.max_bounds.descent;
9430 			return 12; // pretty common default...
9431 		}
9432 
9433 		Size textSize(in char[] text) {
9434 			auto maxWidth = 0;
9435 			auto lineHeight = fontHeight;
9436 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
9437 			foreach(line; text.split('\n')) {
9438 				int textWidth;
9439 				if(font)
9440 					// FIXME: unicode
9441 					textWidth = XTextWidth( font, line.ptr, cast(int) line.length);
9442 				else
9443 					textWidth = fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
9444 
9445 				if(textWidth > maxWidth)
9446 					maxWidth = textWidth;
9447 				h += lineHeight + 4;
9448 			}
9449 			return Size(maxWidth, h);
9450 		}
9451 
9452 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
9453 			// FIXME: we should actually draw unicode.. but until then, I'm going to strip out multibyte chars
9454 			const(char)[] text;
9455 			if(fontset)
9456 				text = originalText;
9457 			else {
9458 				text.reserve(originalText.length);
9459 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
9460 				// then strip the rest so there isn't garbage
9461 				foreach(dchar ch; originalText)
9462 					if(ch < 256)
9463 						text ~= cast(ubyte) ch;
9464 					else
9465 						text ~= 191; // FIXME: using a random character to fill the space
9466 			}
9467 			if(text.length == 0)
9468 				return;
9469 
9470 
9471 			int textHeight = 12;
9472 
9473 			// FIXME: should we clip it to the bounding box?
9474 
9475 			if(font) {
9476 				textHeight = font.max_bounds.ascent + font.max_bounds.descent;
9477 			}
9478 
9479 			auto lines = text.split('\n');
9480 
9481 			auto lineHeight = textHeight;
9482 			textHeight *= lines.length;
9483 
9484 			int cy = y;
9485 
9486 			if(alignment & TextAlignment.VerticalBottom) {
9487 				assert(y2);
9488 				auto h = y2 - y;
9489 				if(h > textHeight) {
9490 					cy += h - textHeight;
9491 					cy -= lineHeight / 2;
9492 				}
9493 			} else if(alignment & TextAlignment.VerticalCenter) {
9494 				assert(y2);
9495 				auto h = y2 - y;
9496 				if(textHeight < h) {
9497 					cy += (h - textHeight) / 2;
9498 					//cy -= lineHeight / 4;
9499 				}
9500 			}
9501 
9502 			foreach(line; text.split('\n')) {
9503 				int textWidth;
9504 				if(font)
9505 					// FIXME: unicode
9506 					textWidth = XTextWidth( font, line.ptr, cast(int) line.length);
9507 				else
9508 					textWidth = 12 * cast(int) line.length;
9509 
9510 				int px = x, py = cy;
9511 
9512 				if(alignment & TextAlignment.Center) {
9513 					assert(x2);
9514 					auto w = x2 - x;
9515 					if(w > textWidth)
9516 						px += (w - textWidth) / 2;
9517 				} else if(alignment & TextAlignment.Right) {
9518 					assert(x2);
9519 					auto pos = x2 - textWidth;
9520 					if(pos > x)
9521 						px = pos;
9522 				}
9523 
9524 				if(fontset)
9525 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
9526 
9527 				else
9528 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
9529 				cy += lineHeight + 4;
9530 			}
9531 		}
9532 
9533 		void drawPixel(int x, int y) {
9534 			XDrawPoint(display, d, gc, x, y);
9535 		}
9536 
9537 		// The basic shapes, outlined
9538 
9539 		void drawLine(int x1, int y1, int x2, int y2) {
9540 			if(foregroundIsNotTransparent)
9541 				XDrawLine(display, d, gc, x1, y1, x2, y2);
9542 		}
9543 
9544 		void drawRectangle(int x, int y, int width, int height) {
9545 			if(backgroundIsNotTransparent) {
9546 				swapColors();
9547 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
9548 				swapColors();
9549 			}
9550 			if(foregroundIsNotTransparent)
9551 				XDrawRectangle(display, d, gc, x, y, width - 1, height - 1);
9552 		}
9553 
9554 		/// Arguments are the points of the bounding rectangle
9555 		void drawEllipse(int x1, int y1, int x2, int y2) {
9556 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
9557 		}
9558 
9559 		// NOTE: start and finish are in units of degrees * 64
9560 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
9561 			if(backgroundIsNotTransparent) {
9562 				swapColors();
9563 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
9564 				swapColors();
9565 			}
9566 			if(foregroundIsNotTransparent)
9567 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
9568 		}
9569 
9570 		void drawPolygon(Point[] vertexes) {
9571 			XPoint[16] pointsBuffer;
9572 			XPoint[] points;
9573 			if(vertexes.length <= pointsBuffer.length)
9574 				points = pointsBuffer[0 .. vertexes.length];
9575 			else
9576 				points.length = vertexes.length;
9577 
9578 			foreach(i, p; vertexes) {
9579 				points[i].x = cast(short) p.x;
9580 				points[i].y = cast(short) p.y;
9581 			}
9582 
9583 			if(backgroundIsNotTransparent) {
9584 				swapColors();
9585 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
9586 				swapColors();
9587 			}
9588 			if(foregroundIsNotTransparent) {
9589 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
9590 			}
9591 		}
9592 	}
9593 
9594 	class XDisconnectException : Exception {
9595 		bool userRequested;
9596 		this(bool userRequested = true) {
9597 			this.userRequested = userRequested;
9598 			super("X disconnected");
9599 		}
9600 	}
9601 
9602 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display*
9603 	class XDisplayConnection {
9604 		private __gshared Display* display;
9605 		private __gshared XIM xim;
9606 		private __gshared char* displayName;
9607 
9608 		private __gshared int connectionSequence_;
9609 
9610 		/// use this for lazy caching when reconnection
9611 		static int connectionSequenceNumber() { return connectionSequence_; }
9612 
9613 		/// Attempts recreation of state, may require application assistance
9614 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
9615 		/// then call this, and if successful, reenter the loop.
9616 		static void discardAndRecreate(string newDisplayString = null) {
9617 			if(insideXEventLoop)
9618 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
9619 
9620 			// 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
9621 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
9622 
9623 			foreach(handle; chnenhm) {
9624 				handle.discardConnectionState();
9625 			}
9626 
9627 			discardState();
9628 
9629 			if(newDisplayString !is null)
9630 				setDisplayName(newDisplayString);
9631 
9632 			auto display = get();
9633 
9634 			foreach(handle; chnenhm) {
9635 				handle.recreateAfterDisconnect();
9636 			}
9637 		}
9638 
9639 		private __gshared EventMask rootEventMask;
9640 
9641 		/++
9642 			Requests the specified input from the root window on the connection, in addition to any other request.
9643 
9644 			
9645 			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.
9646 
9647 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
9648 		+/
9649 		static void addRootInput(EventMask mask) {
9650 			auto old = rootEventMask;
9651 			rootEventMask |= mask;
9652 			get(); // to ensure display connected
9653 			if(display !is null && rootEventMask != old)
9654 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
9655 		}
9656 
9657 		static void discardState() {
9658 			freeImages();
9659 
9660 			foreach(atomPtr; interredAtoms)
9661 				*atomPtr = 0;
9662 			interredAtoms = null;
9663 			interredAtoms.assumeSafeAppend();
9664 
9665 			ScreenPainterImplementation.fontAttempted = false;
9666 			ScreenPainterImplementation.defaultfont = null;
9667 			ScreenPainterImplementation.defaultfontset = null;
9668 
9669 			Image.impl.xshmQueryCompleted = false;
9670 			Image.impl._xshmAvailable = false;
9671 
9672 			SimpleWindow.nativeMapping = null;
9673 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
9674 			// GlobalHotkeyManager
9675 
9676 			display = null;
9677 			xim = null;
9678 		}
9679 
9680 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
9681 		private static void createXIM () {
9682 			import core.stdc.locale : setlocale, LC_ALL;
9683 			import core.stdc.stdio : stderr, fprintf;
9684 			import core.stdc.stdlib : free;
9685 			import core.stdc.string : strdup;
9686 
9687 			static immutable string[3] mtry = [ null, "@im=local", "@im=" ];
9688 
9689 			auto olocale = strdup(setlocale(LC_ALL, null));
9690 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
9691 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
9692 
9693 			//fprintf(stderr, "opening IM...\n");
9694 			foreach (string s; mtry) {
9695 				if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
9696 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
9697 			}
9698 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
9699 		}
9700 
9701 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
9702 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
9703 		static struct ImgList {
9704 			size_t img; // class; hide it from GC
9705 			ImgList* next;
9706 		}
9707 
9708 		static __gshared ImgList* imglist = null;
9709 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
9710 
9711 		static void registerImage (Image img) {
9712 			if (!imglistLocked && img !is null) {
9713 				import core.stdc.stdlib : malloc;
9714 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
9715 				assert(it !is null); // do proper checks
9716 				it.img = cast(size_t)cast(void*)img;
9717 				it.next = imglist;
9718 				imglist = it;
9719 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
9720 			}
9721 		}
9722 
9723 		static void unregisterImage (Image img) {
9724 			if (!imglistLocked && img !is null) {
9725 				import core.stdc.stdlib : free;
9726 				ImgList* prev = null;
9727 				ImgList* cur = imglist;
9728 				while (cur !is null) {
9729 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
9730 					prev = cur;
9731 					cur = cur.next;
9732 				}
9733 				if (cur !is null) {
9734 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
9735 					free(cur);
9736 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
9737 				} else {
9738 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
9739 				}
9740 			}
9741 		}
9742 
9743 		static void freeImages () { // needed for discardAndRecreate
9744 			imglistLocked = true;
9745 			scope(exit) imglistLocked = false;
9746 			ImgList* cur = imglist;
9747 			ImgList* next = null;
9748 			while (cur !is null) {
9749 				import core.stdc.stdlib : free;
9750 				next = cur.next;
9751 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
9752 				(cast(Image)cast(void*)cur.img).dispose();
9753 				free(cur);
9754 				cur = next;
9755 			}
9756 			imglist = null;
9757 		}
9758 
9759 		/// can be used to override normal handling of display name
9760 		/// from environment and/or command line
9761 		static setDisplayName(string newDisplayName) {
9762 			displayName = cast(char*) (newDisplayName ~ '\0');
9763 		}
9764 
9765 		/// resets to the default display string
9766 		static resetDisplayName() {
9767 			displayName = null;
9768 		}
9769 
9770 		///
9771 		static Display* get() {
9772 			if(display is null) {
9773 				if(!librariesSuccessfullyLoaded)
9774 					throw new Exception("Unable to load X11 client libraries");
9775 				display = XOpenDisplay(displayName);
9776 				connectionSequence_++;
9777 				if(display is null)
9778 					throw new Exception("Unable to open X display");
9779 				XSetIOErrorHandler(&x11ioerrCB);
9780 				Bool sup;
9781 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
9782 				createXIM();
9783 				version(with_eventloop) {
9784 					import arsd.eventloop;
9785 					addFileEventListeners(display.fd, &eventListener, null, null);
9786 				}
9787 			}
9788 
9789 			return display;
9790 		}
9791 
9792 		extern(C)
9793 		static int x11ioerrCB(Display* dpy) {
9794 			throw new XDisconnectException(false);
9795 		}
9796 
9797 		version(with_eventloop) {
9798 			import arsd.eventloop;
9799 			static void eventListener(OsFileHandle fd) {
9800 				//this.mtLock();
9801 				//scope(exit) this.mtUnlock();
9802 				while(XPending(display))
9803 					doXNextEvent(display);
9804 			}
9805 		}
9806 
9807 		// close connection on program exit -- we need this to properly free all images
9808 		shared static ~this () { close(); }
9809 
9810 		///
9811 		static void close() {
9812 			if(display is null)
9813 				return;
9814 
9815 			version(with_eventloop) {
9816 				import arsd.eventloop;
9817 				removeFileEventListeners(display.fd);
9818 			}
9819 
9820 			// now remove all registered images to prevent shared memory leaks
9821 			freeImages();
9822 
9823 			XCloseDisplay(display);
9824 			display = null;
9825 		}
9826 	}
9827 
9828 	mixin template NativeImageImplementation() {
9829 		XImage* handle;
9830 		ubyte* rawData;
9831 
9832 		XShmSegmentInfo shminfo;
9833 
9834 		__gshared bool xshmQueryCompleted;
9835 		__gshared bool _xshmAvailable;
9836 		public static @property bool xshmAvailable() {
9837 			if(!xshmQueryCompleted) {
9838 				int i1, i2, i3;
9839 				xshmQueryCompleted = true;
9840 
9841 				auto str = XDisplayConnection.get().display_name;
9842 				// only if we are actually on the same machine does this
9843 				// have any hope, and the query extension only asks if
9844 				// the server can in theory, not in practice.
9845 				if(str is null || (str[0] != ':' && str[0] != '/'))
9846 					_xshmAvailable = false;
9847 				else
9848 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
9849 			}
9850 			return _xshmAvailable;
9851 		}
9852 
9853 		bool usingXshm;
9854 	final:
9855 
9856 		void createImage(int width, int height, bool forcexshm=false) {
9857 			auto display = XDisplayConnection.get();
9858 			assert(display !is null);
9859 			auto screen = DefaultScreen(display);
9860 
9861 			// it will only use shared memory for somewhat largish images,
9862 			// since otherwise we risk wasting shared memory handles on a lot of little ones
9863 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
9864 				usingXshm = true;
9865 				handle = XShmCreateImage(
9866 					display,
9867 					DefaultVisual(display, screen),
9868 					24,
9869 					ImageFormat.ZPixmap,
9870 					null,
9871 					&shminfo,
9872 					width, height);
9873 				assert(handle !is null);
9874 
9875 				assert(handle.bytes_per_line == 4 * width);
9876 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
9877 				//import std.conv; import core.stdc.errno;
9878 				assert(shminfo.shmid >= 0);//, to!string(errno));
9879 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
9880 				assert(rawData != cast(ubyte*) -1);
9881 				shminfo.readOnly = 0;
9882 				XShmAttach(display, &shminfo);
9883 				XDisplayConnection.registerImage(this);
9884 			} else {
9885 				if (forcexshm) throw new Exception("can't create XShm Image");
9886 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
9887 				import core.stdc.stdlib : malloc;
9888 				rawData = cast(ubyte*) malloc(width * height * 4);
9889 
9890 				handle = XCreateImage(
9891 					display,
9892 					DefaultVisual(display, screen),
9893 					24, // bpp
9894 					ImageFormat.ZPixmap,
9895 					0, // offset
9896 					rawData,
9897 					width, height,
9898 					8 /* FIXME */, 4 * width); // padding, bytes per line
9899 			}
9900 		}
9901 
9902 		void dispose() {
9903 			// note: this calls free(rawData) for us
9904 			if(handle) {
9905 				if (usingXshm) {
9906 					XDisplayConnection.unregisterImage(this);
9907 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
9908 				}
9909 				XDestroyImage(handle);
9910 				if(usingXshm) {
9911 					shmdt(shminfo.shmaddr);
9912 					shmctl(shminfo.shmid, IPC_RMID, null);
9913 				}
9914 				handle = null;
9915 			}
9916 		}
9917 
9918 		Color getPixel(int x, int y) {
9919 			auto offset = (y * width + x) * 4;
9920 			Color c;
9921 			c.a = 255;
9922 			c.b = rawData[offset + 0];
9923 			c.g = rawData[offset + 1];
9924 			c.r = rawData[offset + 2];
9925 			return c;
9926 		}
9927 
9928 		void setPixel(int x, int y, Color c) {
9929 			auto offset = (y * width + x) * 4;
9930 			rawData[offset + 0] = c.b;
9931 			rawData[offset + 1] = c.g;
9932 			rawData[offset + 2] = c.r;
9933 		}
9934 
9935 		void convertToRgbaBytes(ubyte[] where) {
9936 			assert(where.length == this.width * this.height * 4);
9937 
9938 			// if rawData had a length....
9939 			//assert(rawData.length == where.length);
9940 			for(int idx = 0; idx < where.length; idx += 4) {
9941 				where[idx + 0] = rawData[idx + 2]; // r
9942 				where[idx + 1] = rawData[idx + 1]; // g
9943 				where[idx + 2] = rawData[idx + 0]; // b
9944 				where[idx + 3] = 255; // a
9945 			}
9946 		}
9947 
9948 		void setFromRgbaBytes(in ubyte[] where) {
9949 			assert(where.length == this.width * this.height * 4);
9950 
9951 			// if rawData had a length....
9952 			//assert(rawData.length == where.length);
9953 			for(int idx = 0; idx < where.length; idx += 4) {
9954 				rawData[idx + 2] = where[idx + 0]; // r
9955 				rawData[idx + 1] = where[idx + 1]; // g
9956 				rawData[idx + 0] = where[idx + 2]; // b
9957 				//rawData[idx + 3] = 255; // a
9958 			}
9959 		}
9960 
9961 	}
9962 
9963 	mixin template NativeSimpleWindowImplementation() {
9964 		GC gc;
9965 		Window window;
9966 		Display* display;
9967 
9968 		Pixmap buffer;
9969 		int bufferw, bufferh; // size of the buffer; can be bigger than window
9970 		XIC xic; // input context
9971 		int curHidden = 0; // counter
9972 		Cursor blankCurPtr = 0;
9973 		int cursorSequenceNumber = 0;
9974 		int warpEventCount = 0; // number of mouse movement events to eat
9975 
9976 		void delegate(XEvent) setSelectionHandler;
9977 		void delegate(in char[]) getSelectionHandler;
9978 
9979 		version(without_opengl) {} else
9980 		GLXContext glc;
9981 
9982 		private void fixFixedSize(bool forced=false) (int width, int height) {
9983 			if (forced || this.resizability == Resizability.fixedSize) {
9984 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
9985 				XSizeHints sh;
9986 				static if (!forced) {
9987 					c_long spr;
9988 					XGetWMNormalHints(display, window, &sh, &spr);
9989 					sh.flags |= PMaxSize | PMinSize;
9990 				} else {
9991 					sh.flags = PMaxSize | PMinSize;
9992 				}
9993 				sh.min_width = width;
9994 				sh.min_height = height;
9995 				sh.max_width = width;
9996 				sh.max_height = height;
9997 				XSetWMNormalHints(display, window, &sh);
9998 				//XFlush(display);
9999 			}
10000 		}
10001 
10002 		ScreenPainter getPainter() {
10003 			return ScreenPainter(this, window);
10004 		}
10005 
10006 		void move(int x, int y) {
10007 			XMoveWindow(display, window, x, y);
10008 		}
10009 
10010 		void resize(int w, int h) {
10011 			if (w < 1) w = 1;
10012 			if (h < 1) h = 1;
10013 			XResizeWindow(display, window, w, h);
10014 
10015 			// calling this now to avoid waiting for the server to
10016 			// acknowledge the resize; draws without returning to the
10017 			// event loop will thus actually work. the server's event
10018 			// btw might overrule this and resize it again
10019 			recordX11Resize(display, this, w, h);
10020 
10021 			// FIXME: do we need to set this as the opengl context to do the glViewport change?
10022 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
10023 		}
10024 
10025 		void moveResize (int x, int y, int w, int h) {
10026 			if (w < 1) w = 1;
10027 			if (h < 1) h = 1;
10028 			XMoveResizeWindow(display, window, x, y, w, h);
10029 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
10030 		}
10031 
10032 		void hideCursor () {
10033 			if (curHidden++ == 0) {
10034 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
10035 					static const(char)[1] cmbmp = 0;
10036 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
10037 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
10038 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
10039 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
10040 					XFreePixmap(display, pm);
10041 				}
10042 				XDefineCursor(display, window, blankCurPtr);
10043 			}
10044 		}
10045 
10046 		void showCursor () {
10047 			if (--curHidden == 0) XUndefineCursor(display, window);
10048 		}
10049 
10050 		void warpMouse (int x, int y) {
10051 			// here i will send dummy "ignore next mouse motion" event,
10052 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
10053 			// and we don't need to report it to the user (as warping is
10054 			// used when the user needs movement deltas).
10055 			//XClientMessageEvent xclient;
10056 			XEvent e;
10057 			e.xclient.type = EventType.ClientMessage;
10058 			e.xclient.window = window;
10059 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
10060 			e.xclient.format = 32;
10061 			e.xclient.data.l[0] = 0;
10062 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
10063 			//{ 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]); }
10064 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
10065 			// now warp pointer...
10066 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
10067 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
10068 			// ...and flush
10069 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
10070 			XFlush(display);
10071 		}
10072 
10073 		void sendDummyEvent () {
10074 			// here i will send dummy event to ping event queue
10075 			XEvent e;
10076 			e.xclient.type = EventType.ClientMessage;
10077 			e.xclient.window = window;
10078 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
10079 			e.xclient.format = 32;
10080 			e.xclient.data.l[0] = 0;
10081 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
10082 			XFlush(display);
10083 		}
10084 
10085 		void setTitle(string title) {
10086 			if (title.ptr is null) title = "";
10087 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
10088 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
10089 			XTextProperty windowName;
10090 			windowName.value = title.ptr;
10091 			windowName.encoding = XA_UTF8; //XA_STRING;
10092 			windowName.format = 8;
10093 			windowName.nitems = cast(uint)title.length;
10094 			XSetWMName(display, window, &windowName);
10095 			char[1024] namebuf = 0;
10096 			auto maxlen = namebuf.length-1;
10097 			if (maxlen > title.length) maxlen = title.length;
10098 			namebuf[0..maxlen] = title[0..maxlen];
10099 			XStoreName(display, window, namebuf.ptr);
10100 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
10101 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
10102 		}
10103 
10104 		string[] getTitles() {
10105 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
10106 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
10107 			XTextProperty textProp;
10108 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
10109 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
10110 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
10111 				} else
10112 					return [];
10113 			} else
10114 				return null;
10115 		}
10116 
10117 		string getTitle() {
10118 			auto titles = getTitles();
10119 			return titles.length ? titles[0] : null;
10120 		}
10121 
10122 		void setMinSize (int minwidth, int minheight) {
10123 			import core.stdc.config : c_long;
10124 			if (minwidth < 1) minwidth = 1;
10125 			if (minheight < 1) minheight = 1;
10126 			XSizeHints sh;
10127 			c_long spr;
10128 			XGetWMNormalHints(display, window, &sh, &spr);
10129 			sh.min_width = minwidth;
10130 			sh.min_height = minheight;
10131 			sh.flags |= PMinSize;
10132 			XSetWMNormalHints(display, window, &sh);
10133 			flushGui();
10134 		}
10135 
10136 		void setMaxSize (int maxwidth, int maxheight) {
10137 			import core.stdc.config : c_long;
10138 			if (maxwidth < 1) maxwidth = 1;
10139 			if (maxheight < 1) maxheight = 1;
10140 			XSizeHints sh;
10141 			c_long spr;
10142 			XGetWMNormalHints(display, window, &sh, &spr);
10143 			sh.max_width = maxwidth;
10144 			sh.max_height = maxheight;
10145 			sh.flags |= PMaxSize;
10146 			XSetWMNormalHints(display, window, &sh);
10147 			flushGui();
10148 		}
10149 
10150 		void setResizeGranularity (int granx, int grany) {
10151 			import core.stdc.config : c_long;
10152 			if (granx < 1) granx = 1;
10153 			if (grany < 1) grany = 1;
10154 			XSizeHints sh;
10155 			c_long spr;
10156 			XGetWMNormalHints(display, window, &sh, &spr);
10157 			sh.width_inc = granx;
10158 			sh.height_inc = grany;
10159 			sh.flags |= PResizeInc;
10160 			XSetWMNormalHints(display, window, &sh);
10161 			flushGui();
10162 		}
10163 
10164 		void setOpacity (uint opacity) {
10165 			if (opacity == uint.max)
10166 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
10167 			else
10168 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
10169 					XA_CARDINAL, 32, PropModeReplace, &opacity, 1);
10170 		}
10171 
10172 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
10173 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
10174 			display = XDisplayConnection.get();
10175 			auto screen = DefaultScreen(display);
10176 
10177 			version(without_opengl) {}
10178 			else {
10179 				if(opengl == OpenGlOptions.yes) {
10180 					GLXFBConfig fbconf = null;
10181 					XVisualInfo* vi = null;
10182 					bool useLegacy = false;
10183 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
10184 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
10185 						int[23] visualAttribs = [
10186 							GLX_X_RENDERABLE , 1/*True*/,
10187 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
10188 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
10189 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
10190 							GLX_RED_SIZE     , 8,
10191 							GLX_GREEN_SIZE   , 8,
10192 							GLX_BLUE_SIZE    , 8,
10193 							GLX_ALPHA_SIZE   , 8,
10194 							GLX_DEPTH_SIZE   , 24,
10195 							GLX_STENCIL_SIZE , 8,
10196 							GLX_DOUBLEBUFFER , 1/*True*/,
10197 							0/*None*/,
10198 						];
10199 						int fbcount;
10200 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
10201 						if (fbcount == 0) {
10202 							useLegacy = true; // try to do at least something
10203 						} else {
10204 							// pick the FB config/visual with the most samples per pixel
10205 							int bestidx = -1, bestns = -1;
10206 							foreach (int fbi; 0..fbcount) {
10207 								int sb, samples;
10208 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
10209 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
10210 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
10211 							}
10212 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
10213 							fbconf = fbc[bestidx];
10214 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
10215 							XFree(fbc);
10216 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
10217 						}
10218 					}
10219 					if (vi is null || useLegacy) {
10220 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
10221 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
10222 						useLegacy = true;
10223 					}
10224 					if (vi is null) throw new Exception("no open gl visual found");
10225 
10226 					XSetWindowAttributes swa;
10227 					auto root = RootWindow(display, screen);
10228 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
10229 
10230 					window = XCreateWindow(display, parent is null ? root : parent.impl.window,
10231 						0, 0, width, height,
10232 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa);
10233 
10234 					// now try to use `glXCreateContextAttribsARB()` if it's here
10235 					if (!useLegacy) {
10236 						// request fairly advanced context, even with stencil buffer!
10237 						int[9] contextAttribs = [
10238 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
10239 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
10240 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
10241 							// for modern context, set "forward compatibility" flag too
10242 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
10243 							0/*None*/,
10244 						];
10245 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
10246 						if (glc is null && sdpyOpenGLContextAllowFallback) {
10247 							sdpyOpenGLContextVersion = 0;
10248 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
10249 						}
10250 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
10251 					} else {
10252 						// fallback to old GLX call
10253 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
10254 							sdpyOpenGLContextVersion = 0;
10255 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
10256 						}
10257 					}
10258 					// sync to ensure any errors generated are processed
10259 					XSync(display, 0/*False*/);
10260 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
10261 					if(glc is null)
10262 						throw new Exception("glc");
10263 				}
10264 			}
10265 
10266 			if(opengl == OpenGlOptions.no) {
10267 
10268 				bool overrideRedirect = false;
10269 				if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)
10270 					overrideRedirect = true;
10271 
10272 				XSetWindowAttributes swa;
10273 				swa.background_pixel = WhitePixel(display, screen);
10274 				swa.border_pixel = BlackPixel(display, screen);
10275 				swa.override_redirect = overrideRedirect;
10276 				auto root = RootWindow(display, screen);
10277 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
10278 
10279 				window = XCreateWindow(display, parent is null ? root : parent.impl.window,
10280 					0, 0, width, height,
10281 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa);
10282 
10283 
10284 
10285 				/*
10286 				window = XCreateSimpleWindow(
10287 					display,
10288 					parent is null ? RootWindow(display, screen) : parent.impl.window,
10289 					0, 0, // x, y
10290 					width, height,
10291 					1, // border width
10292 					BlackPixel(display, screen), // border
10293 					WhitePixel(display, screen)); // background
10294 				*/
10295 
10296 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
10297 				bufferw = width;
10298 				bufferh = height;
10299 
10300 				gc = DefaultGC(display, screen);
10301 
10302 				// clear out the buffer to get us started...
10303 				XSetForeground(display, gc, WhitePixel(display, screen));
10304 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
10305 				XSetForeground(display, gc, BlackPixel(display, screen));
10306 			}
10307 
10308 			// input context
10309 			//TODO: create this only for top-level windows, and reuse that?
10310 			if (XDisplayConnection.xim !is null) {
10311 				xic = XCreateIC(XDisplayConnection.xim,
10312 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
10313 						/*XNClientWindow*/"clientWindow".ptr, window,
10314 						/*XNFocusWindow*/"focusWindow".ptr, window,
10315 						null);
10316 				if (xic is null) {
10317 					import core.stdc.stdio : stderr, fprintf;
10318 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
10319 				}
10320 			}
10321 
10322 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
10323 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
10324 			// window class
10325 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
10326 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
10327 				XClassHint klass;
10328 				XWMHints wh;
10329 				XSizeHints size;
10330 				klass.res_name = sdpyWindowClassStr;
10331 				klass.res_class = sdpyWindowClassStr;
10332 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
10333 			}
10334 
10335 			setTitle(title);
10336 			SimpleWindow.nativeMapping[window] = this;
10337 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
10338 
10339 			// This gives our window a close button
10340 			if (windowType != WindowTypes.eventOnly) {
10341 				Atom atom = XInternAtom(display, "WM_DELETE_WINDOW".ptr, true); // FIXME: does this need to be freed?
10342 				XSetWMProtocols(display, window, &atom, 1);
10343 			}
10344 
10345 			// FIXME: windowType and customizationFlags
10346 			Atom[8] wsatoms; // here, due to goto
10347 			int wmsacount = 0; // here, due to goto
10348 
10349 			try
10350 			final switch(windowType) {
10351 				case WindowTypes.normal:
10352 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
10353 				break;
10354 				case WindowTypes.undecorated:
10355 					motifHideDecorations();
10356 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
10357 				break;
10358 				case WindowTypes.eventOnly:
10359 					_hidden = true;
10360 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
10361 					goto hiddenWindow;
10362 				//break;
10363 				case WindowTypes.nestedChild:
10364 
10365 				break;
10366 
10367 				case WindowTypes.dropdownMenu:
10368 					motifHideDecorations();
10369 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
10370 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
10371 				break;
10372 				case WindowTypes.popupMenu:
10373 					motifHideDecorations();
10374 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
10375 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
10376 				break;
10377 				case WindowTypes.notification:
10378 					motifHideDecorations();
10379 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
10380 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
10381 				break;
10382 				/+
10383 				case WindowTypes.menu:
10384 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
10385 					motifHideDecorations();
10386 				break;
10387 				case WindowTypes.desktop:
10388 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
10389 				break;
10390 				case WindowTypes.dock:
10391 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
10392 				break;
10393 				case WindowTypes.toolbar:
10394 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
10395 				break;
10396 				case WindowTypes.menu:
10397 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
10398 				break;
10399 				case WindowTypes.utility:
10400 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
10401 				break;
10402 				case WindowTypes.splash:
10403 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
10404 				break;
10405 				case WindowTypes.dialog:
10406 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
10407 				break;
10408 				case WindowTypes.tooltip:
10409 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
10410 				break;
10411 				case WindowTypes.notification:
10412 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
10413 				break;
10414 				case WindowTypes.combo:
10415 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
10416 				break;
10417 				case WindowTypes.dnd:
10418 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
10419 				break;
10420 				+/
10421 			}
10422 			catch(Exception e) {
10423 				// XInternAtom failed, prolly a WM
10424 				// that doesn't support these things
10425 			}
10426 
10427 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
10428 			// the two following flags may be ignored by WM
10429 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
10430 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
10431 
10432 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
10433 
10434 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
10435 
10436 			// What would be ideal here is if they only were
10437 			// selected if there was actually an event handler
10438 			// for them...
10439 
10440 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
10441 
10442 			hiddenWindow:
10443 
10444 			// set the pid property for lookup later by window managers
10445 			// a standard convenience
10446 			import core.sys.posix.unistd;
10447 			arch_ulong pid = getpid();
10448 
10449 			XChangeProperty(
10450 				display,
10451 				impl.window,
10452 				GetAtom!("_NET_WM_PID", true)(display),
10453 				XA_CARDINAL,
10454 				32 /* bits */,
10455 				0 /*PropModeReplace*/,
10456 				&pid,
10457 				1);
10458 
10459 
10460 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
10461 				XMapWindow(display, window);
10462 			} else {
10463 				_hidden = true;
10464 			}
10465 		}
10466 
10467 		void selectDefaultInput(bool forceIncludeMouseMotion) {
10468 			auto mask = EventMask.ExposureMask |
10469 				EventMask.KeyPressMask |
10470 				EventMask.KeyReleaseMask |
10471 				EventMask.PropertyChangeMask |
10472 				EventMask.FocusChangeMask |
10473 				EventMask.StructureNotifyMask |
10474 				EventMask.VisibilityChangeMask
10475 				| EventMask.ButtonPressMask
10476 				| EventMask.ButtonReleaseMask
10477 			;
10478 
10479 			// xshm is our shortcut for local connections
10480 			if(Image.impl.xshmAvailable || forceIncludeMouseMotion)
10481 				mask |= EventMask.PointerMotionMask;
10482 			else
10483 				mask |= EventMask.ButtonMotionMask;
10484 
10485 			XSelectInput(display, window, mask);
10486 		}
10487 
10488 
10489 		void setNetWMWindowType(Atom type) {
10490 			Atom[2] atoms;
10491 
10492 			atoms[0] = type;
10493 			// generic fallback
10494 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
10495 
10496 			XChangeProperty(
10497 				display,
10498 				impl.window,
10499 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
10500 				XA_ATOM,
10501 				32 /* bits */,
10502 				0 /*PropModeReplace*/,
10503 				atoms.ptr,
10504 				cast(int) atoms.length);
10505 		}
10506 
10507 		void motifHideDecorations() {
10508 			MwmHints hints;
10509 			hints.flags = MWM_HINTS_DECORATIONS;
10510 
10511 			XChangeProperty(
10512 				display,
10513 				impl.window,
10514 				GetAtom!"_MOTIF_WM_HINTS"(display),
10515 				GetAtom!"_MOTIF_WM_HINTS"(display),
10516 				32 /* bits */,
10517 				0 /*PropModeReplace*/,
10518 				&hints,
10519 				hints.sizeof / 4);
10520 		}
10521 
10522 		/*k8: unused
10523 		void createOpenGlContext() {
10524 
10525 		}
10526 		*/
10527 
10528 		void closeWindow() {
10529 			if (customEventFDRead != -1) {
10530 				import core.sys.posix.unistd : close;
10531 				auto same = customEventFDRead == customEventFDWrite;
10532 
10533 				close(customEventFDRead);
10534 				if(!same)
10535 					close(customEventFDWrite);
10536 				customEventFDRead = -1;
10537 				customEventFDWrite = -1;
10538 			}
10539 			if(buffer)
10540 				XFreePixmap(display, buffer);
10541 			bufferw = bufferh = 0;
10542 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
10543 			XDestroyWindow(display, window);
10544 			XFlush(display);
10545 		}
10546 
10547 		void dispose() {
10548 		}
10549 
10550 		bool destroyed = false;
10551 	}
10552 
10553 	bool insideXEventLoop;
10554 }
10555 
10556 version(X11) {
10557 
10558 	int mouseDoubleClickTimeout = 350; /// double click timeout. X only, you probably shouldn't change this.
10559 
10560 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
10561 		if(width != win.width || height != win.height) {
10562 			win._width = width;
10563 			win._height = height;
10564 
10565 			if(win.openglMode == OpenGlOptions.no) {
10566 				// FIXME: could this be more efficient?
10567 
10568 				if (win.bufferw < width || win.bufferh < height) {
10569 					//{ 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); }
10570 					// grow the internal buffer to match the window...
10571 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
10572 					{
10573 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
10574 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
10575 						scope(exit) XFreeGC(win.display, xgc);
10576 						XSetClipMask(win.display, xgc, None);
10577 						XSetForeground(win.display, xgc, 0);
10578 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
10579 					}
10580 					XCopyArea(display,
10581 						cast(Drawable) win.buffer,
10582 						cast(Drawable) newPixmap,
10583 						win.gc, 0, 0,
10584 						win.bufferw < width ? win.bufferw : win.width,
10585 						win.bufferh < height ? win.bufferh : win.height,
10586 						0, 0);
10587 
10588 					XFreePixmap(display, win.buffer);
10589 					win.buffer = newPixmap;
10590 					win.bufferw = width;
10591 					win.bufferh = height;
10592 				}
10593 
10594 				// clear unused parts of the buffer
10595 				if (win.bufferw > width || win.bufferh > height) {
10596 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
10597 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
10598 					scope(exit) XFreeGC(win.display, xgc);
10599 					XSetClipMask(win.display, xgc, None);
10600 					XSetForeground(win.display, xgc, 0);
10601 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
10602 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
10603 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
10604 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
10605 				}
10606 
10607 			}
10608 
10609 			version(without_opengl) {} else
10610 			if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) {
10611 				glViewport(0, 0, width, height);
10612 			}
10613 
10614 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
10615 
10616 			if(win.windowResized !is null) {
10617 				XUnlockDisplay(display);
10618 				scope(exit) XLockDisplay(display);
10619 				win.windowResized(width, height);
10620 			}
10621 		}
10622 	}
10623 
10624 
10625 	/// Platform-specific, you might use it when doing a custom event loop
10626 	bool doXNextEvent(Display* display) {
10627 		bool done;
10628 		XEvent e;
10629 		XNextEvent(display, &e);
10630 		version(sddddd) {
10631 			import std.stdio, std.conv : to;
10632 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
10633 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
10634 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
10635 			}
10636 		}
10637 
10638 		// filter out compose events
10639 		if (XFilterEvent(&e, None)) {
10640 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
10641 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
10642 			return false;
10643 		}
10644 		// process keyboard mapping changes
10645 		if (e.type == EventType.KeymapNotify) {
10646 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
10647 			XRefreshKeyboardMapping(&e.xmapping);
10648 			return false;
10649 		}
10650 
10651 		version(with_eventloop)
10652 			import arsd.eventloop;
10653 
10654 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
10655 			// see windows impl's comments
10656 			XUnlockDisplay(display);
10657 			scope(exit) XLockDisplay(display);
10658 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
10659 			if(ret == 0)
10660 				return done;
10661 		}
10662 
10663 
10664 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
10665 			if(win.getNativeEventHandler !is null) {
10666 				XUnlockDisplay(display);
10667 				scope(exit) XLockDisplay(display);
10668 				auto ret = win.getNativeEventHandler()(e);
10669 				if(ret == 0)
10670 					return done;
10671 			}
10672 		}
10673 
10674 		switch(e.type) {
10675 		  case EventType.SelectionClear:
10676 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping)
10677 				{ /* FIXME??????? */ }
10678 		  break;
10679 		  case EventType.SelectionRequest:
10680 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
10681 			if(win.setSelectionHandler !is null) {
10682 				XUnlockDisplay(display);
10683 				scope(exit) XLockDisplay(display);
10684 				win.setSelectionHandler(e);
10685 			}
10686 		  break;
10687 		  case EventType.PropertyNotify:
10688 
10689 		  break;
10690 		  case EventType.SelectionNotify:
10691 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
10692 		  	if(win.getSelectionHandler !is null) {
10693 				// FIXME: maybe we should call a different handler for PRIMARY vs CLIPBOARD
10694 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
10695 					XUnlockDisplay(display);
10696 					scope(exit) XLockDisplay(display);
10697 					win.getSelectionHandler(null);
10698 				} else {
10699 					Atom target;
10700 					int format;
10701 					arch_ulong bytesafter, length;
10702 					void* value;
10703 					XGetWindowProperty(
10704 						e.xselection.display,
10705 						e.xselection.requestor,
10706 						e.xselection.property,
10707 						0,
10708 						100000 /* length */,
10709 						//false, /* don't erase it */
10710 						true, /* do erase it lol */
10711 						0 /*AnyPropertyType*/,
10712 						&target, &format, &length, &bytesafter, &value);
10713 
10714 					// FIXME: I don't have to copy it now since it is in char[] instead of string
10715 
10716 					{
10717 						XUnlockDisplay(display);
10718 						scope(exit) XLockDisplay(display);
10719 
10720 						if(target == XA_ATOM) {
10721 							// initial request, see what they are able to work with and request the best one
10722 							// we can handle, if available
10723 
10724 							Atom[] answer = (cast(Atom*) value)[0 .. length];
10725 							Atom best = None;
10726 							foreach(option; answer) {
10727 								if(option == GetAtom!"UTF8_STRING"(display)) {
10728 									best = option;
10729 									break;
10730 								} else if(option == XA_STRING) {
10731 									best = option;
10732 								}
10733 							}
10734 
10735 							//writeln("got ", answer);
10736 
10737 							if(best != None) {
10738 								// actually request the best format
10739 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
10740 							}
10741 						} else if(target == GetAtom!"UTF8_STRING"(display) || target == XA_STRING) {
10742 							win.getSelectionHandler((cast(char[]) value[0 .. length]).idup);
10743 						} else if(target == GetAtom!"INCR"(display)) {
10744 							// incremental
10745 
10746 							//sdpyGettingPaste = true; // FIXME: should prolly be separate for the different selections
10747 
10748 							// FIXME: handle other events while it goes so app doesn't lock up with big pastes
10749 							// can also be optimized if it chunks to the api somehow
10750 
10751 							char[] s;
10752 
10753 							do {
10754 
10755 								XEvent subevent;
10756 								do {
10757 									XMaskEvent(display, EventMask.PropertyChangeMask, &subevent);
10758 								} while(subevent.type != EventType.PropertyNotify || subevent.xproperty.atom != e.xselection.property || subevent.xproperty.state != PropertyNotification.PropertyNewValue);
10759 
10760 								void* subvalue;
10761 								XGetWindowProperty(
10762 									e.xselection.display,
10763 									e.xselection.requestor,
10764 									e.xselection.property,
10765 									0,
10766 									100000 /* length */,
10767 									true, /* erase it to signal we got it and want more */
10768 									0 /*AnyPropertyType*/,
10769 									&target, &format, &length, &bytesafter, &subvalue);
10770 
10771 								s ~= (cast(char*) subvalue)[0 .. length];
10772 
10773 								XFree(subvalue);
10774 							} while(length > 0);
10775 
10776 							win.getSelectionHandler(s);
10777 						} else {
10778 							// unsupported type
10779 						}
10780 					}
10781 					XFree(value);
10782 					/*
10783 					XDeleteProperty(
10784 						e.xselection.display,
10785 						e.xselection.requestor,
10786 						e.xselection.property);
10787 					*/
10788 				}
10789 			}
10790 		  break;
10791 		  case EventType.ConfigureNotify:
10792 			auto event = e.xconfigure;
10793 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
10794 					//version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); }
10795 
10796 				recordX11Resize(display, *win, event.width, event.height);
10797 			}
10798 		  break;
10799 		  case EventType.Expose:
10800 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
10801 				// if it is closing from a popup menu, it can get
10802 				// an Expose event right by the end and trigger a
10803 				// BadDrawable error ... we'll just check
10804 				// closed to handle that.
10805 				if((*win).closed) break;
10806 				if((*win).openglMode == OpenGlOptions.no) {
10807 					bool doCopy = true;
10808 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
10809 					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);
10810 				} else {
10811 					// need to redraw the scene somehow
10812 					XUnlockDisplay(display);
10813 					scope(exit) XLockDisplay(display);
10814 					version(without_opengl) {} else
10815 					win.redrawOpenGlSceneNow();
10816 				}
10817 			}
10818 		  break;
10819 		  case EventType.FocusIn:
10820 		  case EventType.FocusOut:
10821 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
10822 				if (win.xic !is null) {
10823 					//{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); }
10824 					if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic);
10825 				}
10826 
10827 				win._focused = e.type == EventType.FocusIn;
10828 
10829 				if(win.demandingAttention)
10830 					demandAttention(*win, false);
10831 
10832 				if(win.onFocusChange) {
10833 					XUnlockDisplay(display);
10834 					scope(exit) XLockDisplay(display);
10835 					win.onFocusChange(e.type == EventType.FocusIn);
10836 				}
10837 			}
10838 		  break;
10839 		  case EventType.VisibilityNotify:
10840 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
10841 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
10842 						if (win.visibilityChanged !is null) {
10843 								XUnlockDisplay(display);
10844 								scope(exit) XLockDisplay(display);
10845 								win.visibilityChanged(false);
10846 							}
10847 					} else {
10848 						if (win.visibilityChanged !is null) {
10849 							XUnlockDisplay(display);
10850 							scope(exit) XLockDisplay(display);
10851 							win.visibilityChanged(true);
10852 						}
10853 					}
10854 				}
10855 				break;
10856 		  case EventType.ClientMessage:
10857 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
10858 					// "ignore next mouse motion" event, increment ignore counter for teh window
10859 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
10860 						++(*win).warpEventCount;
10861 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
10862 					} else {
10863 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
10864 					}
10865 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
10866 					// user clicked the close button on the window manager
10867 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
10868 						XUnlockDisplay(display);
10869 						scope(exit) XLockDisplay(display);
10870 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
10871 					}
10872 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
10873 					foreach(nai; NotificationAreaIcon.activeIcons)
10874 						nai.newManager();
10875 				}
10876 		  break;
10877 		  case EventType.MapNotify:
10878 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
10879 					(*win)._visible = true;
10880 					if (!(*win)._visibleForTheFirstTimeCalled) {
10881 						(*win)._visibleForTheFirstTimeCalled = true;
10882 						if ((*win).visibleForTheFirstTime !is null) {
10883 							XUnlockDisplay(display);
10884 							scope(exit) XLockDisplay(display);
10885 							version(without_opengl) {} else {
10886 								if((*win).openglMode == OpenGlOptions.yes) {
10887 									(*win).setAsCurrentOpenGlContextNT();
10888 									glViewport(0, 0, (*win).width, (*win).height);
10889 								}
10890 							}
10891 							(*win).visibleForTheFirstTime();
10892 						}
10893 					}
10894 					if ((*win).visibilityChanged !is null) {
10895 						XUnlockDisplay(display);
10896 						scope(exit) XLockDisplay(display);
10897 						(*win).visibilityChanged(true);
10898 					}
10899 				}
10900 		  break;
10901 		  case EventType.UnmapNotify:
10902 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
10903 					win._visible = false;
10904 					if (win.visibilityChanged !is null) {
10905 						XUnlockDisplay(display);
10906 						scope(exit) XLockDisplay(display);
10907 						win.visibilityChanged(false);
10908 					}
10909 			}
10910 		  break;
10911 		  case EventType.DestroyNotify:
10912 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
10913 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
10914 				win._closed = true; // just in case
10915 				win.destroyed = true;
10916 				if (win.xic !is null) {
10917 					XDestroyIC(win.xic);
10918 					win.xic = null; // just in calse
10919 				}
10920 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
10921 				bool anyImportant = false;
10922 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
10923 					if(w.beingOpenKeepsAppOpen) {
10924 						anyImportant = true;
10925 						break;
10926 					}
10927 				if(!anyImportant)
10928 					done = true;
10929 			}
10930 			auto window = e.xdestroywindow.window;
10931 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
10932 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
10933 
10934 			version(with_eventloop) {
10935 				if(done) exit();
10936 			}
10937 		  break;
10938 
10939 		  case EventType.MotionNotify:
10940 			MouseEvent mouse;
10941 			auto event = e.xmotion;
10942 
10943 			mouse.type = MouseEventType.motion;
10944 			mouse.x = event.x;
10945 			mouse.y = event.y;
10946 			mouse.modifierState = event.state;
10947 
10948 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
10949 				mouse.window = *win;
10950 				if (win.warpEventCount > 0) {
10951 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
10952 					--(*win).warpEventCount;
10953 					(*win).mdx(mouse); // so deltas will be correctly updated
10954 				} else {
10955 					win.warpEventCount = 0; // just in case
10956 					(*win).mdx(mouse);
10957 					if((*win).handleMouseEvent) {
10958 						XUnlockDisplay(display);
10959 						scope(exit) XLockDisplay(display);
10960 						(*win).handleMouseEvent(mouse);
10961 					}
10962 				}
10963 			}
10964 
10965 		  	version(with_eventloop)
10966 				send(mouse);
10967 		  break;
10968 		  case EventType.ButtonPress:
10969 		  case EventType.ButtonRelease:
10970 			MouseEvent mouse;
10971 			auto event = e.xbutton;
10972 
10973 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
10974 			mouse.x = event.x;
10975 			mouse.y = event.y;
10976 
10977 			static Time lastMouseDownTime = 0;
10978 
10979 			mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
10980 			if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time;
10981 
10982 			switch(event.button) {
10983 				case 1: mouse.button = MouseButton.left; break; // left
10984 				case 2: mouse.button = MouseButton.middle; break; // middle
10985 				case 3: mouse.button = MouseButton.right; break; // right
10986 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
10987 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
10988 				case 6: break; // idk
10989 				case 7: break; // idk
10990 				case 8: mouse.button = MouseButton.backButton; break;
10991 				case 9: mouse.button = MouseButton.forwardButton; break;
10992 				default:
10993 			}
10994 
10995 			// FIXME: double check this
10996 			mouse.modifierState = event.state;
10997 
10998 			//mouse.modifierState = event.detail;
10999 
11000 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
11001 				mouse.window = *win;
11002 				(*win).mdx(mouse);
11003 				if((*win).handleMouseEvent) {
11004 					XUnlockDisplay(display);
11005 					scope(exit) XLockDisplay(display);
11006 					(*win).handleMouseEvent(mouse);
11007 				}
11008 			}
11009 			version(with_eventloop)
11010 				send(mouse);
11011 		  break;
11012 
11013 		  case EventType.KeyPress:
11014 		  case EventType.KeyRelease:
11015 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
11016 			KeyEvent ke;
11017 			ke.pressed = e.type == EventType.KeyPress;
11018 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
11019 
11020 			auto sym = XKeycodeToKeysym(
11021 				XDisplayConnection.get(),
11022 				e.xkey.keycode,
11023 				0);
11024 
11025 			ke.key = cast(Key) sym;//e.xkey.keycode;
11026 
11027 			ke.modifierState = e.xkey.state;
11028 
11029 			// import std.stdio; writefln("%x", sym);
11030 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
11031 			int charbuflen = 0; // return value of XwcLookupString
11032 			if (ke.pressed) {
11033 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
11034 				if (win !is null && win.xic !is null) {
11035 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
11036 					Status status;
11037 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
11038 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
11039 				} else {
11040 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
11041 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
11042 					char[16] buffer;
11043 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
11044 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
11045 				}
11046 			}
11047 
11048 			// if there's no char, subst one
11049 			if (charbuflen == 0) {
11050 				switch (sym) {
11051 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
11052 					case 0xff8d: // keypad enter
11053 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
11054 					default : // ignore
11055 				}
11056 			}
11057 
11058 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
11059 				ke.window = *win;
11060 				if (win.handleKeyEvent) {
11061 					XUnlockDisplay(display);
11062 					scope(exit) XLockDisplay(display);
11063 					win.handleKeyEvent(ke);
11064 				}
11065 
11066 				// char events are separate since they are on Windows too
11067 				// also, xcompose can generate long char sequences
11068 				// don't send char events if Meta and/or Hyper is pressed
11069 				// TODO: ctrl+char should only send control chars; not yet
11070 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
11071 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
11072 				}
11073 				if (ke.pressed && charbuflen > 0 && (e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0) {
11074 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
11075 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
11076 						if (win.handleCharEvent) {
11077 							XUnlockDisplay(display);
11078 							scope(exit) XLockDisplay(display);
11079 							win.handleCharEvent(ch);
11080 						}
11081 					}
11082 				}
11083 			}
11084 
11085 			version(with_eventloop)
11086 				send(ke);
11087 		  break;
11088 		  default:
11089 		}
11090 
11091 		return done;
11092 	}
11093 }
11094 
11095 /* *************************************** */
11096 /*      Done with simpledisplay stuff      */
11097 /* *************************************** */
11098 
11099 // Necessary C library bindings follow
11100 version(Windows) {} else
11101 version(X11) {
11102 
11103 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
11104 
11105 // X11 bindings needed here
11106 /*
11107 	A little of this is from the bindings project on
11108 	D Source and some of it is copy/paste from the C
11109 	header.
11110 
11111 	The DSource listing consistently used D's long
11112 	where C used long. That's wrong - C long is 32 bit, so
11113 	it should be int in D. I changed that here.
11114 
11115 	Note:
11116 	This isn't complete, just took what I needed for myself.
11117 */
11118 
11119 import core.stdc.stddef : wchar_t;
11120 
11121 interface XLib {
11122 extern(C) nothrow @nogc {
11123 	char* XResourceManagerString(Display*);
11124 	void XrmInitialize();
11125 	XrmDatabase XrmGetStringDatabase(char* data);
11126 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
11127 
11128 	Cursor XCreateFontCursor(Display*, uint shape);
11129 	int XDefineCursor(Display* display, Window w, Cursor cursor);
11130 	int XUndefineCursor(Display* display, Window w);
11131 
11132 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
11133 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
11134 	int XFreeCursor(Display* display, Cursor cursor);
11135 
11136 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
11137 
11138 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
11139 
11140 	char *XKeysymToString(KeySym keysym);
11141 	KeySym XKeycodeToKeysym(
11142 		Display*		/* display */,
11143 		KeyCode		/* keycode */,
11144 		int			/* index */
11145 	);
11146 
11147 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
11148 
11149 	int XFree(void*);
11150 	int XDeleteProperty(Display *display, Window w, Atom property);
11151 
11152 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements);
11153 
11154 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
11155 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
11156 		*actual_type_return, int *actual_format_return, arch_ulong
11157 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
11158 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
11159 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
11160 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
11161 
11162 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
11163 
11164 	Window XGetSelectionOwner(Display *display, Atom selection);
11165 
11166 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
11167 
11168 	Display* XOpenDisplay(const char*);
11169 	int XCloseDisplay(Display*);
11170 
11171 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
11172 
11173 	Bool XSupportsLocale();
11174 	char* XSetLocaleModifiers(const(char)* modifier_list);
11175 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
11176 	Status XCloseOM(XOM om);
11177 
11178 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
11179 	Status XCloseIM(XIM im);
11180 
11181 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
11182 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
11183 	Display* XDisplayOfIM(XIM im);
11184 	char* XLocaleOfIM(XIM im);
11185 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
11186 	void XDestroyIC(XIC ic);
11187 	void XSetICFocus(XIC ic);
11188 	void XUnsetICFocus(XIC ic);
11189 	//wchar_t* XwcResetIC(XIC ic);
11190 	char* XmbResetIC(XIC ic);
11191 	char* Xutf8ResetIC(XIC ic);
11192 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
11193 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
11194 	XIM XIMOfIC(XIC ic);
11195 
11196 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
11197 
11198 
11199 	XFontStruct *XLoadQueryFont(Display *display, in char *name);
11200 	int XFreeFont(Display *display, XFontStruct *font_struct);
11201 	int XSetFont(Display* display, GC gc, Font font);
11202 	int XTextWidth(XFontStruct*, in char*, int);
11203 
11204 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
11205 	int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n);
11206 
11207 	Window XCreateSimpleWindow(
11208 		Display*	/* display */,
11209 		Window		/* parent */,
11210 		int			/* x */,
11211 		int			/* y */,
11212 		uint		/* width */,
11213 		uint		/* height */,
11214 		uint		/* border_width */,
11215 		uint		/* border */,
11216 		uint		/* background */
11217 	);
11218 	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);
11219 
11220 	int XReparentWindow(Display*, Window, Window, int, int);
11221 	int XClearWindow(Display*, Window);
11222 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
11223 	int XMoveWindow(Display*, Window, int, int);
11224 	int XResizeWindow(Display *display, Window w, uint width, uint height);
11225 
11226 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
11227 
11228 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
11229 
11230 	XImage *XCreateImage(
11231 		Display*		/* display */,
11232 		Visual*		/* visual */,
11233 		uint	/* depth */,
11234 		int			/* format */,
11235 		int			/* offset */,
11236 		ubyte*		/* data */,
11237 		uint	/* width */,
11238 		uint	/* height */,
11239 		int			/* bitmap_pad */,
11240 		int			/* bytes_per_line */
11241 	);
11242 
11243 	Status XInitImage (XImage* image);
11244 
11245 	Atom XInternAtom(
11246 		Display*		/* display */,
11247 		const char*	/* atom_name */,
11248 		Bool		/* only_if_exists */
11249 	);
11250 
11251 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
11252 	char* XGetAtomName(Display*, Atom);
11253 	Status XGetAtomNames(Display*, Atom*, int count, char**);
11254 
11255 	int XPutImage(
11256 		Display*	/* display */,
11257 		Drawable	/* d */,
11258 		GC			/* gc */,
11259 		XImage*	/* image */,
11260 		int			/* src_x */,
11261 		int			/* src_y */,
11262 		int			/* dest_x */,
11263 		int			/* dest_y */,
11264 		uint		/* width */,
11265 		uint		/* height */
11266 	);
11267 
11268 	int XDestroyWindow(
11269 		Display*	/* display */,
11270 		Window		/* w */
11271 	);
11272 
11273 	int XDestroyImage(XImage*);
11274 
11275 	int XSelectInput(
11276 		Display*	/* display */,
11277 		Window		/* w */,
11278 		EventMask	/* event_mask */
11279 	);
11280 
11281 	int XMapWindow(
11282 		Display*	/* display */,
11283 		Window		/* w */
11284 	);
11285 
11286 	Status XIconifyWindow(Display*, Window, int);
11287 	int XMapRaised(Display*, Window);
11288 	int XMapSubwindows(Display*, Window);
11289 
11290 	int XNextEvent(
11291 		Display*	/* display */,
11292 		XEvent*		/* event_return */
11293 	);
11294 
11295 	int XMaskEvent(Display*, arch_long, XEvent*);
11296 
11297 	Bool XFilterEvent(XEvent *event, Window window);
11298 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
11299 
11300 	Status XSetWMProtocols(
11301 		Display*	/* display */,
11302 		Window		/* w */,
11303 		Atom*		/* protocols */,
11304 		int			/* count */
11305 	);
11306 
11307 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
11308 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
11309 
11310 
11311 	Status XInitThreads();
11312 	void XLockDisplay (Display* display);
11313 	void XUnlockDisplay (Display* display);
11314 
11315 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
11316 
11317 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
11318 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
11319 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
11320 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
11321 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
11322 
11323 
11324 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
11325 	int XDrawString(Display*, Drawable, GC, int, int, in char*, int);
11326 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
11327 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
11328 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
11329 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
11330 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
11331 	int XDrawPoint(Display*, Drawable, GC, int, int);
11332 	int XSetForeground(Display*, GC, uint);
11333 	int XSetBackground(Display*, GC, uint);
11334 
11335 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
11336 	void XFreeFontSet(Display*, XFontSet);
11337 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int);
11338 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
11339 
11340 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
11341 	int XSetFunction(Display*, GC, int);
11342 
11343 	GC XCreateGC(Display*, Drawable, uint, void*);
11344 	int XCopyGC(Display*, GC, uint, GC);
11345 	int XFreeGC(Display*, GC);
11346 
11347 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
11348 	bool XCheckMaskEvent(Display*, int, XEvent*);
11349 
11350 	int XPending(Display*);
11351 	int XEventsQueued(Display* display, int mode);
11352 
11353 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
11354 	int XFreePixmap(Display*, Pixmap);
11355 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
11356 	int XFlush(Display*);
11357 	int XBell(Display*, int);
11358 	int XSync(Display*, bool);
11359 
11360 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
11361 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
11362 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
11363 
11364 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
11365 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
11366 
11367 	Status XAllocColor(Display*, Colormap, XColor*);
11368 
11369 	int XWithdrawWindow(Display*, Window, int);
11370 	int XUnmapWindow(Display*, Window);
11371 	int XLowerWindow(Display*, Window);
11372 	int XRaiseWindow(Display*, Window);
11373 
11374 	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);
11375 	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);
11376 
11377 	int XGetInputFocus(Display*, Window*, int*);
11378 	int XSetInputFocus(Display*, Window, int, Time);
11379 
11380 	XErrorHandler XSetErrorHandler(XErrorHandler);
11381 
11382 	int XGetErrorText(Display*, int, char*, int);
11383 
11384 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
11385 
11386 
11387 	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);
11388 	int XUngrabPointer(Display *display, Time time);
11389 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
11390 
11391 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
11392 
11393 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
11394 	int XSetClipMask(Display*, GC, Pixmap);
11395 	int XSetClipOrigin(Display*, GC, int, int);
11396 
11397 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
11398 
11399 	void XSetWMName(Display*, Window, XTextProperty*);
11400 	Status XGetWMName(Display*, Window, XTextProperty*);
11401 	int XStoreName(Display* display, Window w, const(char)* window_name);
11402 
11403 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
11404 
11405 }
11406 }
11407 
11408 interface Xext {
11409 extern(C) nothrow @nogc {
11410 	Status XShmAttach(Display*, XShmSegmentInfo*);
11411 	Status XShmDetach(Display*, XShmSegmentInfo*);
11412 	Status XShmPutImage(
11413 		Display*            /* dpy */,
11414 		Drawable            /* d */,
11415 		GC                  /* gc */,
11416 		XImage*             /* image */,
11417 		int                 /* src_x */,
11418 		int                 /* src_y */,
11419 		int                 /* dst_x */,
11420 		int                 /* dst_y */,
11421 		uint        /* src_width */,
11422 		uint        /* src_height */,
11423 		Bool                /* send_event */
11424 	);
11425 
11426 	Status XShmQueryExtension(Display*);
11427 
11428 	XImage *XShmCreateImage(
11429 		Display*            /* dpy */,
11430 		Visual*             /* visual */,
11431 		uint        /* depth */,
11432 		int                 /* format */,
11433 		char*               /* data */,
11434 		XShmSegmentInfo*    /* shminfo */,
11435 		uint        /* width */,
11436 		uint        /* height */
11437 	);
11438 
11439 	Pixmap XShmCreatePixmap(
11440 		Display*            /* dpy */,
11441 		Drawable            /* d */,
11442 		char*               /* data */,
11443 		XShmSegmentInfo*    /* shminfo */,
11444 		uint        /* width */,
11445 		uint        /* height */,
11446 		uint        /* depth */
11447 	);
11448 
11449 }
11450 }
11451 
11452 	// this requires -lXpm
11453 	//int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
11454 
11455 
11456 mixin DynamicLoad!(XLib, "X11") xlib;
11457 mixin DynamicLoad!(Xext, "Xext") xext;
11458 shared static this() {
11459 	xlib.loadDynamicLibrary();
11460 	xext.loadDynamicLibrary();
11461 }
11462 
11463 
11464 extern(C) nothrow @nogc {
11465 
11466 alias XrmDatabase = void*;
11467 struct XrmValue {
11468 	uint size;
11469 	void* addr;
11470 }
11471 
11472 struct XVisualInfo {
11473 	Visual* visual;
11474 	VisualID visualid;
11475 	int screen;
11476 	uint depth;
11477 	int c_class;
11478 	c_ulong red_mask;
11479 	c_ulong green_mask;
11480 	c_ulong blue_mask;
11481 	int colormap_size;
11482 	int bits_per_rgb;
11483 }
11484 
11485 enum VisualNoMask=	0x0;
11486 enum VisualIDMask=	0x1;
11487 enum VisualScreenMask=0x2;
11488 enum VisualDepthMask=	0x4;
11489 enum VisualClassMask=	0x8;
11490 enum VisualRedMaskMask=0x10;
11491 enum VisualGreenMaskMask=0x20;
11492 enum VisualBlueMaskMask=0x40;
11493 enum VisualColormapSizeMask=0x80;
11494 enum VisualBitsPerRGBMask=0x100;
11495 enum VisualAllMask=	0x1FF;
11496 
11497 
11498 // XIM and other crap
11499 struct _XOM {}
11500 struct _XIM {}
11501 struct _XIC {}
11502 alias XOM = _XOM*;
11503 alias XIM = _XIM*;
11504 alias XIC = _XIC*;
11505 
11506 alias XIMStyle = arch_ulong;
11507 enum : arch_ulong {
11508 	XIMPreeditArea      = 0x0001,
11509 	XIMPreeditCallbacks = 0x0002,
11510 	XIMPreeditPosition  = 0x0004,
11511 	XIMPreeditNothing   = 0x0008,
11512 	XIMPreeditNone      = 0x0010,
11513 	XIMStatusArea       = 0x0100,
11514 	XIMStatusCallbacks  = 0x0200,
11515 	XIMStatusNothing    = 0x0400,
11516 	XIMStatusNone       = 0x0800,
11517 }
11518 
11519 
11520 /* X Shared Memory Extension functions */
11521 	//pragma(lib, "Xshm");
11522 	alias arch_ulong ShmSeg;
11523 	struct XShmSegmentInfo {
11524 		ShmSeg shmseg;
11525 		int shmid;
11526 		ubyte* shmaddr;
11527 		Bool readOnly;
11528 	}
11529 
11530 	// and the necessary OS functions
11531 	int shmget(int, size_t, int);
11532 	void* shmat(int, in void*, int);
11533 	int shmdt(in void*);
11534 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
11535 
11536 	enum IPC_PRIVATE = 0;
11537 	enum IPC_CREAT = 512;
11538 	enum IPC_RMID = 0;
11539 
11540 /* MIT-SHM end */
11541 
11542 
11543 enum MappingType:int {
11544 	MappingModifier		=0,
11545 	MappingKeyboard		=1,
11546 	MappingPointer		=2
11547 }
11548 
11549 /* ImageFormat -- PutImage, GetImage */
11550 enum ImageFormat:int {
11551 	XYBitmap	=0,	/* depth 1, XYFormat */
11552 	XYPixmap	=1,	/* depth == drawable depth */
11553 	ZPixmap	=2	/* depth == drawable depth */
11554 }
11555 
11556 enum ModifierName:int {
11557 	ShiftMapIndex	=0,
11558 	LockMapIndex	=1,
11559 	ControlMapIndex	=2,
11560 	Mod1MapIndex	=3,
11561 	Mod2MapIndex	=4,
11562 	Mod3MapIndex	=5,
11563 	Mod4MapIndex	=6,
11564 	Mod5MapIndex	=7
11565 }
11566 
11567 enum ButtonMask:int {
11568 	Button1Mask	=1<<8,
11569 	Button2Mask	=1<<9,
11570 	Button3Mask	=1<<10,
11571 	Button4Mask	=1<<11,
11572 	Button5Mask	=1<<12,
11573 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
11574 }
11575 
11576 enum KeyOrButtonMask:uint {
11577 	ShiftMask	=1<<0,
11578 	LockMask	=1<<1,
11579 	ControlMask	=1<<2,
11580 	Mod1Mask	=1<<3,
11581 	Mod2Mask	=1<<4,
11582 	Mod3Mask	=1<<5,
11583 	Mod4Mask	=1<<6,
11584 	Mod5Mask	=1<<7,
11585 	Button1Mask	=1<<8,
11586 	Button2Mask	=1<<9,
11587 	Button3Mask	=1<<10,
11588 	Button4Mask	=1<<11,
11589 	Button5Mask	=1<<12,
11590 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
11591 }
11592 
11593 enum ButtonName:int {
11594 	Button1	=1,
11595 	Button2	=2,
11596 	Button3	=3,
11597 	Button4	=4,
11598 	Button5	=5
11599 }
11600 
11601 /* Notify modes */
11602 enum NotifyModes:int
11603 {
11604 	NotifyNormal		=0,
11605 	NotifyGrab			=1,
11606 	NotifyUngrab		=2,
11607 	NotifyWhileGrabbed	=3
11608 }
11609 const int NotifyHint	=1;	/* for MotionNotify events */
11610 
11611 /* Notify detail */
11612 enum NotifyDetail:int
11613 {
11614 	NotifyAncestor			=0,
11615 	NotifyVirtual			=1,
11616 	NotifyInferior			=2,
11617 	NotifyNonlinear			=3,
11618 	NotifyNonlinearVirtual	=4,
11619 	NotifyPointer			=5,
11620 	NotifyPointerRoot		=6,
11621 	NotifyDetailNone		=7
11622 }
11623 
11624 /* Visibility notify */
11625 
11626 enum VisibilityNotify:int
11627 {
11628 VisibilityUnobscured		=0,
11629 VisibilityPartiallyObscured	=1,
11630 VisibilityFullyObscured		=2
11631 }
11632 
11633 
11634 enum WindowStackingMethod:int
11635 {
11636 	Above		=0,
11637 	Below		=1,
11638 	TopIf		=2,
11639 	BottomIf	=3,
11640 	Opposite	=4
11641 }
11642 
11643 /* Circulation request */
11644 enum CirculationRequest:int
11645 {
11646 	PlaceOnTop		=0,
11647 	PlaceOnBottom	=1
11648 }
11649 
11650 enum PropertyNotification:int
11651 {
11652 	PropertyNewValue	=0,
11653 	PropertyDelete		=1
11654 }
11655 
11656 enum ColorMapNotification:int
11657 {
11658 	ColormapUninstalled	=0,
11659 	ColormapInstalled		=1
11660 }
11661 
11662 
11663 	struct _XPrivate {}
11664 	struct _XrmHashBucketRec {}
11665 
11666 	alias void* XPointer;
11667 	alias void* XExtData;
11668 
11669 	version( X86_64 ) {
11670 		alias ulong XID;
11671 		alias ulong arch_ulong;
11672 		alias long arch_long;
11673 	} else {
11674 		alias uint XID;
11675 		alias uint arch_ulong;
11676 		alias int arch_long;
11677 	}
11678 
11679 	alias XID Window;
11680 	alias XID Drawable;
11681 	alias XID Pixmap;
11682 
11683 	alias arch_ulong Atom;
11684 	alias int Bool;
11685 	alias Display XDisplay;
11686 
11687 	alias int ByteOrder;
11688 	alias arch_ulong Time;
11689 	alias void ScreenFormat;
11690 
11691 	struct XImage {
11692 		int width, height;			/* size of image */
11693 		int xoffset;				/* number of pixels offset in X direction */
11694 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
11695 		void *data;					/* pointer to image data */
11696 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
11697 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
11698 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
11699 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
11700 		int depth;					/* depth of image */
11701 		int bytes_per_line;			/* accelarator to next line */
11702 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
11703 		arch_ulong red_mask;	/* bits in z arrangment */
11704 		arch_ulong green_mask;
11705 		arch_ulong blue_mask;
11706 		XPointer obdata;			/* hook for the object routines to hang on */
11707 		static struct F {				/* image manipulation routines */
11708 			XImage* function(
11709 				XDisplay* 			/* display */,
11710 				Visual*				/* visual */,
11711 				uint				/* depth */,
11712 				int					/* format */,
11713 				int					/* offset */,
11714 				ubyte*				/* data */,
11715 				uint				/* width */,
11716 				uint				/* height */,
11717 				int					/* bitmap_pad */,
11718 				int					/* bytes_per_line */) create_image;
11719 			int function(XImage *) destroy_image;
11720 			arch_ulong function(XImage *, int, int) get_pixel;
11721 			int function(XImage *, int, int, arch_ulong) put_pixel;
11722 			XImage* function(XImage *, int, int, uint, uint) sub_image;
11723 			int function(XImage *, arch_long) add_pixel;
11724 		}
11725 		F f;
11726 	}
11727 	version(X86_64) static assert(XImage.sizeof == 136);
11728 	else version(X86) static assert(XImage.sizeof == 88);
11729 
11730 struct XCharStruct {
11731 	short       lbearing;       /* origin to left edge of raster */
11732 	short       rbearing;       /* origin to right edge of raster */
11733 	short       width;          /* advance to next char's origin */
11734 	short       ascent;         /* baseline to top edge of raster */
11735 	short       descent;        /* baseline to bottom edge of raster */
11736 	ushort attributes;  /* per char flags (not predefined) */
11737 }
11738 
11739 /*
11740  * To allow arbitrary information with fonts, there are additional properties
11741  * returned.
11742  */
11743 struct XFontProp {
11744 	Atom name;
11745 	arch_ulong card32;
11746 }
11747 
11748 alias Atom Font;
11749 
11750 struct XFontStruct {
11751 	XExtData *ext_data;           /* Hook for extension to hang data */
11752 	Font fid;                     /* Font ID for this font */
11753 	uint direction;           /* Direction the font is painted */
11754 	uint min_char_or_byte2;   /* First character */
11755 	uint max_char_or_byte2;   /* Last character */
11756 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
11757 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
11758 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
11759 	uint default_char;        /* Char to print for undefined character */
11760 	int n_properties;             /* How many properties there are */
11761 	XFontProp *properties;        /* Pointer to array of additional properties*/
11762 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
11763 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
11764 	XCharStruct *per_char;        /* first_char to last_char information */
11765 	int ascent;                   /* Max extent above baseline for spacing */
11766 	int descent;                  /* Max descent below baseline for spacing */
11767 }
11768 
11769 
11770 /*
11771  * Definitions of specific events.
11772  */
11773 struct XKeyEvent
11774 {
11775 	int type;			/* of event */
11776 	arch_ulong serial;		/* # of last request processed by server */
11777 	Bool send_event;	/* true if this came from a SendEvent request */
11778 	Display *display;	/* Display the event was read from */
11779 	Window window;	        /* "event" window it is reported relative to */
11780 	Window root;	        /* root window that the event occurred on */
11781 	Window subwindow;	/* child window */
11782 	Time time;		/* milliseconds */
11783 	int x, y;		/* pointer x, y coordinates in event window */
11784 	int x_root, y_root;	/* coordinates relative to root */
11785 	KeyOrButtonMask state;	/* key or button mask */
11786 	uint keycode;	/* detail */
11787 	Bool same_screen;	/* same screen flag */
11788 }
11789 version(X86_64) static assert(XKeyEvent.sizeof == 96);
11790 alias XKeyEvent XKeyPressedEvent;
11791 alias XKeyEvent XKeyReleasedEvent;
11792 
11793 struct XButtonEvent
11794 {
11795 	int type;		/* of event */
11796 	arch_ulong serial;	/* # of last request processed by server */
11797 	Bool send_event;	/* true if this came from a SendEvent request */
11798 	Display *display;	/* Display the event was read from */
11799 	Window window;	        /* "event" window it is reported relative to */
11800 	Window root;	        /* root window that the event occurred on */
11801 	Window subwindow;	/* child window */
11802 	Time time;		/* milliseconds */
11803 	int x, y;		/* pointer x, y coordinates in event window */
11804 	int x_root, y_root;	/* coordinates relative to root */
11805 	KeyOrButtonMask state;	/* key or button mask */
11806 	uint button;	/* detail */
11807 	Bool same_screen;	/* same screen flag */
11808 }
11809 alias XButtonEvent XButtonPressedEvent;
11810 alias XButtonEvent XButtonReleasedEvent;
11811 
11812 struct XMotionEvent{
11813 	int type;		/* of event */
11814 	arch_ulong serial;	/* # of last request processed by server */
11815 	Bool send_event;	/* true if this came from a SendEvent request */
11816 	Display *display;	/* Display the event was read from */
11817 	Window window;	        /* "event" window reported relative to */
11818 	Window root;	        /* root window that the event occurred on */
11819 	Window subwindow;	/* child window */
11820 	Time time;		/* milliseconds */
11821 	int x, y;		/* pointer x, y coordinates in event window */
11822 	int x_root, y_root;	/* coordinates relative to root */
11823 	KeyOrButtonMask state;	/* key or button mask */
11824 	byte is_hint;		/* detail */
11825 	Bool same_screen;	/* same screen flag */
11826 }
11827 alias XMotionEvent XPointerMovedEvent;
11828 
11829 struct XCrossingEvent{
11830 	int type;		/* of event */
11831 	arch_ulong serial;	/* # of last request processed by server */
11832 	Bool send_event;	/* true if this came from a SendEvent request */
11833 	Display *display;	/* Display the event was read from */
11834 	Window window;	        /* "event" window reported relative to */
11835 	Window root;	        /* root window that the event occurred on */
11836 	Window subwindow;	/* child window */
11837 	Time time;		/* milliseconds */
11838 	int x, y;		/* pointer x, y coordinates in event window */
11839 	int x_root, y_root;	/* coordinates relative to root */
11840 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
11841 	NotifyDetail detail;
11842 	/*
11843 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
11844 	 * NotifyNonlinear,NotifyNonlinearVirtual
11845 	 */
11846 	Bool same_screen;	/* same screen flag */
11847 	Bool focus;		/* Boolean focus */
11848 	KeyOrButtonMask state;	/* key or button mask */
11849 }
11850 alias XCrossingEvent XEnterWindowEvent;
11851 alias XCrossingEvent XLeaveWindowEvent;
11852 
11853 struct XFocusChangeEvent{
11854 	int type;		/* FocusIn or FocusOut */
11855 	arch_ulong serial;	/* # of last request processed by server */
11856 	Bool send_event;	/* true if this came from a SendEvent request */
11857 	Display *display;	/* Display the event was read from */
11858 	Window window;		/* window of event */
11859 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
11860 				   NotifyGrab, NotifyUngrab */
11861 	NotifyDetail detail;
11862 	/*
11863 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
11864 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
11865 	 * NotifyPointerRoot, NotifyDetailNone
11866 	 */
11867 }
11868 alias XFocusChangeEvent XFocusInEvent;
11869 alias XFocusChangeEvent XFocusOutEvent;
11870 
11871 enum CWBackPixmap              = (1L<<0);
11872 enum CWBackPixel               = (1L<<1);
11873 enum CWBorderPixmap            = (1L<<2);
11874 enum CWBorderPixel             = (1L<<3);
11875 enum CWBitGravity              = (1L<<4);
11876 enum CWWinGravity              = (1L<<5);
11877 enum CWBackingStore            = (1L<<6);
11878 enum CWBackingPlanes           = (1L<<7);
11879 enum CWBackingPixel            = (1L<<8);
11880 enum CWOverrideRedirect        = (1L<<9);
11881 enum CWSaveUnder               = (1L<<10);
11882 enum CWEventMask               = (1L<<11);
11883 enum CWDontPropagate           = (1L<<12);
11884 enum CWColormap                = (1L<<13);
11885 enum CWCursor                  = (1L<<14);
11886 
11887 struct XWindowAttributes {
11888 	int x, y;			/* location of window */
11889 	int width, height;		/* width and height of window */
11890 	int border_width;		/* border width of window */
11891 	int depth;			/* depth of window */
11892 	Visual *visual;			/* the associated visual structure */
11893 	Window root;			/* root of screen containing window */
11894 	int class_;			/* InputOutput, InputOnly*/
11895 	int bit_gravity;		/* one of the bit gravity values */
11896 	int win_gravity;		/* one of the window gravity values */
11897 	int backing_store;		/* NotUseful, WhenMapped, Always */
11898 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
11899 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
11900 	Bool save_under;		/* boolean, should bits under be saved? */
11901 	Colormap colormap;		/* color map to be associated with window */
11902 	Bool map_installed;		/* boolean, is color map currently installed*/
11903 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
11904 	arch_long all_event_masks;		/* set of events all people have interest in*/
11905 	arch_long your_event_mask;		/* my event mask */
11906 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
11907 	Bool override_redirect;		/* boolean value for override-redirect */
11908 	Screen *screen;			/* back pointer to correct screen */
11909 }
11910 
11911 enum IsUnmapped = 0;
11912 enum IsUnviewable = 1;
11913 enum IsViewable = 2;
11914 
11915 struct XSetWindowAttributes {
11916 	Pixmap background_pixmap;/* background, None, or ParentRelative */
11917 	arch_ulong background_pixel;/* background pixel */
11918 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
11919 	arch_ulong border_pixel;/* border pixel value */
11920 	int bit_gravity;         /* one of bit gravity values */
11921 	int win_gravity;         /* one of the window gravity values */
11922 	int backing_store;       /* NotUseful, WhenMapped, Always */
11923 	arch_ulong backing_planes;/* planes to be preserved if possible */
11924 	arch_ulong backing_pixel;/* value to use in restoring planes */
11925 	Bool save_under;         /* should bits under be saved? (popups) */
11926 	arch_long event_mask;         /* set of events that should be saved */
11927 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
11928 	Bool override_redirect;  /* boolean value for override_redirect */
11929 	Colormap colormap;       /* color map to be associated with window */
11930 	Cursor cursor;           /* cursor to be displayed (or None) */
11931 }
11932 
11933 
11934 alias int Status;
11935 
11936 
11937 enum EventMask:int
11938 {
11939 	NoEventMask				=0,
11940 	KeyPressMask			=1<<0,
11941 	KeyReleaseMask			=1<<1,
11942 	ButtonPressMask			=1<<2,
11943 	ButtonReleaseMask		=1<<3,
11944 	EnterWindowMask			=1<<4,
11945 	LeaveWindowMask			=1<<5,
11946 	PointerMotionMask		=1<<6,
11947 	PointerMotionHintMask	=1<<7,
11948 	Button1MotionMask		=1<<8,
11949 	Button2MotionMask		=1<<9,
11950 	Button3MotionMask		=1<<10,
11951 	Button4MotionMask		=1<<11,
11952 	Button5MotionMask		=1<<12,
11953 	ButtonMotionMask		=1<<13,
11954 	KeymapStateMask		=1<<14,
11955 	ExposureMask			=1<<15,
11956 	VisibilityChangeMask	=1<<16,
11957 	StructureNotifyMask		=1<<17,
11958 	ResizeRedirectMask		=1<<18,
11959 	SubstructureNotifyMask	=1<<19,
11960 	SubstructureRedirectMask=1<<20,
11961 	FocusChangeMask			=1<<21,
11962 	PropertyChangeMask		=1<<22,
11963 	ColormapChangeMask		=1<<23,
11964 	OwnerGrabButtonMask		=1<<24
11965 }
11966 
11967 struct MwmHints {
11968 	int flags;
11969 	int functions;
11970 	int decorations;
11971 	int input_mode;
11972 	int status;
11973 }
11974 
11975 enum {
11976 	MWM_HINTS_FUNCTIONS = (1L << 0),
11977 	MWM_HINTS_DECORATIONS =  (1L << 1),
11978 
11979 	MWM_FUNC_ALL = (1L << 0),
11980 	MWM_FUNC_RESIZE = (1L << 1),
11981 	MWM_FUNC_MOVE = (1L << 2),
11982 	MWM_FUNC_MINIMIZE = (1L << 3),
11983 	MWM_FUNC_MAXIMIZE = (1L << 4),
11984 	MWM_FUNC_CLOSE = (1L << 5)
11985 }
11986 
11987 import core.stdc.config : c_long, c_ulong;
11988 
11989 	/* Size hints mask bits */
11990 
11991 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
11992 	enum   USSize      = (1L << 1)          /* user specified width, height */;
11993 	enum   PPosition   = (1L << 2)          /* program specified position */;
11994 	enum   PSize       = (1L << 3)          /* program specified size */;
11995 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
11996 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
11997 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
11998 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
11999 	enum   PBaseSize   = (1L << 8);
12000 	enum   PWinGravity = (1L << 9);
12001 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
12002 	struct XSizeHints {
12003 		arch_long flags;         /* marks which fields in this structure are defined */
12004 		int x, y;           /* Obsolete */
12005 		int width, height;  /* Obsolete */
12006 		int min_width, min_height;
12007 		int max_width, max_height;
12008 		int width_inc, height_inc;
12009 		struct Aspect {
12010 			int x;       /* numerator */
12011 			int y;       /* denominator */
12012 		}
12013 
12014 		Aspect min_aspect;
12015 		Aspect max_aspect;
12016 		int base_width, base_height;
12017 		int win_gravity;
12018 		/* this structure may be extended in the future */
12019 	}
12020 
12021 
12022 
12023 enum EventType:int
12024 {
12025 	KeyPress			=2,
12026 	KeyRelease			=3,
12027 	ButtonPress			=4,
12028 	ButtonRelease		=5,
12029 	MotionNotify		=6,
12030 	EnterNotify			=7,
12031 	LeaveNotify			=8,
12032 	FocusIn				=9,
12033 	FocusOut			=10,
12034 	KeymapNotify		=11,
12035 	Expose				=12,
12036 	GraphicsExpose		=13,
12037 	NoExpose			=14,
12038 	VisibilityNotify	=15,
12039 	CreateNotify		=16,
12040 	DestroyNotify		=17,
12041 	UnmapNotify		=18,
12042 	MapNotify			=19,
12043 	MapRequest			=20,
12044 	ReparentNotify		=21,
12045 	ConfigureNotify		=22,
12046 	ConfigureRequest	=23,
12047 	GravityNotify		=24,
12048 	ResizeRequest		=25,
12049 	CirculateNotify		=26,
12050 	CirculateRequest	=27,
12051 	PropertyNotify		=28,
12052 	SelectionClear		=29,
12053 	SelectionRequest	=30,
12054 	SelectionNotify		=31,
12055 	ColormapNotify		=32,
12056 	ClientMessage		=33,
12057 	MappingNotify		=34,
12058 	LASTEvent			=35	/* must be bigger than any event # */
12059 }
12060 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
12061 struct XKeymapEvent
12062 {
12063 	int type;
12064 	arch_ulong serial;	/* # of last request processed by server */
12065 	Bool send_event;	/* true if this came from a SendEvent request */
12066 	Display *display;	/* Display the event was read from */
12067 	Window window;
12068 	byte[32] key_vector;
12069 }
12070 
12071 struct XExposeEvent
12072 {
12073 	int type;
12074 	arch_ulong serial;	/* # of last request processed by server */
12075 	Bool send_event;	/* true if this came from a SendEvent request */
12076 	Display *display;	/* Display the event was read from */
12077 	Window window;
12078 	int x, y;
12079 	int width, height;
12080 	int count;		/* if non-zero, at least this many more */
12081 }
12082 
12083 struct XGraphicsExposeEvent{
12084 	int type;
12085 	arch_ulong serial;	/* # of last request processed by server */
12086 	Bool send_event;	/* true if this came from a SendEvent request */
12087 	Display *display;	/* Display the event was read from */
12088 	Drawable drawable;
12089 	int x, y;
12090 	int width, height;
12091 	int count;		/* if non-zero, at least this many more */
12092 	int major_code;		/* core is CopyArea or CopyPlane */
12093 	int minor_code;		/* not defined in the core */
12094 }
12095 
12096 struct XNoExposeEvent{
12097 	int type;
12098 	arch_ulong serial;	/* # of last request processed by server */
12099 	Bool send_event;	/* true if this came from a SendEvent request */
12100 	Display *display;	/* Display the event was read from */
12101 	Drawable drawable;
12102 	int major_code;		/* core is CopyArea or CopyPlane */
12103 	int minor_code;		/* not defined in the core */
12104 }
12105 
12106 struct XVisibilityEvent{
12107 	int type;
12108 	arch_ulong serial;	/* # of last request processed by server */
12109 	Bool send_event;	/* true if this came from a SendEvent request */
12110 	Display *display;	/* Display the event was read from */
12111 	Window window;
12112 	VisibilityNotify state;		/* Visibility state */
12113 }
12114 
12115 struct XCreateWindowEvent{
12116 	int type;
12117 	arch_ulong serial;	/* # of last request processed by server */
12118 	Bool send_event;	/* true if this came from a SendEvent request */
12119 	Display *display;	/* Display the event was read from */
12120 	Window parent;		/* parent of the window */
12121 	Window window;		/* window id of window created */
12122 	int x, y;		/* window location */
12123 	int width, height;	/* size of window */
12124 	int border_width;	/* border width */
12125 	Bool override_redirect;	/* creation should be overridden */
12126 }
12127 
12128 struct XDestroyWindowEvent
12129 {
12130 	int type;
12131 	arch_ulong serial;		/* # of last request processed by server */
12132 	Bool send_event;	/* true if this came from a SendEvent request */
12133 	Display *display;	/* Display the event was read from */
12134 	Window event;
12135 	Window window;
12136 }
12137 
12138 struct XUnmapEvent
12139 {
12140 	int type;
12141 	arch_ulong serial;		/* # of last request processed by server */
12142 	Bool send_event;	/* true if this came from a SendEvent request */
12143 	Display *display;	/* Display the event was read from */
12144 	Window event;
12145 	Window window;
12146 	Bool from_configure;
12147 }
12148 
12149 struct XMapEvent
12150 {
12151 	int type;
12152 	arch_ulong serial;		/* # of last request processed by server */
12153 	Bool send_event;	/* true if this came from a SendEvent request */
12154 	Display *display;	/* Display the event was read from */
12155 	Window event;
12156 	Window window;
12157 	Bool override_redirect;	/* Boolean, is override set... */
12158 }
12159 
12160 struct XMapRequestEvent
12161 {
12162 	int type;
12163 	arch_ulong serial;	/* # of last request processed by server */
12164 	Bool send_event;	/* true if this came from a SendEvent request */
12165 	Display *display;	/* Display the event was read from */
12166 	Window parent;
12167 	Window window;
12168 }
12169 
12170 struct XReparentEvent
12171 {
12172 	int type;
12173 	arch_ulong serial;	/* # of last request processed by server */
12174 	Bool send_event;	/* true if this came from a SendEvent request */
12175 	Display *display;	/* Display the event was read from */
12176 	Window event;
12177 	Window window;
12178 	Window parent;
12179 	int x, y;
12180 	Bool override_redirect;
12181 }
12182 
12183 struct XConfigureEvent
12184 {
12185 	int type;
12186 	arch_ulong serial;	/* # of last request processed by server */
12187 	Bool send_event;	/* true if this came from a SendEvent request */
12188 	Display *display;	/* Display the event was read from */
12189 	Window event;
12190 	Window window;
12191 	int x, y;
12192 	int width, height;
12193 	int border_width;
12194 	Window above;
12195 	Bool override_redirect;
12196 }
12197 
12198 struct XGravityEvent
12199 {
12200 	int type;
12201 	arch_ulong serial;	/* # of last request processed by server */
12202 	Bool send_event;	/* true if this came from a SendEvent request */
12203 	Display *display;	/* Display the event was read from */
12204 	Window event;
12205 	Window window;
12206 	int x, y;
12207 }
12208 
12209 struct XResizeRequestEvent
12210 {
12211 	int type;
12212 	arch_ulong serial;	/* # of last request processed by server */
12213 	Bool send_event;	/* true if this came from a SendEvent request */
12214 	Display *display;	/* Display the event was read from */
12215 	Window window;
12216 	int width, height;
12217 }
12218 
12219 struct  XConfigureRequestEvent
12220 {
12221 	int type;
12222 	arch_ulong serial;	/* # of last request processed by server */
12223 	Bool send_event;	/* true if this came from a SendEvent request */
12224 	Display *display;	/* Display the event was read from */
12225 	Window parent;
12226 	Window window;
12227 	int x, y;
12228 	int width, height;
12229 	int border_width;
12230 	Window above;
12231 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
12232 	arch_ulong value_mask;
12233 }
12234 
12235 struct XCirculateEvent
12236 {
12237 	int type;
12238 	arch_ulong serial;	/* # of last request processed by server */
12239 	Bool send_event;	/* true if this came from a SendEvent request */
12240 	Display *display;	/* Display the event was read from */
12241 	Window event;
12242 	Window window;
12243 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
12244 }
12245 
12246 struct XCirculateRequestEvent
12247 {
12248 	int type;
12249 	arch_ulong serial;	/* # of last request processed by server */
12250 	Bool send_event;	/* true if this came from a SendEvent request */
12251 	Display *display;	/* Display the event was read from */
12252 	Window parent;
12253 	Window window;
12254 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
12255 }
12256 
12257 struct XPropertyEvent
12258 {
12259 	int type;
12260 	arch_ulong serial;	/* # of last request processed by server */
12261 	Bool send_event;	/* true if this came from a SendEvent request */
12262 	Display *display;	/* Display the event was read from */
12263 	Window window;
12264 	Atom atom;
12265 	Time time;
12266 	PropertyNotification state;		/* NewValue, Deleted */
12267 }
12268 
12269 struct XSelectionClearEvent
12270 {
12271 	int type;
12272 	arch_ulong serial;	/* # of last request processed by server */
12273 	Bool send_event;	/* true if this came from a SendEvent request */
12274 	Display *display;	/* Display the event was read from */
12275 	Window window;
12276 	Atom selection;
12277 	Time time;
12278 }
12279 
12280 struct XSelectionRequestEvent
12281 {
12282 	int type;
12283 	arch_ulong serial;	/* # of last request processed by server */
12284 	Bool send_event;	/* true if this came from a SendEvent request */
12285 	Display *display;	/* Display the event was read from */
12286 	Window owner;
12287 	Window requestor;
12288 	Atom selection;
12289 	Atom target;
12290 	Atom property;
12291 	Time time;
12292 }
12293 
12294 struct XSelectionEvent
12295 {
12296 	int type;
12297 	arch_ulong serial;	/* # of last request processed by server */
12298 	Bool send_event;	/* true if this came from a SendEvent request */
12299 	Display *display;	/* Display the event was read from */
12300 	Window requestor;
12301 	Atom selection;
12302 	Atom target;
12303 	Atom property;		/* ATOM or None */
12304 	Time time;
12305 }
12306 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
12307 
12308 struct XColormapEvent
12309 {
12310 	int type;
12311 	arch_ulong serial;	/* # of last request processed by server */
12312 	Bool send_event;	/* true if this came from a SendEvent request */
12313 	Display *display;	/* Display the event was read from */
12314 	Window window;
12315 	Colormap colormap;	/* COLORMAP or None */
12316 	Bool new_;		/* C++ */
12317 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
12318 }
12319 version(X86_64) static assert(XColormapEvent.sizeof == 56);
12320 
12321 struct XClientMessageEvent
12322 {
12323 	int type;
12324 	arch_ulong serial;	/* # of last request processed by server */
12325 	Bool send_event;	/* true if this came from a SendEvent request */
12326 	Display *display;	/* Display the event was read from */
12327 	Window window;
12328 	Atom message_type;
12329 	int format;
12330 	union Data{
12331 		byte[20] b;
12332 		short[10] s;
12333 		arch_ulong[5] l;
12334 	}
12335 	Data data;
12336 
12337 }
12338 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
12339 
12340 struct XMappingEvent
12341 {
12342 	int type;
12343 	arch_ulong serial;	/* # of last request processed by server */
12344 	Bool send_event;	/* true if this came from a SendEvent request */
12345 	Display *display;	/* Display the event was read from */
12346 	Window window;		/* unused */
12347 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
12348 				   MappingPointer */
12349 	int first_keycode;	/* first keycode */
12350 	int count;		/* defines range of change w. first_keycode*/
12351 }
12352 
12353 struct XErrorEvent
12354 {
12355 	int type;
12356 	Display *display;	/* Display the event was read from */
12357 	XID resourceid;		/* resource id */
12358 	arch_ulong serial;	/* serial number of failed request */
12359 	ubyte error_code;	/* error code of failed request */
12360 	ubyte request_code;	/* Major op-code of failed request */
12361 	ubyte minor_code;	/* Minor op-code of failed request */
12362 }
12363 
12364 struct XAnyEvent
12365 {
12366 	int type;
12367 	arch_ulong serial;	/* # of last request processed by server */
12368 	Bool send_event;	/* true if this came from a SendEvent request */
12369 	Display *display;/* Display the event was read from */
12370 	Window window;	/* window on which event was requested in event mask */
12371 }
12372 
12373 union XEvent{
12374 	int type;		/* must not be changed; first element */
12375 	XAnyEvent xany;
12376 	XKeyEvent xkey;
12377 	XButtonEvent xbutton;
12378 	XMotionEvent xmotion;
12379 	XCrossingEvent xcrossing;
12380 	XFocusChangeEvent xfocus;
12381 	XExposeEvent xexpose;
12382 	XGraphicsExposeEvent xgraphicsexpose;
12383 	XNoExposeEvent xnoexpose;
12384 	XVisibilityEvent xvisibility;
12385 	XCreateWindowEvent xcreatewindow;
12386 	XDestroyWindowEvent xdestroywindow;
12387 	XUnmapEvent xunmap;
12388 	XMapEvent xmap;
12389 	XMapRequestEvent xmaprequest;
12390 	XReparentEvent xreparent;
12391 	XConfigureEvent xconfigure;
12392 	XGravityEvent xgravity;
12393 	XResizeRequestEvent xresizerequest;
12394 	XConfigureRequestEvent xconfigurerequest;
12395 	XCirculateEvent xcirculate;
12396 	XCirculateRequestEvent xcirculaterequest;
12397 	XPropertyEvent xproperty;
12398 	XSelectionClearEvent xselectionclear;
12399 	XSelectionRequestEvent xselectionrequest;
12400 	XSelectionEvent xselection;
12401 	XColormapEvent xcolormap;
12402 	XClientMessageEvent xclient;
12403 	XMappingEvent xmapping;
12404 	XErrorEvent xerror;
12405 	XKeymapEvent xkeymap;
12406 	arch_ulong[24] pad;
12407 }
12408 
12409 
12410 	struct Display {
12411 		XExtData *ext_data;	/* hook for extension to hang data */
12412 		_XPrivate *private1;
12413 		int fd;			/* Network socket. */
12414 		int private2;
12415 		int proto_major_version;/* major version of server's X protocol */
12416 		int proto_minor_version;/* minor version of servers X protocol */
12417 		char *vendor;		/* vendor of the server hardware */
12418 	    	XID private3;
12419 		XID private4;
12420 		XID private5;
12421 		int private6;
12422 		XID function(Display*)resource_alloc;/* allocator function */
12423 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
12424 		int bitmap_unit;	/* padding and data requirements */
12425 		int bitmap_pad;		/* padding requirements on bitmaps */
12426 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
12427 		int nformats;		/* number of pixmap formats in list */
12428 		ScreenFormat *pixmap_format;	/* pixmap format list */
12429 		int private8;
12430 		int release;		/* release of the server */
12431 		_XPrivate *private9;
12432 		_XPrivate *private10;
12433 		int qlen;		/* Length of input event queue */
12434 		arch_ulong last_request_read; /* seq number of last event read */
12435 		arch_ulong request;	/* sequence number of last request. */
12436 		XPointer private11;
12437 		XPointer private12;
12438 		XPointer private13;
12439 		XPointer private14;
12440 		uint max_request_size; /* maximum number 32 bit words in request*/
12441 		_XrmHashBucketRec *db;
12442 		int function  (Display*)private15;
12443 		char *display_name;	/* "host:display" string used on this connect*/
12444 		int default_screen;	/* default screen for operations */
12445 		int nscreens;		/* number of screens on this server*/
12446 		Screen *screens;	/* pointer to list of screens */
12447 		arch_ulong motion_buffer;	/* size of motion buffer */
12448 		arch_ulong private16;
12449 		int min_keycode;	/* minimum defined keycode */
12450 		int max_keycode;	/* maximum defined keycode */
12451 		XPointer private17;
12452 		XPointer private18;
12453 		int private19;
12454 		byte *xdefaults;	/* contents of defaults from server */
12455 		/* there is more to this structure, but it is private to Xlib */
12456 	}
12457 
12458 	// I got these numbers from a C program as a sanity test
12459 	version(X86_64) {
12460 		static assert(Display.sizeof == 296);
12461 		static assert(XPointer.sizeof == 8);
12462 		static assert(XErrorEvent.sizeof == 40);
12463 		static assert(XAnyEvent.sizeof == 40);
12464 		static assert(XMappingEvent.sizeof == 56);
12465 		static assert(XEvent.sizeof == 192);
12466 	} else {
12467 		static assert(Display.sizeof == 176);
12468 		static assert(XPointer.sizeof == 4);
12469 		static assert(XEvent.sizeof == 96);
12470 	}
12471 
12472 struct Depth
12473 {
12474 	int depth;		/* this depth (Z) of the depth */
12475 	int nvisuals;		/* number of Visual types at this depth */
12476 	Visual *visuals;	/* list of visuals possible at this depth */
12477 }
12478 
12479 alias void* GC;
12480 alias c_ulong VisualID;
12481 alias XID Colormap;
12482 alias XID Cursor;
12483 alias XID KeySym;
12484 alias uint KeyCode;
12485 enum None = 0;
12486 }
12487 
12488 version(without_opengl) {}
12489 else {
12490 extern(C) nothrow @nogc {
12491 
12492 
12493 static if(!SdpyIsUsingIVGLBinds) {
12494 enum GLX_USE_GL=            1;       /* support GLX rendering */
12495 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
12496 enum GLX_LEVEL=             3;       /* level in plane stacking */
12497 enum GLX_RGBA=              4;       /* true if RGBA mode */
12498 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
12499 enum GLX_STEREO=            6;       /* stereo buffering supported */
12500 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
12501 enum GLX_RED_SIZE=          8;       /* number of red component bits */
12502 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
12503 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
12504 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
12505 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
12506 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
12507 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
12508 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
12509 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
12510 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
12511 
12512 
12513 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
12514 
12515 
12516 
12517 enum GL_TRUE = 1;
12518 enum GL_FALSE = 0;
12519 alias int GLint;
12520 }
12521 
12522 alias XID GLXContextID;
12523 alias XID GLXPixmap;
12524 alias XID GLXDrawable;
12525 alias XID GLXPbuffer;
12526 alias XID GLXWindow;
12527 alias XID GLXFBConfigID;
12528 alias void* GLXContext;
12529 
12530 }
12531 }
12532 
12533 enum AllocNone = 0;
12534 
12535 extern(C) {
12536 	/* WARNING, this type not in Xlib spec */
12537 	extern(C) alias XIOErrorHandler = int function (Display* display);
12538 }
12539 
12540 extern(C) nothrow @nogc {
12541 struct Screen{
12542 	XExtData *ext_data;		/* hook for extension to hang data */
12543 	Display *display;		/* back pointer to display structure */
12544 	Window root;			/* Root window id. */
12545 	int width, height;		/* width and height of screen */
12546 	int mwidth, mheight;	/* width and height of  in millimeters */
12547 	int ndepths;			/* number of depths possible */
12548 	Depth *depths;			/* list of allowable depths on the screen */
12549 	int root_depth;			/* bits per pixel */
12550 	Visual *root_visual;	/* root visual */
12551 	GC default_gc;			/* GC for the root root visual */
12552 	Colormap cmap;			/* default color map */
12553 	uint white_pixel;
12554 	uint black_pixel;		/* White and Black pixel values */
12555 	int max_maps, min_maps;	/* max and min color maps */
12556 	int backing_store;		/* Never, WhenMapped, Always */
12557 	bool save_unders;
12558 	int root_input_mask;	/* initial root input mask */
12559 }
12560 
12561 struct Visual
12562 {
12563 	XExtData *ext_data;	/* hook for extension to hang data */
12564 	VisualID visualid;	/* visual id of this visual */
12565 	int class_;			/* class of screen (monochrome, etc.) */
12566 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
12567 	int bits_per_rgb;	/* log base 2 of distinct color values */
12568 	int map_entries;	/* color map entries */
12569 }
12570 
12571 	alias Display* _XPrivDisplay;
12572 
12573 	Screen* ScreenOfDisplay(Display* dpy, int scr) {
12574 		assert(dpy !is null);
12575 		return &dpy.screens[scr];
12576 	}
12577 
12578 	Window	RootWindow(Display *dpy,int scr) {
12579 		return ScreenOfDisplay(dpy,scr).root;
12580 	}
12581 
12582 	struct XWMHints {
12583 		arch_long flags;
12584 		Bool input;
12585 		int initial_state;
12586 		Pixmap icon_pixmap;
12587 		Window icon_window;
12588 		int icon_x, icon_y;
12589 		Pixmap icon_mask;
12590 		XID window_group;
12591 	}
12592 
12593 	struct XClassHint {
12594 		char* res_name;
12595 		char* res_class;
12596 	}
12597 
12598 	int DefaultScreen(Display *dpy) {
12599 		return dpy.default_screen;
12600 	}
12601 
12602 	int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
12603 	int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
12604 	int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
12605 	int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
12606 	int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
12607 	auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
12608 
12609 	int ConnectionNumber(Display* dpy) { return dpy.fd; }
12610 
12611 	enum int AnyPropertyType = 0;
12612 	enum int Success = 0;
12613 
12614 	enum int RevertToNone = None;
12615 	enum int PointerRoot = 1;
12616 	enum Time CurrentTime = 0;
12617 	enum int RevertToPointerRoot = PointerRoot;
12618 	enum int RevertToParent = 2;
12619 
12620 	int DefaultDepthOfDisplay(Display* dpy) {
12621 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
12622 	}
12623 
12624 	Visual* DefaultVisual(Display *dpy,int scr) {
12625 		return ScreenOfDisplay(dpy,scr).root_visual;
12626 	}
12627 
12628 	GC DefaultGC(Display *dpy,int scr) {
12629 		return ScreenOfDisplay(dpy,scr).default_gc;
12630 	}
12631 
12632 	uint BlackPixel(Display *dpy,int scr) {
12633 		return ScreenOfDisplay(dpy,scr).black_pixel;
12634 	}
12635 
12636 	uint WhitePixel(Display *dpy,int scr) {
12637 		return ScreenOfDisplay(dpy,scr).white_pixel;
12638 	}
12639 
12640 	alias void* XFontSet; // i think
12641 	struct XmbTextItem {
12642 		char* chars;
12643 		int nchars;
12644 		int delta;
12645 		XFontSet font_set;
12646 	}
12647 
12648 	struct XTextItem {
12649 		char* chars;
12650 		int nchars;
12651 		int delta;
12652 		Font font;
12653 	}
12654 
12655 	enum {
12656 		GXclear        = 0x0, /* 0 */
12657 		GXand          = 0x1, /* src AND dst */
12658 		GXandReverse   = 0x2, /* src AND NOT dst */
12659 		GXcopy         = 0x3, /* src */
12660 		GXandInverted  = 0x4, /* NOT src AND dst */
12661 		GXnoop         = 0x5, /* dst */
12662 		GXxor          = 0x6, /* src XOR dst */
12663 		GXor           = 0x7, /* src OR dst */
12664 		GXnor          = 0x8, /* NOT src AND NOT dst */
12665 		GXequiv        = 0x9, /* NOT src XOR dst */
12666 		GXinvert       = 0xa, /* NOT dst */
12667 		GXorReverse    = 0xb, /* src OR NOT dst */
12668 		GXcopyInverted = 0xc, /* NOT src */
12669 		GXorInverted   = 0xd, /* NOT src OR dst */
12670 		GXnand         = 0xe, /* NOT src OR NOT dst */
12671 		GXset          = 0xf, /* 1 */
12672 	}
12673 	enum QueueMode : int {
12674 		QueuedAlready,
12675 		QueuedAfterReading,
12676 		QueuedAfterFlush
12677 	}
12678 
12679 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
12680 
12681 	struct XPoint {
12682 		short x;
12683 		short y;
12684 	}
12685 
12686 	enum CoordMode:int {
12687 		CoordModeOrigin = 0,
12688 		CoordModePrevious = 1
12689 	}
12690 
12691 	enum PolygonShape:int {
12692 		Complex = 0,
12693 		Nonconvex = 1,
12694 		Convex = 2
12695 	}
12696 
12697 	struct XTextProperty {
12698 		const(char)* value;		/* same as Property routines */
12699 		Atom encoding;			/* prop type */
12700 		int format;				/* prop data format: 8, 16, or 32 */
12701 		arch_ulong nitems;		/* number of data items in value */
12702 	}
12703 
12704 	version( X86_64 ) {
12705 		static assert(XTextProperty.sizeof == 32);
12706 	}
12707 
12708 
12709 	struct XGCValues {
12710 		int function_;           /* logical operation */
12711 		arch_ulong plane_mask;/* plane mask */
12712 		arch_ulong foreground;/* foreground pixel */
12713 		arch_ulong background;/* background pixel */
12714 		int line_width;         /* line width */
12715 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
12716 		int cap_style;          /* CapNotLast, CapButt,
12717 					   CapRound, CapProjecting */
12718 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
12719 		int fill_style;         /* FillSolid, FillTiled,
12720 					   FillStippled, FillOpaeueStippled */
12721 		int fill_rule;          /* EvenOddRule, WindingRule */
12722 		int arc_mode;           /* ArcChord, ArcPieSlice */
12723 		Pixmap tile;            /* tile pixmap for tiling operations */
12724 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
12725 		int ts_x_origin;        /* offset for tile or stipple operations */
12726 		int ts_y_origin;
12727 		Font font;              /* default text font for text operations */
12728 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
12729 		Bool graphics_exposures;/* boolean, should exposures be generated */
12730 		int clip_x_origin;      /* origin for clipping */
12731 		int clip_y_origin;
12732 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
12733 		int dash_offset;        /* patterned/dashed line information */
12734 		char dashes;
12735 	}
12736 
12737 	struct XColor {
12738 		arch_ulong pixel;
12739 		ushort red, green, blue;
12740 		byte flags;
12741 		byte pad;
12742 	}
12743 
12744 	alias XErrorHandler = int function(Display*, XErrorEvent*);
12745 
12746 	struct XRectangle {
12747 		short x;
12748 		short y;
12749 		ushort width;
12750 		ushort height;
12751 	}
12752 
12753 	enum ClipByChildren = 0;
12754 	enum IncludeInferiors = 1;
12755 
12756 	enum Atom XA_PRIMARY = 1;
12757 	enum Atom XA_SECONDARY = 2;
12758 	enum Atom XA_STRING = 31;
12759 	enum Atom XA_CARDINAL = 6;
12760 	enum Atom XA_WM_NAME = 39;
12761 	enum Atom XA_ATOM = 4;
12762 	enum Atom XA_WINDOW = 33;
12763 	enum Atom XA_WM_HINTS = 35;
12764 	enum int PropModeAppend = 2;
12765 	enum int PropModeReplace = 0;
12766 	enum int PropModePrepend = 1;
12767 
12768 	enum int CopyFromParent = 0;
12769 	enum int InputOutput = 1;
12770 
12771 	// XWMHints
12772 	enum InputHint = 1 << 0;
12773 	enum StateHint = 1 << 1;
12774 	enum IconPixmapHint = (1L << 2);
12775 	enum IconWindowHint = (1L << 3);
12776 	enum IconPositionHint = (1L << 4);
12777 	enum IconMaskHint = (1L << 5);
12778 	enum WindowGroupHint = (1L << 6);
12779 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
12780 	enum XUrgencyHint = (1L << 8);
12781 
12782 	// GC Components
12783 	enum GCFunction           =   (1L<<0);
12784 	enum GCPlaneMask         =    (1L<<1);
12785 	enum GCForeground       =     (1L<<2);
12786 	enum GCBackground      =      (1L<<3);
12787 	enum GCLineWidth      =       (1L<<4);
12788 	enum GCLineStyle     =        (1L<<5);
12789 	enum GCCapStyle     =         (1L<<6);
12790 	enum GCJoinStyle   =          (1L<<7);
12791 	enum GCFillStyle  =           (1L<<8);
12792 	enum GCFillRule  =            (1L<<9);
12793 	enum GCTile     =             (1L<<10);
12794 	enum GCStipple           =    (1L<<11);
12795 	enum GCTileStipXOrigin  =     (1L<<12);
12796 	enum GCTileStipYOrigin =      (1L<<13);
12797 	enum GCFont               =   (1L<<14);
12798 	enum GCSubwindowMode     =    (1L<<15);
12799 	enum GCGraphicsExposures=     (1L<<16);
12800 	enum GCClipXOrigin     =      (1L<<17);
12801 	enum GCClipYOrigin    =       (1L<<18);
12802 	enum GCClipMask      =        (1L<<19);
12803 	enum GCDashOffset   =         (1L<<20);
12804 	enum GCDashList    =          (1L<<21);
12805 	enum GCArcMode    =           (1L<<22);
12806 	enum GCLastBit   =            22;
12807 
12808 
12809 	enum int WithdrawnState = 0;
12810 	enum int NormalState = 1;
12811 	enum int IconicState = 3;
12812 
12813 }
12814 } else version (OSXCocoa) {
12815 private:
12816 	alias void* id;
12817 	alias void* Class;
12818 	alias void* SEL;
12819 	alias void* IMP;
12820 	alias void* Ivar;
12821 	alias byte BOOL;
12822 	alias const(void)* CFStringRef;
12823 	alias const(void)* CFAllocatorRef;
12824 	alias const(void)* CFTypeRef;
12825 	alias const(void)* CGContextRef;
12826 	alias const(void)* CGColorSpaceRef;
12827 	alias const(void)* CGImageRef;
12828 	alias ulong CGBitmapInfo;
12829 
12830 	struct objc_super {
12831 		id self;
12832 		Class superclass;
12833 	}
12834 
12835 	struct CFRange {
12836 		long location, length;
12837 	}
12838 
12839 	struct NSPoint {
12840 		double x, y;
12841 
12842 		static fromTuple(T)(T tupl) {
12843 			return NSPoint(tupl.tupleof);
12844 		}
12845 	}
12846 	struct NSSize {
12847 		double width, height;
12848 	}
12849 	struct NSRect {
12850 		NSPoint origin;
12851 		NSSize size;
12852 	}
12853 	alias NSPoint CGPoint;
12854 	alias NSSize CGSize;
12855 	alias NSRect CGRect;
12856 
12857 	struct CGAffineTransform {
12858 		double a, b, c, d, tx, ty;
12859 	}
12860 
12861 	enum NSApplicationActivationPolicyRegular = 0;
12862 	enum NSBackingStoreBuffered = 2;
12863 	enum kCFStringEncodingUTF8 = 0x08000100;
12864 
12865 	enum : size_t {
12866 		NSBorderlessWindowMask = 0,
12867 		NSTitledWindowMask = 1 << 0,
12868 		NSClosableWindowMask = 1 << 1,
12869 		NSMiniaturizableWindowMask = 1 << 2,
12870 		NSResizableWindowMask = 1 << 3,
12871 		NSTexturedBackgroundWindowMask = 1 << 8
12872 	}
12873 
12874 	enum : ulong {
12875 		kCGImageAlphaNone,
12876 		kCGImageAlphaPremultipliedLast,
12877 		kCGImageAlphaPremultipliedFirst,
12878 		kCGImageAlphaLast,
12879 		kCGImageAlphaFirst,
12880 		kCGImageAlphaNoneSkipLast,
12881 		kCGImageAlphaNoneSkipFirst
12882 	}
12883 	enum : ulong {
12884 		kCGBitmapAlphaInfoMask = 0x1F,
12885 		kCGBitmapFloatComponents = (1 << 8),
12886 		kCGBitmapByteOrderMask = 0x7000,
12887 		kCGBitmapByteOrderDefault = (0 << 12),
12888 		kCGBitmapByteOrder16Little = (1 << 12),
12889 		kCGBitmapByteOrder32Little = (2 << 12),
12890 		kCGBitmapByteOrder16Big = (3 << 12),
12891 		kCGBitmapByteOrder32Big = (4 << 12)
12892 	}
12893 	enum CGPathDrawingMode {
12894 		kCGPathFill,
12895 		kCGPathEOFill,
12896 		kCGPathStroke,
12897 		kCGPathFillStroke,
12898 		kCGPathEOFillStroke
12899 	}
12900 	enum objc_AssociationPolicy : size_t {
12901 		OBJC_ASSOCIATION_ASSIGN = 0,
12902 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
12903 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
12904 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
12905 		OBJC_ASSOCIATION_COPY = 0x303 //01403
12906 	}
12907 
12908 	extern(C) {
12909 		id objc_msgSend(id receiver, SEL selector, ...);
12910 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
12911 		id objc_getClass(const(char)* name);
12912 		SEL sel_registerName(const(char)* str);
12913 		Class objc_allocateClassPair(Class superclass, const(char)* name,
12914 									 size_t extra_bytes);
12915 		void objc_registerClassPair(Class cls);
12916 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
12917 		id objc_getAssociatedObject(id object, void* key);
12918 		void objc_setAssociatedObject(id object, void* key, id value,
12919 									  objc_AssociationPolicy policy);
12920 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
12921 		id object_getIvar(id object, Ivar ivar);
12922 		void object_setIvar(id object, Ivar ivar, id value);
12923 		BOOL class_addIvar(Class cls, const(char)* name,
12924 						   size_t size, ubyte alignment, const(char)* types);
12925 
12926 		extern __gshared id NSApp;
12927 
12928 		void CFRelease(CFTypeRef obj);
12929 
12930 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
12931 											const(char)* bytes, long numBytes,
12932 											long encoding,
12933 											BOOL isExternalRepresentation);
12934 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
12935 							 char lossByte, bool isExternalRepresentation,
12936 							 char* buffer, long maxBufLen, long* usedBufLen);
12937 		long CFStringGetLength(CFStringRef theString);
12938 
12939 		CGContextRef CGBitmapContextCreate(void* data,
12940 										   size_t width, size_t height,
12941 										   size_t bitsPerComponent,
12942 										   size_t bytesPerRow,
12943 										   CGColorSpaceRef colorspace,
12944 										   CGBitmapInfo bitmapInfo);
12945 		void CGContextRelease(CGContextRef c);
12946 		ubyte* CGBitmapContextGetData(CGContextRef c);
12947 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
12948 		size_t CGBitmapContextGetWidth(CGContextRef c);
12949 		size_t CGBitmapContextGetHeight(CGContextRef c);
12950 
12951 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
12952 		void CGColorSpaceRelease(CGColorSpaceRef cs);
12953 
12954 		void CGContextSetRGBStrokeColor(CGContextRef c,
12955 										double red, double green, double blue,
12956 										double alpha);
12957 		void CGContextSetRGBFillColor(CGContextRef c,
12958 									  double red, double green, double blue,
12959 									  double alpha);
12960 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
12961 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
12962 									  const(char)* str, size_t length);
12963 		void CGContextStrokeLineSegments(CGContextRef c,
12964 										 const(CGPoint)* points, size_t count);
12965 
12966 		void CGContextBeginPath(CGContextRef c);
12967 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
12968 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
12969 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
12970 							 double startAngle, double endAngle, long clockwise);
12971 		void CGContextAddRect(CGContextRef c, CGRect rect);
12972 		void CGContextAddLines(CGContextRef c,
12973 							   const(CGPoint)* points, size_t count);
12974 		void CGContextSaveGState(CGContextRef c);
12975 		void CGContextRestoreGState(CGContextRef c);
12976 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
12977 								 ulong textEncoding);
12978 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
12979 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
12980 
12981 		void CGImageRelease(CGImageRef image);
12982 	}
12983 
12984 private:
12985     // A convenient method to create a CFString (=NSString) from a D string.
12986     CFStringRef createCFString(string str) {
12987         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
12988                                              kCFStringEncodingUTF8, false);
12989     }
12990 
12991     // Objective-C calls.
12992     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
12993         auto _cmd = sel_registerName(selector.ptr);
12994         alias extern(C) RetType function(id, SEL, T) ExpectedType;
12995         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
12996     }
12997     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
12998         auto _cmd = sel_registerName(selector.ptr);
12999         auto cls = objc_getClass(className);
13000         alias extern(C) RetType function(id, SEL, T) ExpectedType;
13001         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
13002     }
13003     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
13004         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
13005     }
13006 
13007     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
13008     alias objc_msgSend_classMethod!("alloc", id) alloc;
13009     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
13010                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
13011     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
13012     alias objc_msgSend_specialized!("center", void) center;
13013     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
13014     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
13015     alias objc_msgSend_specialized!("release", void) release;
13016     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
13017     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
13018     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
13019     alias objc_msgSend_specialized!("invalidate", void) invalidate;
13020     alias objc_msgSend_specialized!("close", void) close;
13021     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
13022                                     id, double, id, SEL, id, BOOL) scheduledTimer;
13023     alias objc_msgSend_specialized!("run", void) run;
13024     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
13025                                     id) currentNSGraphicsContext;
13026     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
13027     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
13028     alias objc_msgSend_specialized!("superclass", Class) superclass;
13029     alias objc_msgSend_specialized!("init", id) init;
13030     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
13031     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
13032     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
13033                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
13034     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
13035     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
13036     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
13037                                     void, BOOL) activateIgnoringOtherApps;
13038     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
13039                                     id) sharedNSApplication;
13040     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
13041 } else static assert(0, "Unsupported operating system");
13042 
13043 
13044 version(OSXCocoa) {
13045 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
13046 	//
13047 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
13048 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
13049 	//
13050 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
13051 	// Probably won't even fully compile right now
13052 
13053     import std.math : PI;
13054     import std.algorithm : map;
13055     import std.array : array;
13056 
13057     alias SimpleWindow NativeWindowHandle;
13058     alias void delegate(id) NativeEventHandler;
13059 
13060     __gshared Ivar simpleWindowIvar;
13061 
13062     enum KEY_ESCAPE = 27;
13063 
13064     mixin template NativeImageImplementation() {
13065         CGContextRef context;
13066         ubyte* rawData;
13067     final:
13068 
13069 	void convertToRgbaBytes(ubyte[] where) {
13070 		assert(where.length == this.width * this.height * 4);
13071 
13072 		// if rawData had a length....
13073 		//assert(rawData.length == where.length);
13074 		for(long idx = 0; idx < where.length; idx += 4) {
13075 			auto alpha = rawData[idx + 3];
13076 			if(alpha == 255) {
13077 				where[idx + 0] = rawData[idx + 0]; // r
13078 				where[idx + 1] = rawData[idx + 1]; // g
13079 				where[idx + 2] = rawData[idx + 2]; // b
13080 				where[idx + 3] = rawData[idx + 3]; // a
13081 			} else {
13082 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
13083 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
13084 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
13085 				where[idx + 3] = rawData[idx + 3]; // a
13086 
13087 			}
13088 		}
13089 	}
13090 
13091 	void setFromRgbaBytes(in ubyte[] where) {
13092 		// FIXME: this is probably wrong
13093 		assert(where.length == this.width * this.height * 4);
13094 
13095 		// if rawData had a length....
13096 		//assert(rawData.length == where.length);
13097 		for(long idx = 0; idx < where.length; idx += 4) {
13098 			auto alpha = rawData[idx + 3];
13099 			if(alpha == 255) {
13100 				rawData[idx + 0] = where[idx + 0]; // r
13101 				rawData[idx + 1] = where[idx + 1]; // g
13102 				rawData[idx + 2] = where[idx + 2]; // b
13103 				rawData[idx + 3] = where[idx + 3]; // a
13104 			} else {
13105 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
13106 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
13107 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
13108 				rawData[idx + 3] = where[idx + 3]; // a
13109 
13110 			}
13111 		}
13112 	}
13113 
13114 
13115         void createImage(int width, int height, bool forcexshm=false) {
13116             auto colorSpace = CGColorSpaceCreateDeviceRGB();
13117             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
13118                                             colorSpace,
13119                                             kCGImageAlphaPremultipliedLast
13120                                                    |kCGBitmapByteOrder32Big);
13121             CGColorSpaceRelease(colorSpace);
13122             rawData = CGBitmapContextGetData(context);
13123         }
13124         void dispose() {
13125             CGContextRelease(context);
13126         }
13127 
13128         void setPixel(int x, int y, Color c) {
13129             auto offset = (y * width + x) * 4;
13130             if (c.a == 255) {
13131                 rawData[offset + 0] = c.r;
13132                 rawData[offset + 1] = c.g;
13133                 rawData[offset + 2] = c.b;
13134                 rawData[offset + 3] = c.a;
13135             } else {
13136                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
13137                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
13138                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
13139                 rawData[offset + 3] = c.a;
13140             }
13141         }
13142     }
13143 
13144     mixin template NativeScreenPainterImplementation() {
13145         CGContextRef context;
13146         ubyte[4] _outlineComponents;
13147 	id view;
13148 
13149         void create(NativeWindowHandle window) {
13150             context = window.drawingContext;
13151 	    view = window.view;
13152         }
13153 
13154         void dispose() {
13155             	setNeedsDisplay(view, true);
13156         }
13157 
13158 	// NotYetImplementedException
13159 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
13160 	void rasterOp(RasterOp op) {}
13161 	Pen _activePen;
13162 	Color _fillColor;
13163 	Rectangle _clipRectangle;
13164 	void setClipRectangle(int, int, int, int) {}
13165 	void setFont(OperatingSystemFont) {}
13166 	int fontHeight() { return 14; }
13167 
13168 	// end
13169 
13170         void pen(Pen pen) {
13171 	    _activePen = pen;
13172 	    auto color = pen.color; // FIXME
13173             double alphaComponent = color.a/255.0f;
13174             CGContextSetRGBStrokeColor(context,
13175                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
13176 
13177             if (color.a != 255) {
13178                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
13179                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
13180                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
13181                 _outlineComponents[3] = color.a;
13182             } else {
13183                 _outlineComponents[0] = color.r;
13184                 _outlineComponents[1] = color.g;
13185                 _outlineComponents[2] = color.b;
13186                 _outlineComponents[3] = color.a;
13187             }
13188         }
13189 
13190         @property void fillColor(Color color) {
13191             CGContextSetRGBFillColor(context,
13192                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
13193         }
13194 
13195         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
13196 		// NotYetImplementedException for upper left/width/height
13197             auto cgImage = CGBitmapContextCreateImage(image.context);
13198             auto size = CGSize(CGBitmapContextGetWidth(image.context),
13199                                CGBitmapContextGetHeight(image.context));
13200             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
13201             CGImageRelease(cgImage);
13202         }
13203 
13204 	version(OSXCocoa) {} else // NotYetImplementedException
13205         void drawPixmap(Sprite image, int x, int y) {
13206 		// FIXME: is this efficient?
13207             auto cgImage = CGBitmapContextCreateImage(image.context);
13208             auto size = CGSize(CGBitmapContextGetWidth(image.context),
13209                                CGBitmapContextGetHeight(image.context));
13210             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
13211             CGImageRelease(cgImage);
13212         }
13213 
13214 
13215         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
13216 		// FIXME: alignment
13217             if (_outlineComponents[3] != 0) {
13218                 CGContextSaveGState(context);
13219                 auto invAlpha = 1.0f/_outlineComponents[3];
13220                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
13221                                                   _outlineComponents[1]*invAlpha,
13222                                                   _outlineComponents[2]*invAlpha,
13223                                                   _outlineComponents[3]/255.0f);
13224                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
13225 // auto cfstr = cast(id)createCFString(text);
13226 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
13227 // NSPoint(x, y), null);
13228 // CFRelease(cfstr);
13229                 CGContextRestoreGState(context);
13230             }
13231         }
13232 
13233         void drawPixel(int x, int y) {
13234             auto rawData = CGBitmapContextGetData(context);
13235             auto width = CGBitmapContextGetWidth(context);
13236             auto height = CGBitmapContextGetHeight(context);
13237             auto offset = ((height - y - 1) * width + x) * 4;
13238             rawData[offset .. offset+4] = _outlineComponents;
13239         }
13240 
13241         void drawLine(int x1, int y1, int x2, int y2) {
13242             CGPoint[2] linePoints;
13243             linePoints[0] = CGPoint(x1, y1);
13244             linePoints[1] = CGPoint(x2, y2);
13245             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
13246         }
13247 
13248         void drawRectangle(int x, int y, int width, int height) {
13249             CGContextBeginPath(context);
13250             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
13251             CGContextAddRect(context, rect);
13252             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
13253         }
13254 
13255         void drawEllipse(int x1, int y1, int x2, int y2) {
13256             CGContextBeginPath(context);
13257             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
13258             CGContextAddEllipseInRect(context, rect);
13259             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
13260         }
13261 
13262         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
13263             // @@@BUG@@@ Does not support elliptic arc (width != height).
13264             CGContextBeginPath(context);
13265             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
13266                             start*PI/(180*64), finish*PI/(180*64), 0);
13267             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
13268         }
13269 
13270         void drawPolygon(Point[] intPoints) {
13271             CGContextBeginPath(context);
13272             auto points = array(map!(CGPoint.fromTuple)(intPoints));
13273             CGContextAddLines(context, points.ptr, points.length);
13274             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
13275         }
13276     }
13277 
13278     mixin template NativeSimpleWindowImplementation() {
13279         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
13280             synchronized {
13281                 if (NSApp == null) initializeApp();
13282             }
13283 
13284             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
13285 
13286             // create the window.
13287             window = initWithContentRect(alloc("NSWindow"),
13288                                          contentRect,
13289                                          NSTitledWindowMask
13290                                             |NSClosableWindowMask
13291                                             |NSMiniaturizableWindowMask
13292                                             |NSResizableWindowMask,
13293                                          NSBackingStoreBuffered,
13294                                          true);
13295 
13296             // set the title & move the window to center.
13297             auto windowTitle = createCFString(title);
13298             setTitle(window, windowTitle);
13299             CFRelease(windowTitle);
13300             center(window);
13301 
13302             // create area to draw on.
13303             auto colorSpace = CGColorSpaceCreateDeviceRGB();
13304             drawingContext = CGBitmapContextCreate(null, width, height,
13305                                                    8, 4*width, colorSpace,
13306                                                    kCGImageAlphaPremultipliedLast
13307                                                       |kCGBitmapByteOrder32Big);
13308             CGColorSpaceRelease(colorSpace);
13309             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
13310             auto matrix = CGContextGetTextMatrix(drawingContext);
13311             matrix.c = -matrix.c;
13312             matrix.d = -matrix.d;
13313             CGContextSetTextMatrix(drawingContext, matrix);
13314 
13315             // create the subview that things will be drawn on.
13316             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
13317             setContentView(window, view);
13318             object_setIvar(view, simpleWindowIvar, cast(id)this);
13319             release(view);
13320 
13321             setBackgroundColor(window, whiteNSColor);
13322             makeKeyAndOrderFront(window, null);
13323         }
13324         void dispose() {
13325             closeWindow();
13326             release(window);
13327         }
13328         void closeWindow() {
13329             invalidate(timer);
13330             .close(window);
13331         }
13332 
13333         ScreenPainter getPainter() {
13334 		return ScreenPainter(this, this);
13335 	}
13336 
13337         id window;
13338         id timer;
13339         id view;
13340         CGContextRef drawingContext;
13341     }
13342 
13343     extern(C) {
13344     private:
13345         BOOL returnTrue3(id self, SEL _cmd, id app) {
13346             return true;
13347         }
13348         BOOL returnTrue2(id self, SEL _cmd) {
13349             return true;
13350         }
13351 
13352         void pulse(id self, SEL _cmd) {
13353             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
13354             simpleWindow.handlePulse();
13355             setNeedsDisplay(self, true);
13356         }
13357         void drawRect(id self, SEL _cmd, NSRect rect) {
13358             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
13359             auto curCtx = graphicsPort(currentNSGraphicsContext);
13360             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
13361             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
13362                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
13363             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
13364             CGImageRelease(cgImage);
13365         }
13366         void keyDown(id self, SEL _cmd, id event) {
13367             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
13368 
13369             // the event may have multiple characters, and we send them all at
13370             // once.
13371             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
13372                 auto chars = characters(event);
13373                 auto range = CFRange(0, CFStringGetLength(chars));
13374                 auto buffer = new char[range.length*3];
13375                 long actualLength;
13376                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
13377                                  buffer.ptr, cast(int) buffer.length, &actualLength);
13378                 foreach (dchar dc; buffer[0..actualLength]) {
13379                     if (simpleWindow.handleCharEvent)
13380                         simpleWindow.handleCharEvent(dc);
13381 		    // NotYetImplementedException
13382                     //if (simpleWindow.handleKeyEvent)
13383                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
13384                 }
13385             }
13386 
13387             // the event's 'keyCode' is hardware-dependent. I don't think people
13388             // will like it. Let's leave it to the native handler.
13389 
13390             // perform the default action.
13391 
13392 	    // so the default action is to make a bomp sound and i dont want that
13393 	    // sooooooooo yeah not gonna do that.
13394 
13395             //auto superData = objc_super(self, superclass(self));
13396             //alias extern(C) void function(objc_super*, SEL, id) T;
13397             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
13398         }
13399     }
13400 
13401     // initialize the app so that it can be interacted with the user.
13402     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
13403     private void initializeApp() {
13404         // push an autorelease pool to avoid leaking.
13405         init(alloc("NSAutoreleasePool"));
13406 
13407         // create a new NSApp instance
13408         sharedNSApplication;
13409         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
13410 
13411         // create the "Quit" menu.
13412         auto menuBar = init(alloc("NSMenu"));
13413         auto appMenuItem = init(alloc("NSMenuItem"));
13414         addItem(menuBar, appMenuItem);
13415         setMainMenu(NSApp, menuBar);
13416         release(appMenuItem);
13417         release(menuBar);
13418 
13419         auto appMenu = init(alloc("NSMenu"));
13420         auto quitTitle = createCFString("Quit");
13421         auto q = createCFString("q");
13422         auto quitItem = initWithTitle(alloc("NSMenuItem"),
13423                                       quitTitle, sel_registerName("terminate:"), q);
13424         addItem(appMenu, quitItem);
13425         setSubmenu(appMenuItem, appMenu);
13426         release(quitItem);
13427         release(appMenu);
13428         CFRelease(q);
13429         CFRelease(quitTitle);
13430 
13431         // assign a delegate for the application, allow it to quit when the last
13432         // window is closed.
13433         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
13434                                                     "SDWindowCloseDelegate", 0);
13435         class_addMethod(delegateClass,
13436                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
13437                         &returnTrue3, "c@:@");
13438         objc_registerClassPair(delegateClass);
13439 
13440         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
13441         setDelegate(NSApp, appDelegate);
13442         activateIgnoringOtherApps(NSApp, true);
13443 
13444         // create a new view that draws the graphics and respond to keyDown
13445         // events.
13446         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
13447                                                 "SDGraphicsView", (void*).sizeof);
13448         class_addIvar(viewClass, "simpledisplay_simpleWindow",
13449                       (void*).sizeof, (void*).alignof, "^v");
13450         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
13451                         &pulse, "v@:");
13452         class_addMethod(viewClass, sel_registerName("drawRect:"),
13453                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
13454         class_addMethod(viewClass, sel_registerName("isFlipped"),
13455                         &returnTrue2, "c@:");
13456         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
13457                         &returnTrue2, "c@:");
13458         class_addMethod(viewClass, sel_registerName("keyDown:"),
13459                         &keyDown, "v@:@");
13460         objc_registerClassPair(viewClass);
13461         simpleWindowIvar = class_getInstanceVariable(viewClass,
13462                                                      "simpledisplay_simpleWindow");
13463     }
13464 }
13465 
13466 version(without_opengl) {} else
13467 extern(System) nothrow @nogc {
13468 	//enum uint GL_VERSION = 0x1F02;
13469 	//const(char)* glGetString (/*GLenum*/uint);
13470 	version(X11) {
13471 	static if (!SdpyIsUsingIVGLBinds) {
13472 
13473 		enum GLX_X_RENDERABLE = 0x8012;
13474 		enum GLX_DRAWABLE_TYPE = 0x8010;
13475 		enum GLX_RENDER_TYPE = 0x8011;
13476 		enum GLX_X_VISUAL_TYPE = 0x22;
13477 		enum GLX_TRUE_COLOR = 0x8002;
13478 		enum GLX_WINDOW_BIT = 0x00000001;
13479 		enum GLX_RGBA_BIT = 0x00000001;
13480 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
13481 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
13482 		enum GLX_SAMPLES = 0x186a1;
13483 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
13484 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
13485 	}
13486 
13487 		// GLX_EXT_swap_control
13488 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
13489 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
13490 
13491 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
13492 		extern(System) {
13493 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
13494 		}
13495 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
13496 
13497 		// this made public so we don't have to get it again and again
13498 		public bool glXCreateContextAttribsARB_present () {
13499 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
13500 				// get it
13501 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
13502 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
13503 			}
13504 			return (glXCreateContextAttribsARBFn !is null);
13505 		}
13506 
13507 		// this made public so we don't have to get it again and again
13508 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
13509 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
13510 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
13511 		}
13512 
13513 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
13514 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
13515 			if (_glx_swapInterval_fn is null) {
13516 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
13517 				if (_glx_swapInterval_fn is null) {
13518 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
13519 					return;
13520 				}
13521 				version(sdddd) { import std.stdio; writeln("glXSwapIntervalEXT found!"); }
13522 			}
13523 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
13524 		}
13525 	} else version(Windows) {
13526 	static if (!SdpyIsUsingIVGLBinds) {
13527 	enum GL_TRUE = 1;
13528 	enum GL_FALSE = 0;
13529 	alias int GLint;
13530 
13531 	public void* glbindGetProcAddress (const(char)* name) {
13532 		void* res = wglGetProcAddress(name);
13533 		if (res is null) {
13534 			/+
13535 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
13536 			import core.sys.windows.windef, core.sys.windows.winbase;
13537 			__gshared HINSTANCE dll = null;
13538 			if (dll is null) {
13539 				dll = LoadLibraryA("opengl32.dll");
13540 				if (dll is null) return null; // <32, but idc
13541 			}
13542 			res = GetProcAddress(dll, name);
13543 			+/
13544 			res = GetProcAddress(gl.libHandle, name);
13545 		}
13546 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
13547 		return res;
13548 	}
13549 	}
13550 
13551 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
13552 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
13553 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
13554 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
13555 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
13556 
13557 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
13558 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
13559 
13560 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
13561 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
13562 
13563 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
13564 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
13565 
13566 		void wglInitOtherFunctions () {
13567 			if (wglCreateContextAttribsARB is null) {
13568 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
13569 			}
13570 		}
13571 	}
13572 
13573 	static if (!SdpyIsUsingIVGLBinds) {
13574 
13575 	interface GL {
13576 	extern(System) @nogc nothrow {
13577 
13578 		void glGetIntegerv(int, void*);
13579 		void glMatrixMode(int);
13580 		void glPushMatrix();
13581 		void glLoadIdentity();
13582 		void glOrtho(double, double, double, double, double, double);
13583 		void glFrustum(double, double, double, double, double, double);
13584 
13585 		void glPopMatrix();
13586 		void glEnable(int);
13587 		void glDisable(int);
13588 		void glClear(int);
13589 		void glBegin(int);
13590 		void glVertex2f(float, float);
13591 		void glVertex3f(float, float, float);
13592 		void glEnd();
13593 		void glColor3b(byte, byte, byte);
13594 		void glColor3ub(ubyte, ubyte, ubyte);
13595 		void glColor4b(byte, byte, byte, byte);
13596 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
13597 		void glColor3i(int, int, int);
13598 		void glColor3ui(uint, uint, uint);
13599 		void glColor4i(int, int, int, int);
13600 		void glColor4ui(uint, uint, uint, uint);
13601 		void glColor3f(float, float, float);
13602 		void glColor4f(float, float, float, float);
13603 		void glTranslatef(float, float, float);
13604 		void glScalef(float, float, float);
13605 		version(X11) {
13606 			void glSecondaryColor3b(byte, byte, byte);
13607 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
13608 			void glSecondaryColor3i(int, int, int);
13609 			void glSecondaryColor3ui(uint, uint, uint);
13610 			void glSecondaryColor3f(float, float, float);
13611 		}
13612 
13613 		void glDrawElements(int, int, int, void*);
13614 
13615 		void glRotatef(float, float, float, float);
13616 
13617 		uint glGetError();
13618 
13619 		void glDeleteTextures(int, uint*);
13620 
13621 
13622 		void glRasterPos2i(int, int);
13623 		void glDrawPixels(int, int, uint, uint, void*);
13624 		void glClearColor(float, float, float, float);
13625 
13626 
13627 		void glPixelStorei(uint, int);
13628 
13629 		void glGenTextures(uint, uint*);
13630 		void glBindTexture(int, int);
13631 		void glTexParameteri(uint, uint, int);
13632 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
13633 		void glTexImage2D(int, int, int, int, int, int, int, int, in void*);
13634 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
13635 			/*GLsizei*/int width, /*GLsizei*/int height,
13636 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
13637 		version(linux)
13638 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
13639 			/*GLsizei*/int width, /*GLsizei*/int height,
13640 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
13641 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
13642 
13643 		void glLineWidth(int);
13644 
13645 
13646 		void glTexCoord2f(float, float);
13647 		void glVertex2i(int, int);
13648 		void glBlendFunc (int, int);
13649 		void glDepthFunc (int);
13650 		void glViewport(int, int, int, int);
13651 
13652 		void glClearDepth(double);
13653 
13654 		void glReadBuffer(uint);
13655 		void glReadPixels(int, int, int, int, int, int, void*);
13656 
13657 		void glFlush();
13658 		void glFinish();
13659 
13660 		version(Windows) {
13661 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
13662 			HGLRC wglCreateContext(HDC);
13663 			HGLRC wglCreateLayerContext(HDC, int);
13664 			BOOL wglDeleteContext(HGLRC);
13665 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
13666 			HGLRC wglGetCurrentContext();
13667 			HDC wglGetCurrentDC();
13668 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
13669 			PROC wglGetProcAddress(LPCSTR);
13670 			BOOL wglMakeCurrent(HDC, HGLRC);
13671 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
13672 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
13673 			BOOL wglShareLists(HGLRC, HGLRC);
13674 			BOOL wglSwapLayerBuffers(HDC, UINT);
13675 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
13676 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
13677 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
13678 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
13679 		}
13680 
13681 	}
13682 	}
13683 
13684 	interface GLU {
13685 	extern(System) @nogc nothrow {
13686 		void gluLookAt(double, double, double, double, double, double, double, double, double);
13687 		void gluPerspective(double, double, double, double);
13688 
13689 		char* gluErrorString(uint);
13690 	}
13691 	}
13692 
13693 
13694 	enum GL_RED = 0x1903;
13695 	enum GL_ALPHA = 0x1906;
13696 	enum GL_UNPACK_ALIGNMENT = 0x0CF5;
13697 
13698 	enum uint GL_FRONT = 0x0404;
13699 
13700 	enum uint GL_BLEND = 0x0be2;
13701 	enum uint GL_SRC_ALPHA = 0x0302;
13702 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
13703 	enum uint GL_LEQUAL = 0x0203;
13704 
13705 
13706 	enum uint GL_UNSIGNED_BYTE = 0x1401;
13707 	enum uint GL_RGB = 0x1907;
13708 	enum uint GL_BGRA = 0x80e1;
13709 	enum uint GL_RGBA = 0x1908;
13710 	enum uint GL_TEXTURE_2D =   0x0DE1;
13711 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
13712 	enum uint GL_NEAREST = 0x2600;
13713 	enum uint GL_LINEAR = 0x2601;
13714 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
13715 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
13716 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
13717 	enum uint GL_REPEAT = 0x2901;
13718 	enum uint GL_CLAMP = 0x2900;
13719 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
13720 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
13721 	enum uint GL_DECAL = 0x2101;
13722 	enum uint GL_MODULATE = 0x2100;
13723 	enum uint GL_TEXTURE_ENV = 0x2300;
13724 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
13725 	enum uint GL_REPLACE = 0x1E01;
13726 	enum uint GL_LIGHTING = 0x0B50;
13727 	enum uint GL_DITHER = 0x0BD0;
13728 
13729 	enum uint GL_NO_ERROR = 0;
13730 
13731 
13732 
13733 	enum int GL_VIEWPORT = 0x0BA2;
13734 	enum int GL_MODELVIEW = 0x1700;
13735 	enum int GL_TEXTURE = 0x1702;
13736 	enum int GL_PROJECTION = 0x1701;
13737 	enum int GL_DEPTH_TEST = 0x0B71;
13738 
13739 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
13740 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
13741 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
13742 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
13743 
13744 	enum int GL_POINTS = 0x0000;
13745 	enum int GL_LINES =  0x0001;
13746 	enum int GL_LINE_LOOP = 0x0002;
13747 	enum int GL_LINE_STRIP = 0x0003;
13748 	enum int GL_TRIANGLES = 0x0004;
13749 	enum int GL_TRIANGLE_STRIP = 5;
13750 	enum int GL_TRIANGLE_FAN = 6;
13751 	enum int GL_QUADS = 7;
13752 	enum int GL_QUAD_STRIP = 8;
13753 	enum int GL_POLYGON = 9;
13754 	}
13755 }
13756 
13757 version(without_opengl) {} else {
13758 static if(!SdpyIsUsingIVGLBinds) {
13759 	version(Windows) {
13760 		mixin DynamicLoad!(GL, "opengl32", true) gl;
13761 		mixin DynamicLoad!(GLU, "glu32", true) glu;
13762 	} else {
13763 		mixin DynamicLoad!(GL, "GL", true) gl;
13764 		mixin DynamicLoad!(GLU, "GLU", true) glu;
13765 	}
13766 
13767 	shared static this() {
13768 		gl.loadDynamicLibrary();
13769 		glu.loadDynamicLibrary();
13770 	}
13771 }
13772 }
13773 
13774 version(linux) {
13775 	version(with_eventloop) {} else {
13776 		private int epollFd = -1;
13777 		void prepareEventLoop() {
13778 			if(epollFd != -1)
13779 				return; // already initialized, no need to do it again
13780 			import ep = core.sys.linux.epoll;
13781 
13782 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
13783 			if(epollFd == -1)
13784 				throw new Exception("epoll create failure");
13785 		}
13786 	}
13787 } else version(Posix) {
13788 	void prepareEventLoop() {}
13789 }
13790 
13791 version(X11) {
13792 	import core.stdc.locale : LC_ALL; // rdmd fix
13793 	__gshared bool sdx_isUTF8Locale;
13794 
13795 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
13796 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
13797 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
13798 	// anal magic is here. I (Ketmar) hope you like it.
13799 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
13800 	// always return correct unicode symbols. The detection is here 'cause user can change locale
13801 	// later.
13802 
13803 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
13804 	shared static this () {
13805 		if(!librariesSuccessfullyLoaded)
13806 			return;
13807 
13808 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
13809 
13810 		// this doesn't hurt; it may add some locking, but the speed is still
13811 		// allows doing 60 FPS videogames; also, ignore the result, as most
13812 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
13813 		// never seen this failing).
13814 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
13815 
13816 		setlocale(LC_ALL, "");
13817 		// check if out locale is UTF-8
13818 		auto lct = setlocale(LC_CTYPE, null);
13819 		if (lct is null) {
13820 			sdx_isUTF8Locale = false;
13821 		} else {
13822 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
13823 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
13824 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
13825 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
13826 				{
13827 					sdx_isUTF8Locale = true;
13828 					break;
13829 				}
13830 			}
13831 		}
13832 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
13833 	}
13834 }
13835 
13836 mixin template ExperimentalTextComponent2() {
13837 	/+
13838 		Stage 1: get it working monospace
13839 		Stage 2: use proportional font
13840 		Stage 3: allow changes in inline style
13841 		Stage 4: allow new fonts and sizes in the middle
13842 		Stage 5: optimize gap buffer
13843 		Stage 6: optimize layout
13844 		Stage 7: word wrap
13845 		Stage 8: justification
13846 		Stage 9: editing, selection, etc.
13847 	+/
13848 }
13849 
13850 
13851 // Don't use this yet. When I'm happy with it, I will move it to the
13852 // regular module namespace.
13853 mixin template ExperimentalTextComponent() {
13854 
13855 	alias Rectangle = arsd.color.Rectangle;
13856 
13857 	struct ForegroundColor {
13858 		Color color;
13859 		alias color this;
13860 
13861 		this(Color c) {
13862 			color = c;
13863 		}
13864 
13865 		this(int r, int g, int b, int a = 255) {
13866 			color = Color(r, g, b, a);
13867 		}
13868 
13869 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
13870 			return ForegroundColor(mixin("Color." ~ s));
13871 		}
13872 	}
13873 
13874 	struct BackgroundColor {
13875 		Color color;
13876 		alias color this;
13877 
13878 		this(Color c) {
13879 			color = c;
13880 		}
13881 
13882 		this(int r, int g, int b, int a = 255) {
13883 			color = Color(r, g, b, a);
13884 		}
13885 
13886 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
13887 			return BackgroundColor(mixin("Color." ~ s));
13888 		}
13889 	}
13890 
13891 	static class InlineElement {
13892 		string text;
13893 
13894 		BlockElement containingBlock;
13895 
13896 		Color color = Color.black;
13897 		Color backgroundColor = Color.transparent;
13898 		ushort styles;
13899 
13900 		string font;
13901 		int fontSize;
13902 
13903 		int lineHeight;
13904 
13905 		void* identifier;
13906 
13907 		Rectangle boundingBox;
13908 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
13909 
13910 		bool isMergeCompatible(InlineElement other) {
13911 			return
13912 				containingBlock is other.containingBlock &&
13913 				color == other.color &&
13914 				backgroundColor == other.backgroundColor &&
13915 				styles == other.styles &&
13916 				font == other.font &&
13917 				fontSize == other.fontSize &&
13918 				lineHeight == other.lineHeight &&
13919 				true;
13920 		}
13921 
13922 		int xOfIndex(size_t index) {
13923 			if(index < letterXs.length)
13924 				return letterXs[index];
13925 			else
13926 				return boundingBox.right;
13927 		}
13928 
13929 		InlineElement clone() {
13930 			auto ie = new InlineElement();
13931 			ie.tupleof = this.tupleof;
13932 			return ie;
13933 		}
13934 
13935 		InlineElement getPreviousInlineElement() {
13936 			InlineElement prev = null;
13937 			foreach(ie; this.containingBlock.parts) {
13938 				if(ie is this)
13939 					break;
13940 				prev = ie;
13941 			}
13942 			if(prev is null) {
13943 				BlockElement pb;
13944 				BlockElement cb = this.containingBlock;
13945 				moar:
13946 				foreach(ie; this.containingBlock.containingLayout.blocks) {
13947 					if(ie is cb)
13948 						break;
13949 					pb = ie;
13950 				}
13951 				if(pb is null)
13952 					return null;
13953 				if(pb.parts.length == 0) {
13954 					cb = pb;
13955 					goto moar;
13956 				}
13957 
13958 				prev = pb.parts[$-1];
13959 
13960 			}
13961 			return prev;
13962 		}
13963 
13964 		InlineElement getNextInlineElement() {
13965 			InlineElement next = null;
13966 			foreach(idx, ie; this.containingBlock.parts) {
13967 				if(ie is this) {
13968 					if(idx + 1 < this.containingBlock.parts.length)
13969 						next = this.containingBlock.parts[idx + 1];
13970 					break;
13971 				}
13972 			}
13973 			if(next is null) {
13974 				BlockElement n;
13975 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
13976 					if(ie is this.containingBlock) {
13977 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
13978 							n = this.containingBlock.containingLayout.blocks[idx + 1];
13979 						break;
13980 					}
13981 				}
13982 				if(n is null)
13983 					return null;
13984 
13985 				if(n.parts.length)
13986 					next = n.parts[0];
13987 				else {} // FIXME
13988 
13989 			}
13990 			return next;
13991 		}
13992 
13993 	}
13994 
13995 	// Block elements are used entirely for positioning inline elements,
13996 	// which are the things that are actually drawn.
13997 	class BlockElement {
13998 		InlineElement[] parts;
13999 		uint alignment;
14000 
14001 		int whiteSpace; // pre, pre-wrap, wrap
14002 
14003 		TextLayout containingLayout;
14004 
14005 		// inputs
14006 		Point where;
14007 		Size minimumSize;
14008 		Size maximumSize;
14009 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
14010 		void* identifier;
14011 
14012 		Rectangle margin;
14013 		Rectangle padding;
14014 
14015 		// outputs
14016 		Rectangle[] boundingBoxes;
14017 	}
14018 
14019 	struct TextIdentifyResult {
14020 		InlineElement element;
14021 		int offset;
14022 
14023 		private TextIdentifyResult fixupNewline() {
14024 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
14025 				offset--;
14026 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
14027 				offset--;
14028 			}
14029 			return this;
14030 		}
14031 	}
14032 
14033 	class TextLayout {
14034 		BlockElement[] blocks;
14035 		Rectangle boundingBox_;
14036 		Rectangle boundingBox() { return boundingBox_; }
14037 		void boundingBox(Rectangle r) {
14038 			if(r != boundingBox_) {
14039 				boundingBox_ = r;
14040 				layoutInvalidated = true;
14041 			}
14042 		}
14043 
14044 		Rectangle contentBoundingBox() {
14045 			Rectangle r;
14046 			foreach(block; blocks)
14047 			foreach(ie; block.parts) {
14048 				if(ie.boundingBox.right > r.right)
14049 					r.right = ie.boundingBox.right;
14050 				if(ie.boundingBox.bottom > r.bottom)
14051 					r.bottom = ie.boundingBox.bottom;
14052 			}
14053 			return r;
14054 		}
14055 
14056 		BlockElement[] getBlocks() {
14057 			return blocks;
14058 		}
14059 
14060 		InlineElement[] getTexts() {
14061 			InlineElement[] elements;
14062 			foreach(block; blocks)
14063 				elements ~= block.parts;
14064 			return elements;
14065 		}
14066 
14067 		string getPlainText() {
14068 			string text;
14069 			foreach(block; blocks)
14070 				foreach(part; block.parts)
14071 					text ~= part.text;
14072 			return text;
14073 		}
14074 
14075 		string getHtml() {
14076 			return null; // FIXME
14077 		}
14078 
14079 		this(Rectangle boundingBox) {
14080 			this.boundingBox = boundingBox;
14081 		}
14082 
14083 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
14084 			auto be = new BlockElement();
14085 			be.containingLayout = this;
14086 			if(after is null)
14087 				blocks ~= be;
14088 			else {
14089 				foreach(idx, b; blocks) {
14090 					if(b is after.containingBlock) {
14091 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
14092 						break;
14093 					}
14094 				}
14095 			}
14096 			return be;
14097 		}
14098 
14099 		void clear() {
14100 			blocks = null;
14101 			selectionStart = selectionEnd = caret = Caret.init;
14102 		}
14103 
14104 		void addText(Args...)(Args args) {
14105 			if(blocks.length == 0)
14106 				addBlock();
14107 
14108 			InlineElement ie = new InlineElement();
14109 			foreach(idx, arg; args) {
14110 				static if(is(typeof(arg) == ForegroundColor))
14111 					ie.color = arg;
14112 				else static if(is(typeof(arg) == TextFormat)) {
14113 					if(arg & 0x8000) // ~TextFormat.something turns it off
14114 						ie.styles &= arg;
14115 					else
14116 						ie.styles |= arg;
14117 				} else static if(is(typeof(arg) == string)) {
14118 					static if(idx == 0 && args.length > 1)
14119 						static assert(0, "Put styles before the string.");
14120 					size_t lastLineIndex;
14121 					foreach(cidx, char a; arg) {
14122 						if(a == '\n') {
14123 							ie.text = arg[lastLineIndex .. cidx + 1];
14124 							lastLineIndex = cidx + 1;
14125 							ie.containingBlock = blocks[$-1];
14126 							blocks[$-1].parts ~= ie.clone;
14127 							ie.text = null;
14128 						} else {
14129 
14130 						}
14131 					}
14132 
14133 					ie.text = arg[lastLineIndex .. $];
14134 					ie.containingBlock = blocks[$-1];
14135 					blocks[$-1].parts ~= ie.clone;
14136 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
14137 				}
14138 			}
14139 
14140 			invalidateLayout();
14141 		}
14142 
14143 		void tryMerge(InlineElement into, InlineElement what) {
14144 			if(!into.isMergeCompatible(what)) {
14145 				return; // cannot merge, different configs
14146 			}
14147 
14148 			// cool, can merge, bring text together...
14149 			into.text ~= what.text;
14150 
14151 			// and remove what
14152 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
14153 				if(what.containingBlock.parts[a] is what) {
14154 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
14155 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
14156 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
14157 
14158 				}
14159 			}
14160 
14161 			// FIXME: ensure no other carets have a reference to it
14162 		}
14163 
14164 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
14165 		TextIdentifyResult identify(int x, int y, bool exact = false) {
14166 			TextIdentifyResult inexactMatch;
14167 			foreach(block; blocks) {
14168 				foreach(part; block.parts) {
14169 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
14170 
14171 						// FIXME binary search
14172 						int tidx;
14173 						int lastX;
14174 						foreach_reverse(idxo, lx; part.letterXs) {
14175 							int idx = cast(int) idxo;
14176 							if(lx <= x) {
14177 								if(lastX && lastX - x < x - lx)
14178 									tidx = idx + 1;
14179 								else
14180 									tidx = idx;
14181 								break;
14182 							}
14183 							lastX = lx;
14184 						}
14185 
14186 						return TextIdentifyResult(part, tidx).fixupNewline;
14187 					} else if(!exact) {
14188 						// we're not in the box, but are we on the same line?
14189 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
14190 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
14191 					}
14192 				}
14193 			}
14194 
14195 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
14196 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
14197 
14198 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
14199 		}
14200 
14201 		void moveCaretToPixelCoordinates(int x, int y) {
14202 			auto result = identify(x, y);
14203 			caret.inlineElement = result.element;
14204 			caret.offset = result.offset;
14205 		}
14206 
14207 		void selectToPixelCoordinates(int x, int y) {
14208 			auto result = identify(x, y);
14209 
14210 			if(y < caretLastDrawnY1) {
14211 				// on a previous line, carat is selectionEnd
14212 				selectionEnd = caret;
14213 
14214 				selectionStart = Caret(this, result.element, result.offset);
14215 			} else if(y > caretLastDrawnY2) {
14216 				// on a later line
14217 				selectionStart = caret;
14218 
14219 				selectionEnd = Caret(this, result.element, result.offset);
14220 			} else {
14221 				// on the same line...
14222 				if(x <= caretLastDrawnX) {
14223 					selectionEnd = caret;
14224 					selectionStart = Caret(this, result.element, result.offset);
14225 				} else {
14226 					selectionStart = caret;
14227 					selectionEnd = Caret(this, result.element, result.offset);
14228 				}
14229 
14230 			}
14231 		}
14232 
14233 
14234 		/// Call this if the inputs change. It will reflow everything
14235 		void redoLayout(ScreenPainter painter) {
14236 			//painter.setClipRectangle(boundingBox);
14237 			auto pos = Point(boundingBox.left, boundingBox.top);
14238 
14239 			int lastHeight;
14240 			void nl() {
14241 				pos.x = boundingBox.left;
14242 				pos.y += lastHeight;
14243 			}
14244 			foreach(block; blocks) {
14245 				nl();
14246 				foreach(part; block.parts) {
14247 					part.letterXs = null;
14248 
14249 					auto size = painter.textSize(part.text);
14250 					version(Windows)
14251 						if(part.text.length && part.text[$-1] == '\n')
14252 							size.height /= 2; // windows counts the new line at the end, but we don't want that
14253 
14254 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
14255 
14256 					foreach(idx, char c; part.text) {
14257 							// FIXME: unicode
14258 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
14259 					}
14260 
14261 					pos.x += size.width;
14262 					if(pos.x >= boundingBox.right) {
14263 						pos.y += size.height;
14264 						pos.x = boundingBox.left;
14265 						lastHeight = 0;
14266 					} else {
14267 						lastHeight = size.height;
14268 					}
14269 
14270 					if(part.text.length && part.text[$-1] == '\n')
14271 						nl();
14272 				}
14273 			}
14274 
14275 			layoutInvalidated = false;
14276 		}
14277 
14278 		bool layoutInvalidated = true;
14279 		void invalidateLayout() {
14280 			layoutInvalidated = true;
14281 		}
14282 
14283 // FIXME: caret can remain sometimes when inserting
14284 // FIXME: inserting at the beginning once you already have something can eff it up.
14285 		void drawInto(ScreenPainter painter, bool focused = false) {
14286 			if(layoutInvalidated)
14287 				redoLayout(painter);
14288 			foreach(block; blocks) {
14289 				foreach(part; block.parts) {
14290 					painter.outlineColor = part.color;
14291 					painter.fillColor = part.backgroundColor;
14292 
14293 					auto pos = part.boundingBox.upperLeft;
14294 					auto size = part.boundingBox.size;
14295 
14296 					painter.drawText(pos, part.text);
14297 					if(part.styles & TextFormat.underline)
14298 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
14299 					if(part.styles & TextFormat.strikethrough)
14300 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
14301 				}
14302 			}
14303 
14304 			// on every redraw, I will force the caret to be
14305 			// redrawn too, in order to eliminate perceived lag
14306 			// when moving around with the mouse.
14307 			eraseCaret(painter);
14308 
14309 			if(focused) {
14310 				highlightSelection(painter);
14311 				drawCaret(painter);
14312 			}
14313 		}
14314 
14315 		void highlightSelection(ScreenPainter painter) {
14316 			if(selectionStart is selectionEnd)
14317 				return; // no selection
14318 
14319 			if(selectionStart.inlineElement is null) return;
14320 			if(selectionEnd.inlineElement is null) return;
14321 
14322 			assert(selectionStart.inlineElement !is null);
14323 			assert(selectionEnd.inlineElement !is null);
14324 
14325 			painter.rasterOp = RasterOp.xor;
14326 			painter.outlineColor = Color.transparent;
14327 			painter.fillColor = Color(255, 255, 127);
14328 
14329 			auto at = selectionStart.inlineElement;
14330 			auto atOffset = selectionStart.offset;
14331 			bool done;
14332 			while(at) {
14333 				auto box = at.boundingBox;
14334 				if(atOffset < at.letterXs.length)
14335 					box.left = at.letterXs[atOffset];
14336 
14337 				if(at is selectionEnd.inlineElement) {
14338 					if(selectionEnd.offset < at.letterXs.length)
14339 						box.right = at.letterXs[selectionEnd.offset];
14340 					done = true;
14341 				}
14342 
14343 				painter.drawRectangle(box.upperLeft, box.width, box.height);
14344 
14345 				if(done)
14346 					break;
14347 
14348 				at = at.getNextInlineElement();
14349 				atOffset = 0;
14350 			}
14351 		}
14352 
14353 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
14354 		bool caretShowingOnScreen = false;
14355 		void drawCaret(ScreenPainter painter) {
14356 			//painter.setClipRectangle(boundingBox);
14357 			int x, y1, y2;
14358 			if(caret.inlineElement is null) {
14359 				x = boundingBox.left;
14360 				y1 = boundingBox.top + 2;
14361 				y2 = boundingBox.top + painter.fontHeight;
14362 			} else {
14363 				x = caret.inlineElement.xOfIndex(caret.offset);
14364 				y1 = caret.inlineElement.boundingBox.top + 2;
14365 				y2 = caret.inlineElement.boundingBox.bottom - 2;
14366 			}
14367 
14368 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
14369 				eraseCaret(painter);
14370 
14371 			painter.pen = Pen(Color.white, 1);
14372 			painter.rasterOp = RasterOp.xor;
14373 			painter.drawLine(
14374 				Point(x, y1),
14375 				Point(x, y2)
14376 			);
14377 			painter.rasterOp = RasterOp.normal;
14378 			caretShowingOnScreen = !caretShowingOnScreen;
14379 
14380 			if(caretShowingOnScreen) {
14381 				caretLastDrawnX = x;
14382 				caretLastDrawnY1 = y1;
14383 				caretLastDrawnY2 = y2;
14384 			}
14385 		}
14386 
14387 		Rectangle caretBoundingBox() {
14388 			int x, y1, y2;
14389 			if(caret.inlineElement is null) {
14390 				x = boundingBox.left;
14391 				y1 = boundingBox.top + 2;
14392 				y2 = boundingBox.top + 16;
14393 			} else {
14394 				x = caret.inlineElement.xOfIndex(caret.offset);
14395 				y1 = caret.inlineElement.boundingBox.top + 2;
14396 				y2 = caret.inlineElement.boundingBox.bottom - 2;
14397 			}
14398 
14399 			return Rectangle(x, y1, x + 1, y2);
14400 		}
14401 
14402 		void eraseCaret(ScreenPainter painter) {
14403 			//painter.setClipRectangle(boundingBox);
14404 			if(!caretShowingOnScreen) return;
14405 			painter.pen = Pen(Color.white, 1);
14406 			painter.rasterOp = RasterOp.xor;
14407 			painter.drawLine(
14408 				Point(caretLastDrawnX, caretLastDrawnY1),
14409 				Point(caretLastDrawnX, caretLastDrawnY2)
14410 			);
14411 
14412 			caretShowingOnScreen = false;
14413 			painter.rasterOp = RasterOp.normal;
14414 		}
14415 
14416 		/// Caret movement api
14417 		/// These should give the user a logical result based on what they see on screen...
14418 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
14419 		void moveUp() {
14420 			if(caret.inlineElement is null) return;
14421 			auto x = caret.inlineElement.xOfIndex(caret.offset);
14422 			auto y = caret.inlineElement.boundingBox.top + 2;
14423 
14424 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
14425 			if(y < 0)
14426 				return;
14427 
14428 			auto i = identify(x, y);
14429 
14430 			if(i.element) {
14431 				caret.inlineElement = i.element;
14432 				caret.offset = i.offset;
14433 			}
14434 		}
14435 		void moveDown() {
14436 			if(caret.inlineElement is null) return;
14437 			auto x = caret.inlineElement.xOfIndex(caret.offset);
14438 			auto y = caret.inlineElement.boundingBox.bottom - 2;
14439 
14440 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
14441 
14442 			auto i = identify(x, y);
14443 			if(i.element) {
14444 				caret.inlineElement = i.element;
14445 				caret.offset = i.offset;
14446 			}
14447 		}
14448 		void moveLeft() {
14449 			if(caret.inlineElement is null) return;
14450 			if(caret.offset)
14451 				caret.offset--;
14452 			else {
14453 				auto p = caret.inlineElement.getPreviousInlineElement();
14454 				if(p) {
14455 					caret.inlineElement = p;
14456 					if(p.text.length && p.text[$-1] == '\n')
14457 						caret.offset = cast(int) p.text.length - 1;
14458 					else
14459 						caret.offset = cast(int) p.text.length;
14460 				}
14461 			}
14462 		}
14463 		void moveRight() {
14464 			if(caret.inlineElement is null) return;
14465 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
14466 				caret.offset++;
14467 			} else {
14468 				auto p = caret.inlineElement.getNextInlineElement();
14469 				if(p) {
14470 					caret.inlineElement = p;
14471 					caret.offset = 0;
14472 				}
14473 			}
14474 		}
14475 		void moveHome() {
14476 			if(caret.inlineElement is null) return;
14477 			auto x = 0;
14478 			auto y = caret.inlineElement.boundingBox.top + 2;
14479 
14480 			auto i = identify(x, y);
14481 
14482 			if(i.element) {
14483 				caret.inlineElement = i.element;
14484 				caret.offset = i.offset;
14485 			}
14486 		}
14487 		void moveEnd() {
14488 			if(caret.inlineElement is null) return;
14489 			auto x = int.max;
14490 			auto y = caret.inlineElement.boundingBox.top + 2;
14491 
14492 			auto i = identify(x, y);
14493 
14494 			if(i.element) {
14495 				caret.inlineElement = i.element;
14496 				caret.offset = i.offset;
14497 			}
14498 
14499 		}
14500 		void movePageUp(ref Caret caret) {}
14501 		void movePageDown(ref Caret caret) {}
14502 
14503 		void moveDocumentStart(ref Caret caret) {
14504 			if(blocks.length && blocks[0].parts.length)
14505 				caret = Caret(this, blocks[0].parts[0], 0);
14506 			else
14507 				caret = Caret.init;
14508 		}
14509 
14510 		void moveDocumentEnd(ref Caret caret) {
14511 			if(blocks.length) {
14512 				auto parts = blocks[$-1].parts;
14513 				if(parts.length) {
14514 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
14515 				} else {
14516 					caret = Caret.init;
14517 				}
14518 			} else
14519 				caret = Caret.init;
14520 		}
14521 
14522 		void deleteSelection() {
14523 			if(selectionStart is selectionEnd)
14524 				return;
14525 
14526 			if(selectionStart.inlineElement is null) return;
14527 			if(selectionEnd.inlineElement is null) return;
14528 
14529 			assert(selectionStart.inlineElement !is null);
14530 			assert(selectionEnd.inlineElement !is null);
14531 
14532 			auto at = selectionStart.inlineElement;
14533 
14534 			if(selectionEnd.inlineElement is at) {
14535 				// same element, need to chop out
14536 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
14537 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
14538 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
14539 			} else {
14540 				// different elements, we can do it with slicing
14541 				at.text = at.text[0 .. selectionStart.offset];
14542 				if(selectionStart.offset < at.letterXs.length)
14543 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
14544 
14545 				at = at.getNextInlineElement();
14546 
14547 				while(at) {
14548 					if(at is selectionEnd.inlineElement) {
14549 						at.text = at.text[selectionEnd.offset .. $];
14550 						if(selectionEnd.offset < at.letterXs.length)
14551 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
14552 						selectionEnd.offset = 0;
14553 						break;
14554 					} else {
14555 						auto cfd = at;
14556 						cfd.text = null; // delete the whole thing
14557 
14558 						at = at.getNextInlineElement();
14559 
14560 						if(cfd.text.length == 0) {
14561 							// and remove cfd
14562 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
14563 								if(cfd.containingBlock.parts[a] is cfd) {
14564 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
14565 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
14566 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
14567 
14568 								}
14569 							}
14570 						}
14571 					}
14572 				}
14573 			}
14574 
14575 			caret = selectionEnd;
14576 			selectNone();
14577 
14578 			invalidateLayout();
14579 
14580 		}
14581 
14582 		/// Plain text editing api. These work at the current caret inside the selected inline element.
14583 		void insert(in char[] text) {
14584 			foreach(dchar ch; text)
14585 				insert(ch);
14586 		}
14587 		/// ditto
14588 		void insert(dchar ch) {
14589 
14590 			bool selectionDeleted = false;
14591 			if(selectionStart !is selectionEnd) {
14592 				deleteSelection();
14593 				selectionDeleted = true;
14594 			}
14595 
14596 			if(ch == 127) {
14597 				delete_();
14598 				return;
14599 			}
14600 			if(ch == 8) {
14601 				if(!selectionDeleted)
14602 					backspace();
14603 				return;
14604 			}
14605 
14606 			invalidateLayout();
14607 
14608 			if(ch == 13) ch = 10;
14609 			auto e = caret.inlineElement;
14610 			if(e is null) {
14611 				addText("" ~ cast(char) ch) ; // FIXME
14612 				return;
14613 			}
14614 
14615 			if(caret.offset == e.text.length) {
14616 				e.text ~= cast(char) ch; // FIXME
14617 				caret.offset++;
14618 				if(ch == 10) {
14619 					auto c = caret.inlineElement.clone;
14620 					c.text = null;
14621 					c.letterXs = null;
14622 					insertPartAfter(c,e);
14623 					caret = Caret(this, c, 0);
14624 				}
14625 			} else {
14626 				// FIXME cast char sucks
14627 				if(ch == 10) {
14628 					auto c = caret.inlineElement.clone;
14629 					c.text = e.text[caret.offset .. $];
14630 					if(caret.offset < c.letterXs.length)
14631 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
14632 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
14633 					if(caret.offset <= e.letterXs.length) {
14634 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
14635 					}
14636 					insertPartAfter(c,e);
14637 					caret = Caret(this, c, 0);
14638 				} else {
14639 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
14640 					caret.offset++;
14641 				}
14642 			}
14643 		}
14644 
14645 		void insertPartAfter(InlineElement what, InlineElement where) {
14646 			foreach(idx, p; where.containingBlock.parts) {
14647 				if(p is where) {
14648 					if(idx + 1 == where.containingBlock.parts.length)
14649 						where.containingBlock.parts ~= what;
14650 					else
14651 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
14652 					return;
14653 				}
14654 			}
14655 		}
14656 
14657 		void cleanupStructures() {
14658 			for(size_t i = 0; i < blocks.length; i++) {
14659 				auto block = blocks[i];
14660 				for(size_t a = 0; a < block.parts.length; a++) {
14661 					auto part = block.parts[a];
14662 					if(part.text.length == 0) {
14663 						for(size_t b = a; b < block.parts.length - 1; b++)
14664 							block.parts[b] = block.parts[b+1];
14665 						block.parts = block.parts[0 .. $-1];
14666 					}
14667 				}
14668 				if(block.parts.length == 0) {
14669 					for(size_t a = i; a < blocks.length - 1; a++)
14670 						blocks[a] = blocks[a+1];
14671 					blocks = blocks[0 .. $-1];
14672 				}
14673 			}
14674 		}
14675 
14676 		void backspace() {
14677 			try_again:
14678 			auto e = caret.inlineElement;
14679 			if(e is null)
14680 				return;
14681 			if(caret.offset == 0) {
14682 				auto prev = e.getPreviousInlineElement();
14683 				if(prev is null)
14684 					return;
14685 				auto newOffset = cast(int) prev.text.length;
14686 				tryMerge(prev, e);
14687 				caret.inlineElement = prev;
14688 				caret.offset = prev is null ? 0 : newOffset;
14689 
14690 				goto try_again;
14691 			} else if(caret.offset == e.text.length) {
14692 				e.text = e.text[0 .. $-1];
14693 				caret.offset--;
14694 			} else {
14695 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
14696 				caret.offset--;
14697 			}
14698 			//cleanupStructures();
14699 
14700 			invalidateLayout();
14701 		}
14702 		void delete_() {
14703 			if(selectionStart !is selectionEnd)
14704 				deleteSelection();
14705 			else {
14706 				auto before = caret;
14707 				moveRight();
14708 				if(caret != before) {
14709 					backspace();
14710 				}
14711 			}
14712 
14713 			invalidateLayout();
14714 		}
14715 		void overstrike() {}
14716 
14717 		/// Selection API. See also: caret movement.
14718 		void selectAll() {
14719 			moveDocumentStart(selectionStart);
14720 			moveDocumentEnd(selectionEnd);
14721 		}
14722 		bool selectNone() {
14723 			if(selectionStart != selectionEnd) {
14724 				selectionStart = selectionEnd = Caret.init;
14725 				return true;
14726 			}
14727 			return false;
14728 		}
14729 
14730 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
14731 		/// They will modify the current selection if there is one and will splice one in if needed.
14732 		void changeAttributes() {}
14733 
14734 
14735 		/// Text search api. They manipulate the selection and/or caret.
14736 		void findText(string text) {}
14737 		void findIndex(size_t textIndex) {}
14738 
14739 		// sample event handlers
14740 
14741 		void handleEvent(KeyEvent event) {
14742 			//if(event.type == KeyEvent.Type.KeyPressed) {
14743 
14744 			//}
14745 		}
14746 
14747 		void handleEvent(dchar ch) {
14748 
14749 		}
14750 
14751 		void handleEvent(MouseEvent event) {
14752 
14753 		}
14754 
14755 		bool contentEditable; // can it be edited?
14756 		bool contentCaretable; // is there a caret/cursor that moves around in there?
14757 		bool contentSelectable; // selectable?
14758 
14759 		Caret caret;
14760 		Caret selectionStart;
14761 		Caret selectionEnd;
14762 
14763 		bool insertMode;
14764 	}
14765 
14766 	struct Caret {
14767 		TextLayout layout;
14768 		InlineElement inlineElement;
14769 		int offset;
14770 	}
14771 
14772 	enum TextFormat : ushort {
14773 		// decorations
14774 		underline = 1,
14775 		strikethrough = 2,
14776 
14777 		// font selectors
14778 
14779 		bold = 0x4000 | 1, // weight 700
14780 		light = 0x4000 | 2, // weight 300
14781 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
14782 		// bold | light is really invalid but should give weight 500
14783 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
14784 
14785 		italic = 0x4000 | 8,
14786 		smallcaps = 0x4000 | 16,
14787 	}
14788 
14789 	void* findFont(string family, int weight, TextFormat formats) {
14790 		return null;
14791 	}
14792 
14793 }
14794 
14795 static if(UsingSimpledisplayX11) {
14796 
14797 enum _NET_WM_STATE_ADD = 1;
14798 enum _NET_WM_STATE_REMOVE = 0;
14799 enum _NET_WM_STATE_TOGGLE = 2;
14800 
14801 /// X-specific. Use [SimpleWindow.requestAttention] instead for most casesl
14802 void demandAttention(SimpleWindow window, bool needs = true) {
14803 	demandAttention(window.impl.window, needs);
14804 }
14805 
14806 /// ditto
14807 void demandAttention(Window window, bool needs = true) {
14808 	auto display = XDisplayConnection.get();
14809 	auto atom = XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", true);
14810 	if(atom == None)
14811 		return; // non-failure error
14812 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
14813 
14814 	XClientMessageEvent xclient;
14815 
14816 	xclient.type = EventType.ClientMessage;
14817 	xclient.window = window;
14818 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
14819 	xclient.format = 32;
14820 	xclient.data.l[0] = needs ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
14821 	xclient.data.l[1] = atom;
14822 	//xclient.data.l[2] = atom2;
14823 	// [2] == a second property
14824 	// [3] == source. 0 == unknown, 1 == app, 2 == else
14825 
14826 	XSendEvent(
14827 		display,
14828 		RootWindow(display, DefaultScreen(display)),
14829 		false,
14830 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
14831 		cast(XEvent*) &xclient
14832 	);
14833 
14834 	/+
14835 	XChangeProperty(
14836 		display,
14837 		window.impl.window,
14838 		GetAtom!"_NET_WM_STATE"(display),
14839 		XA_ATOM,
14840 		32 /* bits */,
14841 		PropModeAppend,
14842 		&atom,
14843 		1);
14844 	+/
14845 }
14846 
14847 /// X-specific
14848 TrueColorImage getWindowNetWmIcon(Window window) {
14849 	try {
14850 		auto display = XDisplayConnection.get;
14851 
14852 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
14853 
14854 		if (data.length > arch_ulong.sizeof * 2) {
14855 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
14856 			// these are an array of rgba images that we have to convert into pixmaps ourself
14857 
14858 			int width = cast(int) meta[0];
14859 			int height = cast(int) meta[1];
14860 
14861 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
14862 
14863 			static if(arch_ulong.sizeof == 4) {
14864 				bytes = bytes[0 .. width * height * 4];
14865 				alias imageData = bytes;
14866 			} else static if(arch_ulong.sizeof == 8) {
14867 				bytes = bytes[0 .. width * height * 8];
14868 				auto imageData = new ubyte[](4 * width * height);
14869 			} else static assert(0);
14870 
14871 
14872 
14873 			// this returns ARGB. Remember it is little-endian so
14874 			//                                         we have BGRA
14875 			// our thing uses RGBA, which in little endian, is ABGR
14876 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
14877 				auto r = bytes[idx + 2];
14878 				auto g = bytes[idx + 1];
14879 				auto b = bytes[idx + 0];
14880 				auto a = bytes[idx + 3];
14881 
14882 				imageData[idx2 + 0] = r;
14883 				imageData[idx2 + 1] = g;
14884 				imageData[idx2 + 2] = b;
14885 				imageData[idx2 + 3] = a;
14886 			}
14887 
14888 			return new TrueColorImage(width, height, imageData);
14889 		}
14890 
14891 		return null;
14892 	} catch(Exception e) {
14893 		return null;
14894 	}
14895 }
14896 
14897 } /* UsingSimpledisplayX11 */
14898 
14899 
14900 void loadBinNameToWindowClassName () {
14901 	import core.stdc.stdlib : realloc;
14902 	version(linux) {
14903 		// args[0] MAY be empty, so we'll just use this
14904 		import core.sys.posix.unistd : readlink;
14905 		char[1024] ebuf = void; // 1KB should be enough for everyone!
14906 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
14907 		if (len < 1) return;
14908 	} else /*version(Windows)*/ {
14909 		import core.runtime : Runtime;
14910 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
14911 		auto ebuf = Runtime.args[0];
14912 		auto len = ebuf.length;
14913 	}
14914 	auto pos = len;
14915 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
14916 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
14917 	if (sdpyWindowClassStr is null) return; // oops
14918 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
14919 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
14920 }
14921 
14922 /++
14923 	An interface representing a font.
14924 
14925 	This is still MAJOR work in progress.
14926 +/
14927 interface DrawableFont {
14928 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
14929 }
14930 
14931 /++
14932 	Loads a true type font using [arsd.ttf]. That module must be compiled
14933 	in if you choose to use this function.
14934 
14935 	Be warned: this can be slow and memory hungry, especially on remote connections
14936 	to the X server.
14937 
14938 	This is still MAJOR work in progress.
14939 +/
14940 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
14941 	import arsd.ttf;
14942 	static class ArsdTtfFont : DrawableFont {
14943 		TtfFont font;
14944 		int size;
14945 		this(in ubyte[] data, int size) {
14946 			font = TtfFont(data);
14947 			this.size = size;
14948 		}
14949 
14950 		Sprite[string] cache;
14951 
14952 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
14953 			Sprite sprite = (text in cache) ? *(text in cache) : null;
14954 
14955 			auto fg = painter.impl._outlineColor;
14956 			auto bg = painter.impl._fillColor;
14957 
14958 			if(sprite is null) {
14959 				int width, height;
14960 				auto data = font.renderString(text, size, width, height);
14961 				auto image = new TrueColorImage(width, height);
14962 				int pos = 0;
14963 				foreach(y; 0 .. height)
14964 				foreach(x; 0 .. width) {
14965 					fg.a = data[0];
14966 					bg.a = 255;
14967 					auto color = alphaBlend(fg, bg);
14968 					image.imageData.bytes[pos++] = color.r;
14969 					image.imageData.bytes[pos++] = color.g;
14970 					image.imageData.bytes[pos++] = color.b;
14971 					image.imageData.bytes[pos++] = data[0];
14972 					data = data[1 .. $];
14973 				}
14974 				assert(data.length == 0);
14975 
14976 				sprite = new Sprite(painter.window, Image.fromMemoryImage(image));
14977 				cache[text.idup] = sprite;
14978 			}
14979 
14980 			sprite.drawAt(painter, upperLeft);
14981 		}
14982 	}
14983 
14984 	return new ArsdTtfFont(data, size);
14985 }
14986 
14987 class NotYetImplementedException : Exception {
14988 	this(string file = __FILE__, size_t line = __LINE__) {
14989 		super("Not yet implemented", file, line);
14990 	}
14991 }
14992 
14993 ///
14994 __gshared bool librariesSuccessfullyLoaded = true;
14995 ///
14996 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
14997 
14998 private mixin template DynamicLoad(Iface, string library, bool openGLRelated = false) {
14999         static foreach(name; __traits(derivedMembers, Iface))
15000                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
15001 
15002         private void* libHandle;
15003 
15004         void loadDynamicLibrary() {
15005                 version(Posix) {
15006                         import core.sys.posix.dlfcn;
15007 			version(OSX) {
15008 				version(X11)
15009                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
15010 				else
15011                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
15012 			} else
15013                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
15014 
15015 			static void* loadsym(void* l, const char* name) {
15016 				import core.stdc.stdlib;
15017 				if(l is null)
15018 					return &abort;
15019 				return dlsym(l, name);
15020 			}
15021                 } else version(Windows) {
15022                         import core.sys.windows.windows;
15023                         libHandle = LoadLibrary(library ~ ".dll");
15024 			static void* loadsym(void* l, const char* name) {
15025 				import core.stdc.stdlib;
15026 				if(l is null)
15027 					return &abort;
15028 				return GetProcAddress(l, name);
15029 			}
15030                 }
15031                 if(libHandle is null) {
15032 			if(openGLRelated)
15033 				openGlLibrariesSuccessfullyLoaded = false;
15034 			else
15035 				librariesSuccessfullyLoaded = false;
15036                         //throw new Exception("load failure of library " ~ library);
15037 		}
15038                 foreach(name; __traits(derivedMembers, Iface)) {
15039                         mixin("alias tmp = " ~ name ~ ";");
15040                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
15041                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
15042                 }
15043         }
15044 
15045         void unloadDynamicLibrary() {
15046                 version(Posix) {
15047                         import core.sys.posix.dlfcn;
15048                         dlclose(libHandle);
15049                 } else version(Windows) {
15050                         import core.sys.windows.windows;
15051                         FreeLibrary(libHandle);
15052                 }
15053                 foreach(name; __traits(derivedMembers, Iface))
15054                         mixin(name ~ " = null;");
15055         }
15056 }
15057 
15058 private alias scriptable = arsd_jsvar_compatible;