1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 /*
4 	Event Loop would be nices:
5 
6 	* add on idle - runs when nothing else happens
7 	* send messages without a recipient window
8 	* setTimeout
9 	* setInterval
10 
11 */
12 
13 /*
14 	Text layout needs a lot of work. Plain drawText is useful but too
15 	limited. It will need some kind of text context thing which it will
16 	update and you can pass it on and get more details out of it.
17 
18 	It will need a bounding box, a current cursor location that is updated
19 	as drawing continues, and various changable facts (which can also be
20 	changed on the painter i guess) like font, color, size, background,
21 	etc.
22 
23 	We can also fetch the caret location from it somehow.
24 
25 	Should prolly be an overload of drawText
26 
27 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
28 
29 		WS_EX_NOACTIVATE
30 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
31 		full screen windows. Can just set the atom on X. Windows will be harder.
32 
33 		moving windows. resizing windows.
34 
35 		hide cursor, capture cursor, change cursor.
36 
37 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
38 	sure the pieces are there to do its job easily and make other jobs possible.
39 */
40 
41 /++
42 	simpledisplay.d provides basic cross-platform GUI-related functionality,
43 	including creating windows, drawing on them, working with the clipboard,
44 	timers, OpenGL, and more. However, it does NOT provide high level GUI
45 	widgets. See my minigui.d, an extension to this module, for that
46 	functionality.
47 
48 	simpledisplay provides cross-platform wrapping for Windows and Linux
49 	(and perhaps other OSes that use X11), but also does not prevent you
50 	from using the underlying facilities if you need them. It has a goal
51 	of working efficiently over a remote X link (at least as far as Xlib
52 	reasonably allows.)
53 
54 	simpledisplay depends on [arsd.color|color.d], which should be available from the
55 	same place where you got this file. Other than that, however, it has
56 	very few dependencies and ones that don't come with the OS and/or the
57 	compiler are all opt-in.
58 
59 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
60 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
61 
62 	simpledisplay is basically stable. I plan to refactor the internals,
63 	and may add new features and fix bugs, but It do not expect to
64 	significantly change the API. It has been stable a few years already now.
65 
66 	Installation_instructions:
67 
68 	`simpledisplay.d` does not have any dependencies outside the
69 	operating system and `color.d`, so it should just work most the
70 	time, but there are a few caveats on some systems:
71 
72 	Please note when compiling on Win64, you need to explicitly list
73 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
74 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
75 
76 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
77 	console to be automatically allocated.
78 
79 	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.
80 
81 	On Ubuntu, you might need to install X11 development libraries to
82 	successfully link.
83 
84 	$(CONSOLE
85 		$ sudo apt-get install libglc-dev
86 		$ sudo apt-get install libx11-dev
87 	)
88 
89 
90 	Jump_list:
91 
92 	Don't worry, you don't have to read this whole documentation file!
93 
94 	Check out the [#Event-example] and [#Pong-example] to get started quickly.
95 
96 	The main classes you may want to create are [SimpleWindow], [Timer],
97 	[Image], and [Sprite].
98 
99 	The main functions you'll want are [setClipboardText] and [getClipboardText].
100 
101 	There are also platform-specific functions available such as [XDisplayConnection]
102 	and [GetAtom] for X11, among others.
103 
104 	See the examples and topics list below to learn more.
105 
106 
107 	$(H2 About this documentation)
108 
109 	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.
110 
111 	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!
112 
113 	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.
114 
115 	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.
116 
117 	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.
118 
119 	At points, I will talk about implementation details in the documentation. These are sometimes
120 	subject to change, but nevertheless useful to understand what is really going on. You can learn
121 	more about some of the referenced things by searching the web for info about using them from C.
122 	You can always look at the source of simpledisplay.d too for the most authoritative source on
123 	its specific implementation. If you disagree with how I did something, please contact me so we
124 	can discuss it!
125 
126 	Examples:
127 
128 	$(H3 Event-example)
129 	This program creates a window and draws events inside them as they
130 	happen, scrolling the text in the window as needed. Run this program
131 	and experiment to get a feel for where basic input events take place
132 	in the library.
133 
134 	---
135 	// dmd example.d simpledisplay.d color.d
136 	import arsd.simpledisplay;
137 	import std.conv;
138 
139 	void main() {
140 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
141 
142 		int y = 0;
143 
144 		void addLine(string text) {
145 			auto painter = window.draw();
146 
147 			if(y + painter.fontHeight >= window.height) {
148 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
149 				y -= painter.fontHeight;
150 			}
151 
152 			painter.outlineColor = Color.red;
153 			painter.fillColor = Color.black;
154 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
155 
156 			painter.outlineColor = Color.white;
157 
158 			painter.drawText(Point(10, y), text);
159 
160 			y += painter.fontHeight;
161 		}
162 
163 		window.eventLoop(1000,
164 		  () {
165 			addLine("Timer went off!");
166 		  },
167 		  (KeyEvent event) {
168 			addLine(to!string(event));
169 		  },
170 		  (MouseEvent event) {
171 			addLine(to!string(event));
172 		  },
173 		  (dchar ch) {
174 			addLine(to!string(ch));
175 		  }
176 		);
177 	}
178 	---
179 
180 	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.
181 
182 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
183 
184 	---
185 
186 	---
187 
188 	$(H2 Topics)
189 
190 	$(H3 $(ID topic-windows) Windows)
191 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
192 		window on the user's screen.
193 
194 		You may create multiple windows, if the underlying platform supports it. You may check
195 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
196 		SimpleWindow's constructor at runtime to handle those cases.
197 
198 		A single running event loop will handle as many windows as needed.
199 
200 		setEventHandlers function
201 		eventLoop function
202 		draw function
203 		title property
204 
205 	$(H3 $(ID topic-event-loops) Event loops)
206 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
207 
208 		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:
209 
210 		---
211 		// dmd example.d simpledisplay.d color.d
212 		import arsd.simpledisplay;
213 		void main() {
214 			auto window = new SimpleWindow(200, 200);
215 			window.eventLoop(0,
216 			  delegate (dchar) { /* got a character key press */ }
217 			);
218 		}
219 		---
220 
221 		$(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.)
222 
223 		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.
224 
225 		On Linux, simpledisplay also supports my [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
226 
227 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
228 
229 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
230 		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.
231 
232 	$(H3 $(ID topic-input-handling) Input handling)
233 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
234 
235 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
236 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
237 
238 		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:
239 
240 		---
241 		// dmd example.d simpledisplay.d color.d
242 		import arsd.simpledisplay;
243 		void main() {
244 			auto window = new SimpleWindow(200, 200);
245 			{ // introduce sub-scope
246 				auto painter = window.draw(); // begin drawing
247 				/* draw here */
248 				painter.outlineColor = Color.red;
249 				painter.fillColor = Color.black;
250 				painter.drawRectangle(Point(0, 0), 200, 200);
251 			} // end scope, calling `painter`'s destructor, drawing to the screen.
252 			window.eventLoop(0); // handle events
253 		}
254 		---
255 
256 		Painting is done based on two color properties, a pen and a brush.
257 
258 		At this time, the 2d drawing does not support alpha blending. If you need that, use a 2d OpenGL context instead.
259 		FIXME add example of 2d opengl drawing here
260 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
261 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
262 
263 		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.
264 
265 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
266 
267 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
268 
269 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlSceneNow()].
270 
271 		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.
272 
273 		This example program will draw a rectangle on your window:
274 
275 		---
276 		// dmd example.d simpledisplay.d color.d
277 		import arsd.simpledisplay;
278 
279 		void main() {
280 
281 		}
282 		---
283 
284 	$(H3 $(ID topic-images) Displaying images)
285 		You can also load PNG images using [arsd.png].
286 
287 		---
288 		// dmd example.d simpledisplay.d color.d png.d
289 		import arsd.simpledisplay;
290 		import arsd.png;
291 
292 		void main() {
293 			auto image = Image.fromMemoryImage(readPng("image.png"));
294 			displayImage(image);
295 		}
296 		---
297 
298 		Compile with `dmd example.d simpledisplay.d png.d`.
299 
300 		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.
301 
302 	$(H3 $(ID topic-sprites) Sprites)
303 		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.
304 
305 	$(H3 $(ID topic-clipboard) Clipboard)
306 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
307 
308 		It also has helpers for handling X-specific events.
309 
310 	$(H3 $(ID topic-timers) Timers)
311 		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].
312 
313 		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.
314 
315 		---
316 			import arsd.simpledisplay;
317 
318 			void main() {
319 				auto window = new SimpleWindow(400, 400);
320 				// every 100 ms, it will draw a random line
321 				// on the window.
322 				window.eventLoop(100, {
323 					auto painter = window.draw();
324 
325 					import std.random;
326 					// random color
327 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
328 					// random line
329 					painter.drawLine(
330 						Point(uniform(0, window.width), uniform(0, window.height)),
331 						Point(uniform(0, window.width), uniform(0, window.height)));
332 
333 				});
334 			}
335 		---
336 
337 		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.
338 
339 		The pulse timer and instances of the [Timer] class may be combined at will.
340 
341 		---
342 			import arsd.simpledisplay;
343 
344 			void main() {
345 				auto window = new SimpleWindow(400, 400);
346 				auto timer = new Timer(1000, delegate {
347 					auto painter = window.draw();
348 					painter.clear();
349 				});
350 
351 				window.eventLoop(0);
352 			}
353 		---
354 
355 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
356 
357 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
358 		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.
359 
360 		See also: `xwindows.d` from my github.
361 
362 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
363 		`handleNativeEvent` and `handleNativeGlobalEvent`.
364 
365 	$(H3 $(ID topic-integration) Integration with other libraries)
366 		Integration with a third-party event loop is possible.
367 
368 		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.
369 
370 	$(H3 $(ID topic-guis) GUI widgets)
371 		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!
372 
373 		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.
374 
375 		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.)
376 
377 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
378 
379 	$(H2 Platform-specific tips and tricks)
380 
381 	Windows_tips:
382 
383 	You can add icons or manifest files to your exe using a resource file.
384 
385 	To create a Windows .ico file, use the gimp or something. I'll write a helper
386 	program later.
387 
388 	Create `yourapp.rc`:
389 
390 	```rc
391 		1 ICON filename.ico
392 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
393 	```
394 
395 	And `yourapp.exe.manifest`:
396 
397 	```xml
398 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
399 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
400 		<assemblyIdentity
401 		    version="1.0.0.0"
402 		    processorArchitecture="*"
403 		    name="CompanyName.ProductName.YourApplication"
404 		    type="win32"
405 		/>
406 		<description>Your application description here.</description>
407 		<dependency>
408 		    <dependentAssembly>
409 			<assemblyIdentity
410 			    type="win32"
411 			    name="Microsoft.Windows.Common-Controls"
412 			    version="6.0.0.0"
413 			    processorArchitecture="*"
414 			    publicKeyToken="6595b64144ccf1df"
415 			    language="*"
416 			/>
417 		    </dependentAssembly>
418 		</dependency>
419 		</assembly>
420 	```
421 
422 
423 	$(H2 $(ID developer-notes) Developer notes)
424 
425 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
426 	implementation though.
427 
428 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
429 	suck. If I was rewriting it, I wouldn't do it that way again.
430 
431 	This file must not have any more required dependencies. If you need bindings, add
432 	them right to this file. Once it gets into druntime and is there for a while, remove
433 	bindings from here to avoid conflicts (or put them in an appropriate version block
434 	so it continues to just work on old dmd), but wait a couple releases before making the
435 	transition so this module remains usable with older versions of dmd.
436 
437 	You may have optional dependencies if needed by putting them in version blocks or
438 	template functions. You may also extend the module with other modules with UFCS without
439 	actually editing this - that is nice to do if you can.
440 
441 	Try to make functions work the same way across operating systems. I typically make
442 	it thinly wrap Windows, then emulate that on Linux.
443 
444 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
445 	Phobos! So try to avoid it.
446 
447 	See more comments throughout the source.
448 
449 	I realize this file is fairly large, but over half that is just bindings at the bottom
450 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
451 	to understand. I suggest you jump around the source by looking for a particular
452 	declaration you're interested in, like `class SimpleWindow` using your editor's search
453 	function, then look at one piece at a time.
454 
455 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
456 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode. I go by
457 	Destructionator or adam_d_ruppe, depending on which computer I'm logged into.
458 
459 	I live in the eastern United States, so I will most likely not be around at night in
460 	that US east timezone.
461 
462 	License: Copyright Adam D. Ruppe, 2011-2017. Released under the Boost Software License.
463 
464 	Building documentation: You may wish to use the `arsd.ddoc` file from my github with
465 	building the documentation for simpledisplay yourself. It will give it a bit more style.
466 	Simply download the arsd.ddoc file and add it to your compile command when building docs.
467 	`dmd -c simpledisplay.d color.d -D arsd.ddoc`
468 +/
469 module arsd.simpledisplay;
470 
471 // FIXME: tetris demo
472 // FIXME: space invaders demo
473 // FIXME: asteroids demo
474 
475 /++ $(ID Pong-example)
476 	$(H3 Pong)
477 
478 	This program creates a little Pong-like game. Player one is controlled
479 	with the keyboard.  Player two is controlled with the mouse. It demos
480 	the pulse timer, event handling, and some basic drawing.
481 +/
482 unittest {
483 	// dmd example.d simpledisplay.d color.d
484 	import arsd.simpledisplay;
485 
486 	enum paddleMovementSpeed = 8;
487 	enum paddleHeight = 48;
488 
489 	void main() {
490 		auto window = new SimpleWindow(600, 400, "Pong game!");
491 
492 		int playerOnePosition, playerTwoPosition;
493 		int playerOneMovement, playerTwoMovement;
494 		int playerOneScore, playerTwoScore;
495 
496 		int ballX, ballY;
497 		int ballDx, ballDy;
498 
499 		void serve() {
500 			import std.random;
501 
502 			ballX = window.width / 2;
503 			ballY = window.height / 2;
504 			ballDx = uniform(-4, 4) * 3;
505 			ballDy = uniform(-4, 4) * 3;
506 			if(ballDx == 0)
507 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
508 		}
509 
510 		serve();
511 
512 		window.eventLoop(50, // set a 50 ms timer pulls
513 			// This runs once per timer pulse
514 			delegate () {
515 				auto painter = window.draw();
516 
517 				painter.clear();
518 
519 				// Update everyone's motion
520 				playerOnePosition += playerOneMovement;
521 				playerTwoPosition += playerTwoMovement;
522 
523 				ballX += ballDx;
524 				ballY += ballDy;
525 
526 				// Bounce off the top and bottom edges of the window
527 				if(ballY + 7 >= window.height)
528 					ballDy = -ballDy;
529 				if(ballY - 8 <= 0)
530 					ballDy = -ballDy;
531 
532 				// Bounce off the paddle, if it is in position
533 				if(ballX - 8 <= 16) {
534 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
535 						ballDx = -ballDx + 1; // add some speed to keep it interesting
536 						ballDy += playerOneMovement; // and y movement based on your controls too
537 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
538 					} else {
539 						// Missed it
540 						playerTwoScore ++;
541 						serve();
542 					}
543 				}
544 
545 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
546 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
547 						ballDx = -ballDx - 1;
548 						ballDy += playerTwoMovement;
549 						ballX = window.width - 24;
550 					} else {
551 						// Missed it
552 						playerOneScore ++;
553 						serve();
554 					}
555 				}
556 
557 				// Draw the paddles
558 				painter.outlineColor = Color.black;
559 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
560 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
561 
562 				// Draw the ball
563 				painter.fillColor = Color.red;
564 				painter.outlineColor = Color.yellow;
565 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
566 
567 				// Draw the score
568 				painter.outlineColor = Color.blue;
569 				import std.conv;
570 				painter.drawText(Point(64, 4), to!string(playerOneScore));
571 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
572 
573 			},
574 			delegate (KeyEvent event) {
575 				// Player 1's controls are the arrow keys on the keyboard
576 				if(event.key == Key.Down)
577 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
578 				if(event.key == Key.Up)
579 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
580 
581 			},
582 			delegate (MouseEvent event) {
583 				// Player 2's controls are mouse movement while the left button is held down
584 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
585 					if(event.dy > 0)
586 						playerTwoMovement = paddleMovementSpeed;
587 					else if(event.dy < 0)
588 						playerTwoMovement = -paddleMovementSpeed;
589 				} else {
590 					playerTwoMovement = 0;
591 				}
592 			}
593 		);
594 	}
595 }
596 
597 /++ $(ID example-minesweeper)
598 
599 	This minesweeper demo shows how we can implement another classic
600 	game with simpledisplay and shows some mouse input and basic output
601 	code.
602 +/
603 unittest {
604 	import arsd.simpledisplay;
605 
606 	enum GameSquare {
607 		mine = 0,
608 		clear,
609 		m1, m2, m3, m4, m5, m6, m7, m8
610 	}
611 
612 	enum UserSquare {
613 		unknown,
614 		revealed,
615 		flagged,
616 		questioned
617 	}
618 
619 	enum GameState {
620 		inProgress,
621 		lose,
622 		win
623 	}
624 
625 	GameSquare[] board;
626 	UserSquare[] userState;
627 	GameState gameState;
628 	int boardWidth;
629 	int boardHeight;
630 
631 	bool isMine(int x, int y) {
632 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
633 			return false;
634 		return board[y * boardWidth + x] == GameSquare.mine;
635 	}
636 
637 	GameState reveal(int x, int y) {
638 		if(board[y * boardWidth + x] == GameSquare.clear) {
639 			floodFill(userState, boardWidth, boardHeight,
640 				UserSquare.unknown, UserSquare.revealed,
641 				x, y,
642 				(x, y) {
643 					if(board[y * boardWidth + x] == GameSquare.clear)
644 						return true;
645 					else {
646 						userState[y * boardWidth + x] = UserSquare.revealed;
647 						return false;
648 					}
649 				});
650 		} else {
651 			userState[y * boardWidth + x] = UserSquare.revealed;
652 			if(isMine(x, y))
653 				return GameState.lose;
654 		}
655 
656 		foreach(state; userState) {
657 			if(state == UserSquare.unknown || state == UserSquare.questioned)
658 				return GameState.inProgress;
659 		}
660 
661 		return GameState.win;
662 	}
663 
664 	void initializeBoard(int width, int height, int numberOfMines) {
665 		boardWidth = width;
666 		boardHeight = height;
667 		board.length = width * height;
668 
669 		userState.length = width * height;
670 		userState[] = UserSquare.unknown; 
671 
672 		import std.algorithm, std.random, std.range;
673 
674 		board[] = GameSquare.clear;
675 
676 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
677 			board[minePosition] = GameSquare.mine;
678 
679 		int x;
680 		int y;
681 		foreach(idx, ref square; board) {
682 			if(square == GameSquare.clear) {
683 				int danger = 0;
684 				danger += isMine(x-1, y-1)?1:0;
685 				danger += isMine(x-1, y)?1:0;
686 				danger += isMine(x-1, y+1)?1:0;
687 				danger += isMine(x, y-1)?1:0;
688 				danger += isMine(x, y+1)?1:0;
689 				danger += isMine(x+1, y-1)?1:0;
690 				danger += isMine(x+1, y)?1:0;
691 				danger += isMine(x+1, y+1)?1:0;
692 
693 				square = cast(GameSquare) (danger + 1);
694 			}
695 
696 			x++;
697 			if(x == width) {
698 				x = 0;
699 				y++;
700 			}
701 		}
702 	}
703 
704 	void redraw(SimpleWindow window) {
705 		import std.conv;
706 
707 		auto painter = window.draw();
708 
709 		painter.clear();
710 
711 		final switch(gameState) with(GameState) {
712 			case inProgress:
713 				break;
714 			case win:
715 				painter.fillColor = Color.green;
716 				painter.drawRectangle(Point(0, 0), window.width, window.height);
717 				return;
718 			case lose:
719 				painter.fillColor = Color.red;
720 				painter.drawRectangle(Point(0, 0), window.width, window.height);
721 				return;
722 		}
723 
724 		int x = 0;
725 		int y = 0;
726 
727 		foreach(idx, square; board) {
728 			auto state = userState[idx];
729 
730 			final switch(state) with(UserSquare) {
731 				case unknown:
732 					painter.outlineColor = Color.black;
733 					painter.fillColor = Color(128,128,128);
734 
735 					painter.drawRectangle(
736 						Point(x * 20, y * 20),
737 						20, 20
738 					);
739 				break;
740 				case revealed:
741 					if(square == GameSquare.clear) {
742 						painter.outlineColor = Color.white;
743 						painter.fillColor = Color.white;
744 
745 						painter.drawRectangle(
746 							Point(x * 20, y * 20),
747 							20, 20
748 						);
749 					} else {
750 						painter.outlineColor = Color.black;
751 						painter.fillColor = Color.white;
752 
753 						painter.drawText(
754 							Point(x * 20, y * 20),
755 							to!string(square)[1..2],
756 							Point(x * 20 + 20, y * 20 + 20),
757 							TextAlignment.Center | TextAlignment.VerticalCenter);
758 					}
759 				break;
760 				case flagged:
761 					painter.outlineColor = Color.black;
762 					painter.fillColor = Color.red;
763 					painter.drawRectangle(
764 						Point(x * 20, y * 20),
765 						20, 20
766 					);
767 				break;
768 				case questioned:
769 					painter.outlineColor = Color.black;
770 					painter.fillColor = Color.yellow;
771 					painter.drawRectangle(
772 						Point(x * 20, y * 20),
773 						20, 20
774 					);
775 				break;
776 			}
777 
778 			x++;
779 			if(x == boardWidth) {
780 				x = 0;
781 				y++;
782 			}
783 		}
784 
785 	}
786 
787 	void main() {
788 		auto window = new SimpleWindow(200, 200);
789 
790 		initializeBoard(10, 10, 10);
791 
792 		redraw(window);
793 		window.eventLoop(0,
794 			delegate (MouseEvent me) {
795 				if(me.type != MouseEventType.buttonPressed)
796 					return;
797 				auto x = me.x / 20;
798 				auto y = me.y / 20;
799 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
800 					if(me.button == MouseButton.left) {
801 						gameState = reveal(x, y);
802 					} else {
803 						userState[y*boardWidth+x] = UserSquare.flagged;
804 					}
805 					redraw(window);
806 				}
807 			}
808 		);
809 	}
810 }
811 
812 version(without_opengl) {
813 	enum SdpyIsUsingIVGLBinds = false;
814 } else /*version(Posix)*/ {
815 	static if (__traits(compiles, (){import iv.glbinds;})) {
816 		enum SdpyIsUsingIVGLBinds = true;
817 		public import iv.glbinds;
818 		//pragma(msg, "SDPY: using iv.glbinds");
819 	} else {
820 		enum SdpyIsUsingIVGLBinds = false;
821 	}
822 //} else {
823 //	enum SdpyIsUsingIVGLBinds = false;
824 }
825 
826 
827 version(Windows) {
828 	//import core.sys.windows.windows;
829 	import core.sys.windows.winnls;
830 	import core.sys.windows.windef;
831 	import core.sys.windows.basetyps;
832 	import core.sys.windows.winbase;
833 	import core.sys.windows.winuser;
834 	import core.sys.windows.shellapi;
835 	import core.sys.windows.wingdi;
836 	static import gdi = core.sys.windows.wingdi; // so i
837 
838 	pragma(lib, "gdi32");
839 	pragma(lib, "user32");
840 } else version (linux) {
841 	//k8: this is hack for rdmd. sorry.
842 	static import core.sys.linux.epoll;
843 	static import core.sys.linux.timerfd;
844 }
845 
846 
847 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
848 
849 // http://wiki.dlang.org/Simpledisplay.d
850 
851 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
852 
853 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
854 // but can i control the scroll lock led
855 
856 
857 // Note: if you are using Image on X, you might want to do:
858 /*
859 	static if(UsingSimpledisplayX11) {
860 		if(!Image.impl.xshmAvailable) {
861 			// the images will use the slower XPutImage, you might
862 			// want to consider an alternative method to get better speed
863 		}
864 	}
865 
866 	If the shared memory extension is available though, simpledisplay uses it
867 	for a significant speed boost whenever you draw large Images.
868 */
869 
870 // 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.
871 
872 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
873 
874 /*
875 	Biggest FIXME:
876 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
877 
878 		clean up opengl contexts when their windows close
879 
880 		fix resizing the bitmaps/pixmaps
881 */
882 
883 // BTW on Windows:
884 // -L/SUBSYSTEM:WINDOWS:5.0
885 // to dmd will make a nice windows binary w/o a console if you want that.
886 
887 /*
888 	Stuff to add:
889 
890 	use multibyte functions everywhere we can
891 
892 	OpenGL windows
893 	more event stuff
894 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
895 
896 
897 	resizeEvent
898 		and make the windows non-resizable by default,
899 		or perhaps stretched (if I can find something in X like StretchBlt)
900 
901 	take a screenshot function!
902 
903 	Pens and brushes?
904 	Maybe a global event loop?
905 
906 	Mouse deltas
907 	Key items
908 */
909 
910 /*
911 From MSDN:
912 
913 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
914 
915 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.
916 
917 */
918 
919 version(linux) {
920 	version = X11;
921 	version(without_libnotify) {
922 		// we cool
923 	}
924 	else
925 		version = libnotify;
926 }
927 
928 version(libnotify) {
929 	pragma(lib, "dl");
930 	import core.sys.posix.dlfcn;
931 
932 	void delegate()[int] libnotify_action_delegates;
933 	int libnotify_action_delegates_count;
934 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
935 		auto idx = cast(int) user_data;
936 		if(auto dgptr = idx in libnotify_action_delegates) {
937 			(*dgptr)();
938 			libnotify_action_delegates.remove(idx);
939 		}
940 	}
941 
942 	struct C_DynamicLibrary {
943 		void* handle;
944 		this(string name) {
945 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
946 			if(handle is null)
947 				throw new Exception("dlopen");
948 		}
949 
950 		void close() {
951 			dlclose(handle);
952 		}
953 
954 		~this() {
955 			// close
956 		}
957 
958 		template call(string func, Ret, Args...) {
959 			extern(C) Ret function(Args) fptr;
960 			typeof(fptr) call() {
961 				fptr = cast(typeof(fptr)) dlsym(handle, func);
962 				return fptr;
963 			}
964 		}
965 	}
966 
967 	C_DynamicLibrary* libnotify;
968 }
969 
970 version(OSX) {
971 	version(OSXCocoa) {}
972 	else { version = X11; }
973 }
974 	//version = OSXCocoa; // this was written by KennyTM
975 version(FreeBSD)
976 	version = X11;
977 version(Solaris)
978 	version = X11;
979 
980 
981 void featureNotImplemented()() {
982 	version(allow_unimplemented_features)
983 		throw new NotYetImplementedException();
984 	else
985 		static assert(0);
986 }
987 
988 // these are so the static asserts don't trigger unless you want to
989 // add support to it for an OS
990 version(Windows)
991 	version = with_timer;
992 version(linux)
993 	version = with_timer;
994 
995 /// 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.
996 version(Windows)
997 	enum bool UsingSimpledisplayWindows = true;
998 else
999 	enum bool UsingSimpledisplayWindows = false;
1000 
1001 /// 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.
1002 version(X11)
1003 	enum bool UsingSimpledisplayX11 = true;
1004 else
1005 	enum bool UsingSimpledisplayX11 = false;
1006 
1007 /// 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.
1008 version(OSXCocoa)
1009 	enum bool UsingSimpledisplayCocoa = true;
1010 else
1011 	enum bool UsingSimpledisplayCocoa = false;
1012 
1013 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1014 version(Windows)
1015 	enum multipleWindowsSupported = true;
1016 else version(X11)
1017 	enum multipleWindowsSupported = true;
1018 else version(OSXCocoa)
1019 	enum multipleWindowsSupported = true;
1020 else
1021 	static assert(0);
1022 
1023 version(without_opengl)
1024 	enum bool OpenGlEnabled = false;
1025 else
1026 	enum bool OpenGlEnabled = true;
1027 
1028 
1029 /++
1030 	After selecting a type from [WindowTypes], you may further customize
1031 	its behavior by setting one or more of these flags.
1032 
1033 
1034 	The different window types have different meanings of `normal`. If the
1035 	window type already is a good match for what you want to do, you should
1036 	just use [WindowFlags.normal], the default, which will do the right thing
1037 	for your users.
1038 
1039 	The window flags will not always be honored by the operating system
1040 	and window managers; they are hints, not commands.
1041 +/
1042 enum WindowFlags : int {
1043 	normal = 0, ///
1044 	skipTaskbar = 1, ///
1045 	alwaysOnTop = 2, ///
1046 	alwaysOnBottom = 4, ///
1047 	cannotBeActivated = 8, ///
1048 	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.
1049 	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.
1050 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1051 }
1052 
1053 /++
1054 	When creating a window, you can pass a type to SimpleWindow's constructor,
1055 	then further customize the window by changing `WindowFlags`.
1056 
1057 
1058 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1059 	use. The others are there to build a foundation for a higher level GUI toolkit,
1060 	but are themselves not as high level as you might think from their names.
1061 
1062 	This list is based on the EMWH spec for X11.
1063 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1064 +/
1065 enum WindowTypes : int {
1066 	/// An ordinary application window.
1067 	normal,
1068 	/// 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.
1069 	undecorated,
1070 	/// 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.
1071 	eventOnly,
1072 	/// A drop down menu, such as from a menu bar
1073 	dropdownMenu,
1074 	/// A popup menu, such as from a right click
1075 	popupMenu,
1076 	/// A popup bubble notification
1077 	notification,
1078 	/*
1079 	menu, /// a tearable menu bar
1080 	splashScreen, /// a loading splash screen for your application
1081 	tooltip, /// A tiny window showing temporary help text or something.
1082 	comboBoxDropdown,
1083 	dialog,
1084 	toolbar
1085 	*/
1086 	/// a child nested inside the parent. You must pass a parent window to the ctor
1087 	nestedChild,
1088 }
1089 
1090 
1091 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1092 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1093 private __gshared char* sdpyWindowClassStr = null;
1094 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1095 
1096 /**
1097 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1098 	You may want to change context version if you want to use advanced shaders or
1099 	other modern OpenGL techinques. This setting doesn't affect already created
1100 	windows. You may use version 2.1 as your default, which should be supported
1101 	by any box since 2006, so seems to be a reasonable choice.
1102 
1103 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1104 	old context creation code without any version specified. This is the safest
1105 	way to init OpenGL, but it may not give you access to advanced features.
1106 
1107 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1108 */
1109 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1110 
1111 /**
1112 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1113 	pipeline functions, and without "compatible" mode you won't be able to use
1114 	your old non-shader-based code with such contexts. By default SimpleDisplay
1115 	creates compatible context, so you can gradually upgrade your OpenGL code if
1116 	you want to (or leave it as is, as it should "just work").
1117 */
1118 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1119 
1120 /**
1121 	Set to `true` to allow creating OpenGL context with lower version than requested
1122 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1123 	`openGLContextFallbackActivated()` will return `true`.
1124 	*/
1125 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1126 
1127 /**
1128 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1129 	*/
1130 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1131 
1132 
1133 /**
1134 	Set window class name for all following `new SimpleWindow()` calls.
1135 
1136 	WARNING! For Windows, you should set your class name before creating any
1137 	window, and NEVER change it after that!
1138 */
1139 void sdpyWindowClass (const(char)[] v) {
1140 	import core.stdc.stdlib : realloc;
1141 	if (v.length == 0) v = "SimpleDisplayWindow";
1142 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1143 	if (sdpyWindowClassStr is null) return; // oops
1144 	sdpyWindowClassStr[0..v.length+1] = 0;
1145 	sdpyWindowClassStr[0..v.length] = v[];
1146 }
1147 
1148 /**
1149 	Get current window class name.
1150 */
1151 string sdpyWindowClass () {
1152 	if (sdpyWindowClassStr is null) return null;
1153 	foreach (immutable idx; 0..size_t.max-1) {
1154 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1155 	}
1156 	return null;
1157 }
1158 
1159 /++
1160 	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.
1161 +/
1162 float[2] getDpi() {
1163 	float[2] dpi;
1164 	version(Windows) {
1165 		HDC screen = GetDC(null);
1166 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1167 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1168 	} else version(X11) {
1169 		auto display = XDisplayConnection.get;
1170 		auto screen = DefaultScreen(display);
1171 
1172 		void fallback() {
1173 			// 25.4 millimeters in an inch...
1174 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1175 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1176 		}
1177 
1178 		char* resourceString = XResourceManagerString(display);
1179 		XrmInitialize();
1180 
1181 		auto db = XrmGetStringDatabase(resourceString);
1182 
1183 		if (resourceString) {
1184 			XrmValue value;
1185 			char* type;
1186 			if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1187 				if (value.addr) {
1188 					import core.stdc.stdlib;
1189 					dpi[0] = atof(cast(char*) value.addr);
1190 					dpi[1] = dpi[0];
1191 				} else {
1192 					fallback();
1193 				}
1194 			} else {
1195 				fallback();
1196 			}
1197 		} else {
1198 			fallback();
1199 		}
1200 	}
1201 
1202 	return dpi;
1203 }
1204 
1205 version(X11) {
1206 	extern(C) char* XResourceManagerString(Display*);
1207 	extern(C) void XrmInitialize();
1208 	extern(C) XrmDatabase XrmGetStringDatabase(char* data);
1209 	extern(C) bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
1210 	alias XrmDatabase = void*;
1211 	struct XrmValue {
1212 		uint size;
1213 		void* addr;
1214 	}
1215 }
1216 
1217 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width, int height) {
1218 	throw new Exception("not implemented");
1219 	version(none) {
1220 	version(X11) {
1221 		auto display = XDisplayConnection.get;
1222 		auto image = XGetImage(display, handle, 0, 0, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ZPixmap);
1223 
1224 		// https://github.com/adamdruppe/arsd/issues/98
1225 
1226 		// FIXME: copy that shit
1227 
1228 		XDestroyImage(image);
1229 	} else version(Windows) {
1230 		// I just need to BitBlt that shit... BUT WAIT IT IS ALREADY IN A DIB!!!!!!!
1231 
1232 	} else featureNotImplemented();
1233 
1234 	return null;
1235 	}
1236 }
1237 
1238 /++
1239 	The flagship window class.
1240 
1241 
1242 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1243 	out of more advanced or complex features of the underlying windowing system.
1244 
1245 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1246 	and get a suitable window to work with.
1247 
1248 	From there, you can opt into additional features, like custom resizability and OpenGL support
1249 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1250 	and customization flags with the final two constructor arguments.
1251 
1252 	If none of that works for you, you can also create a window using native function calls, then
1253 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1254 	though, if you do this, managing the window is still your own responsibility! Notably, you
1255 	will need to destroy it yourself.
1256 +/
1257 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1258 
1259 	/// Be warned: this can be a very slow operation
1260 	/// FIXME NOT IMPLEMENTED
1261 	TrueColorImage takeScreenshot() {
1262 		version(Windows)
1263 			return trueColorImageFromNativeHandle(impl.hwnd, width, height);
1264 		else version(OSXCocoa)
1265 			throw new NotYetImplementedException();
1266 		else
1267 			return trueColorImageFromNativeHandle(impl.window, width, height);
1268 	}
1269 
1270 	version(X11) {
1271 		void recreateAfterDisconnect() {
1272 			if(!stateDiscarded) return;
1273 
1274 			if(_parent !is null && _parent.stateDiscarded)
1275 				_parent.recreateAfterDisconnect();
1276 
1277 			bool wasHidden = hidden;
1278 
1279 			activeScreenPainter = null; // should already be done but just to confirm
1280 
1281 			impl.createWindow(_width, _height, _title, openglMode, _parent);
1282 
1283 			if(recreateAdditionalConnectionState)
1284 				recreateAdditionalConnectionState();
1285 
1286 			hidden = wasHidden;
1287 			stateDiscarded = false;
1288 		}
1289 
1290 		bool stateDiscarded;
1291 		void discardConnectionState() {
1292 			if(XDisplayConnection.display)
1293 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
1294 			if(discardAdditionalConnectionState)
1295 				discardAdditionalConnectionState();
1296 			stateDiscarded = true;
1297 		}
1298 
1299 		void delegate() discardAdditionalConnectionState;
1300 		void delegate() recreateAdditionalConnectionState;
1301 	}
1302 
1303 
1304 	SimpleWindow _parent;
1305 	bool beingOpenKeepsAppOpen = true;
1306 	/++
1307 		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.
1308 
1309 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
1310 
1311 		Params:
1312 
1313 		width = the width of the window's client area, in pixels
1314 		height = the height of the window's client area, in pixels
1315 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title\ property.
1316 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
1317 		resizable = [Resizability] has three options:
1318 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
1319 			$(P `fixedSize` will not allow the user to resize the window.)
1320 			$(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.)
1321 		windowType = The type of window you want to make.
1322 		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.
1323 		parent = the parent window, if applicable
1324 	+/
1325 	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) {
1326 		this._width = width;
1327 		this._height = height;
1328 		this.openglMode = opengl;
1329 		this.resizability = resizable;
1330 		this.windowType = windowType;
1331 		this.customizationFlags = customizationFlags;
1332 		this._title = (title is null ? "D Application" : title);
1333 		this._parent = parent;
1334 		impl.createWindow(width, height, this._title, opengl, parent);
1335 
1336 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild)
1337 			beingOpenKeepsAppOpen = false;
1338 	}
1339 
1340 	/// Same as above, except using the `Size` struct instead of separate width and height.
1341 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
1342 		this(size.width, size.height, title, opengl, resizable);
1343 	}
1344 
1345 
1346 	/++
1347 		Creates a window based on the given [Image]. It's client area
1348 		width and height is equal to the image. (A window's client area
1349 		is the drawable space inside; it excludes the title bar, etc.)
1350 
1351 		Windows based on images will not be resizable and do not use OpenGL.
1352 
1353 		It will draw the image in upon creation, but this will be overwritten
1354 		upon any draws, including the initial window visible event.
1355 
1356 		You probably do not want to use this and it may be removed from
1357 		the library eventually, or I might change it to be a "permanent"
1358 		background image; one that is automatically drawn on it before any
1359 		other drawing event. idk.
1360 	+/
1361 	this(Image image, string title = null) {
1362 		this(image.width, image.height, title);
1363 		this.image = image;
1364 	}
1365 
1366 	/++
1367 		Wraps a native window handle with very little additional processing - notably no destruction
1368 		this is incomplete so don't use it for much right now. The purpose of this is to make native
1369 		windows created through the low level API (so you can use platform-specific options and
1370 		other details SimpleWindow does not expose) available to the event loop wrappers.
1371 	+/
1372 	this(NativeWindowHandle nativeWindow) {
1373 		version(Windows)
1374 			impl.hwnd = nativeWindow;
1375 		else version(X11) {
1376 			impl.window = nativeWindow;
1377 			display = XDisplayConnection.get(); // get initial display to not segfault
1378 		} else version(OSXCocoa)
1379 			throw new NotYetImplementedException();
1380 		else featureNotImplemented();
1381 		// FIXME: set the size correctly
1382 		_width = 1;
1383 		_height = 1;
1384 		nativeMapping[nativeWindow] = this;
1385 		CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
1386 		_suppressDestruction = true; // so it doesn't try to close
1387 	}
1388 
1389 	/// Experimental, do not use yet
1390 	/++
1391 		Grabs exclusive input from the user until you release it with
1392 		[releaseInputGrab].
1393 
1394 
1395 		Note: it is extremely rude to do this without good reason.
1396 		Reasons may include doing some kind of mouse drag operation
1397 		or popping up a temporary menu that should get events and will
1398 		be dismissed at ease by the user clicking away.
1399 
1400 		Params:
1401 			keyboard = do you want to grab keyboard input?
1402 			mouse = grab mouse input?
1403 			confine = confine the mouse cursor to inside this window?
1404 	+/
1405 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
1406 		static if(UsingSimpledisplayX11) {
1407 			XSync(XDisplayConnection.get, 0);
1408 			if(keyboard)
1409 				XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
1410 			if(mouse) {
1411 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 
1412 				EventMask.PointerMotionMask // FIXME: not efficient
1413 				| EventMask.ButtonPressMask
1414 				| EventMask.ButtonReleaseMask
1415 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
1416 				)
1417 			{
1418 				XSync(XDisplayConnection.get, 0);
1419 				import core.stdc.stdio;
1420 				printf("Grab input failed %d\n", res);
1421 				//throw new Exception("Grab input failed");
1422 			} else {
1423 				// cool
1424 			}
1425 			}
1426 
1427 		} else version(Windows) {
1428 			// FIXME: keyboard?
1429 			SetCapture(impl.hwnd);
1430 			if(confine) {
1431 				RECT rcClip;
1432 				//RECT rcOldClip;
1433 				//GetClipCursor(&rcOldClip); 
1434 				GetWindowRect(hwnd, &rcClip); 
1435 				ClipCursor(&rcClip); 
1436 			}
1437 		} else version(OSXCocoa) {
1438 			throw new NotYetImplementedException();
1439 		} else static assert(0);
1440 	}
1441 
1442 	/++
1443 		Releases the grab acquired by [grabInput].
1444 	+/
1445 	void releaseInputGrab() {
1446 		static if(UsingSimpledisplayX11) {
1447 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
1448 		} else version(Windows) {
1449 			ReleaseCapture();
1450 			ClipCursor(null); 
1451 		} else version(OSXCocoa) {
1452 			throw new NotYetImplementedException();
1453 		} else static assert(0);
1454 	}
1455 
1456 	/++
1457 		Sets the input focus to this window.
1458 
1459 		You shouldn't call this very often - please let the user control the input focus.
1460 	+/
1461 	void focus() {
1462 		static if(UsingSimpledisplayX11) {
1463 			XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
1464 		} else version(Windows) {
1465 			SetFocus(this.impl.hwnd);
1466 		} else version(OSXCocoa) {
1467 			throw new NotYetImplementedException();
1468 		} else static assert(0);
1469 	}
1470 
1471 	/++
1472 		Requests attention from the user for this window.
1473 
1474 
1475 		The typical result of this function is to change the color
1476 		of the taskbar icon, though it may be tweaked on specific
1477 		platforms.
1478 
1479 		It is meant to unobtrusively tell the user that something
1480 		relevant to them happened in the background and they should
1481 		check the window when they get a chance. Upon receiving the
1482 		keyboard focus, the window will automatically return to its
1483 		natural state.
1484 
1485 		If the window already has the keyboard focus, this function
1486 		may do nothing, because the user is presumed to already be
1487 		giving the window attention.
1488 
1489 		Implementation_note:
1490 
1491 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
1492 		atom on X11 and the FlashWindow function on Windows.
1493 	+/
1494 	void requestAttention() {
1495 		if(_focused)
1496 			return;
1497 
1498 		version(Windows) {
1499 			FLASHWINFO info;
1500 			info.cbSize = info.sizeof;
1501 			info.hwnd = impl.hwnd;
1502 			info.dwFlags = FLASHW_TRAY;
1503 			info.uCount = 1;
1504 
1505 			FlashWindowEx(&info);
1506 
1507 		} else version(X11) {
1508 			demandingAttention = true;
1509 			demandAttention(this, true);
1510 		} else version(OSXCocoa) {
1511 			throw new NotYetImplementedException();
1512 		} else static assert(0);
1513 	}
1514 
1515 	private bool _focused;
1516 
1517 	version(X11) private bool demandingAttention;
1518 
1519 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
1520 	/// You'll have to call `close()` manually if you set this delegate.
1521 	void delegate () closeQuery;
1522 
1523 	/// This will be called when window visibility was changed.
1524 	void delegate (bool becomesVisible) visibilityChanged;
1525 
1526 	/// This will be called when window becomes visible for the first time.
1527 	/// You can do OpenGL initialization here. Note that in X11 you can't call
1528 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
1529 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
1530 	private bool _visibleForTheFirstTimeCalled;
1531 	void delegate () visibleForTheFirstTime;
1532 
1533 	/// Returns true if the window has been closed.
1534 	final @property bool closed() { return _closed; }
1535 
1536 	/// Returns true if the window is focused.
1537 	final @property bool focused() { return _focused; }
1538 
1539 	private bool _visible;
1540 	/// Returns true if the window is visible (mapped).
1541 	final @property bool visible() { return _visible; }
1542 
1543 	/// Closes the window. If there are no more open windows, the event loop will terminate.
1544 	void close() {
1545 		if (!_closed) {
1546 			if (onClosing !is null) onClosing();
1547 			impl.closeWindow();
1548 			_closed = true;
1549 		}
1550 	}
1551 
1552 	/// Alias for `hidden = false`
1553 	void show() {
1554 		hidden = false;
1555 	}
1556 
1557 	/// Alias for `hidden = true`
1558 	void hide() {
1559 		hidden = true;
1560 	}
1561 
1562 	/// Hide cursor when it enters the window.
1563 	void hideCursor() {
1564 		version(OSXCocoa) throw new NotYetImplementedException(); else
1565 		if (!_closed) impl.hideCursor();
1566 	}
1567 
1568 	/// Don't hide cursor when it enters the window.
1569 	void showCursor() {
1570 		version(OSXCocoa) throw new NotYetImplementedException(); else
1571 		if (!_closed) impl.showCursor();
1572 	}
1573 
1574 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
1575 	 *
1576 	 * Currently only supported on X11, so Windows implementation will return `false`.
1577 	 *
1578 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
1579 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
1580 	 *       receive "mouse moved here" event.
1581 	 */
1582 	bool warpMouse (int x, int y) {
1583 		version(X11) {
1584 			if (!_closed) { impl.warpMouse(x, y); return true; }
1585 		}
1586 		return false;
1587 	}
1588 
1589 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
1590 	void sendDummyEvent () {
1591 		version(X11) {
1592 			if (!_closed) { impl.sendDummyEvent(); }
1593 		}
1594 	}
1595 
1596 	/// Set window minimal size.
1597 	void setMinSize (int minwidth, int minheight) {
1598 		version(OSXCocoa) throw new NotYetImplementedException(); else
1599 		if (!_closed) impl.setMinSize(minwidth, minheight);
1600 	}
1601 
1602 	/// Set window maximal size.
1603 	void setMaxSize (int maxwidth, int maxheight) {
1604 		version(OSXCocoa) throw new NotYetImplementedException(); else
1605 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
1606 	}
1607 
1608 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
1609 	/// Currently only supported on X11.
1610 	void setResizeGranularity (int granx, int grany) {
1611 		version(OSXCocoa) throw new NotYetImplementedException(); else
1612 		if (!_closed) impl.setResizeGranularity(granx, grany);
1613 	}
1614 
1615 	/// Move window.
1616 	void move(int x, int y) {
1617 		version(OSXCocoa) throw new NotYetImplementedException(); else
1618 		if (!_closed) impl.move(x, y);
1619 	}
1620 
1621 	/// ditto
1622 	void move(Point p) {
1623 		version(OSXCocoa) throw new NotYetImplementedException(); else
1624 		if (!_closed) impl.move(p.x, p.y);
1625 	}
1626 
1627 	/++
1628 		Resize window.
1629 
1630 		Note that the width and height of the window are NOT instantly
1631 		updated - it waits for the window manager to approve the resize
1632 		request, which means you must return to the event loop before the
1633 		width and height are actually changed.
1634 	+/
1635 	void resize(int w, int h) {
1636 		version(OSXCocoa) throw new NotYetImplementedException(); else
1637 		if (!_closed) impl.resize(w, h);
1638 	}
1639 
1640 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
1641 	void moveResize (int x, int y, int w, int h) {
1642 		version(OSXCocoa) throw new NotYetImplementedException(); else
1643 		if (!_closed) impl.moveResize(x, y, w, h);
1644 	}
1645 
1646 	private bool _hidden;
1647 
1648 	/// Returns true if the window is hidden.
1649 	final @property bool hidden() {
1650 		return _hidden;
1651 	}
1652 
1653 	/// Shows or hides the window based on the bool argument.
1654 	final @property void hidden(bool b) {
1655 		_hidden = b;
1656 		version(Windows) {
1657 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
1658 		} else version(X11) {
1659 			if(b)
1660 				//XUnmapWindow(impl.display, impl.window);
1661 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
1662 			else
1663 				XMapWindow(impl.display, impl.window);
1664 		} else version(OSXCocoa) {
1665 			throw new NotYetImplementedException();
1666 		} else static assert(0);
1667 	}
1668 
1669 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
1670 	void opacity(double opacity) @property
1671 	in {
1672 		assert(opacity >= 0 && opacity <= 1);
1673 	} body {
1674 		version (Windows) {
1675 			impl.setOpacity(cast(ubyte)(255 * opacity));
1676 		} else version (X11) {
1677 			impl.setOpacity(cast(uint)(uint.max * opacity));
1678 		} else throw new NotYetImplementedException();
1679 	}
1680 
1681 	/++
1682 		Sets your event handlers, without entering the event loop. Useful if you
1683 		have multiple windows - set the handlers on each window, then only do eventLoop on your main window.
1684 	+/
1685 	void setEventHandlers(T...)(T eventHandlers) {
1686 		// FIXME: add more events
1687 		foreach(handler; eventHandlers) {
1688 			static if(__traits(compiles, handleKeyEvent = handler)) {
1689 				handleKeyEvent = handler;
1690 			} else static if(__traits(compiles, handleCharEvent = handler)) {
1691 				handleCharEvent = handler;
1692 			} else static if(__traits(compiles, handlePulse = handler)) {
1693 				handlePulse = handler;
1694 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
1695 				handleMouseEvent = handler;
1696 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
1697 		}
1698 	}
1699 
1700 	/// The event loop automatically returns when the window is closed
1701 	/// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
1702 	/// pulse timer is created. The event loop will block until an event
1703 	/// arrives or the pulse timer goes off.
1704 	final int eventLoop(T...)(
1705 		long pulseTimeout,    /// set to zero if you don't want a pulse.
1706 		T eventHandlers) /// delegate list like std.concurrency.receive
1707 	{
1708 		setEventHandlers(eventHandlers);
1709 
1710 		version(with_eventloop) {
1711 			// delegates event loop to my other module
1712 			version(X11)
1713 				XFlush(display);
1714 
1715 			import arsd.eventloop;
1716 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
1717 			scope(exit) clearInterval(handle);
1718 
1719 			loop();
1720 			return 0;
1721 		} else version(OSXCocoa) {
1722 			// FIXME
1723 			if (handlePulse !is null && pulseTimeout != 0) {
1724 				timer = scheduledTimer(pulseTimeout*1e-3,
1725 					view, sel_registerName("simpledisplay_pulse"),
1726 					null, true);
1727 			}
1728 
1729             		setNeedsDisplay(view, true);
1730             		run(NSApp);
1731             		return 0;
1732         	} else {
1733 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
1734 			return el.run();
1735 		}
1736 	}
1737 
1738 	/++
1739 		This lets you draw on the window (or its backing buffer) using basic
1740 		2D primitives.
1741 
1742 		Be sure to call this in a limited scope because your changes will not
1743 		actually appear on the window until ScreenPainter's destructor runs.
1744 
1745 		Returns: an instance of [ScreenPainter], which has the drawing methods
1746 		on it to draw on this window.
1747 	+/
1748 	ScreenPainter draw() {
1749 		return impl.getPainter();
1750 	}
1751 
1752 	// This is here to implement the interface we use for various native handlers.
1753 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
1754 
1755 	// maps native window handles to SimpleWindow instances, if there are any
1756 	// you shouldn't need this, but it is public in case you do in a native event handler or something
1757 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
1758 
1759 	/// Width of the window's drawable client area, in pixels.
1760 	@scriptable
1761 	final @property int width() { return _width; }
1762 
1763 	/// Height of the window's drawable client area, in pixels.
1764 	@scriptable
1765 	final @property int height() { return _height; }
1766 
1767 	private int _width;
1768 	private int _height;
1769 
1770 	// HACK: making the best of some copy constructor woes with refcounting
1771 	private ScreenPainterImplementation* activeScreenPainter_;
1772 
1773 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
1774 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
1775 
1776 	private OpenGlOptions openglMode;
1777 	private Resizability resizability;
1778 	private WindowTypes windowType;
1779 	private int customizationFlags;
1780 
1781 	/// `true` if OpenGL was initialized for this window.
1782 	@property bool isOpenGL () const pure nothrow @safe @nogc {
1783 		version(without_opengl)
1784 			return false;
1785 		else
1786 			return (openglMode == OpenGlOptions.yes);
1787 	}
1788 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
1789 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
1790 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
1791 
1792 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
1793 	/// to call this, as it's not recommended to share window between threads.
1794 	void mtLock () {
1795 		version(X11) {
1796 			XLockDisplay(this.display);
1797 		}
1798 	}
1799 
1800 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
1801 	/// to call this, as it's not recommended to share window between threads.
1802 	void mtUnlock () {
1803 		version(X11) {
1804 			XUnlockDisplay(this.display);
1805 		}
1806 	}
1807 
1808 	/// Emit a beep to get user's attention.
1809 	void beep () {
1810 		version(X11) {
1811 			XBell(this.display, 100);
1812 		} else version(Windows) {
1813 			MessageBeep(0xFFFFFFFF);
1814 		}
1815 	}
1816 
1817 
1818 
1819 	version(without_opengl) {} else {
1820 
1821 		/// 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`.
1822 		void delegate() redrawOpenGlScene;
1823 
1824 		/// This will allow you to change OpenGL vsync state.
1825 		final @property void vsync (bool wait) {
1826 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
1827 		  version(X11) {
1828 		    setAsCurrentOpenGlContext();
1829 		    glxSetVSync(display, impl.window, wait);
1830 		  }
1831 		}
1832 
1833 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
1834 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
1835 		/// enough without waiting 'em to finish their frame bussiness.
1836 		bool useGLFinish = true;
1837 
1838 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
1839 		/// 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.
1840 		void redrawOpenGlSceneNow() {
1841 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
1842 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
1843 			if(redrawOpenGlScene is null)
1844 				return;
1845 
1846 			this.mtLock();
1847 			scope(exit) this.mtUnlock();
1848 
1849 			this.setAsCurrentOpenGlContext();
1850 
1851 			redrawOpenGlScene();
1852 
1853 			this.swapOpenGlBuffers();
1854 			// 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.
1855 			if (useGLFinish) glFinish();
1856 		}
1857 
1858 
1859 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
1860 		void setAsCurrentOpenGlContext() {
1861 			assert(openglMode == OpenGlOptions.yes);
1862 			version(X11) {
1863 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
1864 					throw new Exception("glXMakeCurrent");
1865 			} else version(Windows) {
1866 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
1867 				if (!wglMakeCurrent(ghDC, ghRC))
1868 					throw new Exception("wglMakeCurrent"); // let windows users suffer too
1869 			}
1870 		}
1871 
1872 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
1873 		/// This doesn't throw, returning success flag instead.
1874 		bool setAsCurrentOpenGlContextNT() nothrow {
1875 			assert(openglMode == OpenGlOptions.yes);
1876 			version(X11) {
1877 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
1878 			} else version(Windows) {
1879 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
1880 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
1881 			}
1882 		}
1883 
1884 		/// 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.
1885 		/// This doesn't throw, returning success flag instead.
1886 		bool releaseCurrentOpenGlContext() nothrow {
1887 			assert(openglMode == OpenGlOptions.yes);
1888 			version(X11) {
1889 				return (glXMakeCurrent(display, 0, null) != 0);
1890 			} else version(Windows) {
1891 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
1892 				return wglMakeCurrent(ghDC, null) ? true : false;
1893 			}
1894 		}
1895 
1896 		/++
1897 			simpledisplay always uses double buffering, usually automatically. This
1898 			manually swaps the OpenGL buffers.
1899 
1900 
1901 			You should not need to call this yourself because simpledisplay will do it
1902 			for you after calling your `redrawOpenGlScene`.
1903 
1904 			Remember that this may throw an exception, which you can catch in a multithreaded
1905 			application to keep your thread from dying from an unhandled exception.
1906 		+/
1907 		void swapOpenGlBuffers() {
1908 			assert(openglMode == OpenGlOptions.yes);
1909 			version(X11) {
1910 				if (!this._visible) return; // no need to do this if window is invisible
1911 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
1912 				glXSwapBuffers(display, impl.window);
1913 			} else version(Windows) {
1914 				SwapBuffers(ghDC);
1915 			}
1916 		}
1917 	}
1918 
1919 	/++
1920 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
1921 
1922 
1923 		---
1924 			auto window = new SimpleWindow(100, 100, "First title");
1925 			window.title = "A new title";
1926 		---
1927 
1928 		You may call this function at any time.
1929 	+/
1930 	@property void title(string title) {
1931 		_title = title;
1932 		version(OSXCocoa) throw new NotYetImplementedException(); else
1933 		impl.setTitle(title);
1934 	}
1935 
1936 	private string _title;
1937 
1938 	/// Gets the title
1939 	@property string title() {
1940 		if(_title is null)
1941 			_title = getRealTitle();
1942 		return _title;
1943 	}
1944 
1945 	/++
1946 		Get the title as set by the window manager.
1947 		May not match what you attempted to set.
1948 	+/
1949 	string getRealTitle() {
1950 		static if(is(typeof(impl.getTitle())))
1951 			return impl.getTitle();
1952 		else
1953 			return null;
1954 	}
1955 
1956 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user.
1957 	@property void icon(MemoryImage icon) {
1958 		auto tci = icon.getAsTrueColorImage();
1959 		version(Windows) {
1960 			winIcon = new WindowsIcon(icon);
1961 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
1962 		} else version(X11) {
1963 			// FIXME: ensure this is correct
1964 			auto display = XDisplayConnection.get;
1965 			arch_ulong[] buffer;
1966 			buffer ~= icon.width;
1967 			buffer ~= icon.height;
1968 			foreach(c; tci.imageData.colors) {
1969 				arch_ulong b;
1970 				b |= c.a << 24;
1971 				b |= c.r << 16;
1972 				b |= c.g << 8;
1973 				b |= c.b;
1974 				buffer ~= b;
1975 			}
1976 
1977 			XChangeProperty(
1978 				display,
1979 				impl.window,
1980 				GetAtom!"_NET_WM_ICON"(display),
1981 				GetAtom!"CARDINAL"(display),
1982 				32 /* bits */,
1983 				0 /*PropModeReplace*/,
1984 				buffer.ptr,
1985 				cast(int) buffer.length);
1986 		} else version(OSXCocoa) {
1987 			throw new NotYetImplementedException();
1988 		} else static assert(0);
1989 	}
1990 
1991 	version(Windows)
1992 		private WindowsIcon winIcon;
1993 
1994 	bool _suppressDestruction;
1995 
1996 	~this() {
1997 		if(_suppressDestruction)
1998 			return;
1999 		impl.dispose();
2000 	}
2001 
2002 	private bool _closed;
2003 
2004 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
2005 	/*
2006 	ScreenPainter drawTransiently() {
2007 		return impl.getPainter();
2008 	}
2009 	*/
2010 
2011 	/// Draws an image on the window. This is meant to provide quick look
2012 	/// of a static image generated elsewhere.
2013 	@property void image(Image i) {
2014 		version(Windows) {
2015 			BITMAP bm;
2016 			HDC hdc = GetDC(hwnd);
2017 			HDC hdcMem = CreateCompatibleDC(hdc);
2018 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
2019 
2020 			GetObject(i.handle, bm.sizeof, &bm);
2021 
2022 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
2023 
2024 			SelectObject(hdcMem, hbmOld);
2025 			DeleteDC(hdcMem);
2026 			DeleteDC(hwnd);
2027 
2028 			/*
2029 			RECT r;
2030 			r.right = i.width;
2031 			r.bottom = i.height;
2032 			InvalidateRect(hwnd, &r, false);
2033 			*/
2034 		} else
2035 		version(X11) {
2036 			if(!destroyed) {
2037 				if(i.usingXshm)
2038 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
2039 				else
2040 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
2041 			}
2042 		} else
2043 		version(OSXCocoa) {
2044 			draw().drawImage(Point(0, 0), i);
2045 			setNeedsDisplay(view, true);
2046 		} else static assert(0);
2047 	}
2048 
2049 	/++
2050 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
2051 
2052 		---
2053 		window.cursor = GenericCursor.Help;
2054 		// now the window mouse cursor is set to a generic help
2055 		---
2056 
2057 	+/
2058 	@property void cursor(MouseCursor cursor) {
2059 		version(OSXCocoa)
2060 			featureNotImplemented();
2061 		else
2062 		if(this.impl.curHidden <= 0) {
2063 			static if(UsingSimpledisplayX11) {
2064 				auto ch = cursor.cursorHandle;
2065 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
2066 			} else version(Windows) {
2067 				auto ch = cursor.cursorHandle;
2068 				impl.currentCursor = ch;
2069 				SetCursor(ch); // redraw without waiting for mouse movement to update
2070 			} else featureNotImplemented();
2071 		}
2072 
2073 	}
2074 
2075 	/// What follows are the event handlers. These are set automatically
2076 	/// by the eventLoop function, but are still public so you can change
2077 	/// them later. wasPressed == true means key down. false == key up.
2078 
2079 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
2080 	void delegate(KeyEvent ke) handleKeyEvent;
2081 
2082 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
2083 	void delegate(dchar c) handleCharEvent;
2084 
2085 	/// Handles a timer pulse. Settable through setEventHandlers.
2086 	void delegate() handlePulse;
2087 
2088 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
2089 	void delegate(bool) onFocusChange;
2090 
2091 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
2092 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
2093 	void delegate() onClosing;
2094 
2095 	/** Called when we received destroy notification. At this stage we cannot do much with our window
2096 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
2097 	 * last minute cleanup. */
2098 	void delegate() onDestroyed;
2099 
2100 	static if (UsingSimpledisplayX11)
2101 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
2102 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
2103 	 * You will probably never need to setup this handler, it is for very low-level stuff.
2104 	 *
2105 	 * WARNING! Xlib is multithread-locked when this handles is called! */
2106 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
2107 
2108 	//version(Windows)
2109 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
2110 
2111 	private {
2112 		int lastMouseX = int.min;
2113 		int lastMouseY = int.min;
2114 		void mdx(ref MouseEvent ev) {
2115 			if(lastMouseX == int.min || lastMouseY == int.min) {
2116 				ev.dx = 0;
2117 				ev.dy = 0;
2118 			} else {
2119 				ev.dx = ev.x - lastMouseX;
2120 				ev.dy = ev.y - lastMouseY;
2121 			}
2122 
2123 			lastMouseX = ev.x;
2124 			lastMouseY = ev.y;
2125 		}
2126 	}
2127 
2128 	/// Mouse event handler. Settable through setEventHandlers.
2129 	void delegate(MouseEvent) handleMouseEvent;
2130 
2131 	/// use to redraw child widgets if you use system apis to add stuff
2132 	void delegate() paintingFinished;
2133 
2134 	void delegate() paintingFinishedDg() {
2135 		return paintingFinished;
2136 	}
2137 
2138 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
2139 	/// for this to ever happen.
2140 	void delegate(int width, int height) windowResized;
2141 
2142 	/** Platform specific - handle any native messages this window gets.
2143 	  *
2144 	  * Note: this is called *in addition to* other event handlers, unless you return zero indicating that you handled it.
2145 
2146 	  * On Windows, it takes the form of int delegate(HWND,UINT, WPARAM, LPARAM).
2147 
2148 	  * On X11, it takes the form of int delegate(XEvent).
2149 
2150 	  * IMPORTANT: it used to be static in old versions of simpledisplay.d, but I always used
2151 	  * it as if it wasn't static... so now I just fixed it so it isn't anymore.
2152 	**/
2153 	NativeEventHandler handleNativeEvent;
2154 
2155 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
2156 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
2157 	/// this instead and it will work the same way.
2158 	__gshared NativeEventHandler handleNativeGlobalEvent;
2159 
2160 //  private:
2161 	/// The native implementation is available, but you shouldn't use it unless you are
2162 	/// familiar with the underlying operating system, don't mind depending on it, and
2163 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
2164 	/// do what you need to do with handleNativeEvent instead.
2165 	///
2166 	/// This is likely to eventually change to be just a struct holding platform-specific
2167 	/// handles instead of a template mixin at some point because I'm not happy with the
2168 	/// code duplication here (ironically).
2169 	mixin NativeSimpleWindowImplementation!() impl;
2170 
2171 	/**
2172 		This is in-process one-way (from anything to window) event sending mechanics.
2173 		It is thread-safe, so it can be used in multi-threaded applications to send,
2174 		for example, "wake up and repaint" events when thread completed some operation.
2175 		This will allow to avoid using timer pulse to check events with synchronization,
2176 		'cause event handler will be called in UI thread. You can stop guessing which
2177 		pulse frequency will be enough for your app.
2178 		Note that events handlers may be called in arbitrary order, i.e. last registered
2179 		handler can be called first, and vice versa.
2180 	*/
2181 public:
2182 	/** Is our custom event queue empty? Can be used in simple cases to prevent
2183 	 * "spamming" window with events it can't cope with.
2184 	 * It is safe to call this from non-UI threads.
2185 	 */
2186 	@property bool eventQueueEmpty() () {
2187 		synchronized(this) {
2188 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
2189 		}
2190 		return true;
2191 	}
2192 
2193 	/** Does our custom event queue contains at least one with the given type?
2194 	 * Can be used in simple cases to prevent "spamming" window with events
2195 	 * it can't cope with.
2196 	 * It is safe to call this from non-UI threads.
2197 	 */
2198 	@property bool eventQueued(ET:Object) () {
2199 		synchronized(this) {
2200 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
2201 				if (!o.doProcess) {
2202 					if (cast(ET)(o.evt)) return true;
2203 				}
2204 			}
2205 		}
2206 		return false;
2207 	}
2208 
2209 	/** Add listener for custom event. Can be used like this:
2210 	 *
2211 	 * ---------------------
2212 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
2213 	 *   ...
2214 	 *   win.removeEventListener(eid);
2215 	 * ---------------------
2216 	 *
2217 	 * Returns: 0 on failure (should never happen, so ignore it)
2218 	 *
2219 	 * $(WARNING Don't use this method in object destructors!)
2220 	 *
2221 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
2222 	 *           'cause if event handler id counter will overflow, you won't be able
2223 	 *           to register any more events.)
2224 	 */
2225 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
2226 		if (dg is null) return 0; // ignore empty handlers
2227 		synchronized(this) {
2228 			//FIXME: abort on overflow?
2229 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
2230 			EventHandlerEntry e;
2231 			e.dg = delegate (Object o) {
2232 				if (auto co = cast(ET)o) {
2233 					try {
2234 						dg(co);
2235 					} catch (Exception) {
2236 						// sorry!
2237 					}
2238 					return true;
2239 				}
2240 				return false;
2241 			};
2242 			e.id = lastUsedHandlerId;
2243 			auto optr = eventHandlers.ptr;
2244 			eventHandlers ~= e;
2245 			if (eventHandlers.ptr !is optr) {
2246 				import core.memory : GC;
2247 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
2248 			}
2249 			return lastUsedHandlerId;
2250 		}
2251 	}
2252 
2253 	/// Remove event listener. It is safe to pass invalid event id here.
2254 	/// $(WARNING Don't use this method in object destructors!)
2255 	void removeEventListener() (uint id) {
2256 		if (id == 0 || id > lastUsedHandlerId) return;
2257 		synchronized(this) {
2258 			foreach (immutable idx; 0..eventHandlers.length) {
2259 				if (eventHandlers[idx].id == id) {
2260 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
2261 					eventHandlers[$-1].dg = null;
2262 					eventHandlers.length -= 1;
2263 					eventHandlers.assumeSafeAppend;
2264 					return;
2265 				}
2266 			}
2267 		}
2268 	}
2269 
2270 	/// Post event to queue. It is safe to call this from non-UI threads.
2271 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
2272 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
2273 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
2274 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
2275 		if (this.closed) return false; // closed windows can't handle events
2276 
2277 		// remove all events of type `ET`
2278 		void removeAllET () {
2279 			uint eidx = 0, ec = eventQueueUsed;
2280 			auto eptr = eventQueue.ptr;
2281 			while (eidx < ec) {
2282 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
2283 				if (cast(ET)eptr.evt !is null) {
2284 					// i found her!
2285 					if (inCustomEventProcessor) {
2286 						// if we're in custom event processing loop, processor will clear it for us
2287 						eptr.evt = null;
2288 						++eidx;
2289 						++eptr;
2290 					} else {
2291 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2292 						ec = --eventQueueUsed;
2293 						// clear last event (it is already copied)
2294 						eventQueue.ptr[ec].evt = null;
2295 					}
2296 				} else {
2297 					++eidx;
2298 					++eptr;
2299 				}
2300 			}
2301 		}
2302 
2303 		if (evt is null) {
2304 			if (replace) { synchronized(this) removeAllET(); }
2305 			// ignore empty events, they can't be handled anyway
2306 			return false;
2307 		}
2308 
2309 		// add events even if no event FD/event object created yet
2310 		synchronized(this) {
2311 			if (replace) removeAllET();
2312 			if (eventQueueUsed == uint.max) return false; // just in case
2313 			if (eventQueueUsed < eventQueue.length) {
2314 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
2315 			} else {
2316 				if (eventQueue.capacity == eventQueue.length) {
2317 					// need to reallocate; do a trick to ensure that old array is cleared
2318 					auto oarr = eventQueue;
2319 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
2320 					// just in case, do yet another check
2321 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
2322 					import core.memory : GC;
2323 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
2324 				} else {
2325 					auto optr = eventQueue.ptr;
2326 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
2327 					assert(eventQueue.ptr is optr);
2328 				}
2329 				++eventQueueUsed;
2330 				assert(eventQueueUsed == eventQueue.length);
2331 			}
2332 			if (!eventWakeUp()) {
2333 				// can't wake up event processor, so there is no reason to keep the event
2334 				assert(eventQueueUsed > 0);
2335 				eventQueue[--eventQueueUsed].evt = null;
2336 				return false;
2337 			}
2338 			return true;
2339 		}
2340 	}
2341 
2342 	/// Post event to queue. It is safe to call this from non-UI threads.
2343 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
2344 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
2345 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
2346 		return postTimeout!ET(evt, 0, replace);
2347 	}
2348 
2349 private:
2350 	private import core.time : MonoTime;
2351 
2352 	version(X11) {
2353 		__gshared int customEventFD = -1;
2354 		__gshared int customSignalFD = -1;
2355 	} else version(Windows) {
2356 		__gshared HANDLE customEventH = null;
2357 	}
2358 
2359 	// wake up event processor
2360 	bool eventWakeUp () {
2361 		version(X11) {
2362 			import core.sys.posix.unistd : write;
2363 			ulong n = 1;
2364 			if (customEventFD >= 0) write(customEventFD, &n, n.sizeof);
2365 			return true;
2366 		} else version(Windows) {
2367 			if (customEventH !is null) SetEvent(customEventH);
2368 			return true;
2369 		} else {
2370 			// not implemented for other OSes
2371 			return false;
2372 		}
2373 	}
2374 
2375 	static struct QueuedEvent {
2376 		Object evt;
2377 		bool timed = false;
2378 		MonoTime hittime = MonoTime.zero;
2379 		bool doProcess = false; // process event at the current iteration (internal flag)
2380 
2381 		this (Object aevt, uint toutmsecs) {
2382 			evt = aevt;
2383 			if (toutmsecs > 0) {
2384 				import core.time : msecs;
2385 				timed = true;
2386 				hittime = MonoTime.currTime+toutmsecs.msecs;
2387 			}
2388 		}
2389 	}
2390 
2391 	alias CustomEventHandler = bool delegate (Object o) nothrow;
2392 	static struct EventHandlerEntry {
2393 		CustomEventHandler dg;
2394 		uint id;
2395 	}
2396 
2397 	uint lastUsedHandlerId;
2398 	EventHandlerEntry[] eventHandlers;
2399 	QueuedEvent[] eventQueue = null;
2400 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
2401 	bool inCustomEventProcessor = false; // required to properly remove events
2402 
2403 	// process queued events and call custom event handlers
2404 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
2405 	void processCustomEvents () {
2406 		bool hasSomethingToDo = false;
2407 		uint ecount;
2408 		bool ocep;
2409 		synchronized(this) {
2410 			ocep = inCustomEventProcessor;
2411 			inCustomEventProcessor = true;
2412 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
2413 			auto ctt = MonoTime.currTime;
2414 			bool hasEmpty = false;
2415 			// mark events to process (this is required for `eventQueued()`)
2416 			foreach (ref qe; eventQueue[0..ecount]) {
2417 				if (qe.evt is null) { hasEmpty = true; continue; }
2418 				if (qe.timed) {
2419 					qe.doProcess = (qe.hittime <= ctt);
2420 				} else {
2421 					qe.doProcess = true;
2422 				}
2423 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
2424 			}
2425 			if (!hasSomethingToDo) {
2426 				// remove empty events
2427 				if (hasEmpty) {
2428 					uint eidx = 0, ec = eventQueueUsed;
2429 					auto eptr = eventQueue.ptr;
2430 					while (eidx < ec) {
2431 						if (eptr.evt is null) {
2432 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2433 							ec = --eventQueueUsed;
2434 							eventQueue.ptr[ec].evt = null; // make GC life easier
2435 						} else {
2436 							++eidx;
2437 							++eptr;
2438 						}
2439 					}
2440 				}
2441 				inCustomEventProcessor = ocep;
2442 				return;
2443 			}
2444 		}
2445 		// process marked events
2446 		uint efree = 0; // non-processed events will be put at this index
2447 		EventHandlerEntry[] eh;
2448 		Object evt;
2449 		foreach (immutable eidx; 0..ecount) {
2450 			synchronized(this) {
2451 				if (!eventQueue[eidx].doProcess) {
2452 					// skip this event
2453 					assert(efree <= eidx);
2454 					if (efree != eidx) {
2455 						// copy this event to queue start
2456 						eventQueue[efree] = eventQueue[eidx];
2457 						eventQueue[eidx].evt = null; // just in case
2458 					}
2459 					++efree;
2460 					continue;
2461 				}
2462 				evt = eventQueue[eidx].evt;
2463 				eventQueue[eidx].evt = null; // in case event handler will hit GC
2464 				if (evt is null) continue; // just in case
2465 				// try all handlers; this can be slow, but meh...
2466 				eh = eventHandlers;
2467 			}
2468 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
2469 			evt = null;
2470 			eh = null;
2471 		}
2472 		synchronized(this) {
2473 			// move all unprocessed events to queue top; efree holds first "free index"
2474 			foreach (immutable eidx; ecount..eventQueueUsed) {
2475 				assert(efree <= eidx);
2476 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
2477 				++efree;
2478 			}
2479 			eventQueueUsed = efree;
2480 			// wake up event processor on next event loop iteration if we have more queued events
2481 			// also, remove empty events
2482 			bool awaken = false;
2483 			uint eidx = 0, ec = eventQueueUsed;
2484 			auto eptr = eventQueue.ptr;
2485 			while (eidx < ec) {
2486 				if (eptr.evt is null) {
2487 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2488 					ec = --eventQueueUsed;
2489 					eventQueue.ptr[ec].evt = null; // make GC life easier
2490 				} else {
2491 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
2492 					++eidx;
2493 					++eptr;
2494 				}
2495 			}
2496 			inCustomEventProcessor = ocep;
2497 		}
2498 	}
2499 
2500 	// for all windows in nativeMapping
2501 	static void processAllCustomEvents () {
2502 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
2503 			if (sw is null || sw.closed) continue;
2504 			sw.processCustomEvents();
2505 		}
2506 	}
2507 
2508 	// 0: infinite (i.e. no scheduled events in queue)
2509 	uint eventQueueTimeoutMSecs () {
2510 		synchronized(this) {
2511 			if (eventQueueUsed == 0) return 0;
2512 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
2513 			uint res = int.max;
2514 			auto ctt = MonoTime.currTime;
2515 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
2516 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
2517 				if (qe.doProcess) continue; // just in case
2518 				if (!qe.timed) return 1; // minimal
2519 				if (qe.hittime <= ctt) return 1; // minimal
2520 				auto tms = (qe.hittime-ctt).total!"msecs";
2521 				if (tms < 1) tms = 1; // safety net
2522 				if (tms >= int.max) tms = int.max-1; // and another safety net
2523 				if (res > tms) res = cast(uint)tms;
2524 			}
2525 			return (res >= int.max ? 0 : res);
2526 		}
2527 	}
2528 
2529 	// for all windows in nativeMapping
2530 	static uint eventAllQueueTimeoutMSecs () {
2531 		uint res = uint.max;
2532 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
2533 			if (sw is null || sw.closed) continue;
2534 			uint to = sw.eventQueueTimeoutMSecs();
2535 			if (to && to < res) {
2536 				res = to;
2537 				if (to == 1) break; // can't have less than this
2538 			}
2539 		}
2540 		return (res >= int.max ? 0 : res);
2541 	}
2542 }
2543 
2544 
2545 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
2546 /// See [GenericCursor]
2547 class MouseCursor {
2548 	int osId;
2549 	bool isStockCursor;
2550 	private this(int osId) {
2551 		this.osId = osId;
2552 		this.isStockCursor = true;
2553 	}
2554 
2555 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
2556 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
2557 
2558 	version(Windows) {
2559 		HCURSOR cursor_;
2560 		HCURSOR cursorHandle() {
2561 			if(cursor_ is null)
2562 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
2563 			return cursor_;
2564 		}
2565 
2566 	} else static if(UsingSimpledisplayX11) {
2567 		Cursor cursor_ = None;
2568 		int xDisplaySequence;
2569 
2570 		Cursor cursorHandle() {
2571 			if(this.osId == None)
2572 				return None;
2573 
2574 			// we need to reload if we on a new X connection
2575 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
2576 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
2577 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
2578 			}
2579 			return cursor_;
2580 		}
2581 	}
2582 }
2583 
2584 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
2585 // https://tronche.com/gui/x/xlib/appendix/b/
2586 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
2587 /// 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
2588 enum GenericCursorType {
2589 	Default, /// The default arrow pointer.
2590 	Wait, /// A cursor indicating something is loading and the user must wait.
2591 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
2592 	Help, /// A cursor indicating the user can get help about the pointer location.
2593 	Cross, /// A crosshair.
2594 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
2595 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll
2596 	UpArrow, /// An arrow pointing straight up.
2597 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
2598 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
2599 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator)
2600 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator)
2601 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator)
2602 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator)
2603 
2604 }
2605 
2606 /*
2607 	X_plus == css cell == Windows ?
2608 */
2609 
2610 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
2611 static struct GenericCursor {
2612 	static:
2613 	///
2614 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
2615 		static MouseCursor mc;
2616 
2617 		auto type = __traits(getMember, GenericCursorType, str);
2618 
2619 		if(mc is null) {
2620 
2621 			version(Windows) {
2622 				int osId;
2623 				final switch(type) {
2624 					case GenericCursorType.Default: osId = IDC_ARROW; break;
2625 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
2626 					case GenericCursorType.Hand: osId = IDC_HAND; break;
2627 					case GenericCursorType.Help: osId = IDC_HELP; break;
2628 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
2629 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
2630 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
2631 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
2632 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
2633 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
2634 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
2635 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
2636 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
2637 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
2638 				}
2639 			} else static if(UsingSimpledisplayX11) {
2640 				int osId;
2641 				final switch(type) {
2642 					case GenericCursorType.Default: osId = None; break;
2643 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
2644 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
2645 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
2646 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
2647 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
2648 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
2649 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
2650 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
2651 
2652 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
2653 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
2654 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
2655 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
2656 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
2657 				}
2658 
2659 			} else featureNotImplemented();
2660 
2661 			mc = new MouseCursor(osId);
2662 		}
2663 		return mc;
2664 	}
2665 }
2666 
2667 
2668 /++
2669 	If you want to get more control over the event loop, you can use this.
2670 
2671 	Typically though, you can just call [SimpleWindow.eventLoop].
2672 +/
2673 struct EventLoop {
2674 	@disable this();
2675 
2676 	/// Gets a reference to an existing event loop
2677 	static EventLoop get() {
2678 		return EventLoop(0, null);
2679 	}
2680 
2681 	/// Construct an application-global event loop for yourself
2682 	/// See_Also: [SimpleWindow.setEventHandlers]
2683 	this(long pulseTimeout, void delegate() handlePulse) {
2684 		if(impl is null)
2685 			impl = new EventLoopImpl(pulseTimeout, handlePulse);
2686 		else {
2687 			if(pulseTimeout) {
2688 				impl.pulseTimeout = pulseTimeout;
2689 				impl.handlePulse = handlePulse;
2690 			}
2691 		}
2692 		impl.refcount++;
2693 	}
2694 
2695 	~this() {
2696 		if(impl is null)
2697 			return;
2698 		impl.refcount--;
2699 		if(impl.refcount == 0)
2700 			impl.dispose();
2701 
2702 	}
2703 
2704 	this(this) {
2705 		if(impl is null)
2706 			return;
2707 		impl.refcount++;
2708 	}
2709 
2710 	/// Runs the event loop until the whileCondition, if present, returns false
2711 	int run(bool delegate() whileCondition = null) {
2712 		assert(impl !is null);
2713 		impl.notExited = true;
2714 		return impl.run(whileCondition);
2715 	}
2716 
2717 	/// Exits the event loop
2718 	void exit() {
2719 		assert(impl !is null);
2720 		impl.notExited = false;
2721 	}
2722 
2723 	version(linux)
2724 	ref void delegate(int) signalHandler() {
2725 		assert(impl !is null);
2726 		return impl.signalHandler;
2727 	}
2728 
2729 	static EventLoopImpl* impl;
2730 }
2731 
2732 version(linux)
2733 	void delegate(int, int) globalHupHandler;
2734 
2735 version(linux)
2736 	void makeNonBlocking(int fd) {
2737 		import fcntl = core.sys.posix.fcntl;
2738 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
2739 		if(flags == -1)
2740 			throw new Exception("fcntl get");
2741 		flags |= fcntl.O_NONBLOCK;
2742 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
2743 		if(s == -1)
2744 			throw new Exception("fcntl set");
2745 	}
2746 
2747 struct EventLoopImpl {
2748 	int refcount;
2749 
2750 	bool notExited = true;
2751 
2752 	version(linux) {
2753 		static import ep = core.sys.linux.epoll;
2754 		static import unix = core.sys.posix.unistd;
2755 		static import err = core.stdc.errno;
2756 		import core.sys.linux.timerfd;
2757 
2758 		void delegate(int) signalHandler;
2759 	}
2760 
2761 	version(X11) {
2762 		int pulseFd = -1;
2763 		version(linux) ep.epoll_event[16] events = void;
2764 	} else version(Windows) {
2765 		Timer pulser;
2766 		HANDLE[] handles;
2767 	}
2768 
2769 
2770 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2771 	/// to call this, as it's not recommended to share window between threads.
2772 	void mtLock () {
2773 		version(X11) {
2774 			XLockDisplay(this.display);
2775 		}
2776 	}
2777 
2778 	version(X11)
2779 	auto display() { return XDisplayConnection.get; }
2780 
2781 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2782 	/// to call this, as it's not recommended to share window between threads.
2783 	void mtUnlock () {
2784 		version(X11) {
2785 			XUnlockDisplay(this.display);
2786 		}
2787 	}
2788 
2789 	version(with_eventloop)
2790 	void initialize(long pulseTimeout) {}
2791 	else
2792 	void initialize(long pulseTimeout) {
2793 		version(Windows) {
2794 			if(pulseTimeout)
2795 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
2796 
2797 			if (customEventH is null) {
2798 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
2799 				if (customEventH !is null) {
2800 					handles ~= customEventH;
2801 				} else {
2802 					// this is something that should not be; better be safe than sorry
2803 					throw new Exception("can't create eventfd for custom event processing");
2804 				}
2805 			}
2806 
2807 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
2808 		}
2809 
2810 		version(linux) {
2811 			prepareEventLoop();
2812 			{
2813 				auto display = XDisplayConnection.get;
2814 				// adding Xlib file
2815 				ep.epoll_event ev = void;
2816 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
2817 				ev.events = ep.EPOLLIN;
2818 				ev.data.fd = display.fd;
2819 				//import std.conv;
2820 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
2821 					throw new Exception("add x fd");// ~ to!string(epollFd));
2822 				displayFd = display.fd;
2823 			}
2824 
2825 			if(pulseTimeout) {
2826 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
2827 				if(pulseFd == -1)
2828 					throw new Exception("pulse timer create failed");
2829 
2830 				itimerspec value;
2831 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
2832 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
2833 
2834 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
2835 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
2836 
2837 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
2838 					throw new Exception("couldn't make pulse timer");
2839 
2840 				ep.epoll_event ev = void;
2841 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
2842 				ev.events = ep.EPOLLIN;
2843 				ev.data.fd = pulseFd;
2844 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
2845 			}
2846 
2847 			// eventfd for custom events
2848 			if (customEventFD == -1) {
2849 				customEventFD = eventfd(0, 0);
2850 				if (customEventFD >= 0) {
2851 					ep.epoll_event ev = void;
2852 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
2853 					ev.events = ep.EPOLLIN;
2854 					ev.data.fd = customEventFD;
2855 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFD, &ev);
2856 				} else {
2857 					// this is something that should not be; better be safe than sorry
2858 					throw new Exception("can't create eventfd for custom event processing");
2859 				}
2860 			}
2861 
2862 			if (customSignalFD == -1) {
2863 				import core.sys.linux.sys.signalfd;
2864 
2865 				sigset_t sigset;
2866 				auto err = sigemptyset(&sigset);
2867 				assert(!err);
2868 				err = sigaddset(&sigset, SIGINT);
2869 				assert(!err);
2870 				err = sigaddset(&sigset, SIGHUP);
2871 				assert(!err);
2872 				err = sigprocmask(SIG_BLOCK, &sigset, null);
2873 				assert(!err);
2874 
2875 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
2876 				assert(customSignalFD != -1);
2877 
2878 				ep.epoll_event ev = void;
2879 				{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
2880 				ev.events = ep.EPOLLIN;
2881 				ev.data.fd = customSignalFD;
2882 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
2883 			}
2884 		}
2885 
2886 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
2887 
2888 		version(linux) {
2889 			this.mtLock();
2890 			scope(exit) this.mtUnlock();
2891 			XPending(display); // no, really
2892 		}
2893 
2894 		disposed = false;
2895 	}
2896 
2897 	bool disposed = true;
2898 	version(X11)
2899 		int displayFd = -1;
2900 
2901 	version(with_eventloop)
2902 	void dispose() {}
2903 	else
2904 	void dispose() {
2905 		disposed = true;
2906 		version(X11) {
2907 			if(pulseFd != -1) {
2908 				import unix = core.sys.posix.unistd;
2909 				unix.close(pulseFd);
2910 				pulseFd = -1;
2911 			}
2912 
2913 				version(linux)
2914 				if(displayFd != -1) {
2915 					// 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
2916 					ep.epoll_event ev = void;
2917 					{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
2918 					ev.events = ep.EPOLLIN;
2919 					ev.data.fd = displayFd;
2920 					//import std.conv;
2921 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
2922 					displayFd = -1;
2923 				}
2924 
2925 		} else version(Windows) {
2926 			if(pulser !is null) {
2927 				pulser.destroy();
2928 				pulser = null;
2929 			}
2930 			if (customEventH !is null) {
2931 				CloseHandle(customEventH);
2932 				customEventH = null;
2933 			}
2934 		}
2935 	}
2936 
2937 	this(long pulseTimeout, void delegate() handlePulse) {
2938 		this.pulseTimeout = pulseTimeout;
2939 		this.handlePulse = handlePulse;
2940 		initialize(pulseTimeout);
2941 	}
2942 
2943 	private long pulseTimeout;
2944 	void delegate() handlePulse;
2945 
2946 	~this() {
2947 		dispose();
2948 	}
2949 
2950 	version(linux)
2951 	ref int customEventFD() { return SimpleWindow.customEventFD; }
2952 	version(linux)
2953 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
2954 	version(Windows)
2955 	ref auto customEventH() { return SimpleWindow.customEventH; }
2956 
2957 	version(with_eventloop) {
2958 		int loopHelper(bool delegate() whileCondition) {
2959 			// FIXME: whileCondition
2960 			import arsd.eventloop;
2961 			loop();
2962 			return 0;
2963 		}
2964 	} else
2965 	int loopHelper(bool delegate() whileCondition) {
2966 		version(X11) {
2967 			bool done = false;
2968 
2969 			XFlush(display);
2970 			insideXEventLoop = true;
2971 			scope(exit) insideXEventLoop = false;
2972 
2973 			version(linux) {
2974 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
2975 					bool forceXPending = false;
2976 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
2977 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
2978 					{
2979 						this.mtLock();
2980 						scope(exit) this.mtUnlock();
2981 						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
2982 					}
2983 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
2984 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
2985 					if(nfds == -1) {
2986 						if(err.errno == err.EINTR) {
2987 							continue; // interrupted by signal, just try again
2988 						}
2989 						throw new Exception("epoll wait failure");
2990 					}
2991 
2992 					SimpleWindow.processAllCustomEvents(); // anyway
2993 					//version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
2994 					foreach(idx; 0 .. nfds) {
2995 						if(done) break;
2996 						auto fd = events[idx].data.fd;
2997 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
2998 						auto flags = events[idx].events;
2999 						if(flags & ep.EPOLLIN) {
3000 							if (fd == customSignalFD) {
3001 								version(linux) {
3002 									import core.sys.linux.sys.signalfd;
3003 									import core.sys.posix.unistd : read;
3004 									signalfd_siginfo info;
3005 									read(customSignalFD, &info, info.sizeof);
3006 
3007 									auto sig = info.ssi_signo;
3008 
3009 									if(EventLoop.get.signalHandler !is null) {
3010 										EventLoop.get.signalHandler()(sig);
3011 									} else {
3012 										EventLoop.get.exit();
3013 									}
3014 								}
3015 							} else if(fd == display.fd) {
3016 								version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
3017 								this.mtLock();
3018 								scope(exit) this.mtUnlock();
3019 								while(!done && XPending(display)) {
3020 									done = doXNextEvent(this.display);
3021 								}
3022 								forceXPending = false;
3023 							} else if(fd == pulseFd) {
3024 								long expirationCount;
3025 								// 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...
3026 
3027 								handlePulse();
3028 
3029 								// read just to clear the buffer so poll doesn't trigger again
3030 								// BTW I read AFTER the pulse because if the pulse handler takes
3031 								// a lot of time to execute, we don't want the app to get stuck
3032 								// in a loop of timer hits without a chance to do anything else
3033 								//
3034 								// IOW handlePulse happens at most once per pulse interval.
3035 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
3036 							} else if (fd == customEventFD) {
3037 								// we have some custom events; process 'em
3038 								import core.sys.posix.unistd : read;
3039 								ulong n;
3040 								read(customEventFD, &n, n.sizeof); // reset counter value to zero again
3041 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
3042 								//SimpleWindow.processAllCustomEvents();
3043 							} else {
3044 								// some other timer
3045 								version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); }
3046 
3047 								if(Timer* t = fd in Timer.mapping)
3048 									(*t).trigger();
3049 
3050 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3051 									(*pfr).ready(flags);
3052 
3053 								// or i might add support for other FDs too
3054 								// but for now it is just timer
3055 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
3056 							}
3057 						}
3058 						if(flags & ep.EPOLLHUP) {
3059 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3060 								(*pfr).hup(flags);
3061 							if(globalHupHandler)
3062 								globalHupHandler(fd, flags);
3063 						}
3064 						/+
3065 						} else {
3066 							// not interested in OUT, we are just reading here.
3067 							//
3068 							// error or hup might also be reported
3069 							// but it shouldn't here since we are only
3070 							// using a few types of FD and Xlib will report
3071 							// if it dies.
3072 							// so instead of thoughtfully handling it, I'll
3073 							// just throw. for now at least
3074 
3075 							throw new Exception("epoll did something else");
3076 						}
3077 						+/
3078 					}
3079 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
3080 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
3081 					if (!done && forceXPending) {
3082 						this.mtLock();
3083 						scope(exit) this.mtUnlock();
3084 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
3085 						while(!done && XPending(display)) {
3086 							done = doXNextEvent(this.display);
3087 						}
3088 					}
3089 				}
3090 			} else {
3091 				// Generic fallback: yes to simple pulse support,
3092 				// but NO timer support!
3093 
3094 				// FIXME: we could probably support the POSIX timer_create
3095 				// signal-based option, but I'm in no rush to write it since
3096 				// I prefer the fd-based functions.
3097 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
3098 					while(!done &&
3099 						(pulseTimeout == 0 || (XPending(display) > 0)))
3100 					{
3101 						this.mtLock();
3102 						scope(exit) this.mtUnlock();
3103 						done = doXNextEvent(this.display);
3104 					}
3105 					if(!done && pulseTimeout !=0) {
3106 						if(handlePulse !is null)
3107 							handlePulse();
3108 						import core.thread;
3109 						Thread.sleep(dur!"msecs"(pulseTimeout));
3110 					}
3111 				}
3112 			}
3113 		}
3114 		
3115 		version(Windows) {
3116 			int ret = -1;
3117 			MSG message;
3118 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
3119 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
3120 				auto waitResult = MsgWaitForMultipleObjectsEx(
3121 					cast(int) handles.length, handles.ptr,
3122 					(wto == 0 ? INFINITE : wto), /* timeout */
3123 					0x04FF, /* QS_ALLINPUT */
3124 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
3125 
3126 				SimpleWindow.processAllCustomEvents(); // anyway
3127 				enum WAIT_OBJECT_0 = 0;
3128 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
3129 					auto h = handles[waitResult - WAIT_OBJECT_0];
3130 					if(auto e = h in WindowsHandleReader.mapping) {
3131 						(*e).ready();
3132 					}
3133 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
3134 					// message ready
3135 					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
3136 						ret = GetMessage(&message, null, 0, 0);
3137 						if(ret == -1)
3138 							throw new Exception("GetMessage failed");
3139 						TranslateMessage(&message);
3140 						DispatchMessage(&message);
3141 
3142 						if(ret == 0) // WM_QUIT
3143 							break;
3144 					}
3145 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
3146 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
3147 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
3148 					// timeout, should never happen since we aren't using it
3149 				} else if(waitResult == 0xFFFFFFFF) {
3150 						// failed
3151 						throw new Exception("MsgWaitForMultipleObjectsEx failed");
3152 				} else {
3153 					// idk....
3154 				}
3155 			}
3156 
3157 			// return message.wParam;
3158 			return 0;
3159 		} else {
3160 			return 0;
3161 		}
3162 	}
3163 
3164 	int run(bool delegate() whileCondition = null) {
3165 		if(disposed)
3166 			initialize(this.pulseTimeout);
3167 
3168 		version(X11) {
3169 			try {
3170 				return loopHelper(whileCondition);
3171 			} catch(XDisconnectException e) {
3172 				if(e.userRequested) {
3173 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
3174 						item.discardConnectionState();
3175 					XCloseDisplay(XDisplayConnection.display);
3176 				}
3177 
3178 				XDisplayConnection.display = null;
3179 
3180 				this.dispose();
3181 
3182 				throw e;
3183 			}
3184 		} else {
3185 			return loopHelper(whileCondition);
3186 		}
3187 	}
3188 }
3189 
3190 
3191 /++
3192 	Provides an icon on the system notification area (also known as the system tray).
3193 
3194 
3195 	If a notification area is not available with the NotificationIcon object is created,
3196 	it will silently succeed and simply attempt to create one when an area becomes available.
3197 
3198 
3199 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
3200 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
3201 	use the older version.
3202 +/
3203 version(OSXCocoa) {} else // NotYetImplementedException
3204 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
3205 
3206 	version(X11) {
3207 		void recreateAfterDisconnect() {
3208 			stateDiscarded = false;
3209 			clippixmap = None;
3210 			throw new Exception("NOT IMPLEMENTED");
3211 		}
3212 
3213 		bool stateDiscarded;
3214 		void discardConnectionState() {
3215 			stateDiscarded = true;
3216 		}
3217 	}
3218 
3219 
3220 	version(X11) {
3221 		Image img;
3222 
3223 		NativeEventHandler getNativeEventHandler() {
3224 			return delegate int(XEvent e) {
3225 				switch(e.type) {
3226 					case EventType.Expose:
3227 					//case EventType.VisibilityNotify:
3228 						redraw();
3229 					break;
3230 					case EventType.ClientMessage:
3231 						version(sddddd) {
3232 						import std.stdio;
3233 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
3234 						writeln("\t", e.xclient.format);
3235 						writeln("\t", e.xclient.data.l);
3236 						}
3237 					break;
3238 					case EventType.ButtonPress:
3239 						auto event = e.xbutton;
3240 						if (onClick !is null || onClickEx !is null) {
3241 							MouseButton mb = cast(MouseButton)0;
3242 							switch (event.button) {
3243 								case 1: mb = MouseButton.left; break; // left
3244 								case 2: mb = MouseButton.middle; break; // middle
3245 								case 3: mb = MouseButton.right; break; // right
3246 								case 4: mb = MouseButton.wheelUp; break; // scroll up
3247 								case 5: mb = MouseButton.wheelDown; break; // scroll down
3248 								case 6: break; // idk
3249 								case 7: break; // idk
3250 								case 8: mb = MouseButton.backButton; break;
3251 								case 9: mb = MouseButton.forwardButton; break;
3252 								default:
3253 							}
3254 							if (mb) {
3255 								try { onClick()(mb); } catch (Exception) {}
3256 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
3257 							}
3258 						}
3259 					break;
3260 					case EventType.EnterNotify:
3261 						if (onEnter !is null) {
3262 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
3263 						}
3264 						break;
3265 					case EventType.LeaveNotify:
3266 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
3267 						break;
3268 					case EventType.DestroyNotify:
3269 						active = false;
3270 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
3271 					break;
3272 					case EventType.ConfigureNotify:
3273 						auto event = e.xconfigure;
3274 						this.width = event.width;
3275 						this.height = event.height;
3276 						//import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y);
3277 						redraw();
3278 					break;
3279 					default: return 1;
3280 				}
3281 				return 1;
3282 			};
3283 		}
3284 
3285 		/* private */ void hideBalloon() {
3286 			balloon.close();
3287 			version(with_timer)
3288 				timer.destroy();
3289 			balloon = null;
3290 			version(with_timer)
3291 				timer = null;
3292 		}
3293 
3294 		void redraw() {
3295 			if (!active) return;
3296 
3297 			auto display = XDisplayConnection.get;
3298 			auto gc = DefaultGC(display, DefaultScreen(display));
3299 			XClearWindow(display, nativeHandle);
3300 
3301 			XSetClipMask(display, gc, clippixmap);
3302 
3303 			XSetForeground(display, gc,
3304 				cast(uint) 0 << 16 |
3305 				cast(uint) 0 << 8 |
3306 				cast(uint) 0);
3307 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
3308 
3309 			if (img is null) {
3310 				XSetForeground(display, gc,
3311 					cast(uint) 0 << 16 |
3312 					cast(uint) 127 << 8 |
3313 					cast(uint) 0);
3314 				XFillArc(display, nativeHandle,
3315 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
3316 			} else {
3317 				int dx = 0;
3318 				int dy = 0;
3319 				if(width > img.width)
3320 					dx = (width - img.width) / 2;
3321 				if(height > img.height)
3322 					dy = (height - img.height) / 2;
3323 				XSetClipOrigin(display, gc, dx, dy);
3324 
3325 				if (img.usingXshm)
3326 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
3327 				else
3328 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
3329 			}
3330 			XSetClipMask(display, gc, None);
3331 			flushGui();
3332 		}
3333 
3334 		static Window getTrayOwner() {
3335 			auto display = XDisplayConnection.get;
3336 			auto i = cast(int) DefaultScreen(display);
3337 			if(i < 10 && i >= 0) {
3338 				static Atom atom;
3339 				if(atom == None)
3340 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
3341 				return XGetSelectionOwner(display, atom);
3342 			}
3343 			return None;
3344 		}
3345 
3346 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
3347 			auto to = getTrayOwner();
3348 			auto display = XDisplayConnection.get;
3349 			XEvent ev;
3350 			ev.xclient.type = EventType.ClientMessage;
3351 			ev.xclient.window = to;
3352 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
3353 			ev.xclient.format = 32;
3354 			ev.xclient.data.l[0] = CurrentTime;
3355 			ev.xclient.data.l[1] = message;
3356 			ev.xclient.data.l[2] = d1;
3357 			ev.xclient.data.l[3] = d2;
3358 			ev.xclient.data.l[4] = d3;
3359 
3360 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
3361 		}
3362 
3363 		private static NotificationAreaIcon[] activeIcons;
3364 
3365 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
3366 		private void newManager() {
3367 			close();
3368 			createXWin();
3369 
3370 			if(this.clippixmap)
3371 				XFreePixmap(XDisplayConnection.get, clippixmap);
3372 			if(this.originalMemoryImage)
3373 				this.icon = this.originalMemoryImage;
3374 			else if(this.img)
3375 				this.icon = this.img;
3376 		}
3377 
3378 		private void createXWin () {
3379 			// create window
3380 			auto display = XDisplayConnection.get;
3381 
3382 			// to check for MANAGER on root window to catch new/changed tray owners
3383 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
3384 			// so if a thing does appear, we can handle it
3385 			foreach(ai; activeIcons)
3386 				if(ai is this)
3387 					goto alreadythere;
3388 			activeIcons ~= this;
3389 			alreadythere:
3390 
3391 			// and check for an existing tray
3392 			auto trayOwner = getTrayOwner();
3393 			if(trayOwner == None)
3394 				return;
3395 				//throw new Exception("No notification area found");
3396 
3397 			Visual* v = cast(Visual*) CopyFromParent;
3398 			/+
3399 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
3400 			if(visualProp !is null) {
3401 				c_ulong[] info = cast(c_ulong[]) visualProp;
3402 				if(info.length == 1) {
3403 					auto vid = info[0];
3404 					int returned;
3405 					XVisualInfo t;
3406 					t.visualid = vid;
3407 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
3408 					if(got !is null) {
3409 						if(returned == 1) {
3410 							v = got.visual;
3411 							import std.stdio;
3412 							writeln("using special visual ", *got);
3413 						}
3414 						XFree(got);
3415 					}
3416 				}
3417 			}
3418 			+/
3419 
3420 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
3421 			assert(nativeWindow);
3422 
3423 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
3424 
3425 			nativeHandle = nativeWindow;
3426 
3427 			///+
3428 			arch_ulong[2] info;
3429 			info[0] = 0;
3430 			info[1] = 1;
3431 
3432 			string title = this.name is null ? "simpledisplay.d program" : this.name;
3433 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
3434 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
3435 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
3436 
3437 			XChangeProperty(
3438 				display,
3439 				nativeWindow,
3440 				GetAtom!("_XEMBED_INFO", true)(display),
3441 				GetAtom!("_XEMBED_INFO", true)(display),
3442 				32 /* bits */,
3443 				0 /*PropModeReplace*/,
3444 				info.ptr,
3445 				2);
3446 
3447 			import core.sys.posix.unistd;
3448 			arch_ulong pid = getpid();
3449 
3450 			XChangeProperty(
3451 				display,
3452 				nativeWindow,
3453 				GetAtom!("_NET_WM_PID", true)(display),
3454 				XA_CARDINAL,
3455 				32 /* bits */,
3456 				0 /*PropModeReplace*/,
3457 				&pid,
3458 				1);
3459 
3460 			updateNetWmIcon();
3461 
3462 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
3463 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
3464 				XClassHint klass;
3465 				XWMHints wh;
3466 				XSizeHints size;
3467 				klass.res_name = sdpyWindowClassStr;
3468 				klass.res_class = sdpyWindowClassStr;
3469 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
3470 			}
3471 
3472 				// believe it or not, THIS is what xfce needed for the 9999 issue
3473 				XSizeHints sh;
3474 					c_long spr;
3475 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
3476 					sh.flags |= PMaxSize | PMinSize;
3477 				// FIXME maybe nicer resizing
3478 				sh.min_width = 16;
3479 				sh.min_height = 16;
3480 				sh.max_width = 16;
3481 				sh.max_height = 16;
3482 				XSetWMNormalHints(display, nativeWindow, &sh);
3483 
3484 
3485 			//+/
3486 
3487 
3488 			XSelectInput(display, nativeWindow,
3489 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
3490 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
3491 
3492 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
3493 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
3494 			active = true;
3495 		}
3496 
3497 		void updateNetWmIcon() {
3498 			if(img is null) return;
3499 			auto display = XDisplayConnection.get;
3500 			// FIXME: ensure this is correct
3501 			arch_ulong[] buffer;
3502 			auto imgMi = img.toTrueColorImage;
3503 			buffer ~= imgMi.width;
3504 			buffer ~= imgMi.height;
3505 			foreach(c; imgMi.imageData.colors) {
3506 				arch_ulong b;
3507 				b |= c.a << 24;
3508 				b |= c.r << 16;
3509 				b |= c.g << 8;
3510 				b |= c.b;
3511 				buffer ~= b;
3512 			}
3513 
3514 			XChangeProperty(
3515 				display,
3516 				nativeHandle,
3517 				GetAtom!"_NET_WM_ICON"(display),
3518 				GetAtom!"CARDINAL"(display),
3519 				32 /* bits */,
3520 				0 /*PropModeReplace*/,
3521 				buffer.ptr,
3522 				cast(int) buffer.length);
3523 		}
3524 
3525 
3526 
3527 		private SimpleWindow balloon;
3528 		version(with_timer)
3529 		private Timer timer;
3530 
3531 		private Window nativeHandle;
3532 		private Pixmap clippixmap = None;
3533 		private int width = 16;
3534 		private int height = 16;
3535 		private bool active = false;
3536 
3537 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
3538 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
3539 		void delegate () onLeave; /// X11 only.
3540 
3541 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
3542 
3543 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
3544 		void getWindowRect (out int x, out int y, out int width, out int height) {
3545 			if (!active) { width = 1; height = 1; return; } // 1: just in case
3546 			Window dummyw;
3547 			auto dpy = XDisplayConnection.get;
3548 			//XWindowAttributes xwa;
3549 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
3550 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
3551 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
3552 			width = this.width;
3553 			height = this.height;
3554 		}
3555 	}
3556 
3557 	/+
3558 		What I actually want from this:
3559 
3560 		* set / change: icon, tooltip
3561 		* handle: mouse click, right click
3562 		* show: notification bubble.
3563 	+/
3564 
3565 	version(Windows) {
3566 		WindowsIcon win32Icon;
3567 		HWND hwnd;
3568 
3569 		NOTIFYICONDATAW data;
3570 
3571 		NativeEventHandler getNativeEventHandler() {
3572 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
3573 				if(msg == WM_USER) {
3574 					auto event = LOWORD(lParam);
3575 					auto iconId = HIWORD(lParam);
3576 					//auto x = GET_X_LPARAM(wParam);
3577 					//auto y = GET_Y_LPARAM(wParam);
3578 					switch(event) {
3579 						case WM_LBUTTONDOWN:
3580 							onClick()(MouseButton.left);
3581 						break;
3582 						case WM_RBUTTONDOWN:
3583 							onClick()(MouseButton.right);
3584 						break;
3585 						case WM_MBUTTONDOWN:
3586 							onClick()(MouseButton.middle);
3587 						break;
3588 						case WM_MOUSEMOVE:
3589 							// sent, we could use it.
3590 						break;
3591 						case WM_MOUSEWHEEL:
3592 							// NOT SENT
3593 						break;
3594 						//case NIN_KEYSELECT:
3595 						//case NIN_SELECT:
3596 						//break;
3597 						default: {}
3598 					}
3599 				}
3600 				return 0;
3601 			};
3602 		}
3603 
3604 		enum NIF_SHOWTIP = 0x00000080;
3605 
3606 		private static struct NOTIFYICONDATAW {
3607 			DWORD cbSize;
3608 			HWND  hWnd;
3609 			UINT  uID;
3610 			UINT  uFlags;
3611 			UINT  uCallbackMessage;
3612 			HICON hIcon;
3613 			WCHAR[128] szTip;
3614 			DWORD dwState;
3615 			DWORD dwStateMask;
3616 			WCHAR[256] szInfo;
3617 			union {
3618 				UINT uTimeout;
3619 				UINT uVersion;
3620 			}
3621 			WCHAR[64] szInfoTitle;
3622 			DWORD dwInfoFlags;
3623 			GUID  guidItem;
3624 			HICON hBalloonIcon;
3625 		}
3626 
3627 	}
3628 
3629 	/++
3630 		Note that on Windows, only left, right, and middle buttons are sent.
3631 		Mouse wheel buttons are NOT set, so don't rely on those events if your
3632 		program is meant to be used on Windows too.
3633 	+/
3634 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
3635 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
3636 		// but on X, we need an Image, so its canonical ctor is there. They should
3637 		// forward to each other though.
3638 		version(X11) {
3639 			this.name = name;
3640 			this.onClick = onClick;
3641 			createXWin();
3642 			this.icon = icon;
3643 		} else version(Windows) {
3644 			this.onClick = onClick;
3645 			this.win32Icon = new WindowsIcon(icon);
3646 
3647 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
3648 
3649 			static bool registered = false;
3650 			if(!registered) {
3651 				WNDCLASSEX wc;
3652 				wc.cbSize = wc.sizeof;
3653 				wc.hInstance = hInstance;
3654 				wc.lpfnWndProc = &WndProc;
3655 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
3656 				if(!RegisterClassExW(&wc))
3657 					throw new WindowsApiException("RegisterClass");
3658 				registered = true;
3659 			}
3660 
3661 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
3662 			if(hwnd is null)
3663 				throw new Exception("CreateWindow");
3664 
3665 			data.cbSize = data.sizeof;
3666 			data.hWnd = hwnd;
3667 			data.uID = cast(uint) cast(void*) this;
3668 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
3669 				// NIF_INFO means show balloon
3670 			data.uCallbackMessage = WM_USER;
3671 			data.hIcon = this.win32Icon.hIcon;
3672 			data.szTip = ""; // FIXME
3673 			data.dwState = 0; // NIS_HIDDEN; // windows vista
3674 			data.dwStateMask = NIS_HIDDEN; // windows vista
3675 
3676 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
3677 
3678 
3679 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
3680 
3681 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
3682 		} else version(OSXCocoa) {
3683 			throw new NotYetImplementedException();
3684 		} else static assert(0);
3685 	}
3686 
3687 	/// ditto
3688 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
3689 		version(X11) {
3690 			this.onClick = onClick;
3691 			this.name = name;
3692 			createXWin();
3693 			this.icon = icon;
3694 		} else version(Windows) {
3695 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
3696 		} else version(OSXCocoa) {
3697 			throw new NotYetImplementedException();
3698 		} else static assert(0);
3699 	}
3700 
3701 	version(X11) {
3702 		/++
3703 			X-specific extension (for now at least)
3704 		+/
3705 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
3706 			this.onClickEx = onClickEx;
3707 			createXWin();
3708 			if (icon !is null) this.icon = icon;
3709 		}
3710 
3711 		/// ditto
3712 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
3713 			this.onClickEx = onClickEx;
3714 			createXWin();
3715 			this.icon = icon;
3716 		}
3717 	}
3718 
3719 	private void delegate (MouseButton button) onClick_;
3720 
3721 	///
3722 	@property final void delegate(MouseButton) onClick() {
3723 		if(onClick_ is null)
3724 			onClick_ = delegate void(MouseButton) {};
3725 		return onClick_;
3726 	}
3727 
3728 	/// ditto
3729 	@property final void onClick(void delegate(MouseButton) handler) {
3730 		// I made this a property setter so we can wrap smaller arg
3731 		// delegates and just forward all to onClickEx or something.
3732 		onClick_ = handler;
3733 	}
3734 
3735 
3736 	string name_;
3737 	@property void name(string n) {
3738 		name_ = n;
3739 	}
3740 
3741 	@property string name() {
3742 		return name_;
3743 	}
3744 
3745 	private MemoryImage originalMemoryImage;
3746 
3747 	///
3748 	@property void icon(MemoryImage i) {
3749 		version(X11) {
3750 			this.originalMemoryImage = i;
3751 			if (!active) return;
3752 			if (i !is null) {
3753 				this.img = Image.fromMemoryImage(i);
3754 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
3755 				//import std.stdio; writeln("using pixmap ", clippixmap);
3756 				updateNetWmIcon();
3757 				redraw();
3758 			} else {
3759 				if (this.img !is null) {
3760 					this.img = null;
3761 					redraw();
3762 				}
3763 			}
3764 		} else version(Windows) {
3765 			this.win32Icon = new WindowsIcon(i);
3766 
3767 			data.uFlags = NIF_ICON;
3768 			data.hIcon = this.win32Icon.hIcon;
3769 
3770 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
3771 		} else version(OSXCocoa) {
3772 			throw new NotYetImplementedException();
3773 		} else static assert(0);
3774 	}
3775 
3776 	/// ditto
3777 	@property void icon (Image i) {
3778 		version(X11) {
3779 			if (!active) return;
3780 			if (i !is img) {
3781 				originalMemoryImage = null;
3782 				img = i;
3783 				redraw();
3784 			}
3785 		} else version(Windows) {
3786 			this.icon(i is null ? null : i.toTrueColorImage());
3787 		} else version(OSXCocoa) {
3788 			throw new NotYetImplementedException();
3789 		} else static assert(0);
3790 	}
3791 
3792 	/++
3793 		Shows a balloon notification. You can only show one balloon at a time, if you call
3794 		it twice while one is already up, the first balloon will be replaced.
3795 		
3796 		
3797 		The user is free to block notifications and they will automatically disappear after
3798 		a timeout period.
3799 
3800 		Params:
3801 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
3802 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
3803 			icon = the icon to display with the notification. If null, it uses your existing icon.
3804 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
3805 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
3806 	+/
3807 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
3808 		bool useCustom = true;
3809 		version(libnotify) {
3810 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
3811 			try {
3812 				if(!active) return;
3813 
3814 				if(libnotify is null) {
3815 					libnotify = new C_DynamicLibrary("libnotify.so");
3816 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
3817 				}
3818 
3819 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
3820 
3821 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
3822 
3823 				if(onclick) {
3824 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
3825 					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);
3826 					libnotify_action_delegates_count++;
3827 				}
3828 
3829 				// FIXME icon
3830 
3831 				// set hint image-data
3832 				// set default action for onclick
3833 
3834 				void* error;
3835 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
3836 
3837 				useCustom = false;
3838 			} catch(Exception e) {
3839 
3840 			}
3841 		}
3842 		
3843 		version(X11) {
3844 		if(useCustom) {
3845 			if(!active) return;
3846 			if(balloon) {
3847 				hideBalloon();
3848 			}
3849 			// I know there are two specs for this, but one is never
3850 			// implemented by any window manager I have ever seen, and
3851 			// the other is a bloated mess and too complicated for simpledisplay...
3852 			// so doing my own little window instead.
3853 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
3854 
3855 			int x, y, width, height;
3856 			getWindowRect(x, y, width, height);
3857 
3858 			int bx = x - balloon.width;
3859 			int by = y - balloon.height;
3860 			if(bx < 0)
3861 				bx = x + width + balloon.width;
3862 			if(by < 0)
3863 				by = y + height;
3864 
3865 			// just in case, make sure it is actually on scren
3866 			if(bx < 0)
3867 				bx = 0;
3868 			if(by < 0)
3869 				by = 0;
3870 
3871 			balloon.move(bx, by);
3872 			auto painter = balloon.draw();
3873 			painter.fillColor = Color(220, 220, 220);
3874 			painter.outlineColor = Color.black;
3875 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
3876 			auto iconWidth = icon is null ? 0 : icon.width;
3877 			if(icon)
3878 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
3879 			iconWidth += 6; // margin around the icon
3880 
3881 			// draw a close button
3882 			painter.outlineColor = Color(44, 44, 44);
3883 			painter.fillColor = Color(255, 255, 255);
3884 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
3885 			painter.pen = Pen(Color.black, 3);
3886 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
3887 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
3888 			painter.pen = Pen(Color.black, 1);
3889 			painter.fillColor = Color(220, 220, 220);
3890 
3891 			// Draw the title and message
3892 			painter.drawText(Point(4 + iconWidth, 4), title);
3893 			painter.drawLine(
3894 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
3895 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
3896 			);
3897 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
3898 
3899 			balloon.setEventHandlers(
3900 				(MouseEvent ev) {
3901 					if(ev.type == MouseEventType.buttonPressed) {
3902 						if(ev.x > balloon.width - 16 && ev.y < 16)
3903 							hideBalloon();
3904 						else if(onclick)
3905 							onclick();
3906 					}
3907 				}
3908 			);
3909 			balloon.show();
3910 
3911 			version(with_timer)
3912 			timer = new Timer(timeout, &hideBalloon);
3913 			else {} // FIXME
3914 		}
3915 		} else version(Windows) {
3916 			enum NIF_INFO = 0x00000010;
3917 
3918 			data.uFlags = NIF_INFO;
3919 
3920 			// FIXME: go back to the last valid unicode code point
3921 			if(title.length > 40)
3922 				title = title[0 .. 40];
3923 			if(message.length > 220)
3924 				message = message[0 .. 220];
3925 
3926 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
3927 			enum NIIF_LARGE_ICON  = 0x00000020;
3928 			enum NIIF_NOSOUND = 0x00000010;
3929 			enum NIIF_USER = 0x00000004;
3930 			enum NIIF_ERROR = 0x00000003;
3931 			enum NIIF_WARNING = 0x00000002;
3932 			enum NIIF_INFO = 0x00000001;
3933 			enum NIIF_NONE = 0;
3934 
3935 			WCharzBuffer t = WCharzBuffer(title);
3936 			WCharzBuffer m = WCharzBuffer(message);
3937 
3938 			t.copyInto(data.szInfoTitle);
3939 			m.copyInto(data.szInfo);
3940 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
3941 
3942 			if(icon !is null) {
3943 				auto i = new WindowsIcon(icon);
3944 				data.hBalloonIcon = i.hIcon;
3945 				data.dwInfoFlags |= NIIF_USER;
3946 			}
3947 
3948 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
3949 		} else version(OSXCocoa) {
3950 			throw new NotYetImplementedException();
3951 		} else static assert(0);
3952 	}
3953 
3954 	///
3955 	//version(Windows)
3956 	void show() {
3957 		version(X11) {
3958 			if(!hidden)
3959 				return;
3960 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
3961 			hidden = false;
3962 		} else version(Windows) {
3963 			data.uFlags = NIF_STATE;
3964 			data.dwState = 0; // NIS_HIDDEN; // windows vista
3965 			data.dwStateMask = NIS_HIDDEN; // windows vista
3966 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
3967 		} else version(OSXCocoa) {
3968 			throw new NotYetImplementedException();
3969 		} else static assert(0);
3970 	}
3971 
3972 	version(X11)
3973 		bool hidden = false;
3974 
3975 	///
3976 	//version(Windows)
3977 	void hide() {
3978 		version(X11) {
3979 			if(hidden)
3980 				return;
3981 			hidden = true;
3982 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
3983 		} else version(Windows) {
3984 			data.uFlags = NIF_STATE;
3985 			data.dwState = NIS_HIDDEN; // windows vista
3986 			data.dwStateMask = NIS_HIDDEN; // windows vista
3987 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
3988 		} else version(OSXCocoa) {
3989 			throw new NotYetImplementedException();
3990 		} else static assert(0);
3991 	}
3992 
3993 	///
3994 	void close () {
3995 		version(X11) {
3996 			if (active) {
3997 				active = false; // event handler will set this too, but meh
3998 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
3999 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
4000 				flushGui();
4001 			}
4002 		} else version(Windows) {
4003 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
4004 		} else version(OSXCocoa) {
4005 			throw new NotYetImplementedException();
4006 		} else static assert(0);
4007 	}
4008 
4009 	~this() {
4010 		version(X11)
4011 			if(clippixmap != None)
4012 				XFreePixmap(XDisplayConnection.get, clippixmap);
4013 		close();
4014 	}
4015 }
4016 
4017 version(X11)
4018 /// call XFreePixmap on the return value
4019 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
4020 	char[] data = new char[](i.width * i.height / 8 + 2);
4021 	data[] = 0;
4022 
4023 	int bitOffset = 0;
4024 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
4025 		ubyte v = c.a > 128 ? 1 : 0;
4026 		data[bitOffset / 8] |= v << (bitOffset%8);
4027 		bitOffset++;
4028 	}
4029 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
4030 	return handle;
4031 }
4032 
4033 
4034 // basic functions to make timers
4035 /**
4036 	A timer that will trigger your function on a given interval.
4037 
4038 
4039 	You create a timer with an interval and a callback. It will continue
4040 	to fire on the interval until it is destroyed.
4041 
4042 	There are currently no one-off timers (instead, just create one and
4043 	destroy it when it is triggered) nor are there pause/resume functions -
4044 	the timer must again be destroyed and recreated if you want to pause it.
4045 
4046 	auto timer = new Timer(50, { it happened!; });
4047 	timer.destroy();
4048 
4049 	Timers can only be expected to fire when the event loop is running.
4050 */
4051 version(with_timer) {
4052 class Timer {
4053 // FIXME: needs pause and unpause
4054 	// FIXME: I might add overloads for ones that take a count of
4055 	// how many elapsed since last time (on Windows, it will divide
4056 	// the ticks thing given, on Linux it is just available) and
4057 	// maybe one that takes an instance of the Timer itself too
4058 	/// Create a timer with a callback when it triggers.
4059 	this(int intervalInMilliseconds, void delegate() onPulse) {
4060 		assert(onPulse !is null);
4061 
4062 		this.onPulse = onPulse;
4063 
4064 		version(Windows) {
4065 			/*
4066 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
4067 			if(handle == 0)
4068 				throw new Exception("SetTimer fail");
4069 			*/
4070 
4071 			// thanks to Archival 998 for the WaitableTimer blocks
4072 			handle = CreateWaitableTimer(null, false, null);
4073 			long initialTime = 0;
4074 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
4075 				throw new Exception("SetWaitableTimer Failed");
4076 
4077 			mapping[handle] = this;
4078 
4079 		} else version(linux) {
4080 			static import ep = core.sys.linux.epoll;
4081 
4082 			import core.sys.linux.timerfd;
4083 
4084 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
4085 			if(fd == -1)
4086 				throw new Exception("timer create failed");
4087 
4088 			mapping[fd] = this;
4089 
4090 			itimerspec value;
4091 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
4092 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
4093 
4094 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
4095 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
4096 
4097 			if(timerfd_settime(fd, 0, &value, null) == -1)
4098 				throw new Exception("couldn't make pulse timer");
4099 
4100 			version(with_eventloop) {
4101 				import arsd.eventloop;
4102 				addFileEventListeners(fd, &trigger, null, null);
4103 			} else {
4104 				prepareEventLoop();
4105 
4106 				ep.epoll_event ev = void;
4107 				ev.events = ep.EPOLLIN;
4108 				ev.data.fd = fd;
4109 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
4110 			}
4111 		} else featureNotImplemented();
4112 	}
4113 
4114 	/// Stop and destroy the timer object.
4115 	void destroy() {
4116 		version(Windows) {
4117 			if(handle) {
4118 				// KillTimer(null, handle);
4119 				CancelWaitableTimer(cast(void*)handle);
4120 				mapping.remove(handle);
4121 				CloseHandle(handle);
4122 				handle = null;
4123 			}
4124 		} else version(linux) {
4125 			if(fd != -1) {
4126 				import unix = core.sys.posix.unistd;
4127 				static import ep = core.sys.linux.epoll;
4128 
4129 				version(with_eventloop) {
4130 					import arsd.eventloop;
4131 					removeFileEventListeners(fd);
4132 				} else {
4133 					ep.epoll_event ev = void;
4134 					ev.events = ep.EPOLLIN;
4135 					ev.data.fd = fd;
4136 
4137 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
4138 				}
4139 				unix.close(fd);
4140 				mapping.remove(fd);
4141 				fd = -1;
4142 			}
4143 		} else featureNotImplemented();
4144 	}
4145 
4146 	~this() {
4147 		destroy();
4148 	}
4149 
4150 
4151 	void changeTime(int intervalInMilliseconds)
4152 	{
4153 		version(Windows)
4154 		{
4155 			if(handle)
4156 			{
4157 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
4158 				long initialTime = 0;
4159 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
4160 					throw new Exception("couldn't change pulse timer");
4161 			}
4162 		}
4163 	}
4164 
4165 
4166 	private:
4167 
4168 	void delegate() onPulse;
4169 
4170 	void trigger() {
4171 		version(linux) {
4172 			import unix = core.sys.posix.unistd;
4173 			long val;
4174 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
4175 		} else version(Windows) {
4176 
4177 		} else featureNotImplemented();
4178 
4179 		onPulse();
4180 	}
4181 
4182 	version(Windows)
4183 		extern(Windows)
4184 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
4185 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
4186 			if(Timer* t = timer in mapping) {
4187 				try
4188 				(*t).trigger();
4189 				catch(Exception e) { throw new Error(e.msg, e.file, e.line); }
4190 			}
4191 		}
4192 
4193 	version(Windows) {
4194 		//UINT_PTR handle;
4195 		//static Timer[UINT_PTR] mapping;
4196 		HANDLE handle;
4197 		__gshared Timer[HANDLE] mapping;
4198 	} else version(linux) {
4199 		int fd = -1;
4200 		__gshared Timer[int] mapping;
4201 	} else static assert(0, "timer not supported");
4202 }
4203 }
4204 
4205 version(Windows)
4206 /// 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.)
4207 class WindowsHandleReader {
4208 	///
4209 	this(void delegate() onReady, HANDLE handle) {
4210 		this.onReady = onReady;
4211 		this.handle = handle;
4212 
4213 		mapping[handle] = this;
4214 
4215 		enable();
4216 	}
4217 
4218 	///
4219 	void enable() {
4220 		auto el = EventLoop.get().impl;
4221 		el.handles ~= handle;
4222 	}
4223 
4224 	///
4225 	void disable() {
4226 		auto el = EventLoop.get().impl;
4227 		for(int i = 0; i < el.handles.length; i++) {
4228 			if(el.handles[i] is handle) {
4229 				el.handles[i] = el.handles[$-1];
4230 				el.handles = el.handles[0 .. $-1];
4231 				return;
4232 			}
4233 		}
4234 	}
4235 
4236 	void dispose() {
4237 		disable();
4238 		mapping.remove(handle);
4239 		handle = null;
4240 	}
4241 
4242 	void ready() {
4243 		if(onReady)
4244 			onReady();
4245 	}
4246 
4247 	HANDLE handle;
4248 	void delegate() onReady;
4249 
4250 	__gshared WindowsHandleReader[HANDLE] mapping;
4251 }
4252 
4253 version(linux)
4254 /// Lets you add files to the event loop for reading. Use at your own risk.
4255 class PosixFdReader {
4256 	///
4257 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4258 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
4259 	}
4260 
4261 	///
4262 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4263 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
4264 	}
4265 
4266 	///
4267 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4268 		this.onReady = onReady;
4269 		this.fd = fd;
4270 		this.captureWrites = captureWrites;
4271 		this.captureReads = captureReads;
4272 
4273 		mapping[fd] = this;
4274 
4275 		version(with_eventloop) {
4276 			import arsd.eventloop;
4277 			addFileEventListeners(fd, &readyel);
4278 		} else {
4279 			enable();
4280 		}
4281 	}
4282 
4283 	bool captureReads;
4284 	bool captureWrites;
4285 
4286 	version(with_eventloop) {} else
4287 	///
4288 	void enable() {
4289 		prepareEventLoop();
4290 
4291 		static import ep = core.sys.linux.epoll;
4292 		ep.epoll_event ev = void;
4293 		ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
4294 		//import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites);
4295 		ev.data.fd = fd;
4296 		ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
4297 	}
4298 
4299 	version(with_eventloop) {} else
4300 	///
4301 	void disable() {
4302 		prepareEventLoop();
4303 
4304 		static import ep = core.sys.linux.epoll;
4305 		ep.epoll_event ev = void;
4306 		ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
4307 		//import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites);
4308 		ev.data.fd = fd;
4309 		ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
4310 	}
4311 
4312 	version(with_eventloop) {} else
4313 	///
4314 	void dispose() {
4315 		disable();
4316 		mapping.remove(fd);
4317 		fd = -1;
4318 	}
4319 
4320 	void delegate(int, bool, bool) onReady;
4321 
4322 	version(with_eventloop)
4323 	void readyel() {
4324 		onReady(fd, true, true);
4325 	}
4326 
4327 	void ready(uint flags) {
4328 		static import ep = core.sys.linux.epoll;
4329 		onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
4330 	}
4331 
4332 	void hup(uint flags) {
4333 		if(onHup)
4334 			onHup();
4335 	}
4336 
4337 	void delegate() onHup;
4338 
4339 	int fd = -1;
4340 	__gshared PosixFdReader[int] mapping;
4341 }
4342 
4343 // basic functions to access the clipboard
4344 /+
4345 
4346 
4347 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
4348 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
4349 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
4350 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
4351 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
4352 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
4353 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
4354 
4355 +/
4356 
4357 /++
4358 	this does a delegate because it is actually an async call on X...
4359 	the receiver may never be called if the clipboard is empty or unavailable
4360 	gets plain text from the clipboard
4361 +/
4362 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
4363 	version(Windows) {
4364 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
4365 		if(OpenClipboard(hwndOwner) == 0)
4366 			throw new Exception("OpenClipboard");
4367 		scope(exit)
4368 			CloseClipboard();
4369 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
4370 
4371 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
4372 				scope(exit)
4373 					GlobalUnlock(dataHandle);
4374 
4375 				// FIXME: CR/LF conversions
4376 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
4377 				int len = 0;
4378 				auto d = data;
4379 				while(*d) {
4380 					d++;
4381 					len++;
4382 				}
4383 				string s;
4384 				s.reserve(len);
4385 				foreach(dchar ch; data[0 .. len]) {
4386 					s ~= ch;
4387 				}
4388 				receiver(s);
4389 			}
4390 		}
4391 	} else version(X11) {
4392 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
4393 	} else version(OSXCocoa) {
4394 		throw new NotYetImplementedException();
4395 	} else static assert(0);
4396 }
4397 
4398 version(Windows)
4399 struct WCharzBuffer {
4400 	wchar[256] staticBuffer;
4401 	wchar[] buffer;
4402 
4403 	size_t length() {
4404 		return buffer.length;
4405 	}
4406 
4407 	wchar* ptr() {
4408 		return buffer.ptr;
4409 	}
4410 
4411 	wchar[] slice() {
4412 		return buffer;
4413 	}
4414 
4415 	void copyInto(R)(ref R r) {
4416 		static if(is(R == wchar[N], size_t N)) {
4417 			r[0 .. this.length] = slice[];
4418 			r[this.length] = 0;
4419 		} else static assert(0, "can only copy into wchar[n], not " ~ R.stringof);
4420 	}
4421 
4422 	/++
4423 		conversionFlags = [WindowsStringConversionFlags]
4424 	+/
4425 	this(in char[] data, int conversionFlags = 0) {
4426 		conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name
4427 		auto sz = sizeOfConvertedWstring(data, conversionFlags);
4428 		if(sz > staticBuffer.length)
4429 			buffer = new wchar[](sz);
4430 		else
4431 			buffer = staticBuffer[];
4432 
4433 		buffer = makeWindowsString(data, buffer, conversionFlags);
4434 	}
4435 }
4436 
4437 version(Windows)
4438 int sizeOfConvertedWstring(in char[] s, int conversionFlags) {
4439 	int size = 0;
4440 
4441 	if(conversionFlags & WindowsStringConversionFlags.convertNewLines) {
4442 		// need to convert line endings, which means the length will get bigger.
4443 
4444 		// BTW I betcha this could be faster with some simd stuff.
4445 		char last;
4446 		foreach(char ch; s) {
4447 			if(ch == 10 && last != 13)
4448 				size++; // will add a 13 before it...
4449 			size++;
4450 			last = ch;
4451 		}
4452 	} else {
4453 		// no conversion necessary, just estimate based on length
4454 		/*
4455 			I don't think there's any string with a longer length
4456 			in code units when encoded in UTF-16 than it has in UTF-8.
4457 			This will probably over allocate, but that's OK.
4458 		*/
4459 		size = cast(int) s.length;
4460 	}
4461 
4462 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate)
4463 		size++;
4464 
4465 	return size;
4466 }
4467 
4468 version(Windows)
4469 enum WindowsStringConversionFlags : int {
4470 	zeroTerminate = 1,
4471 	convertNewLines = 2,
4472 }
4473 
4474 version(Windows)
4475 class WindowsApiException : Exception {
4476 	char[256] buffer;
4477 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
4478 		assert(msg.length < 100);
4479 
4480 		auto error = GetLastError();
4481 		buffer[0 .. msg.length] = msg;
4482 		buffer[msg.length] = ' ';
4483 
4484 		int pos = cast(int) msg.length + 1;
4485 
4486 		if(error == 0)
4487 			buffer[pos++] = '0';
4488 		else {
4489 			auto init = pos;
4490 			while(error) {
4491 				buffer[pos++] = (error % 10) + '0';
4492 				error /= 10;
4493 			}
4494 			for(int i = 0; i < (pos - init) / 2; i++) {
4495 				char c = buffer[i + init];
4496 				buffer[i + init] = buffer[pos - (i + init) - 1];
4497 				buffer[pos - (i + init) - 1] = c;
4498 			}
4499 		}
4500 
4501 
4502 		super(cast(string) buffer[0 .. pos], file, line, next);
4503 	}
4504 }
4505 
4506 class ErrnoApiException : Exception {
4507 	char[256] buffer;
4508 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
4509 		assert(msg.length < 100);
4510 
4511 		import core.stdc.errno;
4512 		auto error = errno;
4513 		buffer[0 .. msg.length] = msg;
4514 		buffer[msg.length] = ' ';
4515 
4516 		int pos = cast(int) msg.length + 1;
4517 
4518 		if(error == 0)
4519 			buffer[pos++] = '0';
4520 		else {
4521 			auto init = pos;
4522 			while(error) {
4523 				buffer[pos++] = (error % 10) + '0';
4524 				error /= 10;
4525 			}
4526 			for(int i = 0; i < (pos - init) / 2; i++) {
4527 				char c = buffer[i + init];
4528 				buffer[i + init] = buffer[pos - (i + init) - 1];
4529 				buffer[pos - (i + init) - 1] = c;
4530 			}
4531 		}
4532 
4533 
4534 		super(cast(string) buffer[0 .. pos], file, line, next);
4535 	}
4536 
4537 }
4538 
4539 version(Windows)
4540 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) {
4541 	if(str.length == 0)
4542 		return null;
4543 
4544 	int pos = 0;
4545 	dchar last;
4546 	foreach(dchar c; str) {
4547 		if(c <= 0xFFFF) {
4548 			if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13)
4549 				buffer[pos++] = 13;
4550 			buffer[pos++] = cast(wchar) c;
4551 		} else if(c <= 0x10FFFF) {
4552 			buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
4553 			buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
4554 		}
4555 
4556 		last = c;
4557 	}
4558 
4559 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) {
4560 		buffer[pos] = 0;
4561 	}
4562 
4563 	return buffer[0 .. pos];
4564 }
4565 
4566 version(Windows)
4567 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
4568 	if(str.length == 0)
4569 		return null;
4570 
4571 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null);
4572 	if(got == 0) {
4573 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
4574 			throw new Exception("not enough buffer");
4575 		else
4576 			throw new Exception("conversion"); // FIXME: GetLastError
4577 	}
4578 	return buffer[0 .. got];
4579 }
4580 
4581 version(Windows)
4582 string makeUtf8StringFromWindowsString(in wchar[] str) {
4583 	char[] buffer;
4584 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null);
4585 	buffer.length = got;
4586 
4587 	// it is unique because we just allocated it above!
4588 	return cast(string) makeUtf8StringFromWindowsString(str, buffer);
4589 }
4590 
4591 version(Windows)
4592 string makeUtf8StringFromWindowsString(wchar* str) {
4593 	char[] buffer;
4594 	auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null);
4595 	buffer.length = got;
4596 
4597 	got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null);
4598 	if(got == 0) {
4599 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
4600 			throw new Exception("not enough buffer");
4601 		else
4602 			throw new Exception("conversion"); // FIXME: GetLastError
4603 	}
4604 	return cast(string) buffer[0 .. got];
4605 }
4606 
4607 int findIndexOfZero(in wchar[] str) {
4608 	foreach(idx, wchar ch; str)
4609 		if(ch == 0)
4610 			return cast(int) idx;
4611 	return cast(int) str.length;
4612 }
4613 int findIndexOfZero(in char[] str) {
4614 	foreach(idx, char ch; str)
4615 		if(ch == 0)
4616 			return cast(int) idx;
4617 	return cast(int) str.length;
4618 }
4619 
4620 /// copies some text to the clipboard
4621 void setClipboardText(SimpleWindow clipboardOwner, string text) {
4622 	assert(clipboardOwner !is null);
4623 	version(Windows) {
4624 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
4625 			throw new Exception("OpenClipboard");
4626 		scope(exit)
4627 			CloseClipboard();
4628 		EmptyClipboard();
4629 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
4630 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
4631 		if(handle is null) throw new Exception("GlobalAlloc");
4632 		if(auto data = cast(wchar*) GlobalLock(handle)) {
4633 			auto slice = data[0 .. sz];
4634 			scope(failure)
4635 				GlobalUnlock(handle);
4636 
4637 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
4638 
4639 			GlobalUnlock(handle);
4640 			SetClipboardData(CF_UNICODETEXT, handle);
4641 		}
4642 	} else version(X11) {
4643 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
4644 	} else version(OSXCocoa) {
4645 		throw new NotYetImplementedException();
4646 	} else static assert(0);
4647 }
4648 
4649 // 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.
4650 
4651 version(X11) {
4652 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
4653 
4654 	private Atom*[] interredAtoms; // for discardAndRecreate
4655 
4656 	/// Platform specific for X11
4657 	@property Atom GetAtom(string name, bool create = false)(Display* display) {
4658 		static Atom a;
4659 		if(!a) {
4660 			a = XInternAtom(display, name, !create);
4661 			interredAtoms ~= &a;
4662 		}
4663 		if(a == None)
4664 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
4665 		return a;
4666 	}
4667 
4668 	/// Platform specific for X11 - gets atom names as a string
4669 	string getAtomName(Atom atom, Display* display) {
4670 		auto got = XGetAtomName(display, atom);
4671 		scope(exit) XFree(got);
4672 		import core.stdc.string;
4673 		string s = got[0 .. strlen(got)].idup;
4674 		return s;
4675 	}
4676 
4677 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later
4678 	void setPrimarySelection(SimpleWindow window, string text) {
4679 		setX11Selection!"PRIMARY"(window, text);
4680 	}
4681 
4682 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later
4683 	void setSecondarySelection(SimpleWindow window, string text) {
4684 		setX11Selection!"SECONDARY"(window, text);
4685 	}
4686 
4687 	/// The `after` delegate is called after a client requests the UTF8_STRING thing. it is a mega-hack right now!
4688 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
4689 		assert(window !is null);
4690 
4691 		auto display = XDisplayConnection.get();
4692 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
4693 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
4694 		else Atom a = GetAtom!atomName(display);
4695 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
4696 		window.impl.setSelectionHandler = (XEvent ev) {
4697 			XSelectionRequestEvent* event = &ev.xselectionrequest;
4698 			XSelectionEvent selectionEvent;
4699 			selectionEvent.type = EventType.SelectionNotify;
4700 			selectionEvent.display = event.display;
4701 			selectionEvent.requestor = event.requestor;
4702 			selectionEvent.selection = event.selection;
4703 			selectionEvent.time = event.time;
4704 			selectionEvent.target = event.target;
4705 
4706 			if(event.property == None)
4707 				selectionEvent.property = event.target;
4708 			if(event.target == GetAtom!"TARGETS"(display)) {
4709 				/* respond with the supported types */
4710 				Atom[3] tlist;// = [XA_UTF8, XA_STRING, XA_TARGETS];
4711 				tlist[0] = GetAtom!"UTF8_STRING"(display);
4712 				tlist[1] = XA_STRING;
4713 				tlist[2] = GetAtom!"TARGETS"(display);
4714 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, 3);
4715 				selectionEvent.property = event.property;
4716 			} else if(event.target == XA_STRING) {
4717 				selectionEvent.property = event.property;
4718 				XChangeProperty (display,
4719 					selectionEvent.requestor,
4720 					selectionEvent.property,
4721 					event.target,
4722 					8 /* bits */, 0 /* PropModeReplace */,
4723 					text.ptr, cast(int) text.length);
4724 			} else if(event.target == GetAtom!"UTF8_STRING"(display)) {
4725 				selectionEvent.property = event.property;
4726 				XChangeProperty (display,
4727 					selectionEvent.requestor,
4728 					selectionEvent.property,
4729 					event.target,
4730 					8 /* bits */, 0 /* PropModeReplace */,
4731 					text.ptr, cast(int) text.length);
4732 
4733 				if(after)
4734 					after();
4735 			} else {
4736 				selectionEvent.property = None; // I don't know how to handle this type...
4737 			}
4738 
4739 			XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
4740 		};
4741 	}
4742 
4743 	///
4744 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
4745 		getX11Selection!"PRIMARY"(window, handler);
4746 	}
4747 
4748 	///
4749 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler) {
4750 		assert(window !is null);
4751 
4752 		auto display = XDisplayConnection.get();
4753 		auto atom = GetAtom!atomName(display);
4754 
4755 		window.impl.getSelectionHandler = handler;
4756 
4757 		auto target = GetAtom!"TARGETS"(display);
4758 
4759 		// SDD_DATA is "simpledisplay.d data"
4760 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
4761 	}
4762 
4763 	///
4764 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
4765 		Atom actualType;
4766 		int actualFormat;
4767 		arch_ulong actualItems;
4768 		arch_ulong bytesRemaining;
4769 		void* data;
4770 
4771 		auto display = XDisplayConnection.get();
4772 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
4773 			if(actualFormat == 0)
4774 				return null;
4775 			else {
4776 				int byteLength;
4777 				if(actualFormat == 32) {
4778 					// 32 means it is a C long... which is variable length
4779 					actualFormat = cast(int) arch_long.sizeof * 8;
4780 				}
4781 
4782 				// then it is just a bit count
4783 				byteLength = cast(int) (actualItems * actualFormat / 8);
4784 
4785 				auto d = new ubyte[](byteLength);
4786 				d[] = cast(ubyte[]) data[0 .. byteLength];
4787 				XFree(data);
4788 				return d;
4789 			}
4790 		}
4791 		return null;
4792 	}
4793 
4794 	/* defined in the systray spec */
4795 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
4796 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
4797 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
4798 
4799 
4800 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
4801 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
4802 	public class GlobalHotkey {
4803 		KeyEvent key;
4804 		void delegate () handler;
4805 
4806 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
4807 
4808 		/// Create from initialzed KeyEvent object
4809 		this (KeyEvent akey, void delegate () ahandler=null) {
4810 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
4811 			key = akey;
4812 			handler = ahandler;
4813 		}
4814 
4815 		/// Create from emacs-like key name ("C-M-Y", etc.)
4816 		this (const(char)[] akey, void delegate () ahandler=null) {
4817 			key = KeyEvent.parse(akey);
4818 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
4819 			handler = ahandler;
4820 		}
4821 
4822 	}
4823 
4824 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
4825 		//conwriteln("failed to grab key");
4826 		GlobalHotkeyManager.ghfailed = true;
4827 		return 0;
4828 	}
4829 
4830 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
4831 		import core.stdc.stdio;
4832 		char[265] buffer;
4833 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
4834 		printf("ERROR: %s\n", buffer.ptr);
4835 		return 0;
4836 	}
4837 
4838 	/++
4839 		Global hotkey manager. It contains static methods to manage global hotkeys.
4840 
4841 		---
4842 		 try {
4843 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
4844 		} catch (Exception e) {
4845 			conwriteln("ERROR registering hotkey!");
4846 		}
4847 		---
4848 
4849 		The key strings are based on Emacs. In practical terms,
4850 		`M` means `alt` and `H` means the Windows logo key. `C`
4851 		is `ctrl`.
4852 
4853 		$(WARNING
4854 			This is X-specific right now. If you are on
4855 			Windows, try [registerHotKey] instead.
4856 
4857 			We will probably merge these into a single
4858 			interface later.
4859 		)
4860 	+/
4861 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
4862 		version(X11) {
4863 			void recreateAfterDisconnect() {
4864 				throw new Exception("NOT IMPLEMENTED");
4865 			}
4866 			void discardConnectionState() {
4867 				throw new Exception("NOT IMPLEMENTED");
4868 			}
4869 		}
4870 
4871 		private static immutable uint[8] masklist = [ 0,
4872 			KeyOrButtonMask.LockMask,
4873 			KeyOrButtonMask.Mod2Mask,
4874 			KeyOrButtonMask.Mod3Mask,
4875 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
4876 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
4877 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
4878 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
4879 		];
4880 		private __gshared GlobalHotkeyManager ghmanager;
4881 		private __gshared bool ghfailed = false;
4882 
4883 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
4884 			if (modmask == 0) return false;
4885 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
4886 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
4887 			return true;
4888 		}
4889 
4890 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
4891 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
4892 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
4893 			return modmask;
4894 		}
4895 
4896 		private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) {
4897 			uint keycode = cast(uint)ke.key;
4898 			auto dpy = XDisplayConnection.get;
4899 			return XKeysymToKeycode(dpy, keycode);
4900 		}
4901 
4902 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
4903 
4904 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
4905 
4906 		NativeEventHandler getNativeEventHandler () {
4907 			return delegate int (XEvent e) {
4908 				if (e.type != EventType.KeyPress) return 1;
4909 				auto kev = cast(const(XKeyEvent)*)&e;
4910 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
4911 				if (auto ghkp = hash in globalHotkeyList) {
4912 					try {
4913 						ghkp.doHandle();
4914 					} catch (Exception e) {
4915 						import core.stdc.stdio : stderr, fprintf;
4916 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
4917 					}
4918 				}
4919 				return 1;
4920 			};
4921 		}
4922 
4923 		private this () {
4924 			auto dpy = XDisplayConnection.get;
4925 			auto root = RootWindow(dpy, DefaultScreen(dpy));
4926 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
4927 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
4928 		}
4929 
4930 		/// Register new global hotkey with initialized `GlobalHotkey` object.
4931 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
4932 		static void register (GlobalHotkey gh) {
4933 			if (gh is null) return;
4934 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
4935 
4936 			auto dpy = XDisplayConnection.get;
4937 			immutable keycode = keyEvent2KeyCode(gh.key);
4938 
4939 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
4940 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
4941 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
4942 			XSync(dpy, 0/*False*/);
4943 
4944 			Window root = RootWindow(dpy, DefaultScreen(dpy));
4945 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
4946 			ghfailed = false;
4947 			foreach (immutable uint ormask; masklist[]) {
4948 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
4949 			}
4950 			XSync(dpy, 0/*False*/);
4951 			XSetErrorHandler(savedErrorHandler);
4952 
4953 			if (ghfailed) {
4954 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
4955 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
4956 				XSync(dpy, 0/*False*/);
4957 				XSetErrorHandler(savedErrorHandler);
4958 				throw new Exception("cannot register global hotkey");
4959 			}
4960 
4961 			globalHotkeyList[hash] = gh;
4962 		}
4963 
4964 		/// Ditto
4965 		static void register (const(char)[] akey, void delegate () ahandler) {
4966 			register(new GlobalHotkey(akey, ahandler));
4967 		}
4968 
4969 		private static void removeByHash (ulong hash) {
4970 			if (auto ghp = hash in globalHotkeyList) {
4971 				auto dpy = XDisplayConnection.get;
4972 				immutable keycode = keyEvent2KeyCode(ghp.key);
4973 				Window root = RootWindow(dpy, DefaultScreen(dpy));
4974 				XSync(dpy, 0/*False*/);
4975 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
4976 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
4977 				XSync(dpy, 0/*False*/);
4978 				XSetErrorHandler(savedErrorHandler);
4979 				globalHotkeyList.remove(hash);
4980 			}
4981 		}
4982 
4983 		/// Register new global hotkey with previously used `GlobalHotkey` object.
4984 		/// It is safe to unregister unknown or invalid hotkey.
4985 		static void unregister (GlobalHotkey gh) {
4986 			//TODO: add second AA for faster search? prolly doesn't worth it.
4987 			if (gh is null) return;
4988 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
4989 				if (kv.value is gh) {
4990 					removeByHash(kv.key);
4991 					return;
4992 				}
4993 			}
4994 		}
4995 
4996 		/// Ditto.
4997 		static void unregister (const(char)[] key) {
4998 			auto kev = KeyEvent.parse(key);
4999 			immutable keycode = keyEvent2KeyCode(kev);
5000 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
5001 		}
5002 	}
5003 }
5004 
5005 version(Windows) {
5006 	/// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application)
5007 	void sendSyntheticInput(wstring s) {
5008 		INPUT[] inputs;
5009 		inputs.reserve(s.length * 2);
5010 
5011 		foreach(wchar c; s) {
5012 			INPUT input;
5013 			input.type = INPUT_KEYBOARD;
5014 			input.ki.wScan = c;
5015 			input.ki.dwFlags = KEYEVENTF_UNICODE;
5016 			inputs ~= input;
5017 
5018 			input.ki.dwFlags |= KEYEVENTF_KEYUP;
5019 			inputs ~= input;
5020 		}
5021 
5022 		if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
5023 			throw new Exception("SendInput failed");
5024 		}
5025 	}
5026 
5027 
5028 
5029 
5030 	// global hotkey helper function
5031 
5032 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID.
5033 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
5034 		__gshared int hotkeyId = 0;
5035 		int id = ++hotkeyId;
5036 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
5037 			throw new Exception("RegisterHotKey failed");
5038 
5039 		__gshared void delegate()[WPARAM][HWND] handlers;
5040 
5041 		handlers[window.impl.hwnd][id] = handler;
5042 
5043 		int delegate(HWND, UINT, WPARAM, LPARAM) oldHandler;
5044 
5045 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5046 			switch(msg) {
5047 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
5048 				case WM_HOTKEY:
5049 					if(auto list = hwnd in handlers) {
5050 						if(auto h = wParam in *list) {
5051 							(*h)();
5052 							return 0;
5053 						}
5054 					}
5055 				goto default;
5056 				default:
5057 			}
5058 			if(oldHandler)
5059 				return oldHandler(hwnd, msg, wParam, lParam);
5060 			return 1; // pass it on
5061 		};
5062 
5063 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
5064 			oldHandler = window.handleNativeEvent;
5065 			window.handleNativeEvent = nativeEventHandler;
5066 		}
5067 
5068 		return id;
5069 	}
5070 
5071 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by registerHotKey.
5072 	void unregisterHotKey(SimpleWindow window, int id) {
5073 		if(!UnregisterHotKey(window.impl.hwnd, id))
5074 			throw new Exception("UnregisterHotKey");
5075 	}
5076 }
5077 
5078 
5079 
5080 /++
5081 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
5082 
5083 	See_Also:
5084 	$(LIST
5085 		*[ScreenPainter]
5086 		*[ScreenPainter.rasterOp]
5087 	)
5088 +/
5089 enum RasterOp {
5090 	normal, /// Replaces the pixel.
5091 	xor, /// Uses bitwise xor to draw.
5092 }
5093 
5094 // being phobos-free keeps the size WAY down
5095 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
5096 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
5097 package(arsd) const(wchar)* toWStringz(string s) {
5098 	wstring r;
5099 	foreach(dchar c; s)
5100 		r ~= c;
5101 	r ~= '\0';
5102 	return r.ptr;
5103 }
5104 private string[] split(in void[] a, char c) {
5105 		string[] ret;
5106 		size_t previous = 0;
5107 		foreach(i, char ch; cast(ubyte[]) a) {
5108 			if(ch == c) {
5109 				ret ~= cast(string) a[previous .. i];
5110 				previous = i + 1;
5111 			}
5112 		}
5113 		if(previous != a.length)
5114 			ret ~= cast(string) a[previous .. $];
5115 		return ret;
5116 	}
5117 
5118 version(without_opengl) {
5119 	enum OpenGlOptions {
5120 		no,
5121 	}
5122 } else {
5123 	/++
5124 		Determines if you want an OpenGL context created on the new window.
5125 
5126 
5127 		See more: [#topics-3d|in the 3d topic].
5128 
5129 		---
5130 		import arsd.simpledisplay;
5131 		void main() {
5132 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
5133 
5134 			// Set up the matrix
5135 			window.setAsCurrentOpenGlContext(); // make this window active
5136 
5137 			// This is called on each frame, we will draw our scene
5138 			window.redrawOpenGlScene = delegate() {
5139 
5140 			};
5141 
5142 			window.eventLoop(0);
5143 		}
5144 		---
5145 	+/
5146 	enum OpenGlOptions {
5147 		no, /// No OpenGL context is created
5148 		yes, /// Yes, create an OpenGL context
5149 	}
5150 
5151 	version(X11) {
5152 		static if (!SdpyIsUsingIVGLBinds) {
5153 			pragma(lib, "GL");
5154 			pragma(lib, "GLU");
5155 		}
5156 	} else version(Windows) {
5157 		static if (!SdpyIsUsingIVGLBinds) {
5158 			pragma(lib, "opengl32");
5159 			pragma(lib, "glu32");
5160 		}
5161 	} else
5162 		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.");
5163 }
5164 
5165 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
5166 alias Resizablity = Resizability;
5167 
5168 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
5169 enum Resizability {
5170 	fixedSize, /// the window cannot be resized
5171 	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.
5172 	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.
5173 
5174 	// FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events
5175 }
5176 
5177 
5178 /++
5179 	Alignment for $(ScreenPainter.drawText). Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
5180 +/
5181 enum TextAlignment : uint {
5182 	Left = 0, ///
5183 	Center = 1, ///
5184 	Right = 2, ///
5185 
5186 	VerticalTop = 0, ///
5187 	VerticalCenter = 4, ///
5188 	VerticalBottom = 8, ///
5189 }
5190 
5191 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
5192 alias Rectangle = arsd.color.Rectangle;
5193 
5194 
5195 /++
5196 	Keyboard press and release events
5197 +/
5198 struct KeyEvent {
5199 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
5200 	Key key;
5201 	ubyte hardwareCode; /// A platform and hardware specific code for the key
5202 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
5203 
5204 	dchar character; ///
5205 
5206 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
5207 
5208 	SimpleWindow window; /// associated Window
5209 
5210 	// convert key event to simplified string representation a-la emacs
5211 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
5212 		uint dpos = 0;
5213 		void put (const(char)[] s...) nothrow @trusted {
5214 			static if (growdest) {
5215 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
5216 			} else {
5217 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
5218 			}
5219 		}
5220 
5221 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
5222 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
5223 		}
5224 
5225 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
5226 
5227 		// put modifiers
5228 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
5229 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
5230 		putMod(ModifierState.alt, Key.Alt, "Alt+");
5231 		putMod(ModifierState.windows, Key.Shift, "Windows+");
5232 		putMod(ModifierState.shift, Key.Shift, "Shift+");
5233 
5234 		if (this.key) {
5235 			foreach (string kn; __traits(allMembers, Key)) {
5236 				if (this.key == __traits(getMember, Key, kn)) {
5237 					// HACK!
5238 					static if (kn == "N0") put("0");
5239 					else static if (kn == "N1") put("1");
5240 					else static if (kn == "N2") put("2");
5241 					else static if (kn == "N3") put("3");
5242 					else static if (kn == "N4") put("4");
5243 					else static if (kn == "N5") put("5");
5244 					else static if (kn == "N6") put("6");
5245 					else static if (kn == "N7") put("7");
5246 					else static if (kn == "N8") put("8");
5247 					else static if (kn == "N9") put("9");
5248 					else put(kn);
5249 					return dest[0..dpos];
5250 				}
5251 			}
5252 			put("Unknown");
5253 		} else {
5254 			if (dpos && dest[dpos-1] == '+') --dpos;
5255 		}
5256 		return dest[0..dpos];
5257 	}
5258 
5259 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
5260 
5261 	/** Parse string into key name with modifiers. It accepts things like:
5262 	 *
5263 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
5264 	 *
5265 	 * Ctrl+Win+1 -- windows style
5266 	 *
5267 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
5268 	 *
5269 	 * Ctrl Win 1 -- and space
5270 	 *
5271 	 * and even "Win + 1 + Ctrl".
5272 	 */
5273 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
5274 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
5275 
5276 		// remove trailing spaces
5277 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
5278 
5279 		// tokens delimited by blank, '+', or '-'
5280 		// null on eol
5281 		const(char)[] getToken () nothrow @trusted @nogc {
5282 			// remove leading spaces and delimiters
5283 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
5284 			if (name.length == 0) return null; // oops, no more tokens
5285 			// get token
5286 			size_t epos = 0;
5287 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
5288 			assert(epos > 0 && epos <= name.length);
5289 			auto res = name[0..epos];
5290 			name = name[epos..$];
5291 			return res;
5292 		}
5293 
5294 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
5295 			if (s0.length != s1.length) return false;
5296 			foreach (immutable ci, char c0; s0) {
5297 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
5298 				char c1 = s1[ci];
5299 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
5300 				if (c0 != c1) return false;
5301 			}
5302 			return true;
5303 		}
5304 
5305 		if (ignoreModsOut !is null) *ignoreModsOut = false;
5306 		if (updown !is null) *updown = -1;
5307 		KeyEvent res;
5308 		res.key = cast(Key)0; // just in case
5309 		const(char)[] tk, tkn; // last token
5310 		bool allowEmascStyle = true;
5311 		bool ignoreModifiers = false;
5312 		tokenloop: for (;;) {
5313 			tk = tkn;
5314 			tkn = getToken();
5315 			//k8: yay, i took "Bloody Mess" trait from Fallout!
5316 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
5317 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
5318 			if (allowEmascStyle && tkn.length != 0) {
5319 				if (tk.length == 1) {
5320 					char mdc = tk[0];
5321 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
5322 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
5323 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
5324 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
5325 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
5326 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
5327 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
5328 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
5329 				}
5330 			}
5331 			allowEmascStyle = false;
5332 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
5333 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
5334 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
5335 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
5336 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
5337 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
5338 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
5339 			if (tk.length == 0) continue;
5340 			// try key name
5341 			if (res.key == 0) {
5342 				// little hack
5343 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
5344 					final switch (tk[0]) {
5345 						case '0': tk = "N0"; break;
5346 						case '1': tk = "N1"; break;
5347 						case '2': tk = "N2"; break;
5348 						case '3': tk = "N3"; break;
5349 						case '4': tk = "N4"; break;
5350 						case '5': tk = "N5"; break;
5351 						case '6': tk = "N6"; break;
5352 						case '7': tk = "N7"; break;
5353 						case '8': tk = "N8"; break;
5354 						case '9': tk = "N9"; break;
5355 					}
5356 				}
5357 				foreach (string kn; __traits(allMembers, Key)) {
5358 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
5359 				}
5360 			}
5361 			// unknown or duplicate key name, get out of here
5362 			break;
5363 		}
5364 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
5365 		return res; // something
5366 	}
5367 
5368 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
5369 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
5370 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
5371 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
5372 		}
5373 		bool ignoreMods;
5374 		int updown;
5375 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
5376 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
5377 		if (this.key != ke.key) {
5378 			// things like "ctrl+alt" are complicated
5379 			uint tkm = this.modifierState&modmask;
5380 			uint kkm = ke.modifierState&modmask;
5381 			Key tk = this.key;
5382 			// ke
5383 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
5384 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
5385 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
5386 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
5387 			// this
5388 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
5389 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
5390 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
5391 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
5392 			return (tk == ke.key && tkm == kkm);
5393 		}
5394 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
5395 	}
5396 }
5397 
5398 /// sets the application name.
5399 @property string ApplicationName(string name) {
5400 	return _applicationName = name;
5401 }
5402 
5403 string _applicationName;
5404 
5405 /// ditto
5406 @property string ApplicationName() {
5407 	if(_applicationName is null) {
5408 		import core.runtime;
5409 		return Runtime.args[0];
5410 	}
5411 	return _applicationName;
5412 }
5413 
5414 
5415 /// Type of a [MouseEvent]
5416 enum MouseEventType : int {
5417 	motion = 0, /// The mouse moved inside the window
5418 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
5419 	buttonReleased = 2, /// A mouse button was released
5420 }
5421 
5422 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
5423 /++
5424 	Listen for this on your event listeners if you are interested in mouse action.
5425 
5426 	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.
5427 
5428 	Examples:
5429 
5430 	This will draw boxes on the window with the mouse as you hold the left button.
5431 	---
5432 	import arsd.simpledisplay;
5433 
5434 	void main() {
5435 		auto window = new SimpleWindow();
5436 
5437 		window.eventLoop(0,
5438 			(MouseEvent ev) {
5439 				if(ev.modifierState & ModifierState.leftButtonDown) {
5440 					auto painter = window.draw();
5441 					painter.fillColor = Color.red;
5442 					painter.outlineColor = Color.black;
5443 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
5444 				}
5445 			}
5446 		);
5447 	}
5448 	---
5449 +/
5450 struct MouseEvent {
5451 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
5452 
5453 	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.
5454 	int y; /// Current Y position of the cursor when the event fired.
5455 
5456 	int dx; /// Change in X position since last report
5457 	int dy; /// Change in Y position since last report
5458 
5459 	MouseButton button; /// See [MouseButton]
5460 	int modifierState; /// See [ModifierState]
5461 
5462 	/// Returns a linear representation of mouse button,
5463 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
5464 	///
5465 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
5466 	@property ubyte buttonLinear() const {
5467 		import core.bitop;
5468 		if(button == 0)
5469 			return 0;
5470 		return (bsf(button) + 1) & 0b1111;
5471 	}
5472 
5473 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
5474 
5475 	SimpleWindow window; /// The window in which the event happened.
5476 
5477 	Point globalCoordinates() {
5478 		Point p;
5479 		if(window is null)
5480 			throw new Exception("wtf");
5481 		static if(UsingSimpledisplayX11) {
5482 			Window child;
5483 			XTranslateCoordinates(
5484 				XDisplayConnection.get,
5485 				window.impl.window,
5486 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
5487 				x, y, &p.x, &p.y, &child);
5488 			return p;
5489 		} else version(Windows) {
5490 			POINT[1] points;
5491 			points[0].x = x;
5492 			points[0].y = y;
5493 			MapWindowPoints(
5494 				window.impl.hwnd,
5495 				null,
5496 				points.ptr,
5497 				points.length
5498 			);
5499 			p.x = points[0].x;
5500 			p.y = points[0].y;
5501 
5502 			return p;
5503 		} else version(OSXCocoa) {
5504 			throw new NotYetImplementedException();
5505 		} else static assert(0);
5506 	}
5507 
5508 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
5509 
5510 	/**
5511 	can contain emacs-like modifier prefix
5512 	case-insensitive names:
5513 		lmbX/leftX
5514 		rmbX/rightX
5515 		mmbX/middleX
5516 		wheelX
5517 		motion (no prefix allowed)
5518 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
5519 	*/
5520 	static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
5521 		if (str.length == 0) return false; // just in case
5522 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
5523 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
5524 		auto anchor = str;
5525 		uint mods = 0; // uint.max == any
5526 		// interesting bits in kmod
5527 		uint kmodmask =
5528 			ModifierState.shift|
5529 			ModifierState.ctrl|
5530 			ModifierState.alt|
5531 			ModifierState.windows|
5532 			ModifierState.leftButtonDown|
5533 			ModifierState.middleButtonDown|
5534 			ModifierState.rightButtonDown|
5535 			0;
5536 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
5537 		bool wasButtons = false;
5538 		while (str.length) {
5539 			if (str.ptr[0] <= ' ') {
5540 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
5541 				continue;
5542 			}
5543 			// one-letter modifier?
5544 			if (str.length >= 2 && str.ptr[1] == '-') {
5545 				switch (str.ptr[0]) {
5546 					case '*': // "any" modifier (cannot be undone)
5547 						mods = mods.max;
5548 						break;
5549 					case 'C': case 'c': // emacs "ctrl"
5550 						if (mods != mods.max) mods |= ModifierState.ctrl;
5551 						break;
5552 					case 'M': case 'm': // emacs "meta"
5553 						if (mods != mods.max) mods |= ModifierState.alt;
5554 						break;
5555 					case 'S': case 's': // emacs "shift"
5556 						if (mods != mods.max) mods |= ModifierState.shift;
5557 						break;
5558 					case 'H': case 'h': // emacs "hyper" (aka winkey)
5559 						if (mods != mods.max) mods |= ModifierState.windows;
5560 						break;
5561 					default:
5562 						return false; // unknown modifier
5563 				}
5564 				str = str[2..$];
5565 				continue;
5566 			}
5567 			// word
5568 			char[16] buf = void; // locased
5569 			auto wep = 0;
5570 			while (str.length) {
5571 				immutable char ch = str.ptr[0];
5572 				if (ch <= ' ' || ch == '-') break;
5573 				str = str[1..$];
5574 				if (wep > buf.length) return false; // too long
5575 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
5576 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
5577 				else return false; // invalid char
5578 			}
5579 			if (wep == 0) return false; // just in case
5580 			uint bnum;
5581 			enum UpDown { None = -1, Up, Down, Any }
5582 			auto updown = UpDown.None; // 0: up; 1: down
5583 			switch (buf[0..wep]) {
5584 				// left button
5585 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
5586 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
5587 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
5588 				case "lmb": case "left": bnum = 0; break;
5589 				// middle button
5590 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
5591 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
5592 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
5593 				case "mmb": case "middle": bnum = 1; break;
5594 				// right button
5595 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
5596 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
5597 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
5598 				case "rmb": case "right": bnum = 2; break;
5599 				// wheel
5600 				case "wheelup": updown = UpDown.Up; goto case "wheel";
5601 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
5602 				case "wheelany": updown = UpDown.Any; goto case "wheel";
5603 				case "wheel": bnum = 3; break;
5604 				// motion
5605 				case "motion": bnum = 7; break;
5606 				// unknown
5607 				default: return false;
5608 			}
5609 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
5610 			// parse possible "-up" or "-down"
5611 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
5612 				wep = 0;
5613 				foreach (immutable idx, immutable char ch; str[1..$]) {
5614 					if (ch <= ' ' || ch == '-') break;
5615 					assert(idx == wep); // for now; trick
5616 					if (wep > buf.length) { wep = 0; break; } // too long
5617 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
5618 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
5619 					else { wep = 0; break; } // invalid char
5620 				}
5621 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
5622 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
5623 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
5624 				// remove parsed part
5625 				if (updown != UpDown.None) str = str[wep+1..$];
5626 			}
5627 			if (updown == UpDown.None) {
5628 				updown = UpDown.Down;
5629 			}
5630 			wasButtons = wasButtons || (bnum <= 2);
5631 			//assert(updown != UpDown.None);
5632 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
5633 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
5634 			if (lastButt != lastButt.max) {
5635 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
5636 				if (mods != mods.max) {
5637 					uint butbit = 0;
5638 					final switch (lastButt&0x03) {
5639 						case 0: butbit = ModifierState.leftButtonDown; break;
5640 						case 1: butbit = ModifierState.middleButtonDown; break;
5641 						case 2: butbit = ModifierState.rightButtonDown; break;
5642 					}
5643 					     if (lastButt&Flag.Down) mods |= butbit;
5644 					else if (lastButt&Flag.Up) mods &= ~butbit;
5645 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
5646 				}
5647 			}
5648 			// remember last button
5649 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
5650 		}
5651 		// no button -- nothing to do
5652 		if (lastButt == lastButt.max) return false;
5653 		// done parsing, check if something's left
5654 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
5655 		// remove action button from mask
5656 		if ((lastButt&0xff) < 3) {
5657 			final switch (lastButt&0x03) {
5658 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
5659 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
5660 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
5661 			}
5662 		}
5663 		// special case: "Motion" means "ignore buttons"
5664 		if ((lastButt&0xff) == 7 && !wasButtons) {
5665 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
5666 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
5667 		}
5668 		uint kmod = event.modifierState&kmodmask;
5669 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
5670 		// check modifier state
5671 		if (mods != mods.max) {
5672 			if (kmod != mods) return false;
5673 		}
5674 		// now check type
5675 		if ((lastButt&0xff) == 7) {
5676 			// motion
5677 			if (event.type != MouseEventType.motion) return false;
5678 		} else if ((lastButt&0xff) == 3) {
5679 			// wheel
5680 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
5681 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
5682 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
5683 			return false;
5684 		} else {
5685 			// buttons
5686 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
5687 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
5688 			{
5689 				return false;
5690 			}
5691 			// button number
5692 			switch (lastButt&0x03) {
5693 				case 0: if (event.button != MouseButton.left) return false; break;
5694 				case 1: if (event.button != MouseButton.middle) return false; break;
5695 				case 2: if (event.button != MouseButton.right) return false; break;
5696 				default: return false;
5697 			}
5698 		}
5699 		return true;
5700 	}
5701 }
5702 
5703 version(arsd_mevent_strcmp_test) unittest {
5704 	MouseEvent event;
5705 	event.type = MouseEventType.buttonPressed;
5706 	event.button = MouseButton.left;
5707 	event.modifierState = ModifierState.ctrl;
5708 	assert(event == "C-LMB");
5709 	assert(event != "C-LMBUP");
5710 	assert(event != "C-LMB-UP");
5711 	assert(event != "C-S-LMB");
5712 	assert(event == "*-LMB");
5713 	assert(event != "*-LMB-UP");
5714 
5715 	event.type = MouseEventType.buttonReleased;
5716 	assert(event != "C-LMB");
5717 	assert(event == "C-LMBUP");
5718 	assert(event == "C-LMB-UP");
5719 	assert(event != "C-S-LMB");
5720 	assert(event != "*-LMB");
5721 	assert(event == "*-LMB-UP");
5722 
5723 	event.button = MouseButton.right;
5724 	event.modifierState |= ModifierState.shift;
5725 	event.type = MouseEventType.buttonPressed;
5726 	assert(event != "C-LMB");
5727 	assert(event != "C-LMBUP");
5728 	assert(event != "C-LMB-UP");
5729 	assert(event != "C-S-LMB");
5730 	assert(event != "*-LMB");
5731 	assert(event != "*-LMB-UP");
5732 
5733 	assert(event != "C-RMB");
5734 	assert(event != "C-RMBUP");
5735 	assert(event != "C-RMB-UP");
5736 	assert(event == "C-S-RMB");
5737 	assert(event == "*-RMB");
5738 	assert(event != "*-RMB-UP");
5739 }
5740 
5741 /// This gives a few more options to drawing lines and such
5742 struct Pen {
5743 	Color color; /// the foreground color
5744 	int width = 1; /// width of the line
5745 	Style style; /// See [Style]
5746 /+
5747 // From X.h
5748 
5749 #define LineSolid		0
5750 #define LineOnOffDash		1
5751 #define LineDoubleDash		2
5752        LineDou-        The full path of the line is drawn, but the
5753        bleDash         even dashes are filled differently from the
5754                        odd dashes (see fill-style) with CapButt
5755                        style used where even and odd dashes meet.
5756 
5757 
5758 
5759 /* capStyle */
5760 
5761 #define CapNotLast		0
5762 #define CapButt			1
5763 #define CapRound		2
5764 #define CapProjecting		3
5765 
5766 /* joinStyle */
5767 
5768 #define JoinMiter		0
5769 #define JoinRound		1
5770 #define JoinBevel		2
5771 
5772 /* fillStyle */
5773 
5774 #define FillSolid		0
5775 #define FillTiled		1
5776 #define FillStippled		2
5777 #define FillOpaqueStippled	3
5778 
5779 
5780 +/
5781 	/// Style of lines drawn
5782 	enum Style {
5783 		Solid, /// a solid line
5784 		Dashed, /// a dashed line
5785 		Dotted, /// a dotted line
5786 	}
5787 }
5788 
5789 
5790 /++
5791 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
5792 
5793 
5794 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
5795 
5796 	$(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.)
5797 
5798 	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.
5799 
5800 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
5801 
5802 	$(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.
5803 
5804 	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!
5805 
5806 	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!)
5807 
5808 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
5809 
5810 	---
5811 		auto image = new Image(256, 256);
5812 		scope(exit) destroy(image);
5813 	---
5814 
5815 	As long as you don't hold on to it outside the scope.
5816 
5817 	I might change it to be an owned pointer at some point in the future.
5818 
5819 	)
5820 
5821 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
5822 	you can also often get a fair amount of speedup by getting the raw data format and
5823 	writing some custom code.
5824 
5825 	FIXME INSERT EXAMPLES HERE
5826 
5827 
5828 +/
5829 final class Image {
5830 	///
5831 	this(int width, int height, bool forcexshm=false) {
5832 		this.width = width;
5833 		this.height = height;
5834 
5835 		impl.createImage(width, height, forcexshm);
5836 	}
5837 
5838 	///
5839 	this(Size size, bool forcexshm=false) {
5840 		this(size.width, size.height, forcexshm);
5841 	}
5842 
5843 	~this() {
5844 		impl.dispose();
5845 	}
5846 
5847 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
5848 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
5849 	pure const @system nothrow {
5850 		/*
5851 			To use these to draw a blue rectangle with size WxH at position X,Y...
5852 
5853 			// make certain that it will fit before we proceed
5854 			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!
5855 
5856 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
5857 			// (though calculating them isn't really that expensive).
5858 			auto nextLineAdjustment = img.adjustmentForNextLine();
5859 			auto offR = img.redByteOffset();
5860 			auto offB = img.blueByteOffset();
5861 			auto offG = img.greenByteOffset();
5862 			auto bpp = img.bytesPerPixel();
5863 
5864 			auto data = img.getDataPointer();
5865 
5866 			// figure out the starting byte offset
5867 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
5868 
5869 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
5870 
5871 			// and now our drawing loop for the rectangle
5872 			foreach(y; 0 .. H) {
5873 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
5874 				foreach(x; 0 .. W) {
5875 					// write our color
5876 					data[offR] = 0;
5877 					data[offG] = 0;
5878 					data[offB] = 255;
5879 
5880 					data += bpp; // moving to the next pixel is just an addition...
5881 				}
5882 				startOfLine += nextLineAdjustment;
5883 			}
5884 
5885 
5886 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
5887 
5888 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
5889 			can be made into a bitmask or something so we can write them as *uint...
5890 		*/
5891 
5892 		///
5893 		int offsetForTopLeftPixel() {
5894 			version(X11) {
5895 				return 0;
5896 			} else version(Windows) {
5897 				return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
5898 			} else version(OSXCocoa) {
5899 				return 0 ; //throw new NotYetImplementedException();
5900 			} else static assert(0, "fill in this info for other OSes");
5901 		}
5902 
5903 		///
5904 		int offsetForPixel(int x, int y) {
5905 			version(X11) {
5906 				auto offset = (y * width + x) * 4;
5907 				return offset;
5908 			} else version(Windows) {
5909 				auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
5910 				// remember, bmps are upside down
5911 				auto offset = itemsPerLine * (height - y - 1) + x * 3;
5912 				return offset;
5913 			} else version(OSXCocoa) {
5914 				return 0 ; //throw new NotYetImplementedException();
5915 			} else static assert(0, "fill in this info for other OSes");
5916 		}
5917 
5918 		///
5919 		int adjustmentForNextLine() {
5920 			version(X11) {
5921 				return width * 4;
5922 			} else version(Windows) {
5923 				// windows bmps are upside down, so the adjustment is actually negative
5924 				return -((cast(int) width * 3 + 3) / 4) * 4;
5925 			} else version(OSXCocoa) {
5926 				return 0 ; //throw new NotYetImplementedException();
5927 			} else static assert(0, "fill in this info for other OSes");
5928 		}
5929 
5930 		/// once you have the position of a pixel, use these to get to the proper color
5931 		int redByteOffset() {
5932 			version(X11) {
5933 				return 2;
5934 			} else version(Windows) {
5935 				return 2;
5936 			} else version(OSXCocoa) {
5937 				return 0 ; //throw new NotYetImplementedException();
5938 			} else static assert(0, "fill in this info for other OSes");
5939 		}
5940 
5941 		///
5942 		int greenByteOffset() {
5943 			version(X11) {
5944 				return 1;
5945 			} else version(Windows) {
5946 				return 1;
5947 			} else version(OSXCocoa) {
5948 				return 0 ; //throw new NotYetImplementedException();
5949 			} else static assert(0, "fill in this info for other OSes");
5950 		}
5951 
5952 		///
5953 		int blueByteOffset() {
5954 			version(X11) {
5955 				return 0;
5956 			} else version(Windows) {
5957 				return 0;
5958 			} else version(OSXCocoa) {
5959 				return 0 ; //throw new NotYetImplementedException();
5960 			} else static assert(0, "fill in this info for other OSes");
5961 		}
5962 	}
5963 
5964 	///
5965 	final void putPixel(int x, int y, Color c) {
5966 		if(x < 0 || x >= width)
5967 			return;
5968 		if(y < 0 || y >= height)
5969 			return;
5970 
5971 		impl.setPixel(x, y, c);
5972 	}
5973 
5974 	///
5975 	final Color getPixel(int x, int y) {
5976 		if(x < 0 || x >= width)
5977 			return Color.transparent;
5978 		if(y < 0 || y >= height)
5979 			return Color.transparent;
5980 
5981 		version(OSXCocoa) throw new NotYetImplementedException(); else
5982 		return impl.getPixel(x, y);
5983 	}
5984 
5985 	///
5986 	final void opIndexAssign(Color c, int x, int y) {
5987 		putPixel(x, y, c);
5988 	}
5989 
5990 	///
5991 	TrueColorImage toTrueColorImage() {
5992 		auto tci = new TrueColorImage(width, height);
5993 		convertToRgbaBytes(tci.imageData.bytes);
5994 		return tci;
5995 	}
5996 
5997 	///
5998 	static Image fromMemoryImage(MemoryImage i) {
5999 		auto tci = i.getAsTrueColorImage();
6000 		auto img = new Image(tci.width, tci.height);
6001 		img.setRgbaBytes(tci.imageData.bytes);
6002 		return img;
6003 	}
6004 
6005 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
6006 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
6007 	/// if you pass null, it will allocate a new one.
6008 	ubyte[] getRgbaBytes(ubyte[] where = null) {
6009 		if(where is null)
6010 			where = new ubyte[this.width*this.height*4];
6011 		convertToRgbaBytes(where);
6012 		return where;
6013 	}
6014 
6015 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
6016 	void setRgbaBytes(in ubyte[] from ) {
6017 		assert(from.length == this.width * this.height * 4);
6018 		setFromRgbaBytes(from);
6019 	}
6020 
6021 	// FIXME: make properly cross platform by getting rgba right
6022 
6023 	/// warning: this is not portable across platforms because the data format can change
6024 	ubyte* getDataPointer() {
6025 		return impl.rawData;
6026 	}
6027 
6028 	/// for use with getDataPointer
6029 	final int bytesPerLine() const pure @safe nothrow {
6030 		version(Windows)
6031 			return ((cast(int) width * 3 + 3) / 4) * 4;
6032 		else version(X11)
6033 			return 4 * width;
6034 		else version(OSXCocoa)
6035 			return 4 * width;
6036 		else static assert(0);
6037 	}
6038 
6039 	/// for use with getDataPointer
6040 	final int bytesPerPixel() const pure @safe nothrow {
6041 		version(Windows)
6042 			return 3;
6043 		else version(X11)
6044 			return 4;
6045 		else version(OSXCocoa)
6046 			return 4;
6047 		else static assert(0);
6048 	}
6049 
6050 	///
6051 	immutable int width;
6052 
6053 	///
6054 	immutable int height;
6055     //private:
6056 	mixin NativeImageImplementation!() impl;
6057 }
6058 
6059 /// A convenience function to pop up a window displaying the image.
6060 /// If you pass a win, it will draw the image in it. Otherwise, it will
6061 /// create a window with the size of the image and run its event loop, closing
6062 /// when a key is pressed.
6063 void displayImage(Image image, SimpleWindow win = null) {
6064 	if(win is null) {
6065 		win = new SimpleWindow(image);
6066 		{
6067 			auto p = win.draw;
6068 			p.drawImage(Point(0, 0), image);
6069 		}
6070 		win.eventLoop(0,
6071 			(KeyEvent ev) {
6072 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
6073 			} );
6074 	} else {
6075 		win.image = image;
6076 	}
6077 }
6078 
6079 enum FontWeight : int {
6080 	dontcare = 0,
6081 	thin = 100,
6082 	extralight = 200,
6083 	light = 300,
6084 	regular = 400,
6085 	medium = 500,
6086 	semibold = 600,
6087 	bold = 700,
6088 	extrabold = 800,
6089 	heavy = 900
6090 }
6091 
6092 /++
6093 	Represents a font loaded off the operating system or the X server.
6094 
6095 
6096 	While the api here is unified cross platform, the fonts are not necessarily
6097 	available, even across machines of the same platform, so be sure to always check
6098 	for null (using [isNull]) and have a fallback plan.
6099 
6100 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
6101 
6102 	Worst case, a null font will automatically fall back to the default font loaded
6103 	for your system.
6104 +/
6105 class OperatingSystemFont {
6106 
6107 	version(X11) {
6108 		XFontStruct* font;
6109 		XFontSet fontset;
6110 	} else version(Windows) {
6111 		HFONT font;
6112 	} else version(OSXCocoa) {
6113 		// FIXME
6114 	} else static assert(0);
6115 
6116 	///
6117 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
6118 		load(name, size, weight, italic);
6119 	}
6120 
6121 	///
6122 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
6123 		unload();
6124 		version(X11) {
6125 			string weightstr;
6126 			with(FontWeight)
6127 			final switch(weight) {
6128 				case dontcare: weightstr = "*"; break;
6129 				case thin: weightstr = "extralight"; break;
6130 				case extralight: weightstr = "extralight"; break;
6131 				case light: weightstr = "light"; break;
6132 				case regular: weightstr = "regular"; break;
6133 				case medium: weightstr = "medium"; break;
6134 				case semibold: weightstr = "demibold"; break;
6135 				case bold: weightstr = "bold"; break;
6136 				case extrabold: weightstr = "demibold"; break;
6137 				case heavy: weightstr = "black"; break;
6138 			}
6139 			string sizestr;
6140 			if(size == 0)
6141 				sizestr = "*";
6142 			else if(size < 10)
6143 				sizestr = "" ~ cast(char)(size % 10 + '0');
6144 			else
6145 				sizestr = "" ~ cast(char)(size / 10 + '0') ~ cast(char)(size % 10 + '0');
6146 			auto xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
6147 
6148 			//import std.stdio; writeln(xfontstr);
6149 
6150 			auto display = XDisplayConnection.get;
6151 
6152 			font = XLoadQueryFont(display, xfontstr.ptr);
6153 			if(font is null)
6154 				return false;
6155 
6156 			char** lol;
6157 			int lol2;
6158 			char* lol3;
6159 			fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
6160 		} else version(Windows) {
6161 			WCharzBuffer buffer = WCharzBuffer(name);
6162 			font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
6163 		} else version(OSXCocoa) {
6164 			// FIXME
6165 		} else static assert(0);
6166 
6167 		return !isNull();
6168 	}
6169 
6170 	///
6171 	void unload() {
6172 		if(isNull())
6173 			return;
6174 
6175 		version(X11) {
6176 			auto display = XDisplayConnection.display;
6177 
6178 			if(display is null)
6179 				return;
6180 
6181 			if(font)
6182 				XFreeFont(display, font);
6183 			if(fontset)
6184 				XFreeFontSet(display, fontset);
6185 
6186 			font = null;
6187 			fontset = null;
6188 		} else version(Windows) {
6189 			DeleteObject(font);
6190 			font = null;
6191 		} else version(OSXCocoa) {
6192 			// FIXME
6193 		} else static assert(0);
6194 	}
6195 
6196 	/// FIXME not implemented
6197 	void loadDefault() {
6198 
6199 	}
6200 
6201 	///
6202 	bool isNull() {
6203 		version(OSXCocoa) throw new NotYetImplementedException(); else
6204 		return font is null;
6205 	}
6206 
6207 	/* Metrics */
6208 	/+
6209 		GetFontMetrics
6210 		GetABCWidth
6211 		GetKerningPairs
6212 
6213 		XLoadQueryFont
6214 
6215 		if I do it right, I can size it all here, and match
6216 		what happens when I draw the full string with the OS functions.
6217 
6218 		subclasses might do the same thing while getting the glyphs on images
6219 	+/
6220 	struct GlyphInfo {
6221 		int glyph;
6222 
6223 		size_t stringIdxStart;
6224 		size_t stringIdxEnd;
6225 
6226 		Rectangle boundingBox;
6227 	}
6228 	GlyphInfo[] getCharBoxes() {
6229 		return null;
6230 
6231 	}
6232 
6233 	~this() {
6234 		unload();
6235 	}
6236 }
6237 
6238 /**
6239 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
6240 	than constructing it directly. Then, it is reference counted so you can pass it
6241 	at around and when the last ref goes out of scope, the buffered drawing activities
6242 	are all carried out.
6243 
6244 
6245 	Most functions use the outlineColor instead of taking a color themselves.
6246 	ScreenPainter is reference counted and draws its buffer to the screen when its
6247 	final reference goes out of scope.
6248 */
6249 struct ScreenPainter {
6250 	CapableOfBeingDrawnUpon window;
6251 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) {
6252 		this.window = window;
6253 		if(window.closed)
6254 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
6255 		currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
6256 		if(window.activeScreenPainter !is null) {
6257 			impl = window.activeScreenPainter;
6258 			if(impl.referenceCount == 0) {
6259 				impl.window = window;
6260 				impl.create(handle);
6261 			}
6262 			impl.referenceCount++;
6263 		//	writeln("refcount ++ ", impl.referenceCount);
6264 		} else {
6265 			impl = new ScreenPainterImplementation;
6266 			impl.window = window;
6267 			impl.create(handle);
6268 			impl.referenceCount = 1;
6269 			window.activeScreenPainter = impl;
6270 		//	writeln("constructed");
6271 		}
6272 
6273 		copyActiveOriginals();
6274 	}
6275 
6276 	private Pen originalPen;
6277 	private Color originalFillColor;
6278 	private arsd.color.Rectangle originalClipRectangle;
6279 	void copyActiveOriginals() {
6280 		if(impl is null) return;
6281 		originalPen = impl._activePen;
6282 		originalFillColor = impl._fillColor;
6283 		originalClipRectangle = impl._clipRectangle;
6284 	}
6285 
6286 	~this() {
6287 		if(impl is null) return;
6288 		impl.referenceCount--;
6289 		//writeln("refcount -- ", impl.referenceCount);
6290 		if(impl.referenceCount == 0) {
6291 			//writeln("destructed");
6292 			impl.dispose();
6293 			*window.activeScreenPainter = ScreenPainterImplementation.init;
6294 			//import std.stdio; writeln("paint finished");
6295 		} else {
6296 			// there is still an active reference, reset stuff so the
6297 			// next user doesn't get weirdness via the reference
6298 			this.rasterOp = RasterOp.normal;
6299 			pen = originalPen;
6300 			fillColor = originalFillColor;
6301 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
6302 		}
6303 	}
6304 
6305 	this(this) {
6306 		if(impl is null) return;
6307 		impl.referenceCount++;
6308 		//writeln("refcount ++ ", impl.referenceCount);
6309 
6310 		copyActiveOriginals();
6311 	}
6312 
6313 	private int _originX;
6314 	private int _originY;
6315 	@property int originX() { return _originX; }
6316 	@property int originY() { return _originY; }
6317 	@property int originX(int a) {
6318 		//currentClipRectangle.left += a - _originX;
6319 		//currentClipRectangle.right += a - _originX;
6320 		_originX = a;
6321 		return _originX;
6322 	}
6323 	@property int originY(int a) {
6324 		//currentClipRectangle.top += a - _originY;
6325 		//currentClipRectangle.bottom += a - _originY;
6326 		_originY = a;
6327 		return _originY;
6328 	}
6329 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
6330 	private void transform(ref Point p) {
6331 		if(impl is null) return;
6332 		p.x += _originX;
6333 		p.y += _originY;
6334 	}
6335 
6336 	// this needs to be checked BEFORE the originX/Y transformation
6337 	private bool isClipped(Point p) {
6338 		return !currentClipRectangle.contains(p);
6339 	}
6340 	private bool isClipped(Point p, int width, int height) {
6341 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
6342 	}
6343 	private bool isClipped(Point p, Size s) {
6344 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
6345 	}
6346 	private bool isClipped(Point p, Point p2) {
6347 		// need to ensure the end points are actually included inside, so the +1 does that
6348 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
6349 	}
6350 
6351 
6352 	/// Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
6353 	void setClipRectangle(Point pt, int width, int height) {
6354 		if(impl is null) return;
6355 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
6356 			return; // no need to do anything
6357 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
6358 		transform(pt);
6359 
6360 		impl.setClipRectangle(pt.x, pt.y, width, height);
6361 	}
6362 
6363 	/// ditto
6364 	void setClipRectangle(arsd.color.Rectangle rect) {
6365 		if(impl is null) return;
6366 		setClipRectangle(rect.upperLeft, rect.width, rect.height);
6367 	}
6368 
6369 	///
6370 	void setFont(OperatingSystemFont font) {
6371 		if(impl is null) return;
6372 		impl.setFont(font);
6373 	}
6374 
6375 	///
6376 	int fontHeight() {
6377 		if(impl is null) return 0;
6378 		return impl.fontHeight();
6379 	}
6380 
6381 	private Pen activePen;
6382 
6383 	///
6384 	@property void pen(Pen p) {
6385 		if(impl is null) return;
6386 		activePen = p;
6387 		impl.pen(p);
6388 	}
6389 
6390 	///
6391 	@property void outlineColor(Color c) {
6392 		if(impl is null) return;
6393 		if(activePen.color == c)
6394 			return;
6395 		activePen.color = c;
6396 		impl.pen(activePen);
6397 	}
6398 
6399 	///
6400 	@property void fillColor(Color c) {
6401 		if(impl is null) return;
6402 		impl.fillColor(c);
6403 	}
6404 
6405 	///
6406 	@property void rasterOp(RasterOp op) {
6407 		if(impl is null) return;
6408 		impl.rasterOp(op);
6409 	}
6410 
6411 
6412 	void updateDisplay() {
6413 		// FIXME this should do what the dtor does
6414 	}
6415 
6416 	/// 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)
6417 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
6418 		if(impl is null) return;
6419 		if(isClipped(upperLeft, width, height)) return;
6420 		transform(upperLeft);
6421 		version(Windows) {
6422 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
6423 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
6424 			RECT clip = scroll;
6425 			RECT uncovered;
6426 			HRGN hrgn;
6427 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
6428 				throw new Exception("ScrollDC");
6429 
6430 		} else version(X11) {
6431 			// FIXME: clip stuff outside this rectangle
6432 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
6433 		} else version(OSXCocoa) {
6434 			throw new NotYetImplementedException();
6435 		} else static assert(0);
6436 	}
6437 
6438 	///
6439 	void clear(Color color = Color.white()) {
6440 		if(impl is null) return;
6441 		fillColor = color;
6442 		outlineColor = color;
6443 		drawRectangle(Point(0, 0), window.width, window.height);
6444 	}
6445 
6446 	///
6447 	version(OSXCocoa) {} else // NotYetImplementedException
6448 	void drawPixmap(Sprite s, Point upperLeft) {
6449 		if(impl is null) return;
6450 		if(isClipped(upperLeft, s.width, s.height)) return;
6451 		transform(upperLeft);
6452 		impl.drawPixmap(s, upperLeft.x, upperLeft.y);
6453 	}
6454 
6455 	///
6456 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
6457 		if(impl is null) return;
6458 		//if(isClipped(upperLeft, w, h)) return; // FIXME
6459 		transform(upperLeft);
6460 		if(w == 0 || w > i.width)
6461 			w = i.width;
6462 		if(h == 0 || h > i.height)
6463 			h = i.height;
6464 		if(upperLeftOfImage.x < 0)
6465 			upperLeftOfImage.x = 0;
6466 		if(upperLeftOfImage.y < 0)
6467 			upperLeftOfImage.y = 0;
6468 
6469 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
6470 	}
6471 
6472 	///
6473 	Size textSize(in char[] text) {
6474 		if(impl is null) return Size(0, 0);
6475 		return impl.textSize(text);
6476 	}
6477 
6478 	///
6479 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
6480 		if(impl is null) return;
6481 		if(lowerRight.x != 0 || lowerRight.y != 0) {
6482 			if(isClipped(upperLeft, lowerRight)) return;
6483 			transform(lowerRight);
6484 		} else {
6485 			if(isClipped(upperLeft, textSize(text))) return;
6486 		}
6487 		transform(upperLeft);
6488 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
6489 	}
6490 
6491 	/++
6492 		Draws text using a custom font.
6493 
6494 		This is still MAJOR work in progress.
6495 
6496 		Creating a [DrawableFont] can be tricky and require additional dependencies.
6497 	+/
6498 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
6499 		if(impl is null) return;
6500 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
6501 		transform(upperLeft);
6502 		font.drawString(this, upperLeft, text);
6503 	}
6504 
6505 	static struct TextDrawingContext {
6506 		Point boundingBoxUpperLeft;
6507 		Point boundingBoxLowerRight;
6508 
6509 		Point currentLocation;
6510 
6511 		Point lastDrewUpperLeft;
6512 		Point lastDrewLowerRight;
6513 
6514 		// how do i do right aligned rich text?
6515 		// i kinda want to do a pre-made drawing then right align
6516 		// draw the whole block.
6517 		//
6518 		// That's exactly the diff: inline vs block stuff.
6519 
6520 		// I need to get coordinates of an inline section out too,
6521 		// not just a bounding box, but a series of bounding boxes
6522 		// should be ok. Consider what's needed to detect a click
6523 		// on a link in the middle of a paragraph breaking a line.
6524 		//
6525 		// Generally, we should be able to get the rectangles of
6526 		// any portion we draw.
6527 		//
6528 		// It also needs to tell what text is left if it overflows
6529 		// out of the box, so we can do stuff like float images around
6530 		// it. It should not attempt to draw a letter that would be
6531 		// clipped.
6532 		//
6533 		// I might also turn off word wrap stuff.
6534 	}
6535 
6536 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
6537 		if(impl is null) return;
6538 		// FIXME
6539 	}
6540 
6541 	/// Drawing an individual pixel is slow. Avoid it if possible.
6542 	void drawPixel(Point where) {
6543 		if(impl is null) return;
6544 		if(isClipped(where)) return;
6545 		transform(where);
6546 		impl.drawPixel(where.x, where.y);
6547 	}
6548 
6549 
6550 	/// Draws a pen using the current pen / outlineColor
6551 	void drawLine(Point starting, Point ending) {
6552 		if(impl is null) return;
6553 		if(isClipped(starting, ending)) return;
6554 		transform(starting);
6555 		transform(ending);
6556 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
6557 	}
6558 
6559 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
6560 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
6561 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
6562 	void drawRectangle(Point upperLeft, int width, int height) {
6563 		if(impl is null) return;
6564 		if(isClipped(upperLeft, width, height)) return;
6565 		transform(upperLeft);
6566 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
6567 	}
6568 
6569 	/// ditto
6570 	void drawRectangle(Point upperLeft, Size size) {
6571 		if(impl is null) return;
6572 		if(isClipped(upperLeft, size.width, size.height)) return;
6573 		transform(upperLeft);
6574 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
6575 	}
6576 
6577 	/// ditto
6578 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
6579 		if(impl is null) return;
6580 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
6581 		transform(upperLeft);
6582 		transform(lowerRightInclusive);
6583 		impl.drawRectangle(upperLeft.x, upperLeft.y,
6584 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
6585 	}
6586 
6587 	/// Arguments are the points of the bounding rectangle
6588 	void drawEllipse(Point upperLeft, Point lowerRight) {
6589 		if(impl is null) return;
6590 		if(isClipped(upperLeft, lowerRight)) return;
6591 		transform(upperLeft);
6592 		transform(lowerRight);
6593 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
6594 	}
6595 
6596 	/++
6597 		start and finish are units of degrees * 64
6598 	+/
6599 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
6600 		if(impl is null) return;
6601 		// FIXME: not actually implemented
6602 		if(isClipped(upperLeft, width, height)) return;
6603 		transform(upperLeft);
6604 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
6605 	}
6606 
6607 	//this function draws a circle using the drawArc() function above, it requires you to pass the point it
6608 	//will be drawn at as a Point struct and the radius as an int
6609 	void drawCircle(Point upperLeft, int radius) {
6610 		this.drawArc(upperLeft, radius, radius, 0, 0);
6611 	}
6612 
6613 	/// .
6614 	void drawPolygon(Point[] vertexes) {
6615 		if(impl is null) return;
6616 		assert(vertexes.length);
6617 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
6618 		foreach(ref vertex; vertexes) {
6619 			if(vertex.x < minX)
6620 				minX = vertex.x;
6621 			if(vertex.y < minY)
6622 				minY = vertex.y;
6623 			if(vertex.x > maxX)
6624 				maxX = vertex.x;
6625 			if(vertex.y > maxY)
6626 				maxY = vertex.y;
6627 			transform(vertex);
6628 		}
6629 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
6630 		impl.drawPolygon(vertexes);
6631 	}
6632 
6633 	/// ditto
6634 	void drawPolygon(Point[] vertexes...) {
6635 		if(impl is null) return;
6636 		drawPolygon(vertexes);
6637 	}
6638 
6639 
6640 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
6641 
6642 	//mixin NativeScreenPainterImplementation!() impl;
6643 
6644 
6645 	// HACK: if I mixin the impl directly, it won't let me override the copy
6646 	// constructor! The linker complains about there being multiple definitions.
6647 	// I'll make the best of it and reference count it though.
6648 	ScreenPainterImplementation* impl;
6649 }
6650 
6651 	// HACK: I need a pointer to the implementation so it's separate
6652 	struct ScreenPainterImplementation {
6653 		CapableOfBeingDrawnUpon window;
6654 		int referenceCount;
6655 		mixin NativeScreenPainterImplementation!();
6656 	}
6657 
6658 // FIXME: i haven't actually tested the sprite class on MS Windows
6659 
6660 /**
6661 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
6662 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
6663 
6664 
6665 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
6666 	though I'm not sure that's ideal and the implementation might change.
6667 
6668 	You create one by giving a window and an image. It optimizes for that window,
6669 	and copies the image into it to use as the initial picture. Creating a sprite
6670 	can be quite slow (especially over a network connection) so you should do it
6671 	as little as possible and just hold on to your sprite handles after making them.
6672 	simpledisplay does try to do its best though, using the XSHM extension if available,
6673 	but you should still write your code as if it will always be slow.
6674 
6675 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
6676 	a fast operation - much faster than drawing the Image itself every time.
6677 
6678 	`Sprite` represents a scarce resource which should be freed when you
6679 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
6680 	after it has been disposed. If you are unsure about this, don't take chances,
6681 	just let the garbage collector do it for you. But ideally, you can manage its
6682 	lifetime more efficiently.
6683 
6684 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
6685 	support alpha blending in its drawing at this time. That might change in the
6686 	future, but if you need alpha blending right now, use OpenGL instead. See
6687 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
6688 
6689 	FIXME: you are supposed to be able to draw on these similarly to on windows.
6690 	ScreenPainter needs to be refactored to allow that though. So until that is
6691 	done, consider a `Sprite` to have const contents.
6692 */
6693 version(OSXCocoa) {} else // NotYetImplementedException
6694 class Sprite : CapableOfBeingDrawnUpon {
6695 
6696 	///
6697 	ScreenPainter draw() {
6698 		return ScreenPainter(this, handle);
6699 	}
6700 
6701 	/// Be warned: this can be a very slow operation
6702 	/// FIXME NOT IMPLEMENTED
6703 	TrueColorImage takeScreenshot() {
6704 		return trueColorImageFromNativeHandle(handle, width, height);
6705 	}
6706 
6707 	void delegate() paintingFinishedDg() { return null; }
6708 	bool closed() { return false; }
6709 	ScreenPainterImplementation* activeScreenPainter_;
6710 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
6711 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
6712 
6713 	version(Windows)
6714 		private ubyte* rawData;
6715 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
6716 
6717 	this(SimpleWindow win, int width, int height) {
6718 		this._width = width;
6719 		this._height = height;
6720 
6721 		version(X11) {
6722 			auto display = XDisplayConnection.get();
6723 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
6724 		} else version(Windows) {
6725 			BITMAPINFO infoheader;
6726 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
6727 			infoheader.bmiHeader.biWidth = width;
6728 			infoheader.bmiHeader.biHeight = height;
6729 			infoheader.bmiHeader.biPlanes = 1;
6730 			infoheader.bmiHeader.biBitCount = 24;
6731 			infoheader.bmiHeader.biCompression = BI_RGB;
6732 
6733 			// FIXME: this should prolly be a device dependent bitmap...
6734 			handle = CreateDIBSection(
6735 				null,
6736 				&infoheader,
6737 				DIB_RGB_COLORS,
6738 				cast(void**) &rawData,
6739 				null,
6740 				0);
6741 
6742 			if(handle is null)
6743 				throw new Exception("couldn't create pixmap");
6744 		}
6745 	}
6746 
6747 	/// Makes a sprite based on the image with the initial contents from the Image
6748 	this(SimpleWindow win, Image i) {
6749 		this(win, i.width, i.height);
6750 
6751 		version(X11) {
6752 			auto display = XDisplayConnection.get();
6753 			if(i.usingXshm)
6754 				XShmPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height, false);
6755 			else
6756 				XPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height);
6757 		} else version(Windows) {
6758 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
6759 			auto arrLength = itemsPerLine * height;
6760 			rawData[0..arrLength] = i.rawData[0..arrLength];
6761 		} else version(OSXCocoa) {
6762 			// FIXME: I have no idea if this is even any good
6763 			ubyte* rawData;
6764 
6765 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
6766 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
6767 				colorSpace,
6768 				kCGImageAlphaPremultipliedLast
6769 				|kCGBitmapByteOrder32Big);
6770 			CGColorSpaceRelease(colorSpace);
6771 			rawData = CGBitmapContextGetData(context);
6772 
6773 			auto rdl = (width * height * 4);
6774 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
6775 		} else static assert(0);
6776 	}
6777 
6778 	/++
6779 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
6780 	+/
6781 	void drawAt(ScreenPainter painter, Point where) {
6782 		painter.drawPixmap(this, where);
6783 	}
6784 
6785 
6786 	/// Call this when you're ready to get rid of it
6787 	void dispose() {
6788 		version(X11) {
6789 			if(handle)
6790 				XFreePixmap(XDisplayConnection.get(), handle);
6791 			handle = None;
6792 		} else version(Windows) {
6793 			if(handle)
6794 				DeleteObject(handle);
6795 			handle = null;
6796 		} else version(OSXCocoa) {
6797 			if(context)
6798 				CGContextRelease(context);
6799 			context = null;
6800 		} else static assert(0);
6801 
6802 	}
6803 
6804 	~this() {
6805 		dispose();
6806 	}
6807 
6808 	///
6809 	final @property int width() { return _width; }
6810 
6811 	///
6812 	final @property int height() { return _height; }
6813 
6814 	private:
6815 
6816 	int _width;
6817 	int _height;
6818 	version(X11)
6819 		Pixmap handle;
6820 	else version(Windows)
6821 		HBITMAP handle;
6822 	else version(OSXCocoa)
6823 		CGContextRef context;
6824 	else static assert(0);
6825 }
6826 
6827 ///
6828 interface CapableOfBeingDrawnUpon {
6829 	///
6830 	ScreenPainter draw();
6831 	///
6832 	int width();
6833 	///
6834 	int height();
6835 	protected ScreenPainterImplementation* activeScreenPainter();
6836 	protected void activeScreenPainter(ScreenPainterImplementation*);
6837 	bool closed();
6838 
6839 	void delegate() paintingFinishedDg();
6840 
6841 	/// Be warned: this can be a very slow operation
6842 	TrueColorImage takeScreenshot();
6843 }
6844 
6845 /// 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()
6846 void flushGui() {
6847 	version(X11) {
6848 		auto dpy = XDisplayConnection.get();
6849 		XLockDisplay(dpy);
6850 		scope(exit) XUnlockDisplay(dpy);
6851 		XFlush(dpy);
6852 	}
6853 }
6854 
6855 /// Used internal to dispatch events to various classes.
6856 interface CapableOfHandlingNativeEvent {
6857 	NativeEventHandler getNativeEventHandler();
6858 
6859 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
6860 
6861 	version(X11) {
6862 		// if this is impossible, you are allowed to just throw from it
6863 		// Note: if you call it from another object, set a flag cuz the manger will call you again
6864 		void recreateAfterDisconnect();
6865 		// discard any *connection specific* state, but keep enough that you
6866 		// can be recreated if possible. discardConnectionState() is always called immediately
6867 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
6868 		// you need initialization order
6869 		void discardConnectionState();
6870 	}
6871 }
6872 
6873 version(X11)
6874 /++
6875 	State of keys on mouse events, especially motion.
6876 
6877 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
6878 +/
6879 enum ModifierState : uint {
6880 	shift = 1, ///
6881 	capsLock = 2, ///
6882 	ctrl = 4, ///
6883 	alt = 8, /// Not always available on Windows
6884 	windows = 64, /// ditto
6885 	numLock = 16, ///
6886 
6887 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
6888 	middleButtonDown = 512, /// ditto
6889 	rightButtonDown = 1024, /// ditto
6890 }
6891 else version(Windows)
6892 /// ditto
6893 enum ModifierState : uint {
6894 	shift = 4, ///
6895 	ctrl = 8, ///
6896 
6897 	// i'm not sure if the next two are available
6898 	alt = 256, /// not always available on Windows
6899 	windows = 512, /// ditto
6900 
6901 	capsLock = 1024, ///
6902 	numLock = 2048, ///
6903 
6904 	leftButtonDown = 1, /// not available on key events
6905 	middleButtonDown = 16, /// ditto
6906 	rightButtonDown = 2, /// ditto
6907 
6908 	backButtonDown = 0x20, /// not available on X
6909 	forwardButtonDown = 0x40, /// ditto
6910 }
6911 else version(OSXCocoa)
6912 // FIXME FIXME NotYetImplementedException
6913 enum ModifierState : uint {
6914 	shift = 1, ///
6915 	capsLock = 2, ///
6916 	ctrl = 4, ///
6917 	alt = 8, /// Not always available on Windows
6918 	windows = 64, /// ditto
6919 	numLock = 16, ///
6920 
6921 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
6922 	middleButtonDown = 512, /// ditto
6923 	rightButtonDown = 1024, /// ditto
6924 }
6925 
6926 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them
6927 enum MouseButton : int {
6928 	none = 0,
6929 	left = 1, ///
6930 	right = 2, ///
6931 	middle = 4, ///
6932 	wheelUp = 8, ///
6933 	wheelDown = 16, ///
6934 	backButton = 32, /// often found on the thumb and used for back in browsers
6935 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
6936 }
6937 
6938 version(X11) {
6939 	// FIXME: match ASCII whenever we can. Most of it is already there,
6940 	// but there's a few exceptions and mismatches with Windows
6941 
6942 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
6943 	enum Key {
6944 		Escape = 0xff1b, ///
6945 		F1 = 0xffbe, ///
6946 		F2 = 0xffbf, ///
6947 		F3 = 0xffc0, ///
6948 		F4 = 0xffc1, ///
6949 		F5 = 0xffc2, ///
6950 		F6 = 0xffc3, ///
6951 		F7 = 0xffc4, ///
6952 		F8 = 0xffc5, ///
6953 		F9 = 0xffc6, ///
6954 		F10 = 0xffc7, ///
6955 		F11 = 0xffc8, ///
6956 		F12 = 0xffc9, ///
6957 		PrintScreen = 0xff61, ///
6958 		ScrollLock = 0xff14, ///
6959 		Pause = 0xff13, ///
6960 		Grave = 0x60, /// The $(BACKTICK) ~ key
6961 		// number keys across the top of the keyboard
6962 		N1 = 0x31, /// Number key atop the keyboard
6963 		N2 = 0x32, ///
6964 		N3 = 0x33, ///
6965 		N4 = 0x34, ///
6966 		N5 = 0x35, ///
6967 		N6 = 0x36, ///
6968 		N7 = 0x37, ///
6969 		N8 = 0x38, ///
6970 		N9 = 0x39, ///
6971 		N0 = 0x30, ///
6972 		Dash = 0x2d, ///
6973 		Equals = 0x3d, ///
6974 		Backslash = 0x5c, /// The \ | key
6975 		Backspace = 0xff08, ///
6976 		Insert = 0xff63, ///
6977 		Home = 0xff50, ///
6978 		PageUp = 0xff55, ///
6979 		Delete = 0xffff, ///
6980 		End = 0xff57, ///
6981 		PageDown = 0xff56, ///
6982 		Up = 0xff52, ///
6983 		Down = 0xff54, ///
6984 		Left = 0xff51, ///
6985 		Right = 0xff53, ///
6986 
6987 		Tab = 0xff09, ///
6988 		Q = 0x71, ///
6989 		W = 0x77, ///
6990 		E = 0x65, ///
6991 		R = 0x72, ///
6992 		T = 0x74, ///
6993 		Y = 0x79, ///
6994 		U = 0x75, ///
6995 		I = 0x69, ///
6996 		O = 0x6f, ///
6997 		P = 0x70, ///
6998 		LeftBracket = 0x5b, /// the [ { key
6999 		RightBracket = 0x5d, /// the ] } key
7000 		CapsLock = 0xffe5, ///
7001 		A = 0x61, ///
7002 		S = 0x73, ///
7003 		D = 0x64, ///
7004 		F = 0x66, ///
7005 		G = 0x67, ///
7006 		H = 0x68, ///
7007 		J = 0x6a, ///
7008 		K = 0x6b, ///
7009 		L = 0x6c, ///
7010 		Semicolon = 0x3b, ///
7011 		Apostrophe = 0x27, ///
7012 		Enter = 0xff0d, ///
7013 		Shift = 0xffe1, ///
7014 		Z = 0x7a, ///
7015 		X = 0x78, ///
7016 		C = 0x63, ///
7017 		V = 0x76, ///
7018 		B = 0x62, ///
7019 		N = 0x6e, ///
7020 		M = 0x6d, ///
7021 		Comma = 0x2c, ///
7022 		Period = 0x2e, ///
7023 		Slash = 0x2f, /// the / ? key
7024 		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
7025 		Ctrl = 0xffe3, ///
7026 		Windows = 0xffeb, ///
7027 		Alt = 0xffe9, ///
7028 		Space = 0x20, ///
7029 		Alt_r = 0xffea, /// ditto of shift_r
7030 		Windows_r = 0xffec, ///
7031 		Menu = 0xff67, ///
7032 		Ctrl_r = 0xffe4, ///
7033 
7034 		NumLock = 0xff7f, ///
7035 		Divide = 0xffaf, /// The / key on the number pad
7036 		Multiply = 0xffaa, /// The * key on the number pad
7037 		Minus = 0xffad, /// The - key on the number pad
7038 		Plus = 0xffab, /// The + key on the number pad
7039 		PadEnter = 0xff8d, /// Numberpad enter key
7040 		Pad1 = 0xff9c, /// Numberpad keys
7041 		Pad2 = 0xff99, ///
7042 		Pad3 = 0xff9b, ///
7043 		Pad4 = 0xff96, ///
7044 		Pad5 = 0xff9d, ///
7045 		Pad6 = 0xff98, ///
7046 		Pad7 = 0xff95, ///
7047 		Pad8 = 0xff97, ///
7048 		Pad9 = 0xff9a, ///
7049 		Pad0 = 0xff9e, ///
7050 		PadDot = 0xff9f, ///
7051 	}
7052 } else version(Windows) {
7053 	// the character here is for en-us layouts and for illustration only
7054 	// if you actually want to get characters, wait for character events
7055 	// (the argument to your event handler is simply a dchar)
7056 	// those will be converted by the OS for the right locale.
7057 
7058 	enum Key {
7059 		Escape = 0x1b,
7060 		F1 = 0x70,
7061 		F2 = 0x71,
7062 		F3 = 0x72,
7063 		F4 = 0x73,
7064 		F5 = 0x74,
7065 		F6 = 0x75,
7066 		F7 = 0x76,
7067 		F8 = 0x77,
7068 		F9 = 0x78,
7069 		F10 = 0x79,
7070 		F11 = 0x7a,
7071 		F12 = 0x7b,
7072 		PrintScreen = 0x2c,
7073 		ScrollLock = 0x91,
7074 		Pause = 0x13,
7075 		Grave = 0xc0,
7076 		// number keys across the top of the keyboard
7077 		N1 = 0x31,
7078 		N2 = 0x32,
7079 		N3 = 0x33,
7080 		N4 = 0x34,
7081 		N5 = 0x35,
7082 		N6 = 0x36,
7083 		N7 = 0x37,
7084 		N8 = 0x38,
7085 		N9 = 0x39,
7086 		N0 = 0x30,
7087 		Dash = 0xbd,
7088 		Equals = 0xbb,
7089 		Backslash = 0xdc,
7090 		Backspace = 0x08,
7091 		Insert = 0x2d,
7092 		Home = 0x24,
7093 		PageUp = 0x21,
7094 		Delete = 0x2e,
7095 		End = 0x23,
7096 		PageDown = 0x22,
7097 		Up = 0x26,
7098 		Down = 0x28,
7099 		Left = 0x25,
7100 		Right = 0x27,
7101 
7102 		Tab = 0x09,
7103 		Q = 0x51,
7104 		W = 0x57,
7105 		E = 0x45,
7106 		R = 0x52,
7107 		T = 0x54,
7108 		Y = 0x59,
7109 		U = 0x55,
7110 		I = 0x49,
7111 		O = 0x4f,
7112 		P = 0x50,
7113 		LeftBracket = 0xdb,
7114 		RightBracket = 0xdd,
7115 		CapsLock = 0x14,
7116 		A = 0x41,
7117 		S = 0x53,
7118 		D = 0x44,
7119 		F = 0x46,
7120 		G = 0x47,
7121 		H = 0x48,
7122 		J = 0x4a,
7123 		K = 0x4b,
7124 		L = 0x4c,
7125 		Semicolon = 0xba,
7126 		Apostrophe = 0xde,
7127 		Enter = 0x0d,
7128 		Shift = 0x10,
7129 		Z = 0x5a,
7130 		X = 0x58,
7131 		C = 0x43,
7132 		V = 0x56,
7133 		B = 0x42,
7134 		N = 0x4e,
7135 		M = 0x4d,
7136 		Comma = 0xbc,
7137 		Period = 0xbe,
7138 		Slash = 0xbf,
7139 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
7140 		Ctrl = 0x11,
7141 		Windows = 0x5b,
7142 		Alt = -5, // FIXME
7143 		Space = 0x20,
7144 		Alt_r = 0xffea, // ditto of shift_r
7145 		Windows_r = 0x5c, // ditto of shift_r
7146 		Menu = 0x5d,
7147 		Ctrl_r = 0xa3, // ditto of shift_r
7148 
7149 		NumLock = 0x90,
7150 		Divide = 0x6f,
7151 		Multiply = 0x6a,
7152 		Minus = 0x6d,
7153 		Plus = 0x6b,
7154 		PadEnter = -8, // FIXME
7155 		Pad1 = 0x61,
7156 		Pad2 = 0x62,
7157 		Pad3 = 0x63,
7158 		Pad4 = 0x64,
7159 		Pad5 = 0x65,
7160 		Pad6 = 0x66,
7161 		Pad7 = 0x67,
7162 		Pad8 = 0x68,
7163 		Pad9 = 0x69,
7164 		Pad0 = 0x60,
7165 		PadDot = 0x6e,
7166 	}
7167 
7168 	// I'm keeping this around for reference purposes
7169 	// ideally all these buttons will be listed for all platforms,
7170 	// but now now I'm just focusing on my US keyboard
7171 	version(none)
7172 	enum Key {
7173 		LBUTTON = 0x01,
7174 		RBUTTON = 0x02,
7175 		CANCEL = 0x03,
7176 		MBUTTON = 0x04,
7177 		//static if (_WIN32_WINNT > =  0x500) {
7178 		XBUTTON1 = 0x05,
7179 		XBUTTON2 = 0x06,
7180 		//}
7181 		BACK = 0x08,
7182 		TAB = 0x09,
7183 		CLEAR = 0x0C,
7184 		RETURN = 0x0D,
7185 		SHIFT = 0x10,
7186 		CONTROL = 0x11,
7187 		MENU = 0x12,
7188 		PAUSE = 0x13,
7189 		CAPITAL = 0x14,
7190 		KANA = 0x15,
7191 		HANGEUL = 0x15,
7192 		HANGUL = 0x15,
7193 		JUNJA = 0x17,
7194 		FINAL = 0x18,
7195 		HANJA = 0x19,
7196 		KANJI = 0x19,
7197 		ESCAPE = 0x1B,
7198 		CONVERT = 0x1C,
7199 		NONCONVERT = 0x1D,
7200 		ACCEPT = 0x1E,
7201 		MODECHANGE = 0x1F,
7202 		SPACE = 0x20,
7203 		PRIOR = 0x21,
7204 		NEXT = 0x22,
7205 		END = 0x23,
7206 		HOME = 0x24,
7207 		LEFT = 0x25,
7208 		UP = 0x26,
7209 		RIGHT = 0x27,
7210 		DOWN = 0x28,
7211 		SELECT = 0x29,
7212 		PRINT = 0x2A,
7213 		EXECUTE = 0x2B,
7214 		SNAPSHOT = 0x2C,
7215 		INSERT = 0x2D,
7216 		DELETE = 0x2E,
7217 		HELP = 0x2F,
7218 		LWIN = 0x5B,
7219 		RWIN = 0x5C,
7220 		APPS = 0x5D,
7221 		SLEEP = 0x5F,
7222 		NUMPAD0 = 0x60,
7223 		NUMPAD1 = 0x61,
7224 		NUMPAD2 = 0x62,
7225 		NUMPAD3 = 0x63,
7226 		NUMPAD4 = 0x64,
7227 		NUMPAD5 = 0x65,
7228 		NUMPAD6 = 0x66,
7229 		NUMPAD7 = 0x67,
7230 		NUMPAD8 = 0x68,
7231 		NUMPAD9 = 0x69,
7232 		MULTIPLY = 0x6A,
7233 		ADD = 0x6B,
7234 		SEPARATOR = 0x6C,
7235 		SUBTRACT = 0x6D,
7236 		DECIMAL = 0x6E,
7237 		DIVIDE = 0x6F,
7238 		F1 = 0x70,
7239 		F2 = 0x71,
7240 		F3 = 0x72,
7241 		F4 = 0x73,
7242 		F5 = 0x74,
7243 		F6 = 0x75,
7244 		F7 = 0x76,
7245 		F8 = 0x77,
7246 		F9 = 0x78,
7247 		F10 = 0x79,
7248 		F11 = 0x7A,
7249 		F12 = 0x7B,
7250 		F13 = 0x7C,
7251 		F14 = 0x7D,
7252 		F15 = 0x7E,
7253 		F16 = 0x7F,
7254 		F17 = 0x80,
7255 		F18 = 0x81,
7256 		F19 = 0x82,
7257 		F20 = 0x83,
7258 		F21 = 0x84,
7259 		F22 = 0x85,
7260 		F23 = 0x86,
7261 		F24 = 0x87,
7262 		NUMLOCK = 0x90,
7263 		SCROLL = 0x91,
7264 		LSHIFT = 0xA0,
7265 		RSHIFT = 0xA1,
7266 		LCONTROL = 0xA2,
7267 		RCONTROL = 0xA3,
7268 		LMENU = 0xA4,
7269 		RMENU = 0xA5,
7270 		//static if (_WIN32_WINNT > =  0x500) {
7271 		BROWSER_BACK = 0xA6,
7272 		BROWSER_FORWARD = 0xA7,
7273 		BROWSER_REFRESH = 0xA8,
7274 		BROWSER_STOP = 0xA9,
7275 		BROWSER_SEARCH = 0xAA,
7276 		BROWSER_FAVORITES = 0xAB,
7277 		BROWSER_HOME = 0xAC,
7278 		VOLUME_MUTE = 0xAD,
7279 		VOLUME_DOWN = 0xAE,
7280 		VOLUME_UP = 0xAF,
7281 		MEDIA_NEXT_TRACK = 0xB0,
7282 		MEDIA_PREV_TRACK = 0xB1,
7283 		MEDIA_STOP = 0xB2,
7284 		MEDIA_PLAY_PAUSE = 0xB3,
7285 		LAUNCH_MAIL = 0xB4,
7286 		LAUNCH_MEDIA_SELECT = 0xB5,
7287 		LAUNCH_APP1 = 0xB6,
7288 		LAUNCH_APP2 = 0xB7,
7289 		//}
7290 		OEM_1 = 0xBA,
7291 		//static if (_WIN32_WINNT > =  0x500) {
7292 		OEM_PLUS = 0xBB,
7293 		OEM_COMMA = 0xBC,
7294 		OEM_MINUS = 0xBD,
7295 		OEM_PERIOD = 0xBE,
7296 		//}
7297 		OEM_2 = 0xBF,
7298 		OEM_3 = 0xC0,
7299 		OEM_4 = 0xDB,
7300 		OEM_5 = 0xDC,
7301 		OEM_6 = 0xDD,
7302 		OEM_7 = 0xDE,
7303 		OEM_8 = 0xDF,
7304 		//static if (_WIN32_WINNT > =  0x500) {
7305 		OEM_102 = 0xE2,
7306 		//}
7307 		PROCESSKEY = 0xE5,
7308 		//static if (_WIN32_WINNT > =  0x500) {
7309 		PACKET = 0xE7,
7310 		//}
7311 		ATTN = 0xF6,
7312 		CRSEL = 0xF7,
7313 		EXSEL = 0xF8,
7314 		EREOF = 0xF9,
7315 		PLAY = 0xFA,
7316 		ZOOM = 0xFB,
7317 		NONAME = 0xFC,
7318 		PA1 = 0xFD,
7319 		OEM_CLEAR = 0xFE,
7320 	}
7321 
7322 } else version(OSXCocoa) {
7323 	// FIXME
7324 	enum Key {
7325 		Escape = 0x1b,
7326 		F1 = 0x70,
7327 		F2 = 0x71,
7328 		F3 = 0x72,
7329 		F4 = 0x73,
7330 		F5 = 0x74,
7331 		F6 = 0x75,
7332 		F7 = 0x76,
7333 		F8 = 0x77,
7334 		F9 = 0x78,
7335 		F10 = 0x79,
7336 		F11 = 0x7a,
7337 		F12 = 0x7b,
7338 		PrintScreen = 0x2c,
7339 		ScrollLock = -2, // FIXME
7340 		Pause = -3, // FIXME
7341 		Grave = 0xc0,
7342 		// number keys across the top of the keyboard
7343 		N1 = 0x31,
7344 		N2 = 0x32,
7345 		N3 = 0x33,
7346 		N4 = 0x34,
7347 		N5 = 0x35,
7348 		N6 = 0x36,
7349 		N7 = 0x37,
7350 		N8 = 0x38,
7351 		N9 = 0x39,
7352 		N0 = 0x30,
7353 		Dash = 0xbd,
7354 		Equals = 0xbb,
7355 		Backslash = 0xdc,
7356 		Backspace = 0x08,
7357 		Insert = 0x2d,
7358 		Home = 0x24,
7359 		PageUp = 0x21,
7360 		Delete = 0x2e,
7361 		End = 0x23,
7362 		PageDown = 0x22,
7363 		Up = 0x26,
7364 		Down = 0x28,
7365 		Left = 0x25,
7366 		Right = 0x27,
7367 
7368 		Tab = 0x09,
7369 		Q = 0x51,
7370 		W = 0x57,
7371 		E = 0x45,
7372 		R = 0x52,
7373 		T = 0x54,
7374 		Y = 0x59,
7375 		U = 0x55,
7376 		I = 0x49,
7377 		O = 0x4f,
7378 		P = 0x50,
7379 		LeftBracket = 0xdb,
7380 		RightBracket = 0xdd,
7381 		CapsLock = 0x14,
7382 		A = 0x41,
7383 		S = 0x53,
7384 		D = 0x44,
7385 		F = 0x46,
7386 		G = 0x47,
7387 		H = 0x48,
7388 		J = 0x4a,
7389 		K = 0x4b,
7390 		L = 0x4c,
7391 		Semicolon = 0xba,
7392 		Apostrophe = 0xde,
7393 		Enter = 0x0d,
7394 		Shift = 0x10,
7395 		Z = 0x5a,
7396 		X = 0x58,
7397 		C = 0x43,
7398 		V = 0x56,
7399 		B = 0x42,
7400 		N = 0x4e,
7401 		M = 0x4d,
7402 		Comma = 0xbc,
7403 		Period = 0xbe,
7404 		Slash = 0xbf,
7405 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
7406 		Ctrl = 0x11,
7407 		Windows = 0x5b,
7408 		Alt = -5, // FIXME
7409 		Space = 0x20,
7410 		Alt_r = 0xffea, // ditto of shift_r
7411 		Windows_r = -6, // FIXME
7412 		Menu = 0x5d,
7413 		Ctrl_r = -7, // FIXME
7414 
7415 		NumLock = 0x90,
7416 		Divide = 0x6f,
7417 		Multiply = 0x6a,
7418 		Minus = 0x6d,
7419 		Plus = 0x6b,
7420 		PadEnter = -8, // FIXME
7421 		// FIXME for the rest of these:
7422 		Pad1 = 0xff9c,
7423 		Pad2 = 0xff99,
7424 		Pad3 = 0xff9b,
7425 		Pad4 = 0xff96,
7426 		Pad5 = 0xff9d,
7427 		Pad6 = 0xff98,
7428 		Pad7 = 0xff95,
7429 		Pad8 = 0xff97,
7430 		Pad9 = 0xff9a,
7431 		Pad0 = 0xff9e,
7432 		PadDot = 0xff9f,
7433 	}
7434 
7435 }
7436 
7437 /* Additional utilities */
7438 
7439 
7440 Color fromHsl(real h, real s, real l) {
7441 	return arsd.color.fromHsl([h,s,l]);
7442 }
7443 
7444 
7445 
7446 /* ********** What follows is the system-specific implementations *********/
7447 version(Windows) {
7448 
7449 
7450 	// helpers for making HICONs from MemoryImages
7451 	class WindowsIcon {
7452 		struct Win32Icon(int colorCount) {
7453 		align(1):
7454 			uint biSize;
7455 			int biWidth;
7456 			int biHeight;
7457 			ushort biPlanes;
7458 			ushort biBitCount;
7459 			uint biCompression;
7460 			uint biSizeImage;
7461 			int biXPelsPerMeter;
7462 			int biYPelsPerMeter;
7463 			uint biClrUsed;
7464 			uint biClrImportant;
7465 			RGBQUAD[colorCount] biColors;
7466 			/* Pixels:
7467 			Uint8 pixels[]
7468 			*/
7469 			/* Mask:
7470 			Uint8 mask[]
7471 			*/
7472 
7473 			ubyte[4096] data;
7474 
7475 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
7476 				width = mi.width;
7477 				height = mi.height;
7478 
7479 				auto indexedImage = cast(IndexedImage) mi;
7480 				if(indexedImage is null)
7481 					indexedImage = quantize(mi.getAsTrueColorImage());
7482 
7483 				assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
7484 				assert(height %4 == 0);
7485 
7486 				int icon_plen = height*((width+3)&~3);
7487 				int icon_mlen = height*((((width+7)/8)+3)&~3);
7488 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
7489 
7490 				biSize = 40;
7491 				biWidth = width;
7492 				biHeight = height*2;
7493 				biPlanes = 1;
7494 				biBitCount = 8;
7495 				biSizeImage = icon_plen+icon_mlen;
7496 
7497 				int offset = 0;
7498 				int andOff = icon_plen * 8; // the and offset is in bits
7499 				for(int y = height - 1; y >= 0; y--) {
7500 					int off2 = y * width;
7501 					foreach(x; 0 .. width) {
7502 						const b = indexedImage.data[off2 + x];
7503 						data[offset] = b;
7504 						offset++;
7505 
7506 						const andBit = andOff % 8;
7507 						const andIdx = andOff / 8;
7508 						assert(b < indexedImage.palette.length);
7509 						// this is anded to the destination, since and 0 means erase,
7510 						// we want that to  be opaque, and 1 for transparent
7511 						auto transparent = (indexedImage.palette[b].a <= 127);
7512 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
7513 
7514 						andOff++;
7515 					}
7516 
7517 					andOff += andOff % 32;
7518 				}
7519 
7520 				foreach(idx, entry; indexedImage.palette) {
7521 					if(entry.a > 127) {
7522 						biColors[idx].rgbBlue = entry.b;
7523 						biColors[idx].rgbGreen = entry.g;
7524 						biColors[idx].rgbRed = entry.r;
7525 					} else {
7526 						biColors[idx].rgbBlue = 255;
7527 						biColors[idx].rgbGreen = 255;
7528 						biColors[idx].rgbRed = 255;
7529 					}
7530 				}
7531 
7532 				/*
7533 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
7534 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
7535 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
7536 				auto pngMap = fetchPaletteWin32(png);
7537 				biColors[0..pngMap.length] = pngMap[];
7538 				*/
7539 			}
7540 		}
7541 
7542 
7543 		Win32Icon!(256) icon_win32;
7544 
7545 
7546 		this(MemoryImage mi) {
7547 			int icon_len, width, height;
7548 
7549 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
7550 
7551 			/*
7552 			PNG* png = readPnpngData);
7553 			PNGHeader pngh = getHeader(png);
7554 			void* icon_win32;
7555 			if(pngh.depth == 4) {
7556 				auto i = new Win32Icon!(16);
7557 				i.fromPNG(png, pngh, icon_len, width, height);
7558 				icon_win32 = i;
7559 			}
7560 			else if(pngh.depth == 8) {
7561 				auto i = new Win32Icon!(256);
7562 				i.fromPNG(png, pngh, icon_len, width, height);
7563 				icon_win32 = i;
7564 			} else assert(0);
7565 			*/
7566 
7567 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
7568 
7569 			if(hIcon is null) throw new Exception("CreateIconFromResourceEx");
7570 		}
7571 
7572 		~this() {
7573 			DestroyIcon(hIcon);
7574 		}
7575 
7576 		HICON hIcon;
7577 	}
7578 
7579 
7580 
7581 
7582 
7583 
7584 	alias int delegate(HWND, UINT, WPARAM, LPARAM) NativeEventHandler;
7585 	alias HWND NativeWindowHandle;
7586 
7587 	extern(Windows)
7588 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
7589 		try {
7590 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
7591 				// it returns zero if the message is handled, so we won't do anything more there
7592 				// do I like that though?
7593 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam);
7594 				if(ret == 0)
7595 					return ret;
7596 			}
7597 
7598 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
7599 				if(window.getNativeEventHandler !is null) {
7600 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam);
7601 					if(ret == 0)
7602 						return ret;
7603 				}
7604 				if(auto w = cast(SimpleWindow) (*window))
7605 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
7606 				else
7607 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
7608 			} else {
7609 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
7610 			}
7611 		} catch (Exception e) {
7612 			assert(false, "Exception caught in WndProc " ~ e.toString());
7613 		}
7614 	}
7615 
7616 	mixin template NativeScreenPainterImplementation() {
7617 		HDC hdc;
7618 		HWND hwnd;
7619 		//HDC windowHdc;
7620 		HBITMAP oldBmp;
7621 
7622 		void create(NativeWindowHandle window) {
7623 			hwnd = window;
7624 
7625 			if(auto sw = cast(SimpleWindow) this.window) {
7626 				// drawing on a window, double buffer
7627 				auto windowHdc = GetDC(hwnd);
7628 
7629 				auto buffer = sw.impl.buffer;
7630 				hdc = CreateCompatibleDC(windowHdc);
7631 
7632 				ReleaseDC(hwnd, windowHdc);
7633 
7634 				oldBmp = SelectObject(hdc, buffer);
7635 			} else {
7636 				// drawing on something else, draw directly
7637 				hdc = CreateCompatibleDC(null);
7638 				SelectObject(hdc, window);
7639 
7640 			}
7641 
7642 			// X doesn't draw a text background, so neither should we
7643 			SetBkMode(hdc, TRANSPARENT);
7644 
7645 
7646 			static bool triedDefaultGuiFont = false;
7647 			if(!triedDefaultGuiFont) {
7648 				NONCLIENTMETRICS params;
7649 				params.cbSize = params.sizeof;
7650 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
7651 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
7652 				}
7653 				triedDefaultGuiFont = true;
7654 			}
7655 
7656 			if(defaultGuiFont) {
7657 				SelectObject(hdc, defaultGuiFont);
7658 				// DeleteObject(defaultGuiFont);
7659 			}
7660 		}
7661 
7662 		static HFONT defaultGuiFont;
7663 
7664 		void setFont(OperatingSystemFont font) {
7665 			if(font && font.font)
7666 				SelectObject(hdc, font.font);
7667 			else if(defaultGuiFont)
7668 				SelectObject(hdc, defaultGuiFont);
7669 		}
7670 
7671 		arsd.color.Rectangle _clipRectangle;
7672 
7673 		void setClipRectangle(int x, int y, int width, int height) {
7674 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
7675 
7676 			if(width == 0 || height == 0) {
7677 				SelectClipRgn(hdc, null);
7678 			} else {
7679 				auto region = CreateRectRgn(x, y, x + width, y + height);
7680 				SelectClipRgn(hdc, region);
7681 				DeleteObject(region);
7682 			}
7683 		}
7684 
7685 
7686 		// just because we can on Windows...
7687 		//void create(Image image);
7688 
7689 		void dispose() {
7690 			// FIXME: this.window.width/height is probably wrong
7691 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
7692 			// ReleaseDC(hwnd, windowHdc);
7693 
7694 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
7695 			if(cast(SimpleWindow) this.window)
7696 			InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
7697 
7698 			if(originalPen !is null)
7699 				SelectObject(hdc, originalPen);
7700 			if(currentPen !is null)
7701 				DeleteObject(currentPen);
7702 			if(originalBrush !is null)
7703 				SelectObject(hdc, originalBrush);
7704 			if(currentBrush !is null)
7705 				DeleteObject(currentBrush);
7706 
7707 			SelectObject(hdc, oldBmp);
7708 
7709 			DeleteDC(hdc);
7710 
7711 			if(window.paintingFinishedDg !is null)
7712 				window.paintingFinishedDg();
7713 		}
7714 
7715 		HPEN originalPen;
7716 		HPEN currentPen;
7717 
7718 		Pen _activePen;
7719 
7720 		@property void pen(Pen p) {
7721 			_activePen = p;
7722 
7723 			HPEN pen;
7724 			if(p.color.a == 0) {
7725 				pen = GetStockObject(NULL_PEN);
7726 			} else {
7727 				int style = PS_SOLID;
7728 				final switch(p.style) {
7729 					case Pen.Style.Solid:
7730 						style = PS_SOLID;
7731 					break;
7732 					case Pen.Style.Dashed:
7733 						style = PS_DASH;
7734 					break;
7735 					case Pen.Style.Dotted:
7736 						style = PS_DOT;
7737 					break;
7738 				}
7739 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
7740 			}
7741 			auto orig = SelectObject(hdc, pen);
7742 			if(originalPen is null)
7743 				originalPen = orig;
7744 
7745 			if(currentPen !is null)
7746 				DeleteObject(currentPen);
7747 
7748 			currentPen = pen;
7749 
7750 			// the outline is like a foreground since it's done that way on X
7751 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
7752 
7753 		}
7754 
7755 		@property void rasterOp(RasterOp op) {
7756 			int mode;
7757 			final switch(op) {
7758 				case RasterOp.normal:
7759 					mode = R2_COPYPEN;
7760 				break;
7761 				case RasterOp.xor:
7762 					mode = R2_XORPEN;
7763 				break;
7764 			}
7765 			SetROP2(hdc, mode);
7766 		}
7767 
7768 		HBRUSH originalBrush;
7769 		HBRUSH currentBrush;
7770 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
7771 		@property void fillColor(Color c) {
7772 			if(c == _fillColor)
7773 				return;
7774 			_fillColor = c;
7775 			HBRUSH brush;
7776 			if(c.a == 0) {
7777 				brush = GetStockObject(HOLLOW_BRUSH);
7778 			} else {
7779 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
7780 			}
7781 			auto orig = SelectObject(hdc, brush);
7782 			if(originalBrush is null)
7783 				originalBrush = orig;
7784 
7785 			if(currentBrush !is null)
7786 				DeleteObject(currentBrush);
7787 
7788 			currentBrush = brush;
7789 
7790 			// background color is NOT set because X doesn't draw text backgrounds
7791 			//   SetBkColor(hdc, RGB(255, 255, 255));
7792 		}
7793 
7794 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
7795 			BITMAP bm;
7796 
7797 			HDC hdcMem = CreateCompatibleDC(hdc);
7798 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
7799 
7800 			GetObject(i.handle, bm.sizeof, &bm);
7801 
7802 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
7803 
7804 			SelectObject(hdcMem, hbmOld);
7805 			DeleteDC(hdcMem);
7806 		}
7807 
7808 		void drawPixmap(Sprite s, int x, int y) {
7809 			BITMAP bm;
7810 
7811 			HDC hdcMem = CreateCompatibleDC(hdc);
7812 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
7813 
7814 			GetObject(s.handle, bm.sizeof, &bm);
7815 
7816 			BitBlt(hdc, x, y, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
7817 
7818 			SelectObject(hdcMem, hbmOld);
7819 			DeleteDC(hdcMem);
7820 		}
7821 
7822 		Size textSize(scope const(char)[] text) {
7823 			bool dummyX;
7824 			if(text.length == 0) {
7825 				text = " ";
7826 				dummyX = true;
7827 			}
7828 			RECT rect;
7829 			WCharzBuffer buffer = WCharzBuffer(text);
7830 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT);
7831 			return Size(dummyX ? 0 : rect.right, rect.bottom);
7832 		}
7833 
7834 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
7835 			if(text.length && text[$-1] == '\n')
7836 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
7837 
7838 			WCharzBuffer buffer = WCharzBuffer(text);
7839 			if(x2 == 0 && y2 == 0)
7840 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
7841 			else {
7842 				RECT rect;
7843 				rect.left = x;
7844 				rect.top = y;
7845 				rect.right = x2;
7846 				rect.bottom = y2;
7847 
7848 				uint mode = DT_LEFT;
7849 				if(alignment & TextAlignment.Right)
7850 					mode = DT_RIGHT;
7851 				else if(alignment & TextAlignment.Center)
7852 					mode = DT_CENTER;
7853 
7854 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
7855 				if(alignment & TextAlignment.VerticalCenter)
7856 					mode |= DT_VCENTER | DT_SINGLELINE;
7857 
7858 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode);
7859 			}
7860 
7861 			/*
7862 			uint mode;
7863 
7864 			if(alignment & TextAlignment.Center)
7865 				mode = TA_CENTER;
7866 
7867 			SetTextAlign(hdc, mode);
7868 			*/
7869 		}
7870 
7871 		int fontHeight() {
7872 			TEXTMETRIC metric;
7873 			if(GetTextMetricsW(hdc, &metric)) {
7874 				return metric.tmHeight;
7875 			}
7876 
7877 			return 16; // idk just guessing here, maybe we should throw
7878 		}
7879 
7880 		void drawPixel(int x, int y) {
7881 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
7882 		}
7883 
7884 		// The basic shapes, outlined
7885 
7886 		void drawLine(int x1, int y1, int x2, int y2) {
7887 			MoveToEx(hdc, x1, y1, null);
7888 			LineTo(hdc, x2, y2);
7889 		}
7890 
7891 		void drawRectangle(int x, int y, int width, int height) {
7892 			gdi.Rectangle(hdc, x, y, x + width, y + height);
7893 		}
7894 
7895 		/// Arguments are the points of the bounding rectangle
7896 		void drawEllipse(int x1, int y1, int x2, int y2) {
7897 			Ellipse(hdc, x1, y1, x2, y2);
7898 		}
7899 
7900 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
7901 			if((start % (360*64)) == (finish % (360*64)))
7902 				drawEllipse(x1, y1, x1 + width, y1 + height);
7903 			else {
7904 				import core.stdc.math;
7905 				float startAngle = start * 64 * 180 / 3.14159265;
7906 				float endAngle = finish * 64 * 180 / 3.14159265;
7907 				Arc(hdc, x1, y1, x1 + width, y1 + height,
7908 					cast(int)(cos(startAngle) * width / 2 + x1),
7909 					cast(int)(sin(startAngle) * height / 2 + y1),
7910 					cast(int)(cos(endAngle) * width / 2 + x1),
7911 					cast(int)(sin(endAngle) * height / 2 + y1),
7912 				);
7913 			}
7914 		}
7915 
7916 		void drawPolygon(Point[] vertexes) {
7917 			POINT[] points;
7918 			points.length = vertexes.length;
7919 
7920 			foreach(i, p; vertexes) {
7921 				points[i].x = p.x;
7922 				points[i].y = p.y;
7923 			}
7924 
7925 			Polygon(hdc, points.ptr, cast(int) points.length);
7926 		}
7927 	}
7928 
7929 
7930 	// Mix this into the SimpleWindow class
7931 	mixin template NativeSimpleWindowImplementation() {
7932 		int curHidden = 0; // counter
7933 		__gshared static bool[string] knownWinClasses;
7934 		static bool altPressed = false;
7935 
7936 		HANDLE oldCursor;
7937 
7938 		void hideCursor () {
7939 			if(curHidden == 0)
7940 				oldCursor = SetCursor(null);
7941 			++curHidden;
7942 		}
7943 
7944 		void showCursor () {
7945 			--curHidden;
7946 			if(curHidden == 0) {
7947 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
7948 			}
7949 		}
7950 
7951 
7952 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
7953 
7954 		void setMinSize (int minwidth, int minheight) {
7955 			minWidth = minwidth;
7956 			minHeight = minheight;
7957 		}
7958 		void setMaxSize (int maxwidth, int maxheight) {
7959 			maxWidth = maxwidth;
7960 			maxHeight = maxheight;
7961 		}
7962 
7963 		// FIXME i'm not sure that Windows has this functionality
7964 		// though it is nonessential anyway.
7965 		void setResizeGranularity (int granx, int grany) {}
7966 
7967 		ScreenPainter getPainter() {
7968 			return ScreenPainter(this, hwnd);
7969 		}
7970 
7971 		HBITMAP buffer;
7972 
7973 		void setTitle(string title) {
7974 			WCharzBuffer bfr = WCharzBuffer(title);
7975 			SetWindowTextW(hwnd, bfr.ptr);
7976 		}
7977 
7978 		string getTitle() {
7979 			auto len = GetWindowTextLengthW(hwnd);
7980 			if (!len)
7981 				return null;
7982 			wchar[256] tmpBuffer;
7983 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
7984 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
7985 			auto str = buffer[0 .. len2];
7986 			return makeUtf8StringFromWindowsString(str);
7987 		}
7988 
7989 		void move(int x, int y) {
7990 			RECT rect;
7991 			GetWindowRect(hwnd, &rect);
7992 			// move it while maintaining the same size...
7993 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
7994 		}
7995 
7996 		void resize(int w, int h) {
7997 			RECT rect;
7998 			GetWindowRect(hwnd, &rect);
7999 
8000 			RECT client;
8001 			GetClientRect(hwnd, &client);
8002 
8003 			rect.right = rect.right - client.right + w;
8004 			rect.bottom = rect.bottom - client.bottom + h;
8005 
8006 			// same position, new size for the client rectangle
8007 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
8008 
8009 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
8010 		}
8011 
8012 		void moveResize (int x, int y, int w, int h) {
8013 			// what's given is the client rectangle, we need to adjust
8014 
8015 			RECT rect;
8016 			rect.left = x;
8017 			rect.top = y;
8018 			rect.right = w + x;
8019 			rect.bottom = h + y;
8020 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
8021 				throw new Exception("AdjustWindowRect");
8022 
8023 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
8024 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
8025 			if (windowResized !is null) windowResized(w, h);
8026 		}
8027 
8028 		version(without_opengl) {} else {
8029 			HGLRC ghRC;
8030 			HDC ghDC;
8031 		}
8032 
8033 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
8034 			import std.conv : to;
8035 			string cnamec;
8036 			wstring cn;// = "DSimpleWindow\0"w.dup;
8037 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
8038 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
8039 				cnamec = "DSimpleWindow";
8040 			} else {
8041 				cnamec = sdpyWindowClass;
8042 			}
8043 			cn = cnamec.to!wstring ~ "\0"; // just in case, lol
8044 
8045 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
8046 
8047 			if(cnamec !in knownWinClasses) {
8048 				WNDCLASSEX wc;
8049 
8050 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
8051 				// to the object. Maybe.
8052 				wc.cbSize = wc.sizeof;
8053 				wc.cbClsExtra = 0;
8054 				wc.cbWndExtra = 0;
8055 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
8056 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
8057 				wc.hIcon = LoadIcon(hInstance, null);
8058 				wc.hInstance = hInstance;
8059 				wc.lpfnWndProc = &WndProc;
8060 				wc.lpszClassName = cn.ptr;
8061 				wc.hIconSm = null;
8062 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
8063 				if(!RegisterClassExW(&wc))
8064 					throw new WindowsApiException("RegisterClassExW");
8065 				knownWinClasses[cnamec] = true;
8066 			}
8067 
8068 			int style;
8069 
8070 			// FIXME: windowType and customizationFlags
8071 			final switch(windowType) {
8072 				case WindowTypes.normal:
8073 					style = WS_OVERLAPPEDWINDOW;
8074 				break;
8075 				case WindowTypes.undecorated:
8076 					style = WS_POPUP | WS_SYSMENU;
8077 				break;
8078 				case WindowTypes.eventOnly:
8079 					_hidden = true;
8080 				break;
8081 				case WindowTypes.dropdownMenu:
8082 				case WindowTypes.popupMenu:
8083 				case WindowTypes.notification:
8084 					style = WS_POPUP;
8085 				break;
8086 				case WindowTypes.nestedChild:
8087 					style = WS_CHILD;
8088 				break;
8089 			}
8090 
8091 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
8092 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
8093 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
8094 
8095 			hwnd = CreateWindowEx(flags, cn.ptr, toWStringz(title), style | WS_CLIPCHILDREN, // 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
8096 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
8097 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
8098 
8099 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
8100 				setOpacity(255);
8101 
8102 			SimpleWindow.nativeMapping[hwnd] = this;
8103 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
8104 
8105 			if(windowType == WindowTypes.eventOnly)
8106 				return;
8107 
8108 			HDC hdc = GetDC(hwnd);
8109 
8110 
8111 			version(without_opengl) {}
8112 			else {
8113 				if(opengl == OpenGlOptions.yes) {
8114 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
8115 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
8116 					ghDC = hdc;
8117 					PIXELFORMATDESCRIPTOR pfd;
8118 
8119 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
8120 					pfd.nVersion = 1;
8121 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
8122 					pfd.dwLayerMask = PFD_MAIN_PLANE;
8123 					pfd.iPixelType = PFD_TYPE_RGBA;
8124 					pfd.cColorBits = 24;
8125 					pfd.cDepthBits = 24;
8126 					pfd.cAccumBits = 0;
8127 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
8128 
8129 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
8130 
8131 					if ((pixelformat = ChoosePixelFormat(hdc, &pfd)) == 0)
8132 						throw new WindowsApiException("ChoosePixelFormat");
8133 
8134 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
8135 						throw new WindowsApiException("SetPixelFormat");
8136 
8137 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
8138 						// windoze is idiotic: we have to have OpenGL context to get function addresses
8139 						// so we will create fake context to get that stupid address
8140 						auto tmpcc = wglCreateContext(ghDC);
8141 						if (tmpcc !is null) {
8142 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
8143 							wglMakeCurrent(ghDC, tmpcc);
8144 							wglInitOtherFunctions();
8145 						}
8146 					}
8147 
8148 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
8149 						int[9] contextAttribs = [
8150 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
8151 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
8152 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
8153 							// for modern context, set "forward compatibility" flag too
8154 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
8155 							0/*None*/,
8156 						];
8157 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
8158 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
8159 							// activate fallback mode
8160 							// 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;
8161 							ghRC = wglCreateContext(ghDC);
8162 						}
8163 						if (ghRC is null)
8164 							throw new WindowsApiException("wglCreateContextAttribsARB");
8165 					} else {
8166 						// try to do at least something
8167 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
8168 							sdpyOpenGLContextVersion = 0;
8169 							ghRC = wglCreateContext(ghDC);
8170 						}
8171 						if (ghRC is null)
8172 							throw new WindowsApiException("wglCreateContext");
8173 					}
8174 				}
8175 			}
8176 
8177 			if(opengl == OpenGlOptions.no) {
8178 				buffer = CreateCompatibleBitmap(hdc, width, height);
8179 
8180 				auto hdcBmp = CreateCompatibleDC(hdc);
8181 				// make sure it's filled with a blank slate
8182 				auto oldBmp = SelectObject(hdcBmp, buffer);
8183 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
8184 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
8185 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
8186 				SelectObject(hdcBmp, oldBmp);
8187 				SelectObject(hdcBmp, oldBrush);
8188 				SelectObject(hdcBmp, oldPen);
8189 				DeleteDC(hdcBmp);
8190 
8191 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
8192 			}
8193 
8194 			// We want the window's client area to match the image size
8195 			RECT rcClient, rcWindow;
8196 			POINT ptDiff;
8197 			GetClientRect(hwnd, &rcClient);
8198 			GetWindowRect(hwnd, &rcWindow);
8199 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
8200 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
8201 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
8202 
8203 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
8204 				ShowWindow(hwnd, SW_SHOWNORMAL);
8205 			} else {
8206 				_hidden = true;
8207 			}
8208 			this._visibleForTheFirstTimeCalled = false; // hack!
8209 		}
8210 
8211 
8212 		void dispose() {
8213 			if(buffer)
8214 				DeleteObject(buffer);
8215 		}
8216 
8217 		void closeWindow() {
8218 			DestroyWindow(hwnd);
8219 		}
8220 
8221 		bool setOpacity(ubyte alpha) {
8222 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
8223 		}
8224 
8225 		HANDLE currentCursor;
8226 
8227 		// returns zero if it recognized the event
8228 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
8229 			MouseEvent mouse;
8230 
8231 			void mouseEvent() {
8232 				mouse.x = LOWORD(lParam) + offsetX;
8233 				mouse.y = HIWORD(lParam) + offsetY;
8234 				wind.mdx(mouse);
8235 				mouse.modifierState = cast(int) wParam;
8236 				mouse.window = wind;
8237 
8238 				if(wind.handleMouseEvent)
8239 					wind.handleMouseEvent(mouse);
8240 			}
8241 
8242 			switch(msg) {
8243 				case WM_GETMINMAXINFO:
8244 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
8245 
8246 					if(wind.minWidth > 0) {
8247 						RECT rect;
8248 						rect.left = 100;
8249 						rect.top = 100;
8250 						rect.right = wind.minWidth + 100;
8251 						rect.bottom = wind.minHeight + 100;
8252 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
8253 							throw new WindowsApiException("AdjustWindowRect");
8254 
8255 						mmi.ptMinTrackSize.x = rect.right - rect.left;
8256 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
8257 					}
8258 
8259 					if(wind.maxWidth < int.max) {
8260 						RECT rect;
8261 						rect.left = 100;
8262 						rect.top = 100;
8263 						rect.right = wind.maxWidth + 100;
8264 						rect.bottom = wind.maxHeight + 100;
8265 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
8266 							throw new WindowsApiException("AdjustWindowRect");
8267 
8268 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
8269 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
8270 					}
8271 				break;
8272 				case WM_CHAR:
8273 					wchar c = cast(wchar) wParam;
8274 					if(wind.handleCharEvent)
8275 						wind.handleCharEvent(cast(dchar) c);
8276 				break;
8277 				  case WM_SETFOCUS:
8278 				  case WM_KILLFOCUS:
8279 					wind._focused = (msg == WM_SETFOCUS);
8280 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
8281 					if(wind.onFocusChange)
8282 						wind.onFocusChange(msg == WM_SETFOCUS);
8283 				  break;
8284 				case WM_SYSKEYDOWN:
8285 				case WM_SYSKEYUP:
8286 				case WM_KEYDOWN:
8287 				case WM_KEYUP:
8288 					KeyEvent ev;
8289 					ev.key = cast(Key) wParam;
8290 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
8291 					if ((msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP) && wParam == 0x12) ev.key = Key.Alt; // windows does it this way
8292 
8293 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
8294 
8295 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
8296 						ev.modifierState |= ModifierState.shift;
8297 					//k8: this doesn't work; thanks for nothing, windows
8298 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
8299 						ev.modifierState |= ModifierState.alt;*/
8300 					if ((msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP) && wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN);
8301 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
8302 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
8303 						ev.modifierState |= ModifierState.ctrl;
8304 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
8305 						ev.modifierState |= ModifierState.windows;
8306 					if(GetKeyState(Key.NumLock))
8307 						ev.modifierState |= ModifierState.numLock;
8308 					if(GetKeyState(Key.CapsLock))
8309 						ev.modifierState |= ModifierState.capsLock;
8310 
8311 					/+
8312 					// we always want to send the character too, so let's convert it
8313 					ubyte[256] state;
8314 					wchar[16] buffer;
8315 					GetKeyboardState(state.ptr);
8316 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
8317 
8318 					foreach(dchar d; buffer) {
8319 						ev.character = d;
8320 						break;
8321 					}
8322 					+/
8323 
8324 					ev.window = wind;
8325 					if(wind.handleKeyEvent)
8326 						wind.handleKeyEvent(ev);
8327 				break;
8328 				case 0x020a /*WM_MOUSEWHEEL*/:
8329 					mouse.type = cast(MouseEventType) 1;
8330 					mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp);
8331 					mouseEvent();
8332 				break;
8333 				case WM_MOUSEMOVE:
8334 					mouse.type = cast(MouseEventType) 0;
8335 					mouseEvent();
8336 				break;
8337 				case WM_LBUTTONDOWN:
8338 				case WM_LBUTTONDBLCLK:
8339 					mouse.type = cast(MouseEventType) 1;
8340 					mouse.button = MouseButton.left;
8341 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
8342 					mouseEvent();
8343 				break;
8344 				case WM_LBUTTONUP:
8345 					mouse.type = cast(MouseEventType) 2;
8346 					mouse.button = MouseButton.left;
8347 					mouseEvent();
8348 				break;
8349 				case WM_RBUTTONDOWN:
8350 				case WM_RBUTTONDBLCLK:
8351 					mouse.type = cast(MouseEventType) 1;
8352 					mouse.button = MouseButton.right;
8353 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
8354 					mouseEvent();
8355 				break;
8356 				case WM_RBUTTONUP:
8357 					mouse.type = cast(MouseEventType) 2;
8358 					mouse.button = MouseButton.right;
8359 					mouseEvent();
8360 				break;
8361 				case WM_MBUTTONDOWN:
8362 				case WM_MBUTTONDBLCLK:
8363 					mouse.type = cast(MouseEventType) 1;
8364 					mouse.button = MouseButton.middle;
8365 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
8366 					mouseEvent();
8367 				break;
8368 				case WM_MBUTTONUP:
8369 					mouse.type = cast(MouseEventType) 2;
8370 					mouse.button = MouseButton.middle;
8371 					mouseEvent();
8372 				break;
8373 				case WM_XBUTTONDOWN:
8374 				case WM_XBUTTONDBLCLK:
8375 					mouse.type = cast(MouseEventType) 1;
8376 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
8377 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
8378 					mouseEvent();
8379 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
8380 				case WM_XBUTTONUP:
8381 					mouse.type = cast(MouseEventType) 2;
8382 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
8383 					mouseEvent();
8384 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
8385 
8386 				default: return 1;
8387 			}
8388 			return 0;
8389 		}
8390 
8391 		HWND hwnd;
8392 		int oldWidth;
8393 		int oldHeight;
8394 		bool inSizeMove;
8395 
8396 		// the extern(Windows) wndproc should just forward to this
8397 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
8398 			assert(hwnd is this.hwnd);
8399 
8400 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
8401 			switch(msg) {
8402 				case WM_SETCURSOR:
8403 					if(cast(HWND) wParam !is hwnd)
8404 						return 0; // further processing elsewhere
8405 
8406 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
8407 						SetCursor(this.curHidden > 0 ? null : currentCursor);
8408 						return 1;
8409 					} else {
8410 						return DefWindowProc(hwnd, msg, wParam, lParam);
8411 					}
8412 				//break;
8413 
8414 				case WM_CLOSE:
8415 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
8416 				break;
8417 				case WM_DESTROY:
8418 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
8419 					SimpleWindow.nativeMapping.remove(hwnd);
8420 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
8421 
8422 					bool anyImportant = false;
8423 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
8424 						if(w.beingOpenKeepsAppOpen) {
8425 							anyImportant = true;
8426 							break;
8427 						}
8428 					if(!anyImportant) {
8429 						PostQuitMessage(0);
8430 					}
8431 				break;
8432 				case WM_SIZE:
8433 					if(wParam == 1 /* SIZE_MINIMIZED */)
8434 						break;
8435 					_width = LOWORD(lParam);
8436 					_height = HIWORD(lParam);
8437 
8438 					// I want to avoid tearing in the windows (my code is inefficient
8439 					// so this is a hack around that) so while sizing, we don't trigger,
8440 					// but we do want to trigger on events like mazimize.
8441 					if(!inSizeMove)
8442 						goto size_changed;
8443 				break;
8444 				// I don't like the tearing I get when redrawing on WM_SIZE
8445 				// (I know there's other ways to fix that but I don't like that behavior anyway)
8446 				// so instead it is going to redraw only at the end of a size.
8447 				case 0x0231: /* WM_ENTERSIZEMOVE */
8448 					oldWidth = this.width;
8449 					oldHeight = this.height;
8450 					inSizeMove = true;
8451 				break;
8452 				case 0x0232: /* WM_EXITSIZEMOVE */
8453 					inSizeMove = false;
8454 					// nothing relevant changed, don't bother redrawing
8455 					if(oldWidth == width && oldHeight == height)
8456 						break;
8457 
8458 					size_changed:
8459 
8460 					// note: OpenGL windows don't use a backing bmp, so no need to change them
8461 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
8462 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
8463 						// gotta get the double buffer bmp to match the window
8464 					// FIXME: could this be more efficient? It isn't really necessary to make
8465 					// a new buffer if we're sizing down at least.
8466 						auto hdc = GetDC(hwnd);
8467 						auto oldBuffer = buffer;
8468 						buffer = CreateCompatibleBitmap(hdc, width, height);
8469 
8470 						auto hdcBmp = CreateCompatibleDC(hdc);
8471 						auto oldBmp = SelectObject(hdcBmp, buffer);
8472 
8473 						auto hdcOldBmp = CreateCompatibleDC(hdc);
8474 						auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp);
8475 
8476 						BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY);
8477 
8478 						SelectObject(hdcOldBmp, oldOldBmp);
8479 						DeleteDC(hdcOldBmp);
8480 
8481 						SelectObject(hdcBmp, oldBmp);
8482 						DeleteDC(hdcBmp);
8483 
8484 						ReleaseDC(hwnd, hdc);
8485 
8486 						DeleteObject(oldBuffer);
8487 					}
8488 
8489 					version(without_opengl) {} else
8490 					if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
8491 						glViewport(0, 0, width, height);
8492 					}
8493 
8494 					if(windowResized !is null)
8495 						windowResized(width, height);
8496 				break;
8497 				case WM_ERASEBKGND:
8498 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
8499 					if (!this._visibleForTheFirstTimeCalled) {
8500 						this._visibleForTheFirstTimeCalled = true;
8501 						if (this.visibleForTheFirstTime !is null) {
8502 							version(without_opengl) {} else {
8503 								if(openglMode == OpenGlOptions.yes) {
8504 									this.setAsCurrentOpenGlContextNT();
8505 									glViewport(0, 0, width, height);
8506 								}
8507 							}
8508 							this.visibleForTheFirstTime();
8509 						}
8510 					}
8511 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
8512 					version(without_opengl) {} else {
8513 						if (openglMode == OpenGlOptions.yes) return 1;
8514 					}
8515 					// call windows default handler, so it can paint standard controls
8516 					goto default;
8517 				case WM_CTLCOLORBTN:
8518 				case WM_CTLCOLORSTATIC:
8519 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
8520 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
8521 					GetSysColorBrush(COLOR_3DFACE);
8522 				//break;
8523 				case WM_SHOWWINDOW:
8524 					this._visible = (wParam != 0);
8525 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
8526 						this._visibleForTheFirstTimeCalled = true;
8527 						if (this.visibleForTheFirstTime !is null) {
8528 							version(without_opengl) {} else {
8529 								if(openglMode == OpenGlOptions.yes) {
8530 									this.setAsCurrentOpenGlContextNT();
8531 									glViewport(0, 0, width, height);
8532 								}
8533 							}
8534 							this.visibleForTheFirstTime();
8535 						}
8536 					}
8537 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
8538 					break;
8539 				case WM_PAINT: {
8540 					if (!this._visibleForTheFirstTimeCalled) {
8541 						this._visibleForTheFirstTimeCalled = true;
8542 						if (this.visibleForTheFirstTime !is null) {
8543 							version(without_opengl) {} else {
8544 								if(openglMode == OpenGlOptions.yes) {
8545 									this.setAsCurrentOpenGlContextNT();
8546 									glViewport(0, 0, width, height);
8547 								}
8548 							}
8549 							this.visibleForTheFirstTime();
8550 						}
8551 					}
8552 
8553 					BITMAP bm;
8554 					PAINTSTRUCT ps;
8555 
8556 					HDC hdc = BeginPaint(hwnd, &ps);
8557 
8558 					if(openglMode == OpenGlOptions.no) {
8559 
8560 						HDC hdcMem = CreateCompatibleDC(hdc);
8561 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
8562 
8563 						GetObject(buffer, bm.sizeof, &bm);
8564 
8565 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
8566 						if(resizability == Resizability.automaticallyScaleIfPossible)
8567 						StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
8568 						else
8569 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
8570 
8571 						SelectObject(hdcMem, hbmOld);
8572 						DeleteDC(hdcMem);
8573 						EndPaint(hwnd, &ps);
8574 					} else {
8575 						EndPaint(hwnd, &ps);
8576 						version(without_opengl) {} else
8577 							redrawOpenGlSceneNow();
8578 					}
8579 				} break;
8580 				  default:
8581 					return DefWindowProc(hwnd, msg, wParam, lParam);
8582 			}
8583 			 return 0;
8584 
8585 		}
8586 	}
8587 
8588 	mixin template NativeImageImplementation() {
8589 		HBITMAP handle;
8590 		ubyte* rawData;
8591 
8592 	final:
8593 
8594 		Color getPixel(int x, int y) {
8595 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8596 			// remember, bmps are upside down
8597 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
8598 
8599 			Color c;
8600 			c.a = 255;
8601 			c.b = rawData[offset + 0];
8602 			c.g = rawData[offset + 1];
8603 			c.r = rawData[offset + 2];
8604 			return c;
8605 		}
8606 
8607 		void setPixel(int x, int y, Color c) {
8608 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8609 			// remember, bmps are upside down
8610 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
8611 
8612 			rawData[offset + 0] = c.b;
8613 			rawData[offset + 1] = c.g;
8614 			rawData[offset + 2] = c.r;
8615 		}
8616 
8617 		void convertToRgbaBytes(ubyte[] where) {
8618 			assert(where.length == this.width * this.height * 4);
8619 
8620 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8621 			int idx = 0;
8622 			int offset = itemsPerLine * (height - 1);
8623 			// remember, bmps are upside down
8624 			for(int y = height - 1; y >= 0; y--) {
8625 				auto offsetStart = offset;
8626 				for(int x = 0; x < width; x++) {
8627 					where[idx + 0] = rawData[offset + 2]; // r
8628 					where[idx + 1] = rawData[offset + 1]; // g
8629 					where[idx + 2] = rawData[offset + 0]; // b
8630 					where[idx + 3] = 255; // a
8631 					idx += 4;
8632 					offset += 3;
8633 				}
8634 
8635 				offset = offsetStart - itemsPerLine;
8636 			}
8637 		}
8638 
8639 		void setFromRgbaBytes(in ubyte[] what) {
8640 			assert(what.length == this.width * this.height * 4);
8641 
8642 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8643 			int idx = 0;
8644 			int offset = itemsPerLine * (height - 1);
8645 			// remember, bmps are upside down
8646 			for(int y = height - 1; y >= 0; y--) {
8647 				auto offsetStart = offset;
8648 				for(int x = 0; x < width; x++) {
8649 					rawData[offset + 2] = what[idx + 0]; // r
8650 					rawData[offset + 1] = what[idx + 1]; // g
8651 					rawData[offset + 0] = what[idx + 2]; // b
8652 					//where[idx + 3] = 255; // a
8653 					idx += 4;
8654 					offset += 3;
8655 				}
8656 
8657 				offset = offsetStart - itemsPerLine;
8658 			}
8659 		}
8660 
8661 
8662 		void createImage(int width, int height, bool forcexshm=false) {
8663 			BITMAPINFO infoheader;
8664 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
8665 			infoheader.bmiHeader.biWidth = width;
8666 			infoheader.bmiHeader.biHeight = height;
8667 			infoheader.bmiHeader.biPlanes = 1;
8668 			infoheader.bmiHeader.biBitCount = 24;
8669 			infoheader.bmiHeader.biCompression = BI_RGB;
8670 
8671 			handle = CreateDIBSection(
8672 				null,
8673 				&infoheader,
8674 				DIB_RGB_COLORS,
8675 				cast(void**) &rawData,
8676 				null,
8677 				0);
8678 			if(handle is null)
8679 				throw new WindowsApiException("create image failed");
8680 
8681 		}
8682 
8683 		void dispose() {
8684 			DeleteObject(handle);
8685 		}
8686 	}
8687 
8688 	enum KEY_ESCAPE = 27;
8689 }
8690 version(X11) {
8691 	/// This is the default font used. You might change this before doing anything else with
8692 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
8693 	/// for cross-platform compatibility.
8694 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
8695 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
8696 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
8697 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
8698 
8699 	alias int delegate(XEvent) NativeEventHandler;
8700 	alias Window NativeWindowHandle;
8701 
8702 	enum KEY_ESCAPE = 9;
8703 
8704 	mixin template NativeScreenPainterImplementation() {
8705 		Display* display;
8706 		Drawable d;
8707 		Drawable destiny;
8708 
8709 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
8710 		GC gc;
8711 
8712 		__gshared bool fontAttempted;
8713 
8714 		__gshared XFontStruct* defaultfont;
8715 		__gshared XFontSet defaultfontset;
8716 
8717 		XFontStruct* font;
8718 		XFontSet fontset;
8719 
8720 		void create(NativeWindowHandle window) {
8721 			this.display = XDisplayConnection.get();
8722 
8723 			Drawable buffer = None;
8724 			if(auto sw = cast(SimpleWindow) this.window) {
8725 				buffer = sw.impl.buffer;
8726 				this.destiny = cast(Drawable) window;
8727 			} else {
8728 				buffer = cast(Drawable) window;
8729 				this.destiny = None;
8730 			}
8731 
8732 			this.d = cast(Drawable) buffer;
8733 
8734 			auto dgc = DefaultGC(display, DefaultScreen(display));
8735 
8736 			this.gc = XCreateGC(display, d, 0, null);
8737 
8738 			XCopyGC(display, dgc, 0xffffffff, this.gc);
8739 
8740 			if(!fontAttempted) {
8741 				font = XLoadQueryFont(display, xfontstr.ptr);
8742 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
8743 				if(font is null)
8744 					font = XLoadQueryFont(display, "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*".ptr);
8745 
8746 				char** lol;
8747 				int lol2;
8748 				char* lol3;
8749 				fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
8750 
8751 				fontAttempted = true;
8752 
8753 				defaultfont = font;
8754 				defaultfontset = fontset;
8755 			}
8756 
8757 			font = defaultfont;
8758 			fontset = defaultfontset;
8759 
8760 			if(font) {
8761 				XSetFont(display, gc, font.fid);
8762 			}
8763 		}
8764 
8765 		arsd.color.Rectangle _clipRectangle;
8766 		void setClipRectangle(int x, int y, int width, int height) {
8767 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
8768 			if(width == 0 || height == 0)
8769 				XSetClipMask(display, gc, None);
8770 			else {
8771 				XRectangle[1] rects;
8772 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
8773 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
8774 			}
8775 		}
8776 
8777 
8778 		void setFont(OperatingSystemFont font) {
8779 			if(font && font.font) {
8780 				this.font = font.font;
8781 				this.fontset = font.fontset;
8782 				XSetFont(display, gc, font.font.fid);
8783 			} else {
8784 				this.font = defaultfont;
8785 				this.fontset = defaultfontset;
8786 			}
8787 
8788 		}
8789 
8790 		void dispose() {
8791 			this.rasterOp = RasterOp.normal;
8792 
8793 			// FIXME: this.window.width/height is probably wrong
8794 
8795 			// src x,y     then dest x, y
8796 			if(destiny != None) {
8797 				XSetClipMask(display, gc, None);
8798 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
8799 			}
8800 
8801 			XFreeGC(display, gc);
8802 
8803 			version(none) // we don't want to free it because we can use it later
8804 			if(font)
8805 				XFreeFont(display, font);
8806 			version(none) // we don't want to free it because we can use it later
8807 			if(fontset)
8808 				XFreeFontSet(display, fontset);
8809 			XFlush(display);
8810 
8811 			if(window.paintingFinishedDg !is null)
8812 				window.paintingFinishedDg();
8813 		}
8814 
8815 		bool backgroundIsNotTransparent = true;
8816 		bool foregroundIsNotTransparent = true;
8817 
8818 		bool _penInitialized = false;
8819 		Pen _activePen;
8820 
8821 		Color _outlineColor;
8822 		Color _fillColor;
8823 
8824 		@property void pen(Pen p) {
8825 			if(_penInitialized && p == _activePen) {
8826 				return;
8827 			}
8828 			_penInitialized = true;
8829 			_activePen = p;
8830 			_outlineColor = p.color;
8831 
8832 			int style;
8833 
8834 			byte dashLength;
8835 
8836 			final switch(p.style) {
8837 				case Pen.Style.Solid:
8838 					style = 0 /*LineSolid*/;
8839 				break;
8840 				case Pen.Style.Dashed:
8841 					style = 1 /*LineOnOffDash*/;
8842 					dashLength = 4;
8843 				break;
8844 				case Pen.Style.Dotted:
8845 					style = 1 /*LineOnOffDash*/;
8846 					dashLength = 1;
8847 				break;
8848 			}
8849 
8850 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
8851 			if(dashLength)
8852 				XSetDashes(display, gc, 0, &dashLength, 1);
8853 
8854 			if(p.color.a == 0) {
8855 				foregroundIsNotTransparent = false;
8856 				return;
8857 			}
8858 
8859 			foregroundIsNotTransparent = true;
8860 
8861 			XSetForeground(display, gc, colorToX(p.color, display));
8862 		}
8863 
8864 		RasterOp _currentRasterOp;
8865 		bool _currentRasterOpInitialized = false;
8866 		@property void rasterOp(RasterOp op) {
8867 			if(_currentRasterOpInitialized && _currentRasterOp == op)
8868 				return;
8869 			_currentRasterOp = op;
8870 			_currentRasterOpInitialized = true;
8871 			int mode;
8872 			final switch(op) {
8873 				case RasterOp.normal:
8874 					mode = GXcopy;
8875 				break;
8876 				case RasterOp.xor:
8877 					mode = GXxor;
8878 				break;
8879 			}
8880 			XSetFunction(display, gc, mode);
8881 		}
8882 
8883 
8884 		bool _fillColorInitialized = false;
8885 
8886 		@property void fillColor(Color c) {
8887 			if(_fillColorInitialized && _fillColor == c)
8888 				return; // already good, no need to waste time calling it
8889 			_fillColor = c;
8890 			_fillColorInitialized = true;
8891 			if(c.a == 0) {
8892 				backgroundIsNotTransparent = false;
8893 				return;
8894 			}
8895 
8896 			backgroundIsNotTransparent = true;
8897 
8898 			XSetBackground(display, gc, colorToX(c, display));
8899 
8900 		}
8901 
8902 		void swapColors() {
8903 			auto tmp = _fillColor;
8904 			fillColor = _outlineColor;
8905 			auto newPen = _activePen;
8906 			newPen.color = tmp;
8907 			pen(newPen);
8908 		}
8909 
8910 		uint colorToX(Color c, Display* display) {
8911 			auto visual = DefaultVisual(display, DefaultScreen(display));
8912 			import core.bitop;
8913 			uint color = 0;
8914 			{
8915 			auto startBit = bsf(visual.red_mask);
8916 			auto lastBit = bsr(visual.red_mask);
8917 			auto r = cast(uint) c.r;
8918 			r >>= 7 - (lastBit - startBit);
8919 			r <<= startBit;
8920 			color |= r;
8921 			}
8922 			{
8923 			auto startBit = bsf(visual.green_mask);
8924 			auto lastBit = bsr(visual.green_mask);
8925 			auto g = cast(uint) c.g;
8926 			g >>= 7 - (lastBit - startBit);
8927 			g <<= startBit;
8928 			color |= g;
8929 			}
8930 			{
8931 			auto startBit = bsf(visual.blue_mask);
8932 			auto lastBit = bsr(visual.blue_mask);
8933 			auto b = cast(uint) c.b;
8934 			b >>= 7 - (lastBit - startBit);
8935 			b <<= startBit;
8936 			color |= b;
8937 			}
8938 
8939 
8940 
8941 			return color;
8942 		}
8943 
8944 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
8945 			// source x, source y
8946 			if(i.usingXshm)
8947 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
8948 			else
8949 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
8950 		}
8951 
8952 		void drawPixmap(Sprite s, int x, int y) {
8953 			XCopyArea(display, s.handle, d, gc, 0, 0, s.width, s.height, x, y);
8954 		}
8955 
8956 		int fontHeight() {
8957 			if(font)
8958 				return font.max_bounds.ascent + font.max_bounds.descent;
8959 			return 12; // pretty common default...
8960 		}
8961 
8962 		Size textSize(in char[] text) {
8963 			auto maxWidth = 0;
8964 			auto lineHeight = fontHeight;
8965 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
8966 			foreach(line; text.split('\n')) {
8967 				int textWidth;
8968 				if(font)
8969 					// FIXME: unicode
8970 					textWidth = XTextWidth( font, line.ptr, cast(int) line.length);
8971 				else
8972 					textWidth = fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
8973 
8974 				if(textWidth > maxWidth)
8975 					maxWidth = textWidth;
8976 				h += lineHeight + 4;
8977 			}
8978 			return Size(maxWidth, h);
8979 		}
8980 
8981 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
8982 			// FIXME: we should actually draw unicode.. but until then, I'm going to strip out multibyte chars
8983 			const(char)[] text;
8984 			if(fontset)
8985 				text = originalText;
8986 			else {
8987 				text.reserve(originalText.length);
8988 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
8989 				// then strip the rest so there isn't garbage
8990 				foreach(dchar ch; originalText)
8991 					if(ch < 256)
8992 						text ~= cast(ubyte) ch;
8993 					else
8994 						text ~= 191; // FIXME: using a random character to fill the space
8995 			}
8996 			if(text.length == 0)
8997 				return;
8998 
8999 
9000 			int textHeight = 12;
9001 
9002 			// FIXME: should we clip it to the bounding box?
9003 
9004 			if(font) {
9005 				textHeight = font.max_bounds.ascent + font.max_bounds.descent;
9006 			}
9007 
9008 			auto lines = text.split('\n');
9009 
9010 			auto lineHeight = textHeight;
9011 			textHeight *= lines.length;
9012 
9013 			int cy = y;
9014 
9015 			if(alignment & TextAlignment.VerticalBottom) {
9016 				assert(y2);
9017 				auto h = y2 - y;
9018 				if(h > textHeight) {
9019 					cy += h - textHeight;
9020 					cy -= lineHeight / 2;
9021 				}
9022 			} else if(alignment & TextAlignment.VerticalCenter) {
9023 				assert(y2);
9024 				auto h = y2 - y;
9025 				if(textHeight < h) {
9026 					cy += (h - textHeight) / 2;
9027 					//cy -= lineHeight / 4;
9028 				}
9029 			}
9030 
9031 			foreach(line; text.split('\n')) {
9032 				int textWidth;
9033 				if(font)
9034 					// FIXME: unicode
9035 					textWidth = XTextWidth( font, line.ptr, cast(int) line.length);
9036 				else
9037 					textWidth = 12 * cast(int) line.length;
9038 
9039 				int px = x, py = cy;
9040 
9041 				if(alignment & TextAlignment.Center) {
9042 					assert(x2);
9043 					auto w = x2 - x;
9044 					if(w > textWidth)
9045 						px += (w - textWidth) / 2;
9046 				} else if(alignment & TextAlignment.Right) {
9047 					assert(x2);
9048 					auto pos = x2 - textWidth;
9049 					if(pos > x)
9050 						px = pos;
9051 				}
9052 
9053 				if(fontset)
9054 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
9055 
9056 				else
9057 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
9058 				cy += lineHeight + 4;
9059 			}
9060 		}
9061 
9062 		void drawPixel(int x, int y) {
9063 			XDrawPoint(display, d, gc, x, y);
9064 		}
9065 
9066 		// The basic shapes, outlined
9067 
9068 		void drawLine(int x1, int y1, int x2, int y2) {
9069 			if(foregroundIsNotTransparent)
9070 				XDrawLine(display, d, gc, x1, y1, x2, y2);
9071 		}
9072 
9073 		void drawRectangle(int x, int y, int width, int height) {
9074 			if(backgroundIsNotTransparent) {
9075 				swapColors();
9076 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
9077 				swapColors();
9078 			}
9079 			if(foregroundIsNotTransparent)
9080 				XDrawRectangle(display, d, gc, x, y, width - 1, height - 1);
9081 		}
9082 
9083 		/// Arguments are the points of the bounding rectangle
9084 		void drawEllipse(int x1, int y1, int x2, int y2) {
9085 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
9086 		}
9087 
9088 		// NOTE: start and finish are in units of degrees * 64
9089 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
9090 			if(backgroundIsNotTransparent) {
9091 				swapColors();
9092 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
9093 				swapColors();
9094 			}
9095 			if(foregroundIsNotTransparent)
9096 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
9097 		}
9098 
9099 		void drawPolygon(Point[] vertexes) {
9100 			XPoint[16] pointsBuffer;
9101 			XPoint[] points;
9102 			if(vertexes.length <= pointsBuffer.length)
9103 				points = pointsBuffer[0 .. vertexes.length];
9104 			else
9105 				points.length = vertexes.length;
9106 
9107 			foreach(i, p; vertexes) {
9108 				points[i].x = cast(short) p.x;
9109 				points[i].y = cast(short) p.y;
9110 			}
9111 
9112 			if(backgroundIsNotTransparent) {
9113 				swapColors();
9114 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
9115 				swapColors();
9116 			}
9117 			if(foregroundIsNotTransparent) {
9118 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
9119 			}
9120 		}
9121 	}
9122 
9123 	class XDisconnectException : Exception {
9124 		bool userRequested;
9125 		this(bool userRequested = true) {
9126 			this.userRequested = userRequested;
9127 			super("X disconnected");
9128 		}
9129 	}
9130 
9131 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display*
9132 	class XDisplayConnection {
9133 		private __gshared Display* display;
9134 		private __gshared XIM xim;
9135 		private __gshared char* displayName;
9136 
9137 		private __gshared int connectionSequence_;
9138 
9139 		/// use this for lazy caching when reconnection
9140 		static int connectionSequenceNumber() { return connectionSequence_; }
9141 
9142 		/// Attempts recreation of state, may require application assistance
9143 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
9144 		/// then call this, and if successful, reenter the loop.
9145 		static void discardAndRecreate(string newDisplayString = null) {
9146 			if(insideXEventLoop)
9147 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
9148 
9149 			// 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
9150 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
9151 
9152 			foreach(handle; chnenhm) {
9153 				handle.discardConnectionState();
9154 			}
9155 
9156 			discardState();
9157 
9158 			if(newDisplayString !is null)
9159 				setDisplayName(newDisplayString);
9160 
9161 			auto display = get();
9162 
9163 			foreach(handle; chnenhm) {
9164 				handle.recreateAfterDisconnect();
9165 			}
9166 		}
9167 
9168 		private __gshared EventMask rootEventMask;
9169 
9170 		/++
9171 			Requests the specified input from the root window on the connection, in addition to any other request.
9172 
9173 			
9174 			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.
9175 
9176 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
9177 		+/
9178 		static void addRootInput(EventMask mask) {
9179 			auto old = rootEventMask;
9180 			rootEventMask |= mask;
9181 			get(); // to ensure display connected
9182 			if(display !is null && rootEventMask != old)
9183 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
9184 		}
9185 
9186 		static void discardState() {
9187 			freeImages();
9188 
9189 			foreach(atomPtr; interredAtoms)
9190 				*atomPtr = 0;
9191 			interredAtoms = null;
9192 			interredAtoms.assumeSafeAppend();
9193 
9194 			ScreenPainterImplementation.fontAttempted = false;
9195 			ScreenPainterImplementation.defaultfont = null;
9196 			ScreenPainterImplementation.defaultfontset = null;
9197 
9198 			Image.impl.xshmQueryCompleted = false;
9199 			Image.impl._xshmAvailable = false;
9200 
9201 			SimpleWindow.nativeMapping = null;
9202 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
9203 			// GlobalHotkeyManager
9204 
9205 			display = null;
9206 			xim = null;
9207 		}
9208 
9209 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
9210 		private static void createXIM () {
9211 			import core.stdc.locale : setlocale, LC_ALL;
9212 			import core.stdc.stdio : stderr, fprintf;
9213 			import core.stdc.stdlib : free;
9214 			import core.stdc.string : strdup;
9215 
9216 			static immutable string[3] mtry = [ null, "@im=local", "@im=" ];
9217 
9218 			auto olocale = strdup(setlocale(LC_ALL, null));
9219 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
9220 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
9221 
9222 			//fprintf(stderr, "opening IM...\n");
9223 			foreach (string s; mtry) {
9224 				if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
9225 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
9226 			}
9227 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
9228 		}
9229 
9230 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
9231 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
9232 		static struct ImgList {
9233 			size_t img; // class; hide it from GC
9234 			ImgList* next;
9235 		}
9236 
9237 		static __gshared ImgList* imglist = null;
9238 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
9239 
9240 		static void registerImage (Image img) {
9241 			if (!imglistLocked && img !is null) {
9242 				import core.stdc.stdlib : malloc;
9243 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
9244 				assert(it !is null); // do proper checks
9245 				it.img = cast(size_t)cast(void*)img;
9246 				it.next = imglist;
9247 				imglist = it;
9248 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
9249 			}
9250 		}
9251 
9252 		static void unregisterImage (Image img) {
9253 			if (!imglistLocked && img !is null) {
9254 				import core.stdc.stdlib : free;
9255 				ImgList* prev = null;
9256 				ImgList* cur = imglist;
9257 				while (cur !is null) {
9258 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
9259 					prev = cur;
9260 					cur = cur.next;
9261 				}
9262 				if (cur !is null) {
9263 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
9264 					free(cur);
9265 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
9266 				} else {
9267 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
9268 				}
9269 			}
9270 		}
9271 
9272 		static void freeImages () { // needed for discardAndRecreate
9273 			imglistLocked = true;
9274 			scope(exit) imglistLocked = false;
9275 			ImgList* cur = imglist;
9276 			ImgList* next = null;
9277 			while (cur !is null) {
9278 				import core.stdc.stdlib : free;
9279 				next = cur.next;
9280 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
9281 				(cast(Image)cast(void*)cur.img).dispose();
9282 				free(cur);
9283 				cur = next;
9284 			}
9285 			imglist = null;
9286 		}
9287 
9288 		/// can be used to override normal handling of display name
9289 		/// from environment and/or command line
9290 		static setDisplayName(string newDisplayName) {
9291 			displayName = cast(char*) (newDisplayName ~ '\0');
9292 		}
9293 
9294 		/// resets to the default display string
9295 		static resetDisplayName() {
9296 			displayName = null;
9297 		}
9298 
9299 		///
9300 		static Display* get() {
9301 			if(display is null) {
9302 				display = XOpenDisplay(displayName);
9303 				connectionSequence_++;
9304 				if(display is null)
9305 					throw new Exception("Unable to open X display");
9306 				XSetIOErrorHandler(&x11ioerrCB);
9307 				Bool sup;
9308 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
9309 				createXIM();
9310 				version(with_eventloop) {
9311 					import arsd.eventloop;
9312 					addFileEventListeners(display.fd, &eventListener, null, null);
9313 				}
9314 			}
9315 
9316 			return display;
9317 		}
9318 
9319 		extern(C)
9320 		static int x11ioerrCB(Display* dpy) {
9321 			throw new XDisconnectException(false);
9322 		}
9323 
9324 		version(with_eventloop) {
9325 			import arsd.eventloop;
9326 			static void eventListener(OsFileHandle fd) {
9327 				//this.mtLock();
9328 				//scope(exit) this.mtUnlock();
9329 				while(XPending(display))
9330 					doXNextEvent(display);
9331 			}
9332 		}
9333 
9334 		// close connection on program exit -- we need this to properly free all images
9335 		shared static ~this () { close(); }
9336 
9337 		///
9338 		static void close() {
9339 			if(display is null)
9340 				return;
9341 
9342 			version(with_eventloop) {
9343 				import arsd.eventloop;
9344 				removeFileEventListeners(display.fd);
9345 			}
9346 
9347 			// now remove all registered images to prevent shared memory leaks
9348 			freeImages();
9349 
9350 			XCloseDisplay(display);
9351 			display = null;
9352 		}
9353 	}
9354 
9355 	mixin template NativeImageImplementation() {
9356 		XImage* handle;
9357 		ubyte* rawData;
9358 
9359 		XShmSegmentInfo shminfo;
9360 
9361 		__gshared bool xshmQueryCompleted;
9362 		__gshared bool _xshmAvailable;
9363 		public static @property bool xshmAvailable() {
9364 			if(!xshmQueryCompleted) {
9365 				int i1, i2, i3;
9366 				xshmQueryCompleted = true;
9367 				_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
9368 			}
9369 			return _xshmAvailable;
9370 		}
9371 
9372 		bool usingXshm;
9373 	final:
9374 
9375 		void createImage(int width, int height, bool forcexshm=false) {
9376 			auto display = XDisplayConnection.get();
9377 			assert(display !is null);
9378 			auto screen = DefaultScreen(display);
9379 
9380 			// it will only use shared memory for somewhat largish images,
9381 			// since otherwise we risk wasting shared memory handles on a lot of little ones
9382 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
9383 				usingXshm = true;
9384 				handle = XShmCreateImage(
9385 					display,
9386 					DefaultVisual(display, screen),
9387 					24,
9388 					ImageFormat.ZPixmap,
9389 					null,
9390 					&shminfo,
9391 					width, height);
9392 				assert(handle !is null);
9393 
9394 				assert(handle.bytes_per_line == 4 * width);
9395 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
9396 				//import std.conv; import core.stdc.errno;
9397 				assert(shminfo.shmid >= 0);//, to!string(errno));
9398 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
9399 				assert(rawData != cast(ubyte*) -1);
9400 				shminfo.readOnly = 0;
9401 				XShmAttach(display, &shminfo);
9402 				XDisplayConnection.registerImage(this);
9403 			} else {
9404 				if (forcexshm) throw new Exception("can't create XShm Image");
9405 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
9406 				import core.stdc.stdlib : malloc;
9407 				rawData = cast(ubyte*) malloc(width * height * 4);
9408 
9409 				handle = XCreateImage(
9410 					display,
9411 					DefaultVisual(display, screen),
9412 					24, // bpp
9413 					ImageFormat.ZPixmap,
9414 					0, // offset
9415 					rawData,
9416 					width, height,
9417 					8 /* FIXME */, 4 * width); // padding, bytes per line
9418 			}
9419 		}
9420 
9421 		void dispose() {
9422 			// note: this calls free(rawData) for us
9423 			if(handle) {
9424 				if (usingXshm) {
9425 					XDisplayConnection.unregisterImage(this);
9426 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
9427 				}
9428 				XDestroyImage(handle);
9429 				if(usingXshm) {
9430 					shmdt(shminfo.shmaddr);
9431 					shmctl(shminfo.shmid, IPC_RMID, null);
9432 				}
9433 				handle = null;
9434 			}
9435 		}
9436 
9437 		Color getPixel(int x, int y) {
9438 			auto offset = (y * width + x) * 4;
9439 			Color c;
9440 			c.a = 255;
9441 			c.b = rawData[offset + 0];
9442 			c.g = rawData[offset + 1];
9443 			c.r = rawData[offset + 2];
9444 			return c;
9445 		}
9446 
9447 		void setPixel(int x, int y, Color c) {
9448 			auto offset = (y * width + x) * 4;
9449 			rawData[offset + 0] = c.b;
9450 			rawData[offset + 1] = c.g;
9451 			rawData[offset + 2] = c.r;
9452 		}
9453 
9454 		void convertToRgbaBytes(ubyte[] where) {
9455 			assert(where.length == this.width * this.height * 4);
9456 
9457 			// if rawData had a length....
9458 			//assert(rawData.length == where.length);
9459 			for(int idx = 0; idx < where.length; idx += 4) {
9460 				where[idx + 0] = rawData[idx + 2]; // r
9461 				where[idx + 1] = rawData[idx + 1]; // g
9462 				where[idx + 2] = rawData[idx + 0]; // b
9463 				where[idx + 3] = 255; // a
9464 			}
9465 		}
9466 
9467 		void setFromRgbaBytes(in ubyte[] where) {
9468 			assert(where.length == this.width * this.height * 4);
9469 
9470 			// if rawData had a length....
9471 			//assert(rawData.length == where.length);
9472 			for(int idx = 0; idx < where.length; idx += 4) {
9473 				rawData[idx + 2] = where[idx + 0]; // r
9474 				rawData[idx + 1] = where[idx + 1]; // g
9475 				rawData[idx + 0] = where[idx + 2]; // b
9476 				//rawData[idx + 3] = 255; // a
9477 			}
9478 		}
9479 
9480 	}
9481 
9482 	mixin template NativeSimpleWindowImplementation() {
9483 		GC gc;
9484 		Window window;
9485 		Display* display;
9486 
9487 		Pixmap buffer;
9488 		int bufferw, bufferh; // size of the buffer; can be bigger than window
9489 		XIC xic; // input context
9490 		int curHidden = 0; // counter
9491 		Cursor blankCurPtr = 0;
9492 		int cursorSequenceNumber = 0;
9493 		int warpEventCount = 0; // number of mouse movement events to eat
9494 
9495 		void delegate(XEvent) setSelectionHandler;
9496 		void delegate(in char[]) getSelectionHandler;
9497 
9498 		version(without_opengl) {} else
9499 		GLXContext glc;
9500 
9501 		private void fixFixedSize(bool forced=false) (int width, int height) {
9502 			if (forced || this.resizability == Resizability.fixedSize) {
9503 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
9504 				XSizeHints sh;
9505 				static if (!forced) {
9506 					c_long spr;
9507 					XGetWMNormalHints(display, window, &sh, &spr);
9508 					sh.flags |= PMaxSize | PMinSize;
9509 				} else {
9510 					sh.flags = PMaxSize | PMinSize;
9511 				}
9512 				sh.min_width = width;
9513 				sh.min_height = height;
9514 				sh.max_width = width;
9515 				sh.max_height = height;
9516 				XSetWMNormalHints(display, window, &sh);
9517 				//XFlush(display);
9518 			}
9519 		}
9520 
9521 		ScreenPainter getPainter() {
9522 			return ScreenPainter(this, window);
9523 		}
9524 
9525 		void move(int x, int y) {
9526 			XMoveWindow(display, window, x, y);
9527 		}
9528 
9529 		void resize(int w, int h) {
9530 			if (w < 1) w = 1;
9531 			if (h < 1) h = 1;
9532 			XResizeWindow(display, window, w, h);
9533 			// FIXME: do we need to set this as the opengl context to do the glViewport change?
9534 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
9535 		}
9536 
9537 		void moveResize (int x, int y, int w, int h) {
9538 			if (w < 1) w = 1;
9539 			if (h < 1) h = 1;
9540 			XMoveResizeWindow(display, window, x, y, w, h);
9541 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
9542 		}
9543 
9544 		void hideCursor () {
9545 			if (curHidden++ == 0) {
9546 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
9547 					static const(char)[1] cmbmp = 0;
9548 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
9549 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
9550 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
9551 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
9552 					XFreePixmap(display, pm);
9553 				}
9554 				XDefineCursor(display, window, blankCurPtr);
9555 			}
9556 		}
9557 
9558 		void showCursor () {
9559 			if (--curHidden == 0) XUndefineCursor(display, window);
9560 		}
9561 
9562 		void warpMouse (int x, int y) {
9563 			// here i will send dummy "ignore next mouse motion" event,
9564 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
9565 			// and we don't need to report it to the user (as warping is
9566 			// used when the user needs movement deltas).
9567 			//XClientMessageEvent xclient;
9568 			XEvent e;
9569 			e.xclient.type = EventType.ClientMessage;
9570 			e.xclient.window = window;
9571 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
9572 			e.xclient.format = 32;
9573 			e.xclient.data.l[0] = 0;
9574 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
9575 			//{ 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]); }
9576 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
9577 			// now warp pointer...
9578 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
9579 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
9580 			// ...and flush
9581 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
9582 			XFlush(display);
9583 		}
9584 
9585 		void sendDummyEvent () {
9586 			// here i will send dummy event to ping event queue
9587 			XEvent e;
9588 			e.xclient.type = EventType.ClientMessage;
9589 			e.xclient.window = window;
9590 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
9591 			e.xclient.format = 32;
9592 			e.xclient.data.l[0] = 0;
9593 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
9594 			XFlush(display);
9595 		}
9596 
9597 		void setTitle(string title) {
9598 			if (title.ptr is null) title = "";
9599 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
9600 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
9601 			XTextProperty windowName;
9602 			windowName.value = title.ptr;
9603 			windowName.encoding = XA_UTF8; //XA_STRING;
9604 			windowName.format = 8;
9605 			windowName.nitems = cast(uint)title.length;
9606 			XSetWMName(display, window, &windowName);
9607 			char[1024] namebuf = 0;
9608 			auto maxlen = namebuf.length-1;
9609 			if (maxlen > title.length) maxlen = title.length;
9610 			namebuf[0..maxlen] = title[0..maxlen];
9611 			XStoreName(display, window, namebuf.ptr);
9612 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
9613 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
9614 		}
9615 
9616 		string[] getTitles() {
9617 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
9618 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
9619 			XTextProperty textProp;
9620 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
9621 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
9622 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
9623 				} else
9624 					return [];
9625 			} else
9626 				return null;
9627 		}
9628 
9629 		string getTitle() {
9630 			auto titles = getTitles();
9631 			return titles.length ? titles[0] : null;
9632 		}
9633 
9634 		void setMinSize (int minwidth, int minheight) {
9635 			import core.stdc.config : c_long;
9636 			if (minwidth < 1) minwidth = 1;
9637 			if (minheight < 1) minheight = 1;
9638 			XSizeHints sh;
9639 			c_long spr;
9640 			XGetWMNormalHints(display, window, &sh, &spr);
9641 			sh.min_width = minwidth;
9642 			sh.min_height = minheight;
9643 			sh.flags |= PMinSize;
9644 			XSetWMNormalHints(display, window, &sh);
9645 			flushGui();
9646 		}
9647 
9648 		void setMaxSize (int maxwidth, int maxheight) {
9649 			import core.stdc.config : c_long;
9650 			if (maxwidth < 1) maxwidth = 1;
9651 			if (maxheight < 1) maxheight = 1;
9652 			XSizeHints sh;
9653 			c_long spr;
9654 			XGetWMNormalHints(display, window, &sh, &spr);
9655 			sh.max_width = maxwidth;
9656 			sh.max_height = maxheight;
9657 			sh.flags |= PMaxSize;
9658 			XSetWMNormalHints(display, window, &sh);
9659 			flushGui();
9660 		}
9661 
9662 		void setResizeGranularity (int granx, int grany) {
9663 			import core.stdc.config : c_long;
9664 			if (granx < 1) granx = 1;
9665 			if (grany < 1) grany = 1;
9666 			XSizeHints sh;
9667 			c_long spr;
9668 			XGetWMNormalHints(display, window, &sh, &spr);
9669 			sh.width_inc = granx;
9670 			sh.height_inc = grany;
9671 			sh.flags |= PResizeInc;
9672 			XSetWMNormalHints(display, window, &sh);
9673 			flushGui();
9674 		}
9675 
9676 		void setOpacity (uint opacity) {
9677 			if (opacity == uint.max)
9678 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
9679 			else
9680 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
9681 					XA_CARDINAL, 32, PropModeReplace, &opacity, 1);
9682 		}
9683 
9684 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
9685 			display = XDisplayConnection.get();
9686 			auto screen = DefaultScreen(display);
9687 
9688 			version(without_opengl) {}
9689 			else {
9690 				if(opengl == OpenGlOptions.yes) {
9691 					GLXFBConfig fbconf = null;
9692 					XVisualInfo* vi = null;
9693 					bool useLegacy = false;
9694 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
9695 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
9696 						int[23] visualAttribs = [
9697 							GLX_X_RENDERABLE , 1/*True*/,
9698 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
9699 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
9700 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
9701 							GLX_RED_SIZE     , 8,
9702 							GLX_GREEN_SIZE   , 8,
9703 							GLX_BLUE_SIZE    , 8,
9704 							GLX_ALPHA_SIZE   , 8,
9705 							GLX_DEPTH_SIZE   , 24,
9706 							GLX_STENCIL_SIZE , 8,
9707 							GLX_DOUBLEBUFFER , 1/*True*/,
9708 							0/*None*/,
9709 						];
9710 						int fbcount;
9711 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
9712 						if (fbcount == 0) {
9713 							useLegacy = true; // try to do at least something
9714 						} else {
9715 							// pick the FB config/visual with the most samples per pixel
9716 							int bestidx = -1, bestns = -1;
9717 							foreach (int fbi; 0..fbcount) {
9718 								int sb, samples;
9719 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
9720 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
9721 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
9722 							}
9723 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
9724 							fbconf = fbc[bestidx];
9725 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
9726 							XFree(fbc);
9727 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
9728 						}
9729 					}
9730 					if (vi is null || useLegacy) {
9731 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
9732 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
9733 						useLegacy = true;
9734 					}
9735 					if (vi is null) throw new Exception("no open gl visual found");
9736 
9737 					XSetWindowAttributes swa;
9738 					auto root = RootWindow(display, screen);
9739 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
9740 
9741 					window = XCreateWindow(display, parent is null ? root : parent.impl.window,
9742 						0, 0, width, height,
9743 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa);
9744 
9745 					// now try to use `glXCreateContextAttribsARB()` if it's here
9746 					if (!useLegacy) {
9747 						// request fairly advanced context, even with stencil buffer!
9748 						int[9] contextAttribs = [
9749 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
9750 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
9751 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
9752 							// for modern context, set "forward compatibility" flag too
9753 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
9754 							0/*None*/,
9755 						];
9756 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
9757 						if (glc is null && sdpyOpenGLContextAllowFallback) {
9758 							sdpyOpenGLContextVersion = 0;
9759 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
9760 						}
9761 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
9762 					} else {
9763 						// fallback to old GLX call
9764 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
9765 							sdpyOpenGLContextVersion = 0;
9766 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
9767 						}
9768 					}
9769 					// sync to ensure any errors generated are processed
9770 					XSync(display, 0/*False*/);
9771 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
9772 					if(glc is null)
9773 						throw new Exception("glc");
9774 				}
9775 			}
9776 
9777 			if(opengl == OpenGlOptions.no) {
9778 
9779 				bool overrideRedirect = false;
9780 				if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)
9781 					overrideRedirect = true;
9782 
9783 				XSetWindowAttributes swa;
9784 				swa.background_pixel = WhitePixel(display, screen);
9785 				swa.border_pixel = BlackPixel(display, screen);
9786 				swa.override_redirect = overrideRedirect;
9787 				auto root = RootWindow(display, screen);
9788 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
9789 
9790 				window = XCreateWindow(display, parent is null ? root : parent.impl.window,
9791 					0, 0, width, height,
9792 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa);
9793 
9794 
9795 
9796 				/*
9797 				window = XCreateSimpleWindow(
9798 					display,
9799 					parent is null ? RootWindow(display, screen) : parent.impl.window,
9800 					0, 0, // x, y
9801 					width, height,
9802 					1, // border width
9803 					BlackPixel(display, screen), // border
9804 					WhitePixel(display, screen)); // background
9805 				*/
9806 
9807 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
9808 				bufferw = width;
9809 				bufferh = height;
9810 
9811 				gc = DefaultGC(display, screen);
9812 
9813 				// clear out the buffer to get us started...
9814 				XSetForeground(display, gc, WhitePixel(display, screen));
9815 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
9816 				XSetForeground(display, gc, BlackPixel(display, screen));
9817 			}
9818 
9819 			// input context
9820 			//TODO: create this only for top-level windows, and reuse that?
9821 			if (XDisplayConnection.xim !is null) {
9822 				xic = XCreateIC(XDisplayConnection.xim,
9823 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
9824 						/*XNClientWindow*/"clientWindow".ptr, window,
9825 						/*XNFocusWindow*/"focusWindow".ptr, window,
9826 						null);
9827 				if (xic is null) {
9828 					import core.stdc.stdio : stderr, fprintf;
9829 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
9830 				}
9831 			}
9832 
9833 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
9834 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
9835 			// window class
9836 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
9837 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
9838 				XClassHint klass;
9839 				XWMHints wh;
9840 				XSizeHints size;
9841 				klass.res_name = sdpyWindowClassStr;
9842 				klass.res_class = sdpyWindowClassStr;
9843 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
9844 			}
9845 
9846 			setTitle(title);
9847 			SimpleWindow.nativeMapping[window] = this;
9848 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
9849 
9850 			// This gives our window a close button
9851 			if (windowType != WindowTypes.eventOnly) {
9852 				Atom atom = XInternAtom(display, "WM_DELETE_WINDOW".ptr, true); // FIXME: does this need to be freed?
9853 				XSetWMProtocols(display, window, &atom, 1);
9854 			}
9855 
9856 			// FIXME: windowType and customizationFlags
9857 			Atom[8] wsatoms; // here, due to goto
9858 			int wmsacount = 0; // here, due to goto
9859 
9860 			try
9861 			final switch(windowType) {
9862 				case WindowTypes.normal:
9863 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
9864 				break;
9865 				case WindowTypes.undecorated:
9866 					motifHideDecorations();
9867 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
9868 				break;
9869 				case WindowTypes.eventOnly:
9870 					_hidden = true;
9871 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
9872 					goto hiddenWindow;
9873 				//break;
9874 				case WindowTypes.nestedChild:
9875 
9876 				break;
9877 
9878 				case WindowTypes.dropdownMenu:
9879 					motifHideDecorations();
9880 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
9881 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
9882 				break;
9883 				case WindowTypes.popupMenu:
9884 					motifHideDecorations();
9885 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
9886 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
9887 				break;
9888 				case WindowTypes.notification:
9889 					motifHideDecorations();
9890 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
9891 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
9892 				break;
9893 				/+
9894 				case WindowTypes.menu:
9895 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
9896 					motifHideDecorations();
9897 				break;
9898 				case WindowTypes.desktop:
9899 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
9900 				break;
9901 				case WindowTypes.dock:
9902 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
9903 				break;
9904 				case WindowTypes.toolbar:
9905 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
9906 				break;
9907 				case WindowTypes.menu:
9908 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
9909 				break;
9910 				case WindowTypes.utility:
9911 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
9912 				break;
9913 				case WindowTypes.splash:
9914 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
9915 				break;
9916 				case WindowTypes.dialog:
9917 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
9918 				break;
9919 				case WindowTypes.tooltip:
9920 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
9921 				break;
9922 				case WindowTypes.notification:
9923 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
9924 				break;
9925 				case WindowTypes.combo:
9926 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
9927 				break;
9928 				case WindowTypes.dnd:
9929 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
9930 				break;
9931 				+/
9932 			}
9933 			catch(Exception e) {
9934 				// XInternAtom failed, prolly a WM
9935 				// that doesn't support these things
9936 			}
9937 
9938 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
9939 			// the two following flags may be ignored by WM
9940 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
9941 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
9942 
9943 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
9944 
9945 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
9946 
9947 			// What would be ideal here is if they only were
9948 			// selected if there was actually an event handler
9949 			// for them...
9950 
9951 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
9952 
9953 			hiddenWindow:
9954 
9955 			// set the pid property for lookup later by window managers
9956 			// a standard convenience
9957 			import core.sys.posix.unistd;
9958 			arch_ulong pid = getpid();
9959 
9960 			XChangeProperty(
9961 				display,
9962 				impl.window,
9963 				GetAtom!("_NET_WM_PID", true)(display),
9964 				XA_CARDINAL,
9965 				32 /* bits */,
9966 				0 /*PropModeReplace*/,
9967 				&pid,
9968 				1);
9969 
9970 
9971 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
9972 				XMapWindow(display, window);
9973 			} else {
9974 				_hidden = true;
9975 			}
9976 		}
9977 
9978 		void selectDefaultInput(bool forceIncludeMouseMotion) {
9979 			auto mask = EventMask.ExposureMask |
9980 				EventMask.KeyPressMask |
9981 				EventMask.KeyReleaseMask |
9982 				EventMask.PropertyChangeMask |
9983 				EventMask.FocusChangeMask |
9984 				EventMask.StructureNotifyMask |
9985 				EventMask.VisibilityChangeMask
9986 				| EventMask.ButtonPressMask
9987 				| EventMask.ButtonReleaseMask
9988 			;
9989 
9990 			// xshm is our shortcut for local connections
9991 			if(Image.impl.xshmAvailable || forceIncludeMouseMotion)
9992 				mask |= EventMask.PointerMotionMask;
9993 
9994 			XSelectInput(display, window, mask);
9995 		}
9996 
9997 
9998 		void setNetWMWindowType(Atom type) {
9999 			Atom[2] atoms;
10000 
10001 			atoms[0] = type;
10002 			// generic fallback
10003 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
10004 
10005 			XChangeProperty(
10006 				display,
10007 				impl.window,
10008 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
10009 				XA_ATOM,
10010 				32 /* bits */,
10011 				0 /*PropModeReplace*/,
10012 				atoms.ptr,
10013 				cast(int) atoms.length);
10014 		}
10015 
10016 		void motifHideDecorations() {
10017 			MwmHints hints;
10018 			hints.flags = MWM_HINTS_DECORATIONS;
10019 
10020 			XChangeProperty(
10021 				display,
10022 				impl.window,
10023 				GetAtom!"_MOTIF_WM_HINTS"(display),
10024 				GetAtom!"_MOTIF_WM_HINTS"(display),
10025 				32 /* bits */,
10026 				0 /*PropModeReplace*/,
10027 				&hints,
10028 				hints.sizeof / 4);
10029 		}
10030 
10031 		/*k8: unused
10032 		void createOpenGlContext() {
10033 
10034 		}
10035 		*/
10036 
10037 		void closeWindow() {
10038 			if (customEventFD != -1) {
10039 				import core.sys.posix.unistd : close;
10040 				close(customEventFD);
10041 				customEventFD = -1;
10042 			}
10043 			if(buffer)
10044 				XFreePixmap(display, buffer);
10045 			bufferw = bufferh = 0;
10046 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
10047 			XDestroyWindow(display, window);
10048 			XFlush(display);
10049 		}
10050 
10051 		void dispose() {
10052 		}
10053 
10054 		bool destroyed = false;
10055 	}
10056 
10057 	bool insideXEventLoop;
10058 }
10059 
10060 version(X11) {
10061 
10062 	int mouseDoubleClickTimeout = 350; /// double click timeout. X only, you probably shouldn't change this.
10063 
10064 	/// Platform-specific, you might use it when doing a custom event loop
10065 	bool doXNextEvent(Display* display) {
10066 		bool done;
10067 		XEvent e;
10068 		XNextEvent(display, &e);
10069 		version(sddddd) {
10070 			import std.stdio, std.conv : to;
10071 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
10072 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
10073 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
10074 			}
10075 		}
10076 
10077 		// filter out compose events
10078 		if (XFilterEvent(&e, None)) {
10079 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
10080 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
10081 			return false;
10082 		}
10083 		// process keyboard mapping changes
10084 		if (e.type == EventType.KeymapNotify) {
10085 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
10086 			XRefreshKeyboardMapping(&e.xmapping);
10087 			return false;
10088 		}
10089 
10090 		version(with_eventloop)
10091 			import arsd.eventloop;
10092 
10093 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
10094 			// see windows impl's comments
10095 			XUnlockDisplay(display);
10096 			scope(exit) XLockDisplay(display);
10097 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
10098 			if(ret == 0)
10099 				return done;
10100 		}
10101 
10102 
10103 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
10104 			if(win.getNativeEventHandler !is null) {
10105 				XUnlockDisplay(display);
10106 				scope(exit) XLockDisplay(display);
10107 				auto ret = win.getNativeEventHandler()(e);
10108 				if(ret == 0)
10109 					return done;
10110 			}
10111 		}
10112 
10113 		switch(e.type) {
10114 		  case EventType.SelectionClear:
10115 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping)
10116 				{ /* FIXME??????? */ }
10117 		  break;
10118 		  case EventType.SelectionRequest:
10119 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
10120 			if(win.setSelectionHandler !is null) {
10121 				XUnlockDisplay(display);
10122 				scope(exit) XLockDisplay(display);
10123 				win.setSelectionHandler(e);
10124 			}
10125 		  break;
10126 		  case EventType.PropertyNotify:
10127 
10128 		  break;
10129 		  case EventType.SelectionNotify:
10130 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
10131 		  	if(win.getSelectionHandler !is null) {
10132 				// FIXME: maybe we should call a different handler for PRIMARY vs CLIPBOARD
10133 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
10134 					XUnlockDisplay(display);
10135 					scope(exit) XLockDisplay(display);
10136 					win.getSelectionHandler(null);
10137 				} else {
10138 					Atom target;
10139 					int format;
10140 					arch_ulong bytesafter, length;
10141 					void* value;
10142 					XGetWindowProperty(
10143 						e.xselection.display,
10144 						e.xselection.requestor,
10145 						e.xselection.property,
10146 						0,
10147 						100000 /* length */,
10148 						//false, /* don't erase it */
10149 						true, /* do erase it lol */
10150 						0 /*AnyPropertyType*/,
10151 						&target, &format, &length, &bytesafter, &value);
10152 
10153 					// FIXME: I don't have to copy it now since it is in char[] instead of string
10154 
10155 					{
10156 						XUnlockDisplay(display);
10157 						scope(exit) XLockDisplay(display);
10158 
10159 						if(target == XA_ATOM) {
10160 							// initial request, see what they are able to work with and request the best one
10161 							// we can handle, if available
10162 
10163 							Atom[] answer = (cast(Atom*) value)[0 .. length];
10164 							Atom best = None;
10165 							foreach(option; answer) {
10166 								if(option == GetAtom!"UTF8_STRING"(display)) {
10167 									best = option;
10168 									break;
10169 								} else if(option == XA_STRING) {
10170 									best = option;
10171 								}
10172 							}
10173 
10174 							//writeln("got ", answer);
10175 
10176 							if(best != None) {
10177 								// actually request the best format
10178 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
10179 							}
10180 						} else if(target == GetAtom!"UTF8_STRING"(display) || target == XA_STRING) {
10181 							win.getSelectionHandler((cast(char[]) value[0 .. length]).idup);
10182 						} else if(target == GetAtom!"INCR"(display)) {
10183 							// incremental
10184 
10185 							//sdpyGettingPaste = true; // FIXME: should prolly be separate for the different selections
10186 
10187 							// FIXME: handle other events while it goes so app doesn't lock up with big pastes
10188 							// can also be optimized if it chunks to the api somehow
10189 
10190 							char[] s;
10191 
10192 							do {
10193 
10194 								XEvent subevent;
10195 								do {
10196 									XMaskEvent(display, EventMask.PropertyChangeMask, &subevent);
10197 								} while(subevent.type != EventType.PropertyNotify || subevent.xproperty.atom != e.xselection.property || subevent.xproperty.state != PropertyNotification.PropertyNewValue);
10198 
10199 								void* subvalue;
10200 								XGetWindowProperty(
10201 									e.xselection.display,
10202 									e.xselection.requestor,
10203 									e.xselection.property,
10204 									0,
10205 									100000 /* length */,
10206 									true, /* erase it to signal we got it and want more */
10207 									0 /*AnyPropertyType*/,
10208 									&target, &format, &length, &bytesafter, &subvalue);
10209 
10210 								s ~= (cast(char*) subvalue)[0 .. length];
10211 
10212 								XFree(subvalue);
10213 							} while(length > 0);
10214 
10215 							win.getSelectionHandler(s);
10216 						} else {
10217 							// unsupported type
10218 						}
10219 					}
10220 					XFree(value);
10221 					/*
10222 					XDeleteProperty(
10223 						e.xselection.display,
10224 						e.xselection.requestor,
10225 						e.xselection.property);
10226 					*/
10227 				}
10228 			}
10229 		  break;
10230 		  case EventType.ConfigureNotify:
10231 			auto event = e.xconfigure;
10232 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
10233 					//version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); }
10234 				if(event.width != win.width || event.height != win.height) {
10235 					win._width = event.width;
10236 					win._height = event.height;
10237 
10238 					if(win.openglMode == OpenGlOptions.no) {
10239 						// FIXME: could this be more efficient?
10240 
10241 						if (win.bufferw < event.width || win.bufferh < event.height) {
10242 							//{ import core.stdc.stdio; printf("new buffer; old size: %dx%d; new size: %dx%d\n", win.bufferw, win.bufferh, cast(int)event.width, cast(int)event.height); }
10243 							// grow the internal buffer to match the window...
10244 							auto newPixmap = XCreatePixmap(display, cast(Drawable) event.window, event.width, event.height, DefaultDepthOfDisplay(display));
10245 							{
10246 								GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
10247 								XCopyGC(win.display, win.gc, 0xffffffff, xgc);
10248 								scope(exit) XFreeGC(win.display, xgc);
10249 								XSetClipMask(win.display, xgc, None);
10250 								XSetForeground(win.display, xgc, 0);
10251 								XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, event.width, event.height);
10252 							}
10253 							XCopyArea(display,
10254 								cast(Drawable) (*win).buffer,
10255 								cast(Drawable) newPixmap,
10256 								(*win).gc, 0, 0,
10257 								win.bufferw < event.width ? win.bufferw : win.width,
10258 								win.bufferh < event.height ? win.bufferh : win.height,
10259 								0, 0);
10260 
10261 							XFreePixmap(display, win.buffer);
10262 							win.buffer = newPixmap;
10263 							win.bufferw = event.width;
10264 							win.bufferh = event.height;
10265 						}
10266 
10267 						// clear unused parts of the buffer
10268 						if (win.bufferw > event.width || win.bufferh > event.height) {
10269 							GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
10270 							XCopyGC(win.display, win.gc, 0xffffffff, xgc);
10271 							scope(exit) XFreeGC(win.display, xgc);
10272 							XSetClipMask(win.display, xgc, None);
10273 							XSetForeground(win.display, xgc, 0);
10274 							immutable int maxw = (win.bufferw > event.width ? win.bufferw : event.width);
10275 							immutable int maxh = (win.bufferh > event.height ? win.bufferh : event.height);
10276 							XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, event.width, 0, maxw, maxh); // let X11 do clipping
10277 							XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, event.height, maxw, maxh); // let X11 do clipping
10278 						}
10279 
10280 					}
10281 
10282 					version(without_opengl) {} else
10283 					if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) {
10284 						glViewport(0, 0, event.width, event.height);
10285 					}
10286 
10287 					win.fixFixedSize(event.width, event.height); //k8: this does nothing on my FluxBox; wtf?!
10288 
10289 					if(win.windowResized !is null) {
10290 						XUnlockDisplay(display);
10291 						scope(exit) XLockDisplay(display);
10292 						win.windowResized(event.width, event.height);
10293 					}
10294 				}
10295 			}
10296 		  break;
10297 		  case EventType.Expose:
10298 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
10299 				// if it is closing from a popup menu, it can get
10300 				// an Expose event right by the end and trigger a
10301 				// BadDrawable error ... we'll just check
10302 				// closed to handle that.
10303 				if((*win).closed) break;
10304 				if((*win).openglMode == OpenGlOptions.no) {
10305 					bool doCopy = true;
10306 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
10307 					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);
10308 				} else {
10309 					// need to redraw the scene somehow
10310 					XUnlockDisplay(display);
10311 					scope(exit) XLockDisplay(display);
10312 					version(without_opengl) {} else
10313 					win.redrawOpenGlSceneNow();
10314 				}
10315 			}
10316 		  break;
10317 		  case EventType.FocusIn:
10318 		  case EventType.FocusOut:
10319 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
10320 				if (win.xic !is null) {
10321 					//{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); }
10322 					if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic);
10323 				}
10324 
10325 				win._focused = e.type == EventType.FocusIn;
10326 
10327 				if(win.demandingAttention)
10328 					demandAttention(*win, false);
10329 
10330 				if(win.onFocusChange) {
10331 					XUnlockDisplay(display);
10332 					scope(exit) XLockDisplay(display);
10333 					win.onFocusChange(e.type == EventType.FocusIn);
10334 				}
10335 			}
10336 		  break;
10337 		  case EventType.VisibilityNotify:
10338 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
10339 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
10340 						if (win.visibilityChanged !is null) {
10341 								XUnlockDisplay(display);
10342 								scope(exit) XLockDisplay(display);
10343 								win.visibilityChanged(false);
10344 							}
10345 					} else {
10346 						if (win.visibilityChanged !is null) {
10347 							XUnlockDisplay(display);
10348 							scope(exit) XLockDisplay(display);
10349 							win.visibilityChanged(true);
10350 						}
10351 					}
10352 				}
10353 				break;
10354 		  case EventType.ClientMessage:
10355 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
10356 					// "ignore next mouse motion" event, increment ignore counter for teh window
10357 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
10358 						++(*win).warpEventCount;
10359 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
10360 					} else {
10361 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
10362 					}
10363 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
10364 					// user clicked the close button on the window manager
10365 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
10366 						XUnlockDisplay(display);
10367 						scope(exit) XLockDisplay(display);
10368 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
10369 					}
10370 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
10371 					foreach(nai; NotificationAreaIcon.activeIcons)
10372 						nai.newManager();
10373 				}
10374 		  break;
10375 		  case EventType.MapNotify:
10376 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
10377 					(*win)._visible = true;
10378 					if (!(*win)._visibleForTheFirstTimeCalled) {
10379 						(*win)._visibleForTheFirstTimeCalled = true;
10380 						if ((*win).visibleForTheFirstTime !is null) {
10381 							XUnlockDisplay(display);
10382 							scope(exit) XLockDisplay(display);
10383 							version(without_opengl) {} else {
10384 								if((*win).openglMode == OpenGlOptions.yes) {
10385 									(*win).setAsCurrentOpenGlContextNT();
10386 									glViewport(0, 0, (*win).width, (*win).height);
10387 								}
10388 							}
10389 							(*win).visibleForTheFirstTime();
10390 						}
10391 					}
10392 					if ((*win).visibilityChanged !is null) {
10393 						XUnlockDisplay(display);
10394 						scope(exit) XLockDisplay(display);
10395 						(*win).visibilityChanged(true);
10396 					}
10397 				}
10398 		  break;
10399 		  case EventType.UnmapNotify:
10400 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
10401 					win._visible = false;
10402 					if (win.visibilityChanged !is null) {
10403 						XUnlockDisplay(display);
10404 						scope(exit) XLockDisplay(display);
10405 						win.visibilityChanged(false);
10406 					}
10407 			}
10408 		  break;
10409 		  case EventType.DestroyNotify:
10410 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
10411 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
10412 				win._closed = true; // just in case
10413 				win.destroyed = true;
10414 				if (win.xic !is null) {
10415 					XDestroyIC(win.xic);
10416 					win.xic = null; // just in calse
10417 				}
10418 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
10419 				bool anyImportant = false;
10420 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
10421 					if(w.beingOpenKeepsAppOpen) {
10422 						anyImportant = true;
10423 						break;
10424 					}
10425 				if(!anyImportant)
10426 					done = true;
10427 			}
10428 			auto window = e.xdestroywindow.window;
10429 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
10430 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
10431 
10432 			version(with_eventloop) {
10433 				if(done) exit();
10434 			}
10435 		  break;
10436 
10437 		  case EventType.MotionNotify:
10438 			MouseEvent mouse;
10439 			auto event = e.xmotion;
10440 
10441 			mouse.type = MouseEventType.motion;
10442 			mouse.x = event.x;
10443 			mouse.y = event.y;
10444 			mouse.modifierState = event.state;
10445 
10446 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
10447 				mouse.window = *win;
10448 				if (win.warpEventCount > 0) {
10449 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
10450 					--(*win).warpEventCount;
10451 					(*win).mdx(mouse); // so deltas will be correctly updated
10452 				} else {
10453 					win.warpEventCount = 0; // just in case
10454 					(*win).mdx(mouse);
10455 					if((*win).handleMouseEvent) {
10456 						XUnlockDisplay(display);
10457 						scope(exit) XLockDisplay(display);
10458 						(*win).handleMouseEvent(mouse);
10459 					}
10460 				}
10461 			}
10462 
10463 		  	version(with_eventloop)
10464 				send(mouse);
10465 		  break;
10466 		  case EventType.ButtonPress:
10467 		  case EventType.ButtonRelease:
10468 			MouseEvent mouse;
10469 			auto event = e.xbutton;
10470 
10471 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
10472 			mouse.x = event.x;
10473 			mouse.y = event.y;
10474 
10475 			static Time lastMouseDownTime = 0;
10476 
10477 			mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
10478 			if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time;
10479 
10480 			switch(event.button) {
10481 				case 1: mouse.button = MouseButton.left; break; // left
10482 				case 2: mouse.button = MouseButton.middle; break; // middle
10483 				case 3: mouse.button = MouseButton.right; break; // right
10484 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
10485 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
10486 				case 6: break; // idk
10487 				case 7: break; // idk
10488 				case 8: mouse.button = MouseButton.backButton; break;
10489 				case 9: mouse.button = MouseButton.forwardButton; break;
10490 				default:
10491 			}
10492 
10493 			// FIXME: double check this
10494 			mouse.modifierState = event.state;
10495 
10496 			//mouse.modifierState = event.detail;
10497 
10498 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
10499 				mouse.window = *win;
10500 				(*win).mdx(mouse);
10501 				if((*win).handleMouseEvent) {
10502 					XUnlockDisplay(display);
10503 					scope(exit) XLockDisplay(display);
10504 					(*win).handleMouseEvent(mouse);
10505 				}
10506 			}
10507 			version(with_eventloop)
10508 				send(mouse);
10509 		  break;
10510 
10511 		  case EventType.KeyPress:
10512 		  case EventType.KeyRelease:
10513 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
10514 			KeyEvent ke;
10515 			ke.pressed = e.type == EventType.KeyPress;
10516 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
10517 
10518 			auto sym = XKeycodeToKeysym(
10519 				XDisplayConnection.get(),
10520 				e.xkey.keycode,
10521 				0);
10522 
10523 			ke.key = cast(Key) sym;//e.xkey.keycode;
10524 
10525 			ke.modifierState = e.xkey.state;
10526 
10527 			// import std.stdio; writefln("%x", sym);
10528 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
10529 			int charbuflen = 0; // return value of XwcLookupString
10530 			if (ke.pressed) {
10531 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
10532 				if (win !is null && win.xic !is null) {
10533 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
10534 					Status status;
10535 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
10536 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
10537 				} else {
10538 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
10539 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
10540 					char[16] buffer;
10541 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
10542 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
10543 				}
10544 			}
10545 
10546 			// if there's no char, subst one
10547 			if (charbuflen == 0) {
10548 				switch (sym) {
10549 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
10550 					case 0xff8d: // keypad enter
10551 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
10552 					default : // ignore
10553 				}
10554 			}
10555 
10556 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
10557 				ke.window = *win;
10558 				if (win.handleKeyEvent) {
10559 					XUnlockDisplay(display);
10560 					scope(exit) XLockDisplay(display);
10561 					win.handleKeyEvent(ke);
10562 				}
10563 
10564 				// char events are separate since they are on Windows too
10565 				// also, xcompose can generate long char sequences
10566 				// don't send char events if Meta and/or Hyper is pressed
10567 				// TODO: ctrl+char should only send control chars; not yet
10568 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
10569 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
10570 				}
10571 				if (ke.pressed && charbuflen > 0 && (e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0) {
10572 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
10573 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
10574 						if (win.handleCharEvent) {
10575 							XUnlockDisplay(display);
10576 							scope(exit) XLockDisplay(display);
10577 							win.handleCharEvent(ch);
10578 						}
10579 					}
10580 				}
10581 			}
10582 
10583 			version(with_eventloop)
10584 				send(ke);
10585 		  break;
10586 		  default:
10587 		}
10588 
10589 		return done;
10590 	}
10591 }
10592 
10593 /* *************************************** */
10594 /*      Done with simpledisplay stuff      */
10595 /* *************************************** */
10596 
10597 // Necessary C library bindings follow
10598 version(Windows) {} else
10599 version(X11) {
10600 
10601 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
10602 
10603 // X11 bindings needed here
10604 /*
10605 	A little of this is from the bindings project on
10606 	D Source and some of it is copy/paste from the C
10607 	header.
10608 
10609 	The DSource listing consistently used D's long
10610 	where C used long. That's wrong - C long is 32 bit, so
10611 	it should be int in D. I changed that here.
10612 
10613 	Note:
10614 	This isn't complete, just took what I needed for myself.
10615 */
10616 
10617 pragma(lib, "X11");
10618 pragma(lib, "Xext");
10619 import core.stdc.stddef : wchar_t;
10620 
10621 extern(C) nothrow @nogc {
10622 
10623 Cursor XCreateFontCursor(Display*, uint shape);
10624 int XDefineCursor(Display* display, Window w, Cursor cursor);
10625 int XUndefineCursor(Display* display, Window w);
10626 
10627 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
10628 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
10629 int XFreeCursor(Display* display, Cursor cursor);
10630 
10631 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
10632 
10633 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
10634 
10635 char *XKeysymToString(KeySym keysym);
10636 KeySym XKeycodeToKeysym(
10637 	Display*		/* display */,
10638 	KeyCode		/* keycode */,
10639 	int			/* index */
10640 );
10641 
10642 
10643 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
10644 
10645 int XFree(void*);
10646 int XDeleteProperty(Display *display, Window w, Atom property);
10647 
10648 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements);
10649 
10650 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
10651 	long_offset, arch_long long_length, Bool del, Atom req_type, Atom
10652 	*actual_type_return, int *actual_format_return, arch_ulong
10653 	*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
10654 Atom* XListProperties(Display *display, Window w, int *num_prop_return);
10655 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
10656 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
10657 
10658 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
10659 
10660 Window XGetSelectionOwner(Display *display, Atom selection);
10661 
10662 struct XVisualInfo {
10663 	Visual* visual;
10664 	VisualID visualid;
10665 	int screen;
10666 	uint depth;
10667 	int c_class;
10668 	c_ulong red_mask;
10669 	c_ulong green_mask;
10670 	c_ulong blue_mask;
10671 	int colormap_size;
10672 	int bits_per_rgb;
10673 }
10674 
10675 enum VisualNoMask=	0x0;
10676 enum VisualIDMask=	0x1;
10677 enum VisualScreenMask=0x2;
10678 enum VisualDepthMask=	0x4;
10679 enum VisualClassMask=	0x8;
10680 enum VisualRedMaskMask=0x10;
10681 enum VisualGreenMaskMask=0x20;
10682 enum VisualBlueMaskMask=0x40;
10683 enum VisualColormapSizeMask=0x80;
10684 enum VisualBitsPerRGBMask=0x100;
10685 enum VisualAllMask=	0x1FF;
10686 
10687 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
10688 
10689 
10690 
10691 Display* XOpenDisplay(const char*);
10692 int XCloseDisplay(Display*);
10693 
10694 Bool XQueryExtension(Display*, const char*, int*, int*, int*);
10695 
10696 // XIM and other crap
10697 struct _XOM {}
10698 struct _XIM {}
10699 struct _XIC {}
10700 alias XOM = _XOM*;
10701 alias XIM = _XIM*;
10702 alias XIC = _XIC*;
10703 Bool XSupportsLocale();
10704 char* XSetLocaleModifiers(const(char)* modifier_list);
10705 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
10706 Status XCloseOM(XOM om);
10707 
10708 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
10709 Status XCloseIM(XIM im);
10710 
10711 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
10712 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
10713 Display* XDisplayOfIM(XIM im);
10714 char* XLocaleOfIM(XIM im);
10715 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
10716 void XDestroyIC(XIC ic);
10717 void XSetICFocus(XIC ic);
10718 void XUnsetICFocus(XIC ic);
10719 //wchar_t* XwcResetIC(XIC ic);
10720 char* XmbResetIC(XIC ic);
10721 char* Xutf8ResetIC(XIC ic);
10722 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
10723 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
10724 XIM XIMOfIC(XIC ic);
10725 
10726 alias XIMStyle = arch_ulong;
10727 enum : arch_ulong {
10728 	XIMPreeditArea      = 0x0001,
10729 	XIMPreeditCallbacks = 0x0002,
10730 	XIMPreeditPosition  = 0x0004,
10731 	XIMPreeditNothing   = 0x0008,
10732 	XIMPreeditNone      = 0x0010,
10733 	XIMStatusArea       = 0x0100,
10734 	XIMStatusCallbacks  = 0x0200,
10735 	XIMStatusNothing    = 0x0400,
10736 	XIMStatusNone       = 0x0800,
10737 }
10738 
10739 
10740 /* X Shared Memory Extension functions */
10741 	//pragma(lib, "Xshm");
10742 	alias arch_ulong ShmSeg;
10743 	struct XShmSegmentInfo {
10744 		ShmSeg shmseg;
10745 		int shmid;
10746 		ubyte* shmaddr;
10747 		Bool readOnly;
10748 	}
10749 	Status XShmAttach(Display*, XShmSegmentInfo*);
10750 	Status XShmDetach(Display*, XShmSegmentInfo*);
10751 	Status XShmPutImage(
10752 		Display*            /* dpy */,
10753 		Drawable            /* d */,
10754 		GC                  /* gc */,
10755 		XImage*             /* image */,
10756 		int                 /* src_x */,
10757 		int                 /* src_y */,
10758 		int                 /* dst_x */,
10759 		int                 /* dst_y */,
10760 		uint        /* src_width */,
10761 		uint        /* src_height */,
10762 		Bool                /* send_event */
10763 	);
10764 
10765 	Status XShmQueryExtension(Display*);
10766 
10767 	XImage *XShmCreateImage(
10768 		Display*            /* dpy */,
10769 		Visual*             /* visual */,
10770 		uint        /* depth */,
10771 		int                 /* format */,
10772 		char*               /* data */,
10773 		XShmSegmentInfo*    /* shminfo */,
10774 		uint        /* width */,
10775 		uint        /* height */
10776 	);
10777 
10778 	Pixmap XShmCreatePixmap(
10779 		Display*            /* dpy */,
10780 		Drawable            /* d */,
10781 		char*               /* data */,
10782 		XShmSegmentInfo*    /* shminfo */,
10783 		uint        /* width */,
10784 		uint        /* height */,
10785 		uint        /* depth */
10786 	);
10787 
10788 	// and the necessary OS functions
10789 	int shmget(int, size_t, int);
10790 	void* shmat(int, in void*, int);
10791 	int shmdt(in void*);
10792 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
10793 
10794 	enum IPC_PRIVATE = 0;
10795 	enum IPC_CREAT = 512;
10796 	enum IPC_RMID = 0;
10797 
10798 /* MIT-SHM end */
10799 
10800 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
10801 
10802 
10803 enum MappingType:int {
10804 	MappingModifier		=0,
10805 	MappingKeyboard		=1,
10806 	MappingPointer		=2
10807 }
10808 
10809 /* ImageFormat -- PutImage, GetImage */
10810 enum ImageFormat:int {
10811 	XYBitmap	=0,	/* depth 1, XYFormat */
10812 	XYPixmap	=1,	/* depth == drawable depth */
10813 	ZPixmap	=2	/* depth == drawable depth */
10814 }
10815 
10816 enum ModifierName:int {
10817 	ShiftMapIndex	=0,
10818 	LockMapIndex	=1,
10819 	ControlMapIndex	=2,
10820 	Mod1MapIndex	=3,
10821 	Mod2MapIndex	=4,
10822 	Mod3MapIndex	=5,
10823 	Mod4MapIndex	=6,
10824 	Mod5MapIndex	=7
10825 }
10826 
10827 enum ButtonMask:int {
10828 	Button1Mask	=1<<8,
10829 	Button2Mask	=1<<9,
10830 	Button3Mask	=1<<10,
10831 	Button4Mask	=1<<11,
10832 	Button5Mask	=1<<12,
10833 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
10834 }
10835 
10836 enum KeyOrButtonMask:uint {
10837 	ShiftMask	=1<<0,
10838 	LockMask	=1<<1,
10839 	ControlMask	=1<<2,
10840 	Mod1Mask	=1<<3,
10841 	Mod2Mask	=1<<4,
10842 	Mod3Mask	=1<<5,
10843 	Mod4Mask	=1<<6,
10844 	Mod5Mask	=1<<7,
10845 	Button1Mask	=1<<8,
10846 	Button2Mask	=1<<9,
10847 	Button3Mask	=1<<10,
10848 	Button4Mask	=1<<11,
10849 	Button5Mask	=1<<12,
10850 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
10851 }
10852 
10853 enum ButtonName:int {
10854 	Button1	=1,
10855 	Button2	=2,
10856 	Button3	=3,
10857 	Button4	=4,
10858 	Button5	=5
10859 }
10860 
10861 /* Notify modes */
10862 enum NotifyModes:int
10863 {
10864 	NotifyNormal		=0,
10865 	NotifyGrab			=1,
10866 	NotifyUngrab		=2,
10867 	NotifyWhileGrabbed	=3
10868 }
10869 const int NotifyHint	=1;	/* for MotionNotify events */
10870 
10871 /* Notify detail */
10872 enum NotifyDetail:int
10873 {
10874 	NotifyAncestor			=0,
10875 	NotifyVirtual			=1,
10876 	NotifyInferior			=2,
10877 	NotifyNonlinear			=3,
10878 	NotifyNonlinearVirtual	=4,
10879 	NotifyPointer			=5,
10880 	NotifyPointerRoot		=6,
10881 	NotifyDetailNone		=7
10882 }
10883 
10884 /* Visibility notify */
10885 
10886 enum VisibilityNotify:int
10887 {
10888 VisibilityUnobscured		=0,
10889 VisibilityPartiallyObscured	=1,
10890 VisibilityFullyObscured		=2
10891 }
10892 
10893 
10894 enum WindowStackingMethod:int
10895 {
10896 	Above		=0,
10897 	Below		=1,
10898 	TopIf		=2,
10899 	BottomIf	=3,
10900 	Opposite	=4
10901 }
10902 
10903 /* Circulation request */
10904 enum CirculationRequest:int
10905 {
10906 	PlaceOnTop		=0,
10907 	PlaceOnBottom	=1
10908 }
10909 
10910 enum PropertyNotification:int
10911 {
10912 	PropertyNewValue	=0,
10913 	PropertyDelete		=1
10914 }
10915 
10916 enum ColorMapNotification:int
10917 {
10918 	ColormapUninstalled	=0,
10919 	ColormapInstalled		=1
10920 }
10921 
10922 
10923 	struct _XPrivate {}
10924 	struct _XrmHashBucketRec {}
10925 
10926 	alias void* XPointer;
10927 	alias void* XExtData;
10928 
10929 	version( X86_64 ) {
10930 		alias ulong XID;
10931 		alias ulong arch_ulong;
10932 		alias long arch_long;
10933 	} else {
10934 		alias uint XID;
10935 		alias uint arch_ulong;
10936 		alias int arch_long;
10937 	}
10938 
10939 	alias XID Window;
10940 	alias XID Drawable;
10941 	alias XID Pixmap;
10942 
10943 	alias arch_ulong Atom;
10944 	alias int Bool;
10945 	alias Display XDisplay;
10946 
10947 	alias int ByteOrder;
10948 	alias arch_ulong Time;
10949 	alias void ScreenFormat;
10950 
10951 	struct XImage {
10952 		int width, height;			/* size of image */
10953 		int xoffset;				/* number of pixels offset in X direction */
10954 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
10955 		void *data;					/* pointer to image data */
10956 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
10957 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
10958 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
10959 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
10960 		int depth;					/* depth of image */
10961 		int bytes_per_line;			/* accelarator to next line */
10962 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
10963 		arch_ulong red_mask;	/* bits in z arrangment */
10964 		arch_ulong green_mask;
10965 		arch_ulong blue_mask;
10966 		XPointer obdata;			/* hook for the object routines to hang on */
10967 		static struct F {				/* image manipulation routines */
10968 			XImage* function(
10969 				XDisplay* 			/* display */,
10970 				Visual*				/* visual */,
10971 				uint				/* depth */,
10972 				int					/* format */,
10973 				int					/* offset */,
10974 				ubyte*				/* data */,
10975 				uint				/* width */,
10976 				uint				/* height */,
10977 				int					/* bitmap_pad */,
10978 				int					/* bytes_per_line */) create_image;
10979 			int function(XImage *) destroy_image;
10980 			arch_ulong function(XImage *, int, int) get_pixel;
10981 			int function(XImage *, int, int, arch_ulong) put_pixel;
10982 			XImage* function(XImage *, int, int, uint, uint) sub_image;
10983 			int function(XImage *, arch_long) add_pixel;
10984 		}
10985 		F f;
10986 	}
10987 	version(X86_64) static assert(XImage.sizeof == 136);
10988 	else version(X86) static assert(XImage.sizeof == 88);
10989 
10990 struct XCharStruct {
10991 	short       lbearing;       /* origin to left edge of raster */
10992 	short       rbearing;       /* origin to right edge of raster */
10993 	short       width;          /* advance to next char's origin */
10994 	short       ascent;         /* baseline to top edge of raster */
10995 	short       descent;        /* baseline to bottom edge of raster */
10996 	ushort attributes;  /* per char flags (not predefined) */
10997 }
10998 
10999 /*
11000  * To allow arbitrary information with fonts, there are additional properties
11001  * returned.
11002  */
11003 struct XFontProp {
11004 	Atom name;
11005 	arch_ulong card32;
11006 }
11007 
11008 alias Atom Font;
11009 
11010 struct XFontStruct {
11011 	XExtData *ext_data;           /* Hook for extension to hang data */
11012 	Font fid;                     /* Font ID for this font */
11013 	uint direction;           /* Direction the font is painted */
11014 	uint min_char_or_byte2;   /* First character */
11015 	uint max_char_or_byte2;   /* Last character */
11016 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
11017 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
11018 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
11019 	uint default_char;        /* Char to print for undefined character */
11020 	int n_properties;             /* How many properties there are */
11021 	XFontProp *properties;        /* Pointer to array of additional properties*/
11022 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
11023 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
11024 	XCharStruct *per_char;        /* first_char to last_char information */
11025 	int ascent;                   /* Max extent above baseline for spacing */
11026 	int descent;                  /* Max descent below baseline for spacing */
11027 }
11028 
11029 	XFontStruct *XLoadQueryFont(Display *display, in char *name);
11030 	int XFreeFont(Display *display, XFontStruct *font_struct);
11031 	int XSetFont(Display* display, GC gc, Font font);
11032 	int XTextWidth(XFontStruct*, in char*, int);
11033 
11034 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
11035 	int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n);
11036 
11037 
11038 
11039 /*
11040  * Definitions of specific events.
11041  */
11042 struct XKeyEvent
11043 {
11044 	int type;			/* of event */
11045 	arch_ulong serial;		/* # of last request processed by server */
11046 	Bool send_event;	/* true if this came from a SendEvent request */
11047 	Display *display;	/* Display the event was read from */
11048 	Window window;	        /* "event" window it is reported relative to */
11049 	Window root;	        /* root window that the event occurred on */
11050 	Window subwindow;	/* child window */
11051 	Time time;		/* milliseconds */
11052 	int x, y;		/* pointer x, y coordinates in event window */
11053 	int x_root, y_root;	/* coordinates relative to root */
11054 	KeyOrButtonMask state;	/* key or button mask */
11055 	uint keycode;	/* detail */
11056 	Bool same_screen;	/* same screen flag */
11057 }
11058 version(X86_64) static assert(XKeyEvent.sizeof == 96);
11059 alias XKeyEvent XKeyPressedEvent;
11060 alias XKeyEvent XKeyReleasedEvent;
11061 
11062 struct XButtonEvent
11063 {
11064 	int type;		/* of event */
11065 	arch_ulong serial;	/* # of last request processed by server */
11066 	Bool send_event;	/* true if this came from a SendEvent request */
11067 	Display *display;	/* Display the event was read from */
11068 	Window window;	        /* "event" window it is reported relative to */
11069 	Window root;	        /* root window that the event occurred on */
11070 	Window subwindow;	/* child window */
11071 	Time time;		/* milliseconds */
11072 	int x, y;		/* pointer x, y coordinates in event window */
11073 	int x_root, y_root;	/* coordinates relative to root */
11074 	KeyOrButtonMask state;	/* key or button mask */
11075 	uint button;	/* detail */
11076 	Bool same_screen;	/* same screen flag */
11077 }
11078 alias XButtonEvent XButtonPressedEvent;
11079 alias XButtonEvent XButtonReleasedEvent;
11080 
11081 struct XMotionEvent{
11082 	int type;		/* of event */
11083 	arch_ulong serial;	/* # of last request processed by server */
11084 	Bool send_event;	/* true if this came from a SendEvent request */
11085 	Display *display;	/* Display the event was read from */
11086 	Window window;	        /* "event" window reported relative to */
11087 	Window root;	        /* root window that the event occurred on */
11088 	Window subwindow;	/* child window */
11089 	Time time;		/* milliseconds */
11090 	int x, y;		/* pointer x, y coordinates in event window */
11091 	int x_root, y_root;	/* coordinates relative to root */
11092 	KeyOrButtonMask state;	/* key or button mask */
11093 	byte is_hint;		/* detail */
11094 	Bool same_screen;	/* same screen flag */
11095 }
11096 alias XMotionEvent XPointerMovedEvent;
11097 
11098 struct XCrossingEvent{
11099 	int type;		/* of event */
11100 	arch_ulong serial;	/* # of last request processed by server */
11101 	Bool send_event;	/* true if this came from a SendEvent request */
11102 	Display *display;	/* Display the event was read from */
11103 	Window window;	        /* "event" window reported relative to */
11104 	Window root;	        /* root window that the event occurred on */
11105 	Window subwindow;	/* child window */
11106 	Time time;		/* milliseconds */
11107 	int x, y;		/* pointer x, y coordinates in event window */
11108 	int x_root, y_root;	/* coordinates relative to root */
11109 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
11110 	NotifyDetail detail;
11111 	/*
11112 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
11113 	 * NotifyNonlinear,NotifyNonlinearVirtual
11114 	 */
11115 	Bool same_screen;	/* same screen flag */
11116 	Bool focus;		/* Boolean focus */
11117 	KeyOrButtonMask state;	/* key or button mask */
11118 }
11119 alias XCrossingEvent XEnterWindowEvent;
11120 alias XCrossingEvent XLeaveWindowEvent;
11121 
11122 struct XFocusChangeEvent{
11123 	int type;		/* FocusIn or FocusOut */
11124 	arch_ulong serial;	/* # of last request processed by server */
11125 	Bool send_event;	/* true if this came from a SendEvent request */
11126 	Display *display;	/* Display the event was read from */
11127 	Window window;		/* window of event */
11128 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
11129 				   NotifyGrab, NotifyUngrab */
11130 	NotifyDetail detail;
11131 	/*
11132 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
11133 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
11134 	 * NotifyPointerRoot, NotifyDetailNone
11135 	 */
11136 }
11137 alias XFocusChangeEvent XFocusInEvent;
11138 alias XFocusChangeEvent XFocusOutEvent;
11139 Window XCreateSimpleWindow(
11140 	Display*	/* display */,
11141 	Window		/* parent */,
11142 	int			/* x */,
11143 	int			/* y */,
11144 	uint		/* width */,
11145 	uint		/* height */,
11146 	uint		/* border_width */,
11147 	uint		/* border */,
11148 	uint		/* background */
11149 );
11150 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);
11151 
11152 int XReparentWindow(Display*, Window, Window, int, int);
11153 int XClearWindow(Display*, Window);
11154 int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
11155 int XMoveWindow(Display*, Window, int, int);
11156 int XResizeWindow(Display *display, Window w, uint width, uint height);
11157 
11158 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
11159 
11160 enum CWBackPixmap              = (1L<<0);
11161 enum CWBackPixel               = (1L<<1);
11162 enum CWBorderPixmap            = (1L<<2);
11163 enum CWBorderPixel             = (1L<<3);
11164 enum CWBitGravity              = (1L<<4);
11165 enum CWWinGravity              = (1L<<5);
11166 enum CWBackingStore            = (1L<<6);
11167 enum CWBackingPlanes           = (1L<<7);
11168 enum CWBackingPixel            = (1L<<8);
11169 enum CWOverrideRedirect        = (1L<<9);
11170 enum CWSaveUnder               = (1L<<10);
11171 enum CWEventMask               = (1L<<11);
11172 enum CWDontPropagate           = (1L<<12);
11173 enum CWColormap                = (1L<<13);
11174 enum CWCursor                  = (1L<<14);
11175 
11176 struct XWindowAttributes {
11177 	int x, y;			/* location of window */
11178 	int width, height;		/* width and height of window */
11179 	int border_width;		/* border width of window */
11180 	int depth;			/* depth of window */
11181 	Visual *visual;			/* the associated visual structure */
11182 	Window root;			/* root of screen containing window */
11183 	int class_;			/* InputOutput, InputOnly*/
11184 	int bit_gravity;		/* one of the bit gravity values */
11185 	int win_gravity;		/* one of the window gravity values */
11186 	int backing_store;		/* NotUseful, WhenMapped, Always */
11187 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
11188 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
11189 	Bool save_under;		/* boolean, should bits under be saved? */
11190 	Colormap colormap;		/* color map to be associated with window */
11191 	Bool map_installed;		/* boolean, is color map currently installed*/
11192 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
11193 	arch_long all_event_masks;		/* set of events all people have interest in*/
11194 	arch_long your_event_mask;		/* my event mask */
11195 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
11196 	Bool override_redirect;		/* boolean value for override-redirect */
11197 	Screen *screen;			/* back pointer to correct screen */
11198 }
11199 
11200 enum IsUnmapped = 0;
11201 enum IsUnviewable = 1;
11202 enum IsViewable = 2;
11203 
11204 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
11205 
11206 struct XSetWindowAttributes {
11207 	Pixmap background_pixmap;/* background, None, or ParentRelative */
11208 	arch_ulong background_pixel;/* background pixel */
11209 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
11210 	arch_ulong border_pixel;/* border pixel value */
11211 	int bit_gravity;         /* one of bit gravity values */
11212 	int win_gravity;         /* one of the window gravity values */
11213 	int backing_store;       /* NotUseful, WhenMapped, Always */
11214 	arch_ulong backing_planes;/* planes to be preserved if possible */
11215 	arch_ulong backing_pixel;/* value to use in restoring planes */
11216 	Bool save_under;         /* should bits under be saved? (popups) */
11217 	arch_long event_mask;         /* set of events that should be saved */
11218 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
11219 	Bool override_redirect;  /* boolean value for override_redirect */
11220 	Colormap colormap;       /* color map to be associated with window */
11221 	Cursor cursor;           /* cursor to be displayed (or None) */
11222 }
11223 
11224 
11225 
11226 
11227 XImage *XCreateImage(
11228 	Display*		/* display */,
11229 	Visual*		/* visual */,
11230 	uint	/* depth */,
11231 	int			/* format */,
11232 	int			/* offset */,
11233 	ubyte*		/* data */,
11234 	uint	/* width */,
11235 	uint	/* height */,
11236 	int			/* bitmap_pad */,
11237 	int			/* bytes_per_line */
11238 );
11239 
11240 Status XInitImage (XImage* image);
11241 
11242 Atom XInternAtom(
11243 	Display*		/* display */,
11244 	const char*	/* atom_name */,
11245 	Bool		/* only_if_exists */
11246 );
11247 
11248 Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
11249 char* XGetAtomName(Display*, Atom);
11250 Status XGetAtomNames(Display*, Atom*, int count, char**);
11251 
11252 alias int Status;
11253 
11254 
11255 enum EventMask:int
11256 {
11257 	NoEventMask				=0,
11258 	KeyPressMask			=1<<0,
11259 	KeyReleaseMask			=1<<1,
11260 	ButtonPressMask			=1<<2,
11261 	ButtonReleaseMask		=1<<3,
11262 	EnterWindowMask			=1<<4,
11263 	LeaveWindowMask			=1<<5,
11264 	PointerMotionMask		=1<<6,
11265 	PointerMotionHintMask	=1<<7,
11266 	Button1MotionMask		=1<<8,
11267 	Button2MotionMask		=1<<9,
11268 	Button3MotionMask		=1<<10,
11269 	Button4MotionMask		=1<<11,
11270 	Button5MotionMask		=1<<12,
11271 	ButtonMotionMask		=1<<13,
11272 	KeymapStateMask		=1<<14,
11273 	ExposureMask			=1<<15,
11274 	VisibilityChangeMask	=1<<16,
11275 	StructureNotifyMask		=1<<17,
11276 	ResizeRedirectMask		=1<<18,
11277 	SubstructureNotifyMask	=1<<19,
11278 	SubstructureRedirectMask=1<<20,
11279 	FocusChangeMask			=1<<21,
11280 	PropertyChangeMask		=1<<22,
11281 	ColormapChangeMask		=1<<23,
11282 	OwnerGrabButtonMask		=1<<24
11283 }
11284 
11285 int XPutImage(
11286 	Display*	/* display */,
11287 	Drawable	/* d */,
11288 	GC			/* gc */,
11289 	XImage*	/* image */,
11290 	int			/* src_x */,
11291 	int			/* src_y */,
11292 	int			/* dest_x */,
11293 	int			/* dest_y */,
11294 	uint		/* width */,
11295 	uint		/* height */
11296 );
11297 
11298 int XDestroyWindow(
11299 	Display*	/* display */,
11300 	Window		/* w */
11301 );
11302 
11303 int XDestroyImage(XImage*);
11304 
11305 int XSelectInput(
11306 	Display*	/* display */,
11307 	Window		/* w */,
11308 	EventMask	/* event_mask */
11309 );
11310 
11311 int XMapWindow(
11312 	Display*	/* display */,
11313 	Window		/* w */
11314 );
11315 
11316 struct MwmHints {
11317 	int flags;
11318 	int functions;
11319 	int decorations;
11320 	int input_mode;
11321 	int status;
11322 }
11323 
11324 enum {
11325 	MWM_HINTS_FUNCTIONS = (1L << 0),
11326 	MWM_HINTS_DECORATIONS =  (1L << 1),
11327 
11328 	MWM_FUNC_ALL = (1L << 0),
11329 	MWM_FUNC_RESIZE = (1L << 1),
11330 	MWM_FUNC_MOVE = (1L << 2),
11331 	MWM_FUNC_MINIMIZE = (1L << 3),
11332 	MWM_FUNC_MAXIMIZE = (1L << 4),
11333 	MWM_FUNC_CLOSE = (1L << 5)
11334 }
11335 
11336 Status XIconifyWindow(Display*, Window, int);
11337 int XMapRaised(Display*, Window);
11338 int XMapSubwindows(Display*, Window);
11339 
11340 int XNextEvent(
11341 	Display*	/* display */,
11342 	XEvent*		/* event_return */
11343 );
11344 
11345 int XMaskEvent(Display*, arch_long, XEvent*);
11346 
11347 Bool XFilterEvent(XEvent *event, Window window);
11348 int XRefreshKeyboardMapping(XMappingEvent *event_map);
11349 
11350 Status XSetWMProtocols(
11351 	Display*	/* display */,
11352 	Window		/* w */,
11353 	Atom*		/* protocols */,
11354 	int			/* count */
11355 );
11356 
11357 import core.stdc.config : c_long, c_ulong;
11358 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
11359 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
11360 
11361 	/* Size hints mask bits */
11362 
11363 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
11364 	enum   USSize      = (1L << 1)          /* user specified width, height */;
11365 	enum   PPosition   = (1L << 2)          /* program specified position */;
11366 	enum   PSize       = (1L << 3)          /* program specified size */;
11367 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
11368 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
11369 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
11370 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
11371 	enum   PBaseSize   = (1L << 8);
11372 	enum   PWinGravity = (1L << 9);
11373 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
11374 	struct XSizeHints {
11375 		arch_long flags;         /* marks which fields in this structure are defined */
11376 		int x, y;           /* Obsolete */
11377 		int width, height;  /* Obsolete */
11378 		int min_width, min_height;
11379 		int max_width, max_height;
11380 		int width_inc, height_inc;
11381 		struct Aspect {
11382 			int x;       /* numerator */
11383 			int y;       /* denominator */
11384 		}
11385 
11386 		Aspect min_aspect;
11387 		Aspect max_aspect;
11388 		int base_width, base_height;
11389 		int win_gravity;
11390 		/* this structure may be extended in the future */
11391 	}
11392 
11393 
11394 
11395 enum EventType:int
11396 {
11397 	KeyPress			=2,
11398 	KeyRelease			=3,
11399 	ButtonPress			=4,
11400 	ButtonRelease		=5,
11401 	MotionNotify		=6,
11402 	EnterNotify			=7,
11403 	LeaveNotify			=8,
11404 	FocusIn				=9,
11405 	FocusOut			=10,
11406 	KeymapNotify		=11,
11407 	Expose				=12,
11408 	GraphicsExpose		=13,
11409 	NoExpose			=14,
11410 	VisibilityNotify	=15,
11411 	CreateNotify		=16,
11412 	DestroyNotify		=17,
11413 	UnmapNotify		=18,
11414 	MapNotify			=19,
11415 	MapRequest			=20,
11416 	ReparentNotify		=21,
11417 	ConfigureNotify		=22,
11418 	ConfigureRequest	=23,
11419 	GravityNotify		=24,
11420 	ResizeRequest		=25,
11421 	CirculateNotify		=26,
11422 	CirculateRequest	=27,
11423 	PropertyNotify		=28,
11424 	SelectionClear		=29,
11425 	SelectionRequest	=30,
11426 	SelectionNotify		=31,
11427 	ColormapNotify		=32,
11428 	ClientMessage		=33,
11429 	MappingNotify		=34,
11430 	LASTEvent			=35	/* must be bigger than any event # */
11431 }
11432 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
11433 struct XKeymapEvent
11434 {
11435 	int type;
11436 	arch_ulong serial;	/* # of last request processed by server */
11437 	Bool send_event;	/* true if this came from a SendEvent request */
11438 	Display *display;	/* Display the event was read from */
11439 	Window window;
11440 	byte[32] key_vector;
11441 }
11442 
11443 struct XExposeEvent
11444 {
11445 	int type;
11446 	arch_ulong serial;	/* # of last request processed by server */
11447 	Bool send_event;	/* true if this came from a SendEvent request */
11448 	Display *display;	/* Display the event was read from */
11449 	Window window;
11450 	int x, y;
11451 	int width, height;
11452 	int count;		/* if non-zero, at least this many more */
11453 }
11454 
11455 struct XGraphicsExposeEvent{
11456 	int type;
11457 	arch_ulong serial;	/* # of last request processed by server */
11458 	Bool send_event;	/* true if this came from a SendEvent request */
11459 	Display *display;	/* Display the event was read from */
11460 	Drawable drawable;
11461 	int x, y;
11462 	int width, height;
11463 	int count;		/* if non-zero, at least this many more */
11464 	int major_code;		/* core is CopyArea or CopyPlane */
11465 	int minor_code;		/* not defined in the core */
11466 }
11467 
11468 struct XNoExposeEvent{
11469 	int type;
11470 	arch_ulong serial;	/* # of last request processed by server */
11471 	Bool send_event;	/* true if this came from a SendEvent request */
11472 	Display *display;	/* Display the event was read from */
11473 	Drawable drawable;
11474 	int major_code;		/* core is CopyArea or CopyPlane */
11475 	int minor_code;		/* not defined in the core */
11476 }
11477 
11478 struct XVisibilityEvent{
11479 	int type;
11480 	arch_ulong serial;	/* # of last request processed by server */
11481 	Bool send_event;	/* true if this came from a SendEvent request */
11482 	Display *display;	/* Display the event was read from */
11483 	Window window;
11484 	VisibilityNotify state;		/* Visibility state */
11485 }
11486 
11487 struct XCreateWindowEvent{
11488 	int type;
11489 	arch_ulong serial;	/* # of last request processed by server */
11490 	Bool send_event;	/* true if this came from a SendEvent request */
11491 	Display *display;	/* Display the event was read from */
11492 	Window parent;		/* parent of the window */
11493 	Window window;		/* window id of window created */
11494 	int x, y;		/* window location */
11495 	int width, height;	/* size of window */
11496 	int border_width;	/* border width */
11497 	Bool override_redirect;	/* creation should be overridden */
11498 }
11499 
11500 struct XDestroyWindowEvent
11501 {
11502 	int type;
11503 	arch_ulong serial;		/* # of last request processed by server */
11504 	Bool send_event;	/* true if this came from a SendEvent request */
11505 	Display *display;	/* Display the event was read from */
11506 	Window event;
11507 	Window window;
11508 }
11509 
11510 struct XUnmapEvent
11511 {
11512 	int type;
11513 	arch_ulong serial;		/* # of last request processed by server */
11514 	Bool send_event;	/* true if this came from a SendEvent request */
11515 	Display *display;	/* Display the event was read from */
11516 	Window event;
11517 	Window window;
11518 	Bool from_configure;
11519 }
11520 
11521 struct XMapEvent
11522 {
11523 	int type;
11524 	arch_ulong serial;		/* # of last request processed by server */
11525 	Bool send_event;	/* true if this came from a SendEvent request */
11526 	Display *display;	/* Display the event was read from */
11527 	Window event;
11528 	Window window;
11529 	Bool override_redirect;	/* Boolean, is override set... */
11530 }
11531 
11532 struct XMapRequestEvent
11533 {
11534 	int type;
11535 	arch_ulong serial;	/* # of last request processed by server */
11536 	Bool send_event;	/* true if this came from a SendEvent request */
11537 	Display *display;	/* Display the event was read from */
11538 	Window parent;
11539 	Window window;
11540 }
11541 
11542 struct XReparentEvent
11543 {
11544 	int type;
11545 	arch_ulong serial;	/* # of last request processed by server */
11546 	Bool send_event;	/* true if this came from a SendEvent request */
11547 	Display *display;	/* Display the event was read from */
11548 	Window event;
11549 	Window window;
11550 	Window parent;
11551 	int x, y;
11552 	Bool override_redirect;
11553 }
11554 
11555 struct XConfigureEvent
11556 {
11557 	int type;
11558 	arch_ulong serial;	/* # of last request processed by server */
11559 	Bool send_event;	/* true if this came from a SendEvent request */
11560 	Display *display;	/* Display the event was read from */
11561 	Window event;
11562 	Window window;
11563 	int x, y;
11564 	int width, height;
11565 	int border_width;
11566 	Window above;
11567 	Bool override_redirect;
11568 }
11569 
11570 struct XGravityEvent
11571 {
11572 	int type;
11573 	arch_ulong serial;	/* # of last request processed by server */
11574 	Bool send_event;	/* true if this came from a SendEvent request */
11575 	Display *display;	/* Display the event was read from */
11576 	Window event;
11577 	Window window;
11578 	int x, y;
11579 }
11580 
11581 struct XResizeRequestEvent
11582 {
11583 	int type;
11584 	arch_ulong serial;	/* # of last request processed by server */
11585 	Bool send_event;	/* true if this came from a SendEvent request */
11586 	Display *display;	/* Display the event was read from */
11587 	Window window;
11588 	int width, height;
11589 }
11590 
11591 struct  XConfigureRequestEvent
11592 {
11593 	int type;
11594 	arch_ulong serial;	/* # of last request processed by server */
11595 	Bool send_event;	/* true if this came from a SendEvent request */
11596 	Display *display;	/* Display the event was read from */
11597 	Window parent;
11598 	Window window;
11599 	int x, y;
11600 	int width, height;
11601 	int border_width;
11602 	Window above;
11603 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
11604 	arch_ulong value_mask;
11605 }
11606 
11607 struct XCirculateEvent
11608 {
11609 	int type;
11610 	arch_ulong serial;	/* # of last request processed by server */
11611 	Bool send_event;	/* true if this came from a SendEvent request */
11612 	Display *display;	/* Display the event was read from */
11613 	Window event;
11614 	Window window;
11615 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
11616 }
11617 
11618 struct XCirculateRequestEvent
11619 {
11620 	int type;
11621 	arch_ulong serial;	/* # of last request processed by server */
11622 	Bool send_event;	/* true if this came from a SendEvent request */
11623 	Display *display;	/* Display the event was read from */
11624 	Window parent;
11625 	Window window;
11626 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
11627 }
11628 
11629 struct XPropertyEvent
11630 {
11631 	int type;
11632 	arch_ulong serial;	/* # of last request processed by server */
11633 	Bool send_event;	/* true if this came from a SendEvent request */
11634 	Display *display;	/* Display the event was read from */
11635 	Window window;
11636 	Atom atom;
11637 	Time time;
11638 	PropertyNotification state;		/* NewValue, Deleted */
11639 }
11640 
11641 struct XSelectionClearEvent
11642 {
11643 	int type;
11644 	arch_ulong serial;	/* # of last request processed by server */
11645 	Bool send_event;	/* true if this came from a SendEvent request */
11646 	Display *display;	/* Display the event was read from */
11647 	Window window;
11648 	Atom selection;
11649 	Time time;
11650 }
11651 
11652 struct XSelectionRequestEvent
11653 {
11654 	int type;
11655 	arch_ulong serial;	/* # of last request processed by server */
11656 	Bool send_event;	/* true if this came from a SendEvent request */
11657 	Display *display;	/* Display the event was read from */
11658 	Window owner;
11659 	Window requestor;
11660 	Atom selection;
11661 	Atom target;
11662 	Atom property;
11663 	Time time;
11664 }
11665 
11666 struct XSelectionEvent
11667 {
11668 	int type;
11669 	arch_ulong serial;	/* # of last request processed by server */
11670 	Bool send_event;	/* true if this came from a SendEvent request */
11671 	Display *display;	/* Display the event was read from */
11672 	Window requestor;
11673 	Atom selection;
11674 	Atom target;
11675 	Atom property;		/* ATOM or None */
11676 	Time time;
11677 }
11678 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
11679 
11680 struct XColormapEvent
11681 {
11682 	int type;
11683 	arch_ulong serial;	/* # of last request processed by server */
11684 	Bool send_event;	/* true if this came from a SendEvent request */
11685 	Display *display;	/* Display the event was read from */
11686 	Window window;
11687 	Colormap colormap;	/* COLORMAP or None */
11688 	Bool new_;		/* C++ */
11689 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
11690 }
11691 version(X86_64) static assert(XColormapEvent.sizeof == 56);
11692 
11693 struct XClientMessageEvent
11694 {
11695 	int type;
11696 	arch_ulong serial;	/* # of last request processed by server */
11697 	Bool send_event;	/* true if this came from a SendEvent request */
11698 	Display *display;	/* Display the event was read from */
11699 	Window window;
11700 	Atom message_type;
11701 	int format;
11702 	union Data{
11703 		byte[20] b;
11704 		short[10] s;
11705 		arch_ulong[5] l;
11706 	}
11707 	Data data;
11708 
11709 }
11710 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
11711 
11712 struct XMappingEvent
11713 {
11714 	int type;
11715 	arch_ulong serial;	/* # of last request processed by server */
11716 	Bool send_event;	/* true if this came from a SendEvent request */
11717 	Display *display;	/* Display the event was read from */
11718 	Window window;		/* unused */
11719 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
11720 				   MappingPointer */
11721 	int first_keycode;	/* first keycode */
11722 	int count;		/* defines range of change w. first_keycode*/
11723 }
11724 
11725 struct XErrorEvent
11726 {
11727 	int type;
11728 	Display *display;	/* Display the event was read from */
11729 	XID resourceid;		/* resource id */
11730 	arch_ulong serial;	/* serial number of failed request */
11731 	ubyte error_code;	/* error code of failed request */
11732 	ubyte request_code;	/* Major op-code of failed request */
11733 	ubyte minor_code;	/* Minor op-code of failed request */
11734 }
11735 
11736 struct XAnyEvent
11737 {
11738 	int type;
11739 	arch_ulong serial;	/* # of last request processed by server */
11740 	Bool send_event;	/* true if this came from a SendEvent request */
11741 	Display *display;/* Display the event was read from */
11742 	Window window;	/* window on which event was requested in event mask */
11743 }
11744 
11745 union XEvent{
11746 	int type;		/* must not be changed; first element */
11747 	XAnyEvent xany;
11748 	XKeyEvent xkey;
11749 	XButtonEvent xbutton;
11750 	XMotionEvent xmotion;
11751 	XCrossingEvent xcrossing;
11752 	XFocusChangeEvent xfocus;
11753 	XExposeEvent xexpose;
11754 	XGraphicsExposeEvent xgraphicsexpose;
11755 	XNoExposeEvent xnoexpose;
11756 	XVisibilityEvent xvisibility;
11757 	XCreateWindowEvent xcreatewindow;
11758 	XDestroyWindowEvent xdestroywindow;
11759 	XUnmapEvent xunmap;
11760 	XMapEvent xmap;
11761 	XMapRequestEvent xmaprequest;
11762 	XReparentEvent xreparent;
11763 	XConfigureEvent xconfigure;
11764 	XGravityEvent xgravity;
11765 	XResizeRequestEvent xresizerequest;
11766 	XConfigureRequestEvent xconfigurerequest;
11767 	XCirculateEvent xcirculate;
11768 	XCirculateRequestEvent xcirculaterequest;
11769 	XPropertyEvent xproperty;
11770 	XSelectionClearEvent xselectionclear;
11771 	XSelectionRequestEvent xselectionrequest;
11772 	XSelectionEvent xselection;
11773 	XColormapEvent xcolormap;
11774 	XClientMessageEvent xclient;
11775 	XMappingEvent xmapping;
11776 	XErrorEvent xerror;
11777 	XKeymapEvent xkeymap;
11778 	arch_ulong[24] pad;
11779 }
11780 
11781 
11782 	struct Display {
11783 		XExtData *ext_data;	/* hook for extension to hang data */
11784 		_XPrivate *private1;
11785 		int fd;			/* Network socket. */
11786 		int private2;
11787 		int proto_major_version;/* major version of server's X protocol */
11788 		int proto_minor_version;/* minor version of servers X protocol */
11789 		char *vendor;		/* vendor of the server hardware */
11790 	    	XID private3;
11791 		XID private4;
11792 		XID private5;
11793 		int private6;
11794 		XID function(Display*)resource_alloc;/* allocator function */
11795 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
11796 		int bitmap_unit;	/* padding and data requirements */
11797 		int bitmap_pad;		/* padding requirements on bitmaps */
11798 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
11799 		int nformats;		/* number of pixmap formats in list */
11800 		ScreenFormat *pixmap_format;	/* pixmap format list */
11801 		int private8;
11802 		int release;		/* release of the server */
11803 		_XPrivate *private9;
11804 		_XPrivate *private10;
11805 		int qlen;		/* Length of input event queue */
11806 		arch_ulong last_request_read; /* seq number of last event read */
11807 		arch_ulong request;	/* sequence number of last request. */
11808 		XPointer private11;
11809 		XPointer private12;
11810 		XPointer private13;
11811 		XPointer private14;
11812 		uint max_request_size; /* maximum number 32 bit words in request*/
11813 		_XrmHashBucketRec *db;
11814 		int function  (Display*)private15;
11815 		char *display_name;	/* "host:display" string used on this connect*/
11816 		int default_screen;	/* default screen for operations */
11817 		int nscreens;		/* number of screens on this server*/
11818 		Screen *screens;	/* pointer to list of screens */
11819 		arch_ulong motion_buffer;	/* size of motion buffer */
11820 		arch_ulong private16;
11821 		int min_keycode;	/* minimum defined keycode */
11822 		int max_keycode;	/* maximum defined keycode */
11823 		XPointer private17;
11824 		XPointer private18;
11825 		int private19;
11826 		byte *xdefaults;	/* contents of defaults from server */
11827 		/* there is more to this structure, but it is private to Xlib */
11828 	}
11829 
11830 	// I got these numbers from a C program as a sanity test
11831 	version(X86_64) {
11832 		static assert(Display.sizeof == 296);
11833 		static assert(XPointer.sizeof == 8);
11834 		static assert(XErrorEvent.sizeof == 40);
11835 		static assert(XAnyEvent.sizeof == 40);
11836 		static assert(XMappingEvent.sizeof == 56);
11837 		static assert(XEvent.sizeof == 192);
11838 	} else {
11839 		static assert(Display.sizeof == 176);
11840 		static assert(XPointer.sizeof == 4);
11841 		static assert(XEvent.sizeof == 96);
11842 	}
11843 
11844 struct Depth
11845 {
11846 	int depth;		/* this depth (Z) of the depth */
11847 	int nvisuals;		/* number of Visual types at this depth */
11848 	Visual *visuals;	/* list of visuals possible at this depth */
11849 }
11850 
11851 alias void* GC;
11852 alias c_ulong VisualID;
11853 alias XID Colormap;
11854 alias XID Cursor;
11855 alias XID KeySym;
11856 alias uint KeyCode;
11857 enum None = 0;
11858 }
11859 
11860 version(without_opengl) {}
11861 else {
11862 extern(C) nothrow @nogc {
11863 
11864 
11865 static if(!SdpyIsUsingIVGLBinds) {
11866 enum GLX_USE_GL=            1;       /* support GLX rendering */
11867 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
11868 enum GLX_LEVEL=             3;       /* level in plane stacking */
11869 enum GLX_RGBA=              4;       /* true if RGBA mode */
11870 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
11871 enum GLX_STEREO=            6;       /* stereo buffering supported */
11872 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
11873 enum GLX_RED_SIZE=          8;       /* number of red component bits */
11874 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
11875 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
11876 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
11877 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
11878 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
11879 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
11880 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
11881 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
11882 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
11883 
11884 
11885 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
11886 
11887 
11888 
11889 enum GL_TRUE = 1;
11890 enum GL_FALSE = 0;
11891 alias int GLint;
11892 }
11893 
11894 alias XID GLXContextID;
11895 alias XID GLXPixmap;
11896 alias XID GLXDrawable;
11897 alias XID GLXPbuffer;
11898 alias XID GLXWindow;
11899 alias XID GLXFBConfigID;
11900 alias void* GLXContext;
11901 
11902 static if (!SdpyIsUsingIVGLBinds) {
11903 	 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
11904 			const int *attrib_list);
11905 
11906 	 void glXCopyContext(Display *dpy, GLXContext src,
11907 			GLXContext dst, arch_ulong mask);
11908 
11909 	 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
11910 			GLXContext share_list, Bool direct);
11911 
11912 	 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
11913 			Pixmap pixmap);
11914 
11915 	 void glXDestroyContext(Display *dpy, GLXContext ctx);
11916 
11917 	 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
11918 
11919 	 int glXGetConfig(Display *dpy, XVisualInfo *vis,
11920 			int attrib, int *value);
11921 
11922 	 GLXContext glXGetCurrentContext();
11923 
11924 	 GLXDrawable glXGetCurrentDrawable();
11925 
11926 	 Bool glXIsDirect(Display *dpy, GLXContext ctx);
11927 
11928 	 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
11929 			GLXContext ctx);
11930 
11931 	 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
11932 
11933 	 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
11934 
11935 	 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
11936 
11937 	 void glXUseXFont(Font font, int first, int count, int list_base);
11938 
11939 	 void glXWaitGL();
11940 
11941 	 void glXWaitX();
11942 }
11943 
11944 }
11945 }
11946 
11947 enum AllocNone = 0;
11948 
11949 extern(C) {
11950 	/* WARNING, this type not in Xlib spec */
11951 	extern(C) alias XIOErrorHandler = int function (Display* display);
11952 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
11953 }
11954 
11955 extern(C) nothrow @nogc {
11956 struct Screen{
11957 	XExtData *ext_data;		/* hook for extension to hang data */
11958 	Display *display;		/* back pointer to display structure */
11959 	Window root;			/* Root window id. */
11960 	int width, height;		/* width and height of screen */
11961 	int mwidth, mheight;	/* width and height of  in millimeters */
11962 	int ndepths;			/* number of depths possible */
11963 	Depth *depths;			/* list of allowable depths on the screen */
11964 	int root_depth;			/* bits per pixel */
11965 	Visual *root_visual;	/* root visual */
11966 	GC default_gc;			/* GC for the root root visual */
11967 	Colormap cmap;			/* default color map */
11968 	uint white_pixel;
11969 	uint black_pixel;		/* White and Black pixel values */
11970 	int max_maps, min_maps;	/* max and min color maps */
11971 	int backing_store;		/* Never, WhenMapped, Always */
11972 	bool save_unders;
11973 	int root_input_mask;	/* initial root input mask */
11974 }
11975 
11976 struct Visual
11977 {
11978 	XExtData *ext_data;	/* hook for extension to hang data */
11979 	VisualID visualid;	/* visual id of this visual */
11980 	int class_;			/* class of screen (monochrome, etc.) */
11981 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
11982 	int bits_per_rgb;	/* log base 2 of distinct color values */
11983 	int map_entries;	/* color map entries */
11984 }
11985 
11986 	alias Display* _XPrivDisplay;
11987 
11988 	Screen* ScreenOfDisplay(Display* dpy, int scr) {
11989 		assert(dpy !is null);
11990 		return &dpy.screens[scr];
11991 	}
11992 
11993 	Window	RootWindow(Display *dpy,int scr) {
11994 		return ScreenOfDisplay(dpy,scr).root;
11995 	}
11996 
11997 	struct XWMHints {
11998 		arch_long flags;
11999 		Bool input;
12000 		int initial_state;
12001 		Pixmap icon_pixmap;
12002 		Window icon_window;
12003 		int icon_x, icon_y;
12004 		Pixmap icon_mask;
12005 		XID window_group;
12006 	}
12007 
12008 	struct XClassHint {
12009 		char* res_name;
12010 		char* res_class;
12011 	}
12012 
12013 	Status XInitThreads();
12014 	void XLockDisplay (Display* display);
12015 	void XUnlockDisplay (Display* display);
12016 
12017 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
12018 
12019 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
12020 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
12021 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
12022 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
12023 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
12024 
12025 
12026 	// this requires -lXpm
12027 	int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
12028 
12029 	int DefaultScreen(Display *dpy) {
12030 		return dpy.default_screen;
12031 	}
12032 
12033 	int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
12034 	int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
12035 	int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
12036 	int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
12037 	int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
12038 	auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
12039 
12040 	int ConnectionNumber(Display* dpy) { return dpy.fd; }
12041 
12042 	enum int AnyPropertyType = 0;
12043 	enum int Success = 0;
12044 
12045 	enum int RevertToNone = None;
12046 	enum int PointerRoot = 1;
12047 	enum Time CurrentTime = 0;
12048 	enum int RevertToPointerRoot = PointerRoot;
12049 	enum int RevertToParent = 2;
12050 
12051 	int DefaultDepthOfDisplay(Display* dpy) {
12052 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
12053 	}
12054 
12055 	Visual* DefaultVisual(Display *dpy,int scr) {
12056 		return ScreenOfDisplay(dpy,scr).root_visual;
12057 	}
12058 
12059 	GC DefaultGC(Display *dpy,int scr) {
12060 		return ScreenOfDisplay(dpy,scr).default_gc;
12061 	}
12062 
12063 	uint BlackPixel(Display *dpy,int scr) {
12064 		return ScreenOfDisplay(dpy,scr).black_pixel;
12065 	}
12066 
12067 	uint WhitePixel(Display *dpy,int scr) {
12068 		return ScreenOfDisplay(dpy,scr).white_pixel;
12069 	}
12070 
12071 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
12072 	int XDrawString(Display*, Drawable, GC, int, int, in char*, int);
12073 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
12074 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
12075 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
12076 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
12077 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
12078 	int XDrawPoint(Display*, Drawable, GC, int, int);
12079 	int XSetForeground(Display*, GC, uint);
12080 	int XSetBackground(Display*, GC, uint);
12081 
12082 	alias void* XFontSet; // i think
12083 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
12084 	void XFreeFontSet(Display*, XFontSet);
12085 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int);
12086 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
12087 	struct XmbTextItem {
12088 		char* chars;
12089 		int nchars;
12090 		int delta;
12091 		XFontSet font_set;
12092 	}
12093 
12094 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
12095 	struct XTextItem {
12096 		char* chars;
12097 		int nchars;
12098 		int delta;
12099 		Font font;
12100 	}
12101 
12102 	int XSetFunction(Display*, GC, int);
12103 	enum {
12104 		GXclear        = 0x0, /* 0 */
12105 		GXand          = 0x1, /* src AND dst */
12106 		GXandReverse   = 0x2, /* src AND NOT dst */
12107 		GXcopy         = 0x3, /* src */
12108 		GXandInverted  = 0x4, /* NOT src AND dst */
12109 		GXnoop         = 0x5, /* dst */
12110 		GXxor          = 0x6, /* src XOR dst */
12111 		GXor           = 0x7, /* src OR dst */
12112 		GXnor          = 0x8, /* NOT src AND NOT dst */
12113 		GXequiv        = 0x9, /* NOT src XOR dst */
12114 		GXinvert       = 0xa, /* NOT dst */
12115 		GXorReverse    = 0xb, /* src OR NOT dst */
12116 		GXcopyInverted = 0xc, /* NOT src */
12117 		GXorInverted   = 0xd, /* NOT src OR dst */
12118 		GXnand         = 0xe, /* NOT src OR NOT dst */
12119 		GXset          = 0xf, /* 1 */
12120 	}
12121 
12122 	GC XCreateGC(Display*, Drawable, uint, void*);
12123 	int XCopyGC(Display*, GC, uint, GC);
12124 	int XFreeGC(Display*, GC);
12125 
12126 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
12127 	bool XCheckMaskEvent(Display*, int, XEvent*);
12128 
12129 	int XPending(Display*);
12130 	int XEventsQueued(Display* display, int mode);
12131 	enum QueueMode : int {
12132 		QueuedAlready,
12133 		QueuedAfterReading,
12134 		QueuedAfterFlush
12135 	}
12136 
12137 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
12138 	int XFreePixmap(Display*, Pixmap);
12139 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
12140 	int XFlush(Display*);
12141 	int XBell(Display*, int);
12142 	int XSync(Display*, bool);
12143 
12144 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
12145 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
12146 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
12147 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
12148 
12149 	struct XPoint {
12150 		short x;
12151 		short y;
12152 	}
12153 
12154 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
12155 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
12156 
12157 	enum CoordMode:int {
12158 		CoordModeOrigin = 0,
12159 		CoordModePrevious = 1
12160 	}
12161 
12162 	enum PolygonShape:int {
12163 		Complex = 0,
12164 		Nonconvex = 1,
12165 		Convex = 2
12166 	}
12167 
12168 	struct XTextProperty {
12169 		const(char)* value;		/* same as Property routines */
12170 		Atom encoding;			/* prop type */
12171 		int format;				/* prop data format: 8, 16, or 32 */
12172 		arch_ulong nitems;		/* number of data items in value */
12173 	}
12174 
12175 	version( X86_64 ) {
12176 		static assert(XTextProperty.sizeof == 32);
12177 	}
12178 
12179 
12180 	struct XGCValues {
12181 		int function_;           /* logical operation */
12182 		arch_ulong plane_mask;/* plane mask */
12183 		arch_ulong foreground;/* foreground pixel */
12184 		arch_ulong background;/* background pixel */
12185 		int line_width;         /* line width */
12186 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
12187 		int cap_style;          /* CapNotLast, CapButt,
12188 					   CapRound, CapProjecting */
12189 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
12190 		int fill_style;         /* FillSolid, FillTiled,
12191 					   FillStippled, FillOpaeueStippled */
12192 		int fill_rule;          /* EvenOddRule, WindingRule */
12193 		int arc_mode;           /* ArcChord, ArcPieSlice */
12194 		Pixmap tile;            /* tile pixmap for tiling operations */
12195 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
12196 		int ts_x_origin;        /* offset for tile or stipple operations */
12197 		int ts_y_origin;
12198 		Font font;              /* default text font for text operations */
12199 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
12200 		Bool graphics_exposures;/* boolean, should exposures be generated */
12201 		int clip_x_origin;      /* origin for clipping */
12202 		int clip_y_origin;
12203 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
12204 		int dash_offset;        /* patterned/dashed line information */
12205 		char dashes;
12206 	}
12207 
12208 	struct XColor {
12209 		arch_ulong pixel;
12210 		ushort red, green, blue;
12211 		byte flags;
12212 		byte pad;
12213 	}
12214 	Status XAllocColor(Display*, Colormap, XColor*);
12215 
12216 	int XWithdrawWindow(Display*, Window, int);
12217 	int XUnmapWindow(Display*, Window);
12218 	int XLowerWindow(Display*, Window);
12219 	int XRaiseWindow(Display*, Window);
12220 
12221 	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);
12222 	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);
12223 
12224 	int XGetInputFocus(Display*, Window*, int*);
12225 	int XSetInputFocus(Display*, Window, int, Time);
12226 	alias XErrorHandler = int function(Display*, XErrorEvent*);
12227 	XErrorHandler XSetErrorHandler(XErrorHandler);
12228 
12229 	int XGetErrorText(Display*, int, char*, int);
12230 
12231 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
12232 
12233 
12234 	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);
12235 	int XUngrabPointer(Display *display, Time time);
12236 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
12237 
12238 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
12239 
12240 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
12241 	int XSetClipMask(Display*, GC, Pixmap);
12242 	int XSetClipOrigin(Display*, GC, int, int);
12243 
12244 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
12245 
12246 	struct XRectangle {
12247 		short x;
12248 		short y;
12249 		ushort width;
12250 		ushort height;
12251 	}
12252 
12253 	void XSetWMName(Display*, Window, XTextProperty*);
12254 	Status XGetWMName(Display*, Window, XTextProperty*);
12255 	int XStoreName(Display* display, Window w, const(char)* window_name);
12256 
12257 	enum ClipByChildren = 0;
12258 	enum IncludeInferiors = 1;
12259 
12260 	enum Atom XA_PRIMARY = 1;
12261 	enum Atom XA_SECONDARY = 2;
12262 	enum Atom XA_STRING = 31;
12263 	enum Atom XA_CARDINAL = 6;
12264 	enum Atom XA_WM_NAME = 39;
12265 	enum Atom XA_ATOM = 4;
12266 	enum Atom XA_WINDOW = 33;
12267 	enum Atom XA_WM_HINTS = 35;
12268 	enum int PropModeAppend = 2;
12269 	enum int PropModeReplace = 0;
12270 	enum int PropModePrepend = 1;
12271 
12272 	enum int CopyFromParent = 0;
12273 	enum int InputOutput = 1;
12274 
12275 	// XWMHints
12276 	enum InputHint = 1 << 0;
12277 	enum StateHint = 1 << 1;
12278 	enum IconPixmapHint = (1L << 2);
12279 	enum IconWindowHint = (1L << 3);
12280 	enum IconPositionHint = (1L << 4);
12281 	enum IconMaskHint = (1L << 5);
12282 	enum WindowGroupHint = (1L << 6);
12283 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
12284 	enum XUrgencyHint = (1L << 8);
12285 
12286 	// GC Components
12287 	enum GCFunction           =   (1L<<0);
12288 	enum GCPlaneMask         =    (1L<<1);
12289 	enum GCForeground       =     (1L<<2);
12290 	enum GCBackground      =      (1L<<3);
12291 	enum GCLineWidth      =       (1L<<4);
12292 	enum GCLineStyle     =        (1L<<5);
12293 	enum GCCapStyle     =         (1L<<6);
12294 	enum GCJoinStyle   =          (1L<<7);
12295 	enum GCFillStyle  =           (1L<<8);
12296 	enum GCFillRule  =            (1L<<9);
12297 	enum GCTile     =             (1L<<10);
12298 	enum GCStipple           =    (1L<<11);
12299 	enum GCTileStipXOrigin  =     (1L<<12);
12300 	enum GCTileStipYOrigin =      (1L<<13);
12301 	enum GCFont               =   (1L<<14);
12302 	enum GCSubwindowMode     =    (1L<<15);
12303 	enum GCGraphicsExposures=     (1L<<16);
12304 	enum GCClipXOrigin     =      (1L<<17);
12305 	enum GCClipYOrigin    =       (1L<<18);
12306 	enum GCClipMask      =        (1L<<19);
12307 	enum GCDashOffset   =         (1L<<20);
12308 	enum GCDashList    =          (1L<<21);
12309 	enum GCArcMode    =           (1L<<22);
12310 	enum GCLastBit   =            22;
12311 
12312 
12313 	enum int WithdrawnState = 0;
12314 	enum int NormalState = 1;
12315 	enum int IconicState = 3;
12316 
12317 }
12318 } else version (OSXCocoa) {
12319 private:
12320 	alias void* id;
12321 	alias void* Class;
12322 	alias void* SEL;
12323 	alias void* IMP;
12324 	alias void* Ivar;
12325 	alias byte BOOL;
12326 	alias const(void)* CFStringRef;
12327 	alias const(void)* CFAllocatorRef;
12328 	alias const(void)* CFTypeRef;
12329 	alias const(void)* CGContextRef;
12330 	alias const(void)* CGColorSpaceRef;
12331 	alias const(void)* CGImageRef;
12332 	alias ulong CGBitmapInfo;
12333 
12334 	struct objc_super {
12335 		id self;
12336 		Class superclass;
12337 	}
12338 
12339 	struct CFRange {
12340 		long location, length;
12341 	}
12342 
12343 	struct NSPoint {
12344 		double x, y;
12345 
12346 		static fromTuple(T)(T tupl) {
12347 			return NSPoint(tupl.tupleof);
12348 		}
12349 	}
12350 	struct NSSize {
12351 		double width, height;
12352 	}
12353 	struct NSRect {
12354 		NSPoint origin;
12355 		NSSize size;
12356 	}
12357 	alias NSPoint CGPoint;
12358 	alias NSSize CGSize;
12359 	alias NSRect CGRect;
12360 
12361 	struct CGAffineTransform {
12362 		double a, b, c, d, tx, ty;
12363 	}
12364 
12365 	enum NSApplicationActivationPolicyRegular = 0;
12366 	enum NSBackingStoreBuffered = 2;
12367 	enum kCFStringEncodingUTF8 = 0x08000100;
12368 
12369 	enum : size_t {
12370 		NSBorderlessWindowMask = 0,
12371 		NSTitledWindowMask = 1 << 0,
12372 		NSClosableWindowMask = 1 << 1,
12373 		NSMiniaturizableWindowMask = 1 << 2,
12374 		NSResizableWindowMask = 1 << 3,
12375 		NSTexturedBackgroundWindowMask = 1 << 8
12376 	}
12377 
12378 	enum : ulong {
12379 		kCGImageAlphaNone,
12380 		kCGImageAlphaPremultipliedLast,
12381 		kCGImageAlphaPremultipliedFirst,
12382 		kCGImageAlphaLast,
12383 		kCGImageAlphaFirst,
12384 		kCGImageAlphaNoneSkipLast,
12385 		kCGImageAlphaNoneSkipFirst
12386 	}
12387 	enum : ulong {
12388 		kCGBitmapAlphaInfoMask = 0x1F,
12389 		kCGBitmapFloatComponents = (1 << 8),
12390 		kCGBitmapByteOrderMask = 0x7000,
12391 		kCGBitmapByteOrderDefault = (0 << 12),
12392 		kCGBitmapByteOrder16Little = (1 << 12),
12393 		kCGBitmapByteOrder32Little = (2 << 12),
12394 		kCGBitmapByteOrder16Big = (3 << 12),
12395 		kCGBitmapByteOrder32Big = (4 << 12)
12396 	}
12397 	enum CGPathDrawingMode {
12398 		kCGPathFill,
12399 		kCGPathEOFill,
12400 		kCGPathStroke,
12401 		kCGPathFillStroke,
12402 		kCGPathEOFillStroke
12403 	}
12404 	enum objc_AssociationPolicy : size_t {
12405 		OBJC_ASSOCIATION_ASSIGN = 0,
12406 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
12407 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
12408 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
12409 		OBJC_ASSOCIATION_COPY = 0x303 //01403
12410 	}
12411 
12412 	extern(C) {
12413 		id objc_msgSend(id receiver, SEL selector, ...);
12414 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
12415 		id objc_getClass(const(char)* name);
12416 		SEL sel_registerName(const(char)* str);
12417 		Class objc_allocateClassPair(Class superclass, const(char)* name,
12418 									 size_t extra_bytes);
12419 		void objc_registerClassPair(Class cls);
12420 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
12421 		id objc_getAssociatedObject(id object, void* key);
12422 		void objc_setAssociatedObject(id object, void* key, id value,
12423 									  objc_AssociationPolicy policy);
12424 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
12425 		id object_getIvar(id object, Ivar ivar);
12426 		void object_setIvar(id object, Ivar ivar, id value);
12427 		BOOL class_addIvar(Class cls, const(char)* name,
12428 						   size_t size, ubyte alignment, const(char)* types);
12429 
12430 		extern __gshared id NSApp;
12431 
12432 		void CFRelease(CFTypeRef obj);
12433 
12434 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
12435 											const(char)* bytes, long numBytes,
12436 											long encoding,
12437 											BOOL isExternalRepresentation);
12438 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
12439 							 char lossByte, bool isExternalRepresentation,
12440 							 char* buffer, long maxBufLen, long* usedBufLen);
12441 		long CFStringGetLength(CFStringRef theString);
12442 
12443 		CGContextRef CGBitmapContextCreate(void* data,
12444 										   size_t width, size_t height,
12445 										   size_t bitsPerComponent,
12446 										   size_t bytesPerRow,
12447 										   CGColorSpaceRef colorspace,
12448 										   CGBitmapInfo bitmapInfo);
12449 		void CGContextRelease(CGContextRef c);
12450 		ubyte* CGBitmapContextGetData(CGContextRef c);
12451 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
12452 		size_t CGBitmapContextGetWidth(CGContextRef c);
12453 		size_t CGBitmapContextGetHeight(CGContextRef c);
12454 
12455 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
12456 		void CGColorSpaceRelease(CGColorSpaceRef cs);
12457 
12458 		void CGContextSetRGBStrokeColor(CGContextRef c,
12459 										double red, double green, double blue,
12460 										double alpha);
12461 		void CGContextSetRGBFillColor(CGContextRef c,
12462 									  double red, double green, double blue,
12463 									  double alpha);
12464 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
12465 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
12466 									  const(char)* str, size_t length);
12467 		void CGContextStrokeLineSegments(CGContextRef c,
12468 										 const(CGPoint)* points, size_t count);
12469 
12470 		void CGContextBeginPath(CGContextRef c);
12471 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
12472 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
12473 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
12474 							 double startAngle, double endAngle, long clockwise);
12475 		void CGContextAddRect(CGContextRef c, CGRect rect);
12476 		void CGContextAddLines(CGContextRef c,
12477 							   const(CGPoint)* points, size_t count);
12478 		void CGContextSaveGState(CGContextRef c);
12479 		void CGContextRestoreGState(CGContextRef c);
12480 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
12481 								 ulong textEncoding);
12482 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
12483 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
12484 
12485 		void CGImageRelease(CGImageRef image);
12486 	}
12487 
12488 private:
12489     // A convenient method to create a CFString (=NSString) from a D string.
12490     CFStringRef createCFString(string str) {
12491         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
12492                                              kCFStringEncodingUTF8, false);
12493     }
12494 
12495     // Objective-C calls.
12496     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
12497         auto _cmd = sel_registerName(selector.ptr);
12498         alias extern(C) RetType function(id, SEL, T) ExpectedType;
12499         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
12500     }
12501     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
12502         auto _cmd = sel_registerName(selector.ptr);
12503         auto cls = objc_getClass(className);
12504         alias extern(C) RetType function(id, SEL, T) ExpectedType;
12505         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
12506     }
12507     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
12508         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
12509     }
12510 
12511     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
12512     alias objc_msgSend_classMethod!("alloc", id) alloc;
12513     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
12514                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
12515     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
12516     alias objc_msgSend_specialized!("center", void) center;
12517     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
12518     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
12519     alias objc_msgSend_specialized!("release", void) release;
12520     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
12521     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
12522     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
12523     alias objc_msgSend_specialized!("invalidate", void) invalidate;
12524     alias objc_msgSend_specialized!("close", void) close;
12525     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
12526                                     id, double, id, SEL, id, BOOL) scheduledTimer;
12527     alias objc_msgSend_specialized!("run", void) run;
12528     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
12529                                     id) currentNSGraphicsContext;
12530     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
12531     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
12532     alias objc_msgSend_specialized!("superclass", Class) superclass;
12533     alias objc_msgSend_specialized!("init", id) init;
12534     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
12535     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
12536     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
12537                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
12538     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
12539     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
12540     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
12541                                     void, BOOL) activateIgnoringOtherApps;
12542     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
12543                                     id) sharedNSApplication;
12544     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
12545 } else static assert(0, "Unsupported operating system");
12546 
12547 
12548 version(OSXCocoa) {
12549 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
12550 	//
12551 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
12552 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
12553 	//
12554 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
12555 	// Probably won't even fully compile right now
12556 
12557     import std.math : PI;
12558     import std.algorithm : map;
12559     import std.array : array;
12560 
12561     alias SimpleWindow NativeWindowHandle;
12562     alias void delegate(id) NativeEventHandler;
12563 
12564     __gshared Ivar simpleWindowIvar;
12565 
12566     enum KEY_ESCAPE = 27;
12567 
12568     mixin template NativeImageImplementation() {
12569         CGContextRef context;
12570         ubyte* rawData;
12571     final:
12572 
12573 	void convertToRgbaBytes(ubyte[] where) {
12574 		assert(where.length == this.width * this.height * 4);
12575 
12576 		// if rawData had a length....
12577 		//assert(rawData.length == where.length);
12578 		for(long idx = 0; idx < where.length; idx += 4) {
12579 			auto alpha = rawData[idx + 3];
12580 			if(alpha == 255) {
12581 				where[idx + 0] = rawData[idx + 0]; // r
12582 				where[idx + 1] = rawData[idx + 1]; // g
12583 				where[idx + 2] = rawData[idx + 2]; // b
12584 				where[idx + 3] = rawData[idx + 3]; // a
12585 			} else {
12586 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
12587 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
12588 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
12589 				where[idx + 3] = rawData[idx + 3]; // a
12590 
12591 			}
12592 		}
12593 	}
12594 
12595 	void setFromRgbaBytes(in ubyte[] where) {
12596 		// FIXME: this is probably wrong
12597 		assert(where.length == this.width * this.height * 4);
12598 
12599 		// if rawData had a length....
12600 		//assert(rawData.length == where.length);
12601 		for(long idx = 0; idx < where.length; idx += 4) {
12602 			auto alpha = rawData[idx + 3];
12603 			if(alpha == 255) {
12604 				rawData[idx + 0] = where[idx + 0]; // r
12605 				rawData[idx + 1] = where[idx + 1]; // g
12606 				rawData[idx + 2] = where[idx + 2]; // b
12607 				rawData[idx + 3] = where[idx + 3]; // a
12608 			} else {
12609 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
12610 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
12611 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
12612 				rawData[idx + 3] = where[idx + 3]; // a
12613 
12614 			}
12615 		}
12616 	}
12617 
12618 
12619         void createImage(int width, int height, bool forcexshm=false) {
12620             auto colorSpace = CGColorSpaceCreateDeviceRGB();
12621             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
12622                                             colorSpace,
12623                                             kCGImageAlphaPremultipliedLast
12624                                                    |kCGBitmapByteOrder32Big);
12625             CGColorSpaceRelease(colorSpace);
12626             rawData = CGBitmapContextGetData(context);
12627         }
12628         void dispose() {
12629             CGContextRelease(context);
12630         }
12631 
12632         void setPixel(int x, int y, Color c) {
12633             auto offset = (y * width + x) * 4;
12634             if (c.a == 255) {
12635                 rawData[offset + 0] = c.r;
12636                 rawData[offset + 1] = c.g;
12637                 rawData[offset + 2] = c.b;
12638                 rawData[offset + 3] = c.a;
12639             } else {
12640                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
12641                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
12642                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
12643                 rawData[offset + 3] = c.a;
12644             }
12645         }
12646     }
12647 
12648     mixin template NativeScreenPainterImplementation() {
12649         CGContextRef context;
12650         ubyte[4] _outlineComponents;
12651 	id view;
12652 
12653         void create(NativeWindowHandle window) {
12654             context = window.drawingContext;
12655 	    view = window.view;
12656         }
12657 
12658         void dispose() {
12659             	setNeedsDisplay(view, true);
12660         }
12661 
12662 	// NotYetImplementedException
12663 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
12664 	void rasterOp(RasterOp op) {}
12665 	Pen _activePen;
12666 	Color _fillColor;
12667 	Rectangle _clipRectangle;
12668 	void setClipRectangle(int, int, int, int) {}
12669 	void setFont(OperatingSystemFont) {}
12670 	int fontHeight() { return 14; }
12671 
12672 	// end
12673 
12674         void pen(Pen pen) {
12675 	    _activePen = pen;
12676 	    auto color = pen.color; // FIXME
12677             double alphaComponent = color.a/255.0f;
12678             CGContextSetRGBStrokeColor(context,
12679                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
12680 
12681             if (color.a != 255) {
12682                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
12683                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
12684                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
12685                 _outlineComponents[3] = color.a;
12686             } else {
12687                 _outlineComponents[0] = color.r;
12688                 _outlineComponents[1] = color.g;
12689                 _outlineComponents[2] = color.b;
12690                 _outlineComponents[3] = color.a;
12691             }
12692         }
12693 
12694         @property void fillColor(Color color) {
12695             CGContextSetRGBFillColor(context,
12696                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
12697         }
12698 
12699         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
12700 		// NotYetImplementedException for upper left/width/height
12701             auto cgImage = CGBitmapContextCreateImage(image.context);
12702             auto size = CGSize(CGBitmapContextGetWidth(image.context),
12703                                CGBitmapContextGetHeight(image.context));
12704             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
12705             CGImageRelease(cgImage);
12706         }
12707 
12708 	version(OSXCocoa) {} else // NotYetImplementedException
12709         void drawPixmap(Sprite image, int x, int y) {
12710 		// FIXME: is this efficient?
12711             auto cgImage = CGBitmapContextCreateImage(image.context);
12712             auto size = CGSize(CGBitmapContextGetWidth(image.context),
12713                                CGBitmapContextGetHeight(image.context));
12714             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
12715             CGImageRelease(cgImage);
12716         }
12717 
12718 
12719         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
12720 		// FIXME: alignment
12721             if (_outlineComponents[3] != 0) {
12722                 CGContextSaveGState(context);
12723                 auto invAlpha = 1.0f/_outlineComponents[3];
12724                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
12725                                                   _outlineComponents[1]*invAlpha,
12726                                                   _outlineComponents[2]*invAlpha,
12727                                                   _outlineComponents[3]/255.0f);
12728                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
12729 // auto cfstr = cast(id)createCFString(text);
12730 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
12731 // NSPoint(x, y), null);
12732 // CFRelease(cfstr);
12733                 CGContextRestoreGState(context);
12734             }
12735         }
12736 
12737         void drawPixel(int x, int y) {
12738             auto rawData = CGBitmapContextGetData(context);
12739             auto width = CGBitmapContextGetWidth(context);
12740             auto height = CGBitmapContextGetHeight(context);
12741             auto offset = ((height - y - 1) * width + x) * 4;
12742             rawData[offset .. offset+4] = _outlineComponents;
12743         }
12744 
12745         void drawLine(int x1, int y1, int x2, int y2) {
12746             CGPoint[2] linePoints;
12747             linePoints[0] = CGPoint(x1, y1);
12748             linePoints[1] = CGPoint(x2, y2);
12749             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
12750         }
12751 
12752         void drawRectangle(int x, int y, int width, int height) {
12753             CGContextBeginPath(context);
12754             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
12755             CGContextAddRect(context, rect);
12756             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
12757         }
12758 
12759         void drawEllipse(int x1, int y1, int x2, int y2) {
12760             CGContextBeginPath(context);
12761             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
12762             CGContextAddEllipseInRect(context, rect);
12763             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
12764         }
12765 
12766         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
12767             // @@@BUG@@@ Does not support elliptic arc (width != height).
12768             CGContextBeginPath(context);
12769             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
12770                             start*PI/(180*64), finish*PI/(180*64), 0);
12771             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
12772         }
12773 
12774         void drawPolygon(Point[] intPoints) {
12775             CGContextBeginPath(context);
12776             auto points = array(map!(CGPoint.fromTuple)(intPoints));
12777             CGContextAddLines(context, points.ptr, points.length);
12778             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
12779         }
12780     }
12781 
12782     mixin template NativeSimpleWindowImplementation() {
12783         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
12784             synchronized {
12785                 if (NSApp == null) initializeApp();
12786             }
12787 
12788             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
12789 
12790             // create the window.
12791             window = initWithContentRect(alloc("NSWindow"),
12792                                          contentRect,
12793                                          NSTitledWindowMask
12794                                             |NSClosableWindowMask
12795                                             |NSMiniaturizableWindowMask
12796                                             |NSResizableWindowMask,
12797                                          NSBackingStoreBuffered,
12798                                          true);
12799 
12800             // set the title & move the window to center.
12801             auto windowTitle = createCFString(title);
12802             setTitle(window, windowTitle);
12803             CFRelease(windowTitle);
12804             center(window);
12805 
12806             // create area to draw on.
12807             auto colorSpace = CGColorSpaceCreateDeviceRGB();
12808             drawingContext = CGBitmapContextCreate(null, width, height,
12809                                                    8, 4*width, colorSpace,
12810                                                    kCGImageAlphaPremultipliedLast
12811                                                       |kCGBitmapByteOrder32Big);
12812             CGColorSpaceRelease(colorSpace);
12813             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
12814             auto matrix = CGContextGetTextMatrix(drawingContext);
12815             matrix.c = -matrix.c;
12816             matrix.d = -matrix.d;
12817             CGContextSetTextMatrix(drawingContext, matrix);
12818 
12819             // create the subview that things will be drawn on.
12820             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
12821             setContentView(window, view);
12822             object_setIvar(view, simpleWindowIvar, cast(id)this);
12823             release(view);
12824 
12825             setBackgroundColor(window, whiteNSColor);
12826             makeKeyAndOrderFront(window, null);
12827         }
12828         void dispose() {
12829             closeWindow();
12830             release(window);
12831         }
12832         void closeWindow() {
12833             invalidate(timer);
12834             .close(window);
12835         }
12836 
12837         ScreenPainter getPainter() {
12838 		return ScreenPainter(this, this);
12839 	}
12840 
12841         id window;
12842         id timer;
12843         id view;
12844         CGContextRef drawingContext;
12845     }
12846 
12847     extern(C) {
12848     private:
12849         BOOL returnTrue3(id self, SEL _cmd, id app) {
12850             return true;
12851         }
12852         BOOL returnTrue2(id self, SEL _cmd) {
12853             return true;
12854         }
12855 
12856         void pulse(id self, SEL _cmd) {
12857             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
12858             simpleWindow.handlePulse();
12859             setNeedsDisplay(self, true);
12860         }
12861         void drawRect(id self, SEL _cmd, NSRect rect) {
12862             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
12863             auto curCtx = graphicsPort(currentNSGraphicsContext);
12864             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
12865             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
12866                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
12867             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
12868             CGImageRelease(cgImage);
12869         }
12870         void keyDown(id self, SEL _cmd, id event) {
12871             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
12872 
12873             // the event may have multiple characters, and we send them all at
12874             // once.
12875             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
12876                 auto chars = characters(event);
12877                 auto range = CFRange(0, CFStringGetLength(chars));
12878                 auto buffer = new char[range.length*3];
12879                 long actualLength;
12880                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
12881                                  buffer.ptr, cast(int) buffer.length, &actualLength);
12882                 foreach (dchar dc; buffer[0..actualLength]) {
12883                     if (simpleWindow.handleCharEvent)
12884                         simpleWindow.handleCharEvent(dc);
12885 		    // NotYetImplementedException
12886                     //if (simpleWindow.handleKeyEvent)
12887                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
12888                 }
12889             }
12890 
12891             // the event's 'keyCode' is hardware-dependent. I don't think people
12892             // will like it. Let's leave it to the native handler.
12893 
12894             // perform the default action.
12895 
12896 	    // so the default action is to make a bomp sound and i dont want that
12897 	    // sooooooooo yeah not gonna do that.
12898 
12899             //auto superData = objc_super(self, superclass(self));
12900             //alias extern(C) void function(objc_super*, SEL, id) T;
12901             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
12902         }
12903     }
12904 
12905     // initialize the app so that it can be interacted with the user.
12906     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
12907     private void initializeApp() {
12908         // push an autorelease pool to avoid leaking.
12909         init(alloc("NSAutoreleasePool"));
12910 
12911         // create a new NSApp instance
12912         sharedNSApplication;
12913         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
12914 
12915         // create the "Quit" menu.
12916         auto menuBar = init(alloc("NSMenu"));
12917         auto appMenuItem = init(alloc("NSMenuItem"));
12918         addItem(menuBar, appMenuItem);
12919         setMainMenu(NSApp, menuBar);
12920         release(appMenuItem);
12921         release(menuBar);
12922 
12923         auto appMenu = init(alloc("NSMenu"));
12924         auto quitTitle = createCFString("Quit");
12925         auto q = createCFString("q");
12926         auto quitItem = initWithTitle(alloc("NSMenuItem"),
12927                                       quitTitle, sel_registerName("terminate:"), q);
12928         addItem(appMenu, quitItem);
12929         setSubmenu(appMenuItem, appMenu);
12930         release(quitItem);
12931         release(appMenu);
12932         CFRelease(q);
12933         CFRelease(quitTitle);
12934 
12935         // assign a delegate for the application, allow it to quit when the last
12936         // window is closed.
12937         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
12938                                                     "SDWindowCloseDelegate", 0);
12939         class_addMethod(delegateClass,
12940                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
12941                         &returnTrue3, "c@:@");
12942         objc_registerClassPair(delegateClass);
12943 
12944         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
12945         setDelegate(NSApp, appDelegate);
12946         activateIgnoringOtherApps(NSApp, true);
12947 
12948         // create a new view that draws the graphics and respond to keyDown
12949         // events.
12950         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
12951                                                 "SDGraphicsView", (void*).sizeof);
12952         class_addIvar(viewClass, "simpledisplay_simpleWindow",
12953                       (void*).sizeof, (void*).alignof, "^v");
12954         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
12955                         &pulse, "v@:");
12956         class_addMethod(viewClass, sel_registerName("drawRect:"),
12957                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
12958         class_addMethod(viewClass, sel_registerName("isFlipped"),
12959                         &returnTrue2, "c@:");
12960         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
12961                         &returnTrue2, "c@:");
12962         class_addMethod(viewClass, sel_registerName("keyDown:"),
12963                         &keyDown, "v@:@");
12964         objc_registerClassPair(viewClass);
12965         simpleWindowIvar = class_getInstanceVariable(viewClass,
12966                                                      "simpledisplay_simpleWindow");
12967     }
12968 }
12969 
12970 version(without_opengl) {} else
12971 extern(System) nothrow @nogc {
12972 	//enum uint GL_VERSION = 0x1F02;
12973 	//const(char)* glGetString (/*GLenum*/uint);
12974 	version(X11) {
12975 	static if (!SdpyIsUsingIVGLBinds) {
12976 		struct __GLXFBConfigRec {}
12977 		alias GLXFBConfig = __GLXFBConfigRec*;
12978 
12979 		enum GLX_X_RENDERABLE = 0x8012;
12980 		enum GLX_DRAWABLE_TYPE = 0x8010;
12981 		enum GLX_RENDER_TYPE = 0x8011;
12982 		enum GLX_X_VISUAL_TYPE = 0x22;
12983 		enum GLX_TRUE_COLOR = 0x8002;
12984 		enum GLX_WINDOW_BIT = 0x00000001;
12985 		enum GLX_RGBA_BIT = 0x00000001;
12986 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
12987 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
12988 		enum GLX_SAMPLES = 0x186a1;
12989 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
12990 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
12991 
12992 		GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
12993 		int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
12994 		XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
12995 
12996 		char* glXQueryExtensionsString (Display*, int);
12997 		void* glXGetProcAddress (const(char)*);
12998 
12999 		alias glbindGetProcAddress = glXGetProcAddress;
13000 	}
13001 
13002 		// GLX_EXT_swap_control
13003 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
13004 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
13005 
13006 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
13007 		extern(System) {
13008 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
13009 		}
13010 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
13011 
13012 		// this made public so we don't have to get it again and again
13013 		public bool glXCreateContextAttribsARB_present () {
13014 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
13015 				// get it
13016 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
13017 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
13018 			}
13019 			return (glXCreateContextAttribsARBFn !is null);
13020 		}
13021 
13022 		// this made public so we don't have to get it again and again
13023 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
13024 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
13025 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
13026 		}
13027 
13028 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
13029 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
13030 			if (_glx_swapInterval_fn is null) {
13031 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
13032 				if (_glx_swapInterval_fn is null) {
13033 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
13034 					return;
13035 				}
13036 				version(sdddd) { import std.stdio; writeln("glXSwapIntervalEXT found!"); }
13037 			}
13038 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
13039 		}
13040 	} else version(Windows) {
13041 	static if (!SdpyIsUsingIVGLBinds) {
13042 	enum GL_TRUE = 1;
13043 	enum GL_FALSE = 0;
13044 	alias int GLint;
13045 
13046 	public void* glbindGetProcAddress (const(char)* name) {
13047 		void* res = wglGetProcAddress(name);
13048 		if (res is null) {
13049 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
13050 			import core.sys.windows.windef, core.sys.windows.winbase;
13051 			__gshared HINSTANCE dll = null;
13052 			if (dll is null) {
13053 				dll = LoadLibraryA("opengl32.dll");
13054 				if (dll is null) return null; // <32, but idc
13055 			}
13056 			res = GetProcAddress(dll, name);
13057 		}
13058 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
13059 		return res;
13060 	}
13061 	}
13062 
13063 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
13064 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
13065 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
13066 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
13067 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
13068 
13069 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
13070 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
13071 
13072 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
13073 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
13074 
13075 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
13076 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
13077 
13078 		void wglInitOtherFunctions () {
13079 			if (wglCreateContextAttribsARB is null) {
13080 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
13081 			}
13082 		}
13083 	}
13084 
13085 	static if (!SdpyIsUsingIVGLBinds) {
13086 	void glGetIntegerv(int, void*);
13087 	void glMatrixMode(int);
13088 	void glPushMatrix();
13089 	void glLoadIdentity();
13090 	void glOrtho(double, double, double, double, double, double);
13091 	void glFrustum(double, double, double, double, double, double);
13092 
13093 	void gluLookAt(double, double, double, double, double, double, double, double, double);
13094 	void gluPerspective(double, double, double, double);
13095 
13096 	void glPopMatrix();
13097 	void glEnable(int);
13098 	void glDisable(int);
13099 	void glClear(int);
13100 	void glBegin(int);
13101 	void glVertex2f(float, float);
13102 	void glVertex3f(float, float, float);
13103 	void glEnd();
13104 	void glColor3b(byte, byte, byte);
13105 	void glColor3ub(ubyte, ubyte, ubyte);
13106 	void glColor4b(byte, byte, byte, byte);
13107 	void glColor4ub(ubyte, ubyte, ubyte, ubyte);
13108 	void glColor3i(int, int, int);
13109 	void glColor3ui(uint, uint, uint);
13110 	void glColor4i(int, int, int, int);
13111 	void glColor4ui(uint, uint, uint, uint);
13112 	void glColor3f(float, float, float);
13113 	void glColor4f(float, float, float, float);
13114 	void glTranslatef(float, float, float);
13115 	void glScalef(float, float, float);
13116 	void glSecondaryColor3b(byte, byte, byte);
13117 	void glSecondaryColor3ub(ubyte, ubyte, ubyte);
13118 	void glSecondaryColor3i(int, int, int);
13119 	void glSecondaryColor3ui(uint, uint, uint);
13120 	void glSecondaryColor3f(float, float, float);
13121 
13122 	void glDrawElements(int, int, int, void*);
13123 
13124 	void glRotatef(float, float, float, float);
13125 
13126 	uint glGetError();
13127 
13128 	void glDeleteTextures(int, uint*);
13129 
13130 	char* gluErrorString(uint);
13131 
13132 	void glRasterPos2i(int, int);
13133 	void glDrawPixels(int, int, uint, uint, void*);
13134 	void glClearColor(float, float, float, float);
13135 
13136 
13137 
13138 	void glGenTextures(uint, uint*);
13139 	void glBindTexture(int, int);
13140 	void glTexParameteri(uint, uint, int);
13141 	void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
13142 	void glTexImage2D(int, int, int, int, int, int, int, int, in void*);
13143 	void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
13144 		/*GLsizei*/int width, /*GLsizei*/int height,
13145 		uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
13146 	void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
13147 		/*GLsizei*/int width, /*GLsizei*/int height,
13148 		uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
13149 	void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
13150 
13151 
13152 	void glTexCoord2f(float, float);
13153 	void glVertex2i(int, int);
13154 	void glBlendFunc (int, int);
13155 	void glDepthFunc (int);
13156 	void glViewport(int, int, int, int);
13157 
13158 	void glClearDepth(double);
13159 
13160 	void glReadBuffer(uint);
13161 	void glReadPixels(int, int, int, int, int, int, void*);
13162 
13163 	void glFlush();
13164 	void glFinish();
13165 
13166 	enum uint GL_FRONT = 0x0404;
13167 
13168 	enum uint GL_BLEND = 0x0be2;
13169 	enum uint GL_SRC_ALPHA = 0x0302;
13170 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
13171 	enum uint GL_LEQUAL = 0x0203;
13172 
13173 
13174 	enum uint GL_UNSIGNED_BYTE = 0x1401;
13175 	enum uint GL_RGB = 0x1907;
13176 	enum uint GL_BGRA = 0x80e1;
13177 	enum uint GL_RGBA = 0x1908;
13178 	enum uint GL_TEXTURE_2D =   0x0DE1;
13179 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
13180 	enum uint GL_NEAREST = 0x2600;
13181 	enum uint GL_LINEAR = 0x2601;
13182 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
13183 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
13184 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
13185 	enum uint GL_REPEAT = 0x2901;
13186 	enum uint GL_CLAMP = 0x2900;
13187 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
13188 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
13189 	enum uint GL_DECAL = 0x2101;
13190 	enum uint GL_MODULATE = 0x2100;
13191 	enum uint GL_TEXTURE_ENV = 0x2300;
13192 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
13193 	enum uint GL_REPLACE = 0x1E01;
13194 	enum uint GL_LIGHTING = 0x0B50;
13195 	enum uint GL_DITHER = 0x0BD0;
13196 
13197 	enum uint GL_NO_ERROR = 0;
13198 
13199 
13200 
13201 	enum int GL_VIEWPORT = 0x0BA2;
13202 	enum int GL_MODELVIEW = 0x1700;
13203 	enum int GL_TEXTURE = 0x1702;
13204 	enum int GL_PROJECTION = 0x1701;
13205 	enum int GL_DEPTH_TEST = 0x0B71;
13206 
13207 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
13208 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
13209 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
13210 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
13211 
13212 	enum int GL_POINTS = 0x0000;
13213 	enum int GL_LINES =  0x0001;
13214 	enum int GL_LINE_LOOP = 0x0002;
13215 	enum int GL_LINE_STRIP = 0x0003;
13216 	enum int GL_TRIANGLES = 0x0004;
13217 	enum int GL_TRIANGLE_STRIP = 5;
13218 	enum int GL_TRIANGLE_FAN = 6;
13219 	enum int GL_QUADS = 7;
13220 	enum int GL_QUAD_STRIP = 8;
13221 	enum int GL_POLYGON = 9;
13222 	}
13223 }
13224 
13225 version(linux) {
13226 	version(with_eventloop) {} else {
13227 		private int epollFd = -1;
13228 		void prepareEventLoop() {
13229 			if(epollFd != -1)
13230 				return; // already initialized, no need to do it again
13231 			import ep = core.sys.linux.epoll;
13232 
13233 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
13234 			if(epollFd == -1)
13235 				throw new Exception("epoll create failure");
13236 		}
13237 	}
13238 
13239 }
13240 
13241 version(X11) {
13242 	import core.stdc.locale : LC_ALL; // rdmd fix
13243 	__gshared bool sdx_isUTF8Locale;
13244 
13245 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
13246 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
13247 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
13248 	// anal magic is here. I (Ketmar) hope you like it.
13249 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
13250 	// always return correct unicode symbols. The detection is here 'cause user can change locale
13251 	// later.
13252 	shared static this () {
13253 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
13254 
13255 		// this doesn't hurt; it may add some locking, but the speed is still
13256 		// allows doing 60 FPS videogames; also, ignore the result, as most
13257 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
13258 		// never seen this failing).
13259 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
13260 
13261 		setlocale(LC_ALL, "");
13262 		// check if out locale is UTF-8
13263 		auto lct = setlocale(LC_CTYPE, null);
13264 		if (lct is null) {
13265 			sdx_isUTF8Locale = false;
13266 		} else {
13267 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
13268 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
13269 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
13270 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
13271 				{
13272 					sdx_isUTF8Locale = true;
13273 					break;
13274 				}
13275 			}
13276 		}
13277 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
13278 	}
13279 }
13280 
13281 mixin template ExperimentalTextComponent2() {
13282 	/+
13283 		Stage 1: get it working monospace
13284 		Stage 2: use proportional font
13285 		Stage 3: allow changes in inline style
13286 		Stage 4: allow new fonts and sizes in the middle
13287 		Stage 5: optimize gap buffer
13288 		Stage 6: optimize layout
13289 		Stage 7: word wrap
13290 		Stage 8: justification
13291 		Stage 9: editing, selection, etc.
13292 	+/
13293 }
13294 
13295 
13296 // Don't use this yet. When I'm happy with it, I will move it to the
13297 // regular module namespace.
13298 mixin template ExperimentalTextComponent() {
13299 
13300 	alias Rectangle = arsd.color.Rectangle;
13301 
13302 	struct ForegroundColor {
13303 		Color color;
13304 		alias color this;
13305 
13306 		this(Color c) {
13307 			color = c;
13308 		}
13309 
13310 		this(int r, int g, int b, int a = 255) {
13311 			color = Color(r, g, b, a);
13312 		}
13313 
13314 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
13315 			return ForegroundColor(mixin("Color." ~ s));
13316 		}
13317 	}
13318 
13319 	struct BackgroundColor {
13320 		Color color;
13321 		alias color this;
13322 
13323 		this(Color c) {
13324 			color = c;
13325 		}
13326 
13327 		this(int r, int g, int b, int a = 255) {
13328 			color = Color(r, g, b, a);
13329 		}
13330 
13331 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
13332 			return BackgroundColor(mixin("Color." ~ s));
13333 		}
13334 	}
13335 
13336 	static class InlineElement {
13337 		string text;
13338 
13339 		BlockElement containingBlock;
13340 
13341 		Color color = Color.black;
13342 		Color backgroundColor = Color.transparent;
13343 		ushort styles;
13344 
13345 		string font;
13346 		int fontSize;
13347 
13348 		int lineHeight;
13349 
13350 		void* identifier;
13351 
13352 		Rectangle boundingBox;
13353 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
13354 
13355 		bool isMergeCompatible(InlineElement other) {
13356 			return
13357 				containingBlock is other.containingBlock &&
13358 				color == other.color &&
13359 				backgroundColor == other.backgroundColor &&
13360 				styles == other.styles &&
13361 				font == other.font &&
13362 				fontSize == other.fontSize &&
13363 				lineHeight == other.lineHeight &&
13364 				true;
13365 		}
13366 
13367 		int xOfIndex(size_t index) {
13368 			if(index < letterXs.length)
13369 				return letterXs[index];
13370 			else
13371 				return boundingBox.right;
13372 		}
13373 
13374 		InlineElement clone() {
13375 			auto ie = new InlineElement();
13376 			ie.tupleof = this.tupleof;
13377 			return ie;
13378 		}
13379 
13380 		InlineElement getPreviousInlineElement() {
13381 			InlineElement prev = null;
13382 			foreach(ie; this.containingBlock.parts) {
13383 				if(ie is this)
13384 					break;
13385 				prev = ie;
13386 			}
13387 			if(prev is null) {
13388 				BlockElement pb;
13389 				BlockElement cb = this.containingBlock;
13390 				moar:
13391 				foreach(ie; this.containingBlock.containingLayout.blocks) {
13392 					if(ie is cb)
13393 						break;
13394 					pb = ie;
13395 				}
13396 				if(pb is null)
13397 					return null;
13398 				if(pb.parts.length == 0) {
13399 					cb = pb;
13400 					goto moar;
13401 				}
13402 
13403 				prev = pb.parts[$-1];
13404 
13405 			}
13406 			return prev;
13407 		}
13408 
13409 		InlineElement getNextInlineElement() {
13410 			InlineElement next = null;
13411 			foreach(idx, ie; this.containingBlock.parts) {
13412 				if(ie is this) {
13413 					if(idx + 1 < this.containingBlock.parts.length)
13414 						next = this.containingBlock.parts[idx + 1];
13415 					break;
13416 				}
13417 			}
13418 			if(next is null) {
13419 				BlockElement n;
13420 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
13421 					if(ie is this.containingBlock) {
13422 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
13423 							n = this.containingBlock.containingLayout.blocks[idx + 1];
13424 						break;
13425 					}
13426 				}
13427 				if(n is null)
13428 					return null;
13429 
13430 				if(n.parts.length)
13431 					next = n.parts[0];
13432 				else {} // FIXME
13433 
13434 			}
13435 			return next;
13436 		}
13437 
13438 	}
13439 
13440 	// Block elements are used entirely for positioning inline elements,
13441 	// which are the things that are actually drawn.
13442 	class BlockElement {
13443 		InlineElement[] parts;
13444 		uint alignment;
13445 
13446 		int whiteSpace; // pre, pre-wrap, wrap
13447 
13448 		TextLayout containingLayout;
13449 
13450 		// inputs
13451 		Point where;
13452 		Size minimumSize;
13453 		Size maximumSize;
13454 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
13455 		void* identifier;
13456 
13457 		Rectangle margin;
13458 		Rectangle padding;
13459 
13460 		// outputs
13461 		Rectangle[] boundingBoxes;
13462 	}
13463 
13464 	struct TextIdentifyResult {
13465 		InlineElement element;
13466 		int offset;
13467 
13468 		private TextIdentifyResult fixupNewline() {
13469 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
13470 				offset--;
13471 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
13472 				offset--;
13473 			}
13474 			return this;
13475 		}
13476 	}
13477 
13478 	class TextLayout {
13479 		BlockElement[] blocks;
13480 		Rectangle boundingBox_;
13481 		Rectangle boundingBox() { return boundingBox_; }
13482 		void boundingBox(Rectangle r) {
13483 			if(r != boundingBox_) {
13484 				boundingBox_ = r;
13485 				layoutInvalidated = true;
13486 			}
13487 		}
13488 
13489 		Rectangle contentBoundingBox() {
13490 			Rectangle r;
13491 			foreach(block; blocks)
13492 			foreach(ie; block.parts) {
13493 				if(ie.boundingBox.right > r.right)
13494 					r.right = ie.boundingBox.right;
13495 				if(ie.boundingBox.bottom > r.bottom)
13496 					r.bottom = ie.boundingBox.bottom;
13497 			}
13498 			return r;
13499 		}
13500 
13501 		BlockElement[] getBlocks() {
13502 			return blocks;
13503 		}
13504 
13505 		InlineElement[] getTexts() {
13506 			InlineElement[] elements;
13507 			foreach(block; blocks)
13508 				elements ~= block.parts;
13509 			return elements;
13510 		}
13511 
13512 		string getPlainText() {
13513 			string text;
13514 			foreach(block; blocks)
13515 				foreach(part; block.parts)
13516 					text ~= part.text;
13517 			return text;
13518 		}
13519 
13520 		string getHtml() {
13521 			return null; // FIXME
13522 		}
13523 
13524 		this(Rectangle boundingBox) {
13525 			this.boundingBox = boundingBox;
13526 		}
13527 
13528 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
13529 			auto be = new BlockElement();
13530 			be.containingLayout = this;
13531 			if(after is null)
13532 				blocks ~= be;
13533 			else {
13534 				foreach(idx, b; blocks) {
13535 					if(b is after.containingBlock) {
13536 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
13537 						break;
13538 					}
13539 				}
13540 			}
13541 			return be;
13542 		}
13543 
13544 		void clear() {
13545 			blocks = null;
13546 			selectionStart = selectionEnd = caret = Caret.init;
13547 		}
13548 
13549 		void addText(Args...)(Args args) {
13550 			if(blocks.length == 0)
13551 				addBlock();
13552 
13553 			InlineElement ie = new InlineElement();
13554 			foreach(idx, arg; args) {
13555 				static if(is(typeof(arg) == ForegroundColor))
13556 					ie.color = arg;
13557 				else static if(is(typeof(arg) == TextFormat)) {
13558 					if(arg & 0x8000) // ~TextFormat.something turns it off
13559 						ie.styles &= arg;
13560 					else
13561 						ie.styles |= arg;
13562 				} else static if(is(typeof(arg) == string)) {
13563 					static if(idx == 0 && args.length > 1)
13564 						static assert(0, "Put styles before the string.");
13565 					size_t lastLineIndex;
13566 					foreach(cidx, char a; arg) {
13567 						if(a == '\n') {
13568 							ie.text = arg[lastLineIndex .. cidx + 1];
13569 							lastLineIndex = cidx + 1;
13570 							ie.containingBlock = blocks[$-1];
13571 							blocks[$-1].parts ~= ie.clone;
13572 							ie.text = null;
13573 						} else {
13574 
13575 						}
13576 					}
13577 
13578 					ie.text = arg[lastLineIndex .. $];
13579 					ie.containingBlock = blocks[$-1];
13580 					blocks[$-1].parts ~= ie.clone;
13581 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
13582 				}
13583 			}
13584 
13585 			invalidateLayout();
13586 		}
13587 
13588 		void tryMerge(InlineElement into, InlineElement what) {
13589 			if(!into.isMergeCompatible(what)) {
13590 				return; // cannot merge, different configs
13591 			}
13592 
13593 			// cool, can merge, bring text together...
13594 			into.text ~= what.text;
13595 
13596 			// and remove what
13597 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
13598 				if(what.containingBlock.parts[a] is what) {
13599 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
13600 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
13601 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
13602 
13603 				}
13604 			}
13605 
13606 			// FIXME: ensure no other carets have a reference to it
13607 		}
13608 
13609 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
13610 		TextIdentifyResult identify(int x, int y, bool exact = false) {
13611 			TextIdentifyResult inexactMatch;
13612 			foreach(block; blocks) {
13613 				foreach(part; block.parts) {
13614 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
13615 
13616 						// FIXME binary search
13617 						int tidx;
13618 						int lastX;
13619 						foreach_reverse(idxo, lx; part.letterXs) {
13620 							int idx = cast(int) idxo;
13621 							if(lx <= x) {
13622 								if(lastX && lastX - x < x - lx)
13623 									tidx = idx + 1;
13624 								else
13625 									tidx = idx;
13626 								break;
13627 							}
13628 							lastX = lx;
13629 						}
13630 
13631 						return TextIdentifyResult(part, tidx).fixupNewline;
13632 					} else if(!exact) {
13633 						// we're not in the box, but are we on the same line?
13634 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
13635 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
13636 					}
13637 				}
13638 			}
13639 
13640 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
13641 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
13642 
13643 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
13644 		}
13645 
13646 		void moveCaretToPixelCoordinates(int x, int y) {
13647 			auto result = identify(x, y);
13648 			caret.inlineElement = result.element;
13649 			caret.offset = result.offset;
13650 		}
13651 
13652 		void selectToPixelCoordinates(int x, int y) {
13653 			auto result = identify(x, y);
13654 
13655 			if(y < caretLastDrawnY1) {
13656 				// on a previous line, carat is selectionEnd
13657 				selectionEnd = caret;
13658 
13659 				selectionStart = Caret(this, result.element, result.offset);
13660 			} else if(y > caretLastDrawnY2) {
13661 				// on a later line
13662 				selectionStart = caret;
13663 
13664 				selectionEnd = Caret(this, result.element, result.offset);
13665 			} else {
13666 				// on the same line...
13667 				if(x <= caretLastDrawnX) {
13668 					selectionEnd = caret;
13669 					selectionStart = Caret(this, result.element, result.offset);
13670 				} else {
13671 					selectionStart = caret;
13672 					selectionEnd = Caret(this, result.element, result.offset);
13673 				}
13674 
13675 			}
13676 		}
13677 
13678 
13679 		/// Call this if the inputs change. It will reflow everything
13680 		void redoLayout(ScreenPainter painter) {
13681 			//painter.setClipRectangle(boundingBox);
13682 			auto pos = Point(boundingBox.left, boundingBox.top);
13683 
13684 			int lastHeight;
13685 			void nl() {
13686 				pos.x = boundingBox.left;
13687 				pos.y += lastHeight;
13688 			}
13689 			foreach(block; blocks) {
13690 				nl();
13691 				foreach(part; block.parts) {
13692 					part.letterXs = null;
13693 
13694 					auto size = painter.textSize(part.text);
13695 					version(Windows)
13696 						if(part.text.length && part.text[$-1] == '\n')
13697 							size.height /= 2; // windows counts the new line at the end, but we don't want that
13698 
13699 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
13700 
13701 					foreach(idx, char c; part.text) {
13702 							// FIXME: unicode
13703 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
13704 					}
13705 
13706 					pos.x += size.width;
13707 					if(pos.x >= boundingBox.right) {
13708 						pos.y += size.height;
13709 						pos.x = boundingBox.left;
13710 						lastHeight = 0;
13711 					} else {
13712 						lastHeight = size.height;
13713 					}
13714 
13715 					if(part.text.length && part.text[$-1] == '\n')
13716 						nl();
13717 				}
13718 			}
13719 
13720 			layoutInvalidated = false;
13721 		}
13722 
13723 		bool layoutInvalidated = true;
13724 		void invalidateLayout() {
13725 			layoutInvalidated = true;
13726 		}
13727 
13728 // FIXME: caret can remain sometimes when inserting
13729 // FIXME: inserting at the beginning once you already have something can eff it up.
13730 		void drawInto(ScreenPainter painter, bool focused = false) {
13731 			if(layoutInvalidated)
13732 				redoLayout(painter);
13733 			foreach(block; blocks) {
13734 				foreach(part; block.parts) {
13735 					painter.outlineColor = part.color;
13736 					painter.fillColor = part.backgroundColor;
13737 
13738 					auto pos = part.boundingBox.upperLeft;
13739 					auto size = part.boundingBox.size;
13740 
13741 					painter.drawText(pos, part.text);
13742 					if(part.styles & TextFormat.underline)
13743 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
13744 					if(part.styles & TextFormat.strikethrough)
13745 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
13746 				}
13747 			}
13748 
13749 			// on every redraw, I will force the caret to be
13750 			// redrawn too, in order to eliminate perceived lag
13751 			// when moving around with the mouse.
13752 			eraseCaret(painter);
13753 
13754 			if(focused) {
13755 				highlightSelection(painter);
13756 				drawCaret(painter);
13757 			}
13758 		}
13759 
13760 		void highlightSelection(ScreenPainter painter) {
13761 			if(selectionStart is selectionEnd)
13762 				return; // no selection
13763 
13764 			if(selectionStart.inlineElement is null) return;
13765 			if(selectionEnd.inlineElement is null) return;
13766 
13767 			assert(selectionStart.inlineElement !is null);
13768 			assert(selectionEnd.inlineElement !is null);
13769 
13770 			painter.rasterOp = RasterOp.xor;
13771 			painter.outlineColor = Color.transparent;
13772 			painter.fillColor = Color(255, 255, 127);
13773 
13774 			auto at = selectionStart.inlineElement;
13775 			auto atOffset = selectionStart.offset;
13776 			bool done;
13777 			while(at) {
13778 				auto box = at.boundingBox;
13779 				if(atOffset < at.letterXs.length)
13780 					box.left = at.letterXs[atOffset];
13781 
13782 				if(at is selectionEnd.inlineElement) {
13783 					if(selectionEnd.offset < at.letterXs.length)
13784 						box.right = at.letterXs[selectionEnd.offset];
13785 					done = true;
13786 				}
13787 
13788 				painter.drawRectangle(box.upperLeft, box.width, box.height);
13789 
13790 				if(done)
13791 					break;
13792 
13793 				at = at.getNextInlineElement();
13794 				atOffset = 0;
13795 			}
13796 		}
13797 
13798 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
13799 		bool caretShowingOnScreen = false;
13800 		void drawCaret(ScreenPainter painter) {
13801 			//painter.setClipRectangle(boundingBox);
13802 			int x, y1, y2;
13803 			if(caret.inlineElement is null) {
13804 				x = boundingBox.left;
13805 				y1 = boundingBox.top + 2;
13806 				y2 = boundingBox.top + painter.fontHeight;
13807 			} else {
13808 				x = caret.inlineElement.xOfIndex(caret.offset);
13809 				y1 = caret.inlineElement.boundingBox.top + 2;
13810 				y2 = caret.inlineElement.boundingBox.bottom - 2;
13811 			}
13812 
13813 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
13814 				eraseCaret(painter);
13815 
13816 			painter.pen = Pen(Color.white, 1);
13817 			painter.rasterOp = RasterOp.xor;
13818 			painter.drawLine(
13819 				Point(x, y1),
13820 				Point(x, y2)
13821 			);
13822 			painter.rasterOp = RasterOp.normal;
13823 			caretShowingOnScreen = !caretShowingOnScreen;
13824 
13825 			if(caretShowingOnScreen) {
13826 				caretLastDrawnX = x;
13827 				caretLastDrawnY1 = y1;
13828 				caretLastDrawnY2 = y2;
13829 			}
13830 		}
13831 
13832 		Rectangle caretBoundingBox() {
13833 			int x, y1, y2;
13834 			if(caret.inlineElement is null) {
13835 				x = boundingBox.left;
13836 				y1 = boundingBox.top + 2;
13837 				y2 = boundingBox.top + 16;
13838 			} else {
13839 				x = caret.inlineElement.xOfIndex(caret.offset);
13840 				y1 = caret.inlineElement.boundingBox.top + 2;
13841 				y2 = caret.inlineElement.boundingBox.bottom - 2;
13842 			}
13843 
13844 			return Rectangle(x, y1, x + 1, y2);
13845 		}
13846 
13847 		void eraseCaret(ScreenPainter painter) {
13848 			//painter.setClipRectangle(boundingBox);
13849 			if(!caretShowingOnScreen) return;
13850 			painter.pen = Pen(Color.white, 1);
13851 			painter.rasterOp = RasterOp.xor;
13852 			painter.drawLine(
13853 				Point(caretLastDrawnX, caretLastDrawnY1),
13854 				Point(caretLastDrawnX, caretLastDrawnY2)
13855 			);
13856 
13857 			caretShowingOnScreen = false;
13858 			painter.rasterOp = RasterOp.normal;
13859 		}
13860 
13861 		/// Caret movement api
13862 		/// These should give the user a logical result based on what they see on screen...
13863 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
13864 		void moveUp() {
13865 			if(caret.inlineElement is null) return;
13866 			auto x = caret.inlineElement.xOfIndex(caret.offset);
13867 			auto y = caret.inlineElement.boundingBox.top + 2;
13868 
13869 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
13870 			if(y < 0)
13871 				return;
13872 
13873 			auto i = identify(x, y);
13874 
13875 			if(i.element) {
13876 				caret.inlineElement = i.element;
13877 				caret.offset = i.offset;
13878 			}
13879 		}
13880 		void moveDown() {
13881 			if(caret.inlineElement is null) return;
13882 			auto x = caret.inlineElement.xOfIndex(caret.offset);
13883 			auto y = caret.inlineElement.boundingBox.bottom - 2;
13884 
13885 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
13886 
13887 			auto i = identify(x, y);
13888 			if(i.element) {
13889 				caret.inlineElement = i.element;
13890 				caret.offset = i.offset;
13891 			}
13892 		}
13893 		void moveLeft() {
13894 			if(caret.inlineElement is null) return;
13895 			if(caret.offset)
13896 				caret.offset--;
13897 			else {
13898 				auto p = caret.inlineElement.getPreviousInlineElement();
13899 				if(p) {
13900 					caret.inlineElement = p;
13901 					if(p.text.length && p.text[$-1] == '\n')
13902 						caret.offset = cast(int) p.text.length - 1;
13903 					else
13904 						caret.offset = cast(int) p.text.length;
13905 				}
13906 			}
13907 		}
13908 		void moveRight() {
13909 			if(caret.inlineElement is null) return;
13910 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
13911 				caret.offset++;
13912 			} else {
13913 				auto p = caret.inlineElement.getNextInlineElement();
13914 				if(p) {
13915 					caret.inlineElement = p;
13916 					caret.offset = 0;
13917 				}
13918 			}
13919 		}
13920 		void moveHome() {
13921 			if(caret.inlineElement is null) return;
13922 			auto x = 0;
13923 			auto y = caret.inlineElement.boundingBox.top + 2;
13924 
13925 			auto i = identify(x, y);
13926 
13927 			if(i.element) {
13928 				caret.inlineElement = i.element;
13929 				caret.offset = i.offset;
13930 			}
13931 		}
13932 		void moveEnd() {
13933 			if(caret.inlineElement is null) return;
13934 			auto x = int.max;
13935 			auto y = caret.inlineElement.boundingBox.top + 2;
13936 
13937 			auto i = identify(x, y);
13938 
13939 			if(i.element) {
13940 				caret.inlineElement = i.element;
13941 				caret.offset = i.offset;
13942 			}
13943 
13944 		}
13945 		void movePageUp(ref Caret caret) {}
13946 		void movePageDown(ref Caret caret) {}
13947 
13948 		void moveDocumentStart(ref Caret caret) {
13949 			if(blocks.length && blocks[0].parts.length)
13950 				caret = Caret(this, blocks[0].parts[0], 0);
13951 			else
13952 				caret = Caret.init;
13953 		}
13954 
13955 		void moveDocumentEnd(ref Caret caret) {
13956 			if(blocks.length) {
13957 				auto parts = blocks[$-1].parts;
13958 				if(parts.length) {
13959 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
13960 				} else {
13961 					caret = Caret.init;
13962 				}
13963 			} else
13964 				caret = Caret.init;
13965 		}
13966 
13967 		void deleteSelection() {
13968 			if(selectionStart is selectionEnd)
13969 				return;
13970 
13971 			if(selectionStart.inlineElement is null) return;
13972 			if(selectionEnd.inlineElement is null) return;
13973 
13974 			assert(selectionStart.inlineElement !is null);
13975 			assert(selectionEnd.inlineElement !is null);
13976 
13977 			auto at = selectionStart.inlineElement;
13978 
13979 			if(selectionEnd.inlineElement is at) {
13980 				// same element, need to chop out
13981 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
13982 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
13983 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
13984 			} else {
13985 				// different elements, we can do it with slicing
13986 				at.text = at.text[0 .. selectionStart.offset];
13987 				if(selectionStart.offset < at.letterXs.length)
13988 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
13989 
13990 				at = at.getNextInlineElement();
13991 
13992 				while(at) {
13993 					if(at is selectionEnd.inlineElement) {
13994 						at.text = at.text[selectionEnd.offset .. $];
13995 						if(selectionEnd.offset < at.letterXs.length)
13996 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
13997 						selectionEnd.offset = 0;
13998 						break;
13999 					} else {
14000 						auto cfd = at;
14001 						cfd.text = null; // delete the whole thing
14002 
14003 						at = at.getNextInlineElement();
14004 
14005 						if(cfd.text.length == 0) {
14006 							// and remove cfd
14007 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
14008 								if(cfd.containingBlock.parts[a] is cfd) {
14009 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
14010 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
14011 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
14012 
14013 								}
14014 							}
14015 						}
14016 					}
14017 				}
14018 			}
14019 
14020 			caret = selectionEnd;
14021 			selectNone();
14022 
14023 			invalidateLayout();
14024 
14025 		}
14026 
14027 		/// Plain text editing api. These work at the current caret inside the selected inline element.
14028 		void insert(in char[] text) {
14029 			foreach(dchar ch; text)
14030 				insert(ch);
14031 		}
14032 		/// ditto
14033 		void insert(dchar ch) {
14034 
14035 			bool selectionDeleted = false;
14036 			if(selectionStart !is selectionEnd) {
14037 				deleteSelection();
14038 				selectionDeleted = true;
14039 			}
14040 
14041 			if(ch == 127) {
14042 				delete_();
14043 				return;
14044 			}
14045 			if(ch == 8) {
14046 				if(!selectionDeleted)
14047 					backspace();
14048 				return;
14049 			}
14050 
14051 			invalidateLayout();
14052 
14053 			if(ch == 13) ch = 10;
14054 			auto e = caret.inlineElement;
14055 			if(e is null) {
14056 				addText("" ~ cast(char) ch) ; // FIXME
14057 				return;
14058 			}
14059 
14060 			if(caret.offset == e.text.length) {
14061 				e.text ~= cast(char) ch; // FIXME
14062 				caret.offset++;
14063 				if(ch == 10) {
14064 					auto c = caret.inlineElement.clone;
14065 					c.text = null;
14066 					c.letterXs = null;
14067 					insertPartAfter(c,e);
14068 					caret = Caret(this, c, 0);
14069 				}
14070 			} else {
14071 				// FIXME cast char sucks
14072 				if(ch == 10) {
14073 					auto c = caret.inlineElement.clone;
14074 					c.text = e.text[caret.offset .. $];
14075 					if(caret.offset < c.letterXs.length)
14076 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
14077 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
14078 					if(caret.offset <= e.letterXs.length) {
14079 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
14080 					}
14081 					insertPartAfter(c,e);
14082 					caret = Caret(this, c, 0);
14083 				} else {
14084 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
14085 					caret.offset++;
14086 				}
14087 			}
14088 		}
14089 
14090 		void insertPartAfter(InlineElement what, InlineElement where) {
14091 			foreach(idx, p; where.containingBlock.parts) {
14092 				if(p is where) {
14093 					if(idx + 1 == where.containingBlock.parts.length)
14094 						where.containingBlock.parts ~= what;
14095 					else
14096 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
14097 					return;
14098 				}
14099 			}
14100 		}
14101 
14102 		void cleanupStructures() {
14103 			for(size_t i = 0; i < blocks.length; i++) {
14104 				auto block = blocks[i];
14105 				for(size_t a = 0; a < block.parts.length; a++) {
14106 					auto part = block.parts[a];
14107 					if(part.text.length == 0) {
14108 						for(size_t b = a; b < block.parts.length - 1; b++)
14109 							block.parts[b] = block.parts[b+1];
14110 						block.parts = block.parts[0 .. $-1];
14111 					}
14112 				}
14113 				if(block.parts.length == 0) {
14114 					for(size_t a = i; a < blocks.length - 1; a++)
14115 						blocks[a] = blocks[a+1];
14116 					blocks = blocks[0 .. $-1];
14117 				}
14118 			}
14119 		}
14120 
14121 		void backspace() {
14122 			try_again:
14123 			auto e = caret.inlineElement;
14124 			if(e is null)
14125 				return;
14126 			if(caret.offset == 0) {
14127 				auto prev = e.getPreviousInlineElement();
14128 				if(prev is null)
14129 					return;
14130 				auto newOffset = cast(int) prev.text.length;
14131 				tryMerge(prev, e);
14132 				caret.inlineElement = prev;
14133 				caret.offset = prev is null ? 0 : newOffset;
14134 
14135 				goto try_again;
14136 			} else if(caret.offset == e.text.length) {
14137 				e.text = e.text[0 .. $-1];
14138 				caret.offset--;
14139 			} else {
14140 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
14141 				caret.offset--;
14142 			}
14143 			//cleanupStructures();
14144 
14145 			invalidateLayout();
14146 		}
14147 		void delete_() {
14148 			if(selectionStart !is selectionEnd)
14149 				deleteSelection();
14150 			else {
14151 				auto before = caret;
14152 				moveRight();
14153 				if(caret != before) {
14154 					backspace();
14155 				}
14156 			}
14157 
14158 			invalidateLayout();
14159 		}
14160 		void overstrike() {}
14161 
14162 		/// Selection API. See also: caret movement.
14163 		void selectAll() {
14164 			moveDocumentStart(selectionStart);
14165 			moveDocumentEnd(selectionEnd);
14166 		}
14167 		bool selectNone() {
14168 			if(selectionStart != selectionEnd) {
14169 				selectionStart = selectionEnd = Caret.init;
14170 				return true;
14171 			}
14172 			return false;
14173 		}
14174 
14175 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
14176 		/// They will modify the current selection if there is one and will splice one in if needed.
14177 		void changeAttributes() {}
14178 
14179 
14180 		/// Text search api. They manipulate the selection and/or caret.
14181 		void findText(string text) {}
14182 		void findIndex(size_t textIndex) {}
14183 
14184 		// sample event handlers
14185 
14186 		void handleEvent(KeyEvent event) {
14187 			//if(event.type == KeyEvent.Type.KeyPressed) {
14188 
14189 			//}
14190 		}
14191 
14192 		void handleEvent(dchar ch) {
14193 
14194 		}
14195 
14196 		void handleEvent(MouseEvent event) {
14197 
14198 		}
14199 
14200 		bool contentEditable; // can it be edited?
14201 		bool contentCaretable; // is there a caret/cursor that moves around in there?
14202 		bool contentSelectable; // selectable?
14203 
14204 		Caret caret;
14205 		Caret selectionStart;
14206 		Caret selectionEnd;
14207 
14208 		bool insertMode;
14209 	}
14210 
14211 	struct Caret {
14212 		TextLayout layout;
14213 		InlineElement inlineElement;
14214 		int offset;
14215 	}
14216 
14217 	enum TextFormat : ushort {
14218 		// decorations
14219 		underline = 1,
14220 		strikethrough = 2,
14221 
14222 		// font selectors
14223 
14224 		bold = 0x4000 | 1, // weight 700
14225 		light = 0x4000 | 2, // weight 300
14226 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
14227 		// bold | light is really invalid but should give weight 500
14228 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
14229 
14230 		italic = 0x4000 | 8,
14231 		smallcaps = 0x4000 | 16,
14232 	}
14233 
14234 	void* findFont(string family, int weight, TextFormat formats) {
14235 		return null;
14236 	}
14237 
14238 }
14239 
14240 static if(UsingSimpledisplayX11) {
14241 
14242 enum _NET_WM_STATE_ADD = 1;
14243 enum _NET_WM_STATE_REMOVE = 0;
14244 enum _NET_WM_STATE_TOGGLE = 2;
14245 
14246 /// X-specific. Use [SimpleWindow.requestAttention] instead for most casesl
14247 void demandAttention(SimpleWindow window, bool needs = true) {
14248 	demandAttention(window.impl.window, needs);
14249 }
14250 
14251 /// ditto
14252 void demandAttention(Window window, bool needs = true) {
14253 	auto display = XDisplayConnection.get();
14254 	auto atom = XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", true);
14255 	if(atom == None)
14256 		return; // non-failure error
14257 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
14258 
14259 	XClientMessageEvent xclient;
14260 
14261 	xclient.type = EventType.ClientMessage;
14262 	xclient.window = window;
14263 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
14264 	xclient.format = 32;
14265 	xclient.data.l[0] = needs ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
14266 	xclient.data.l[1] = atom;
14267 	//xclient.data.l[2] = atom2;
14268 	// [2] == a second property
14269 	// [3] == source. 0 == unknown, 1 == app, 2 == else
14270 
14271 	XSendEvent(
14272 		display,
14273 		RootWindow(display, DefaultScreen(display)),
14274 		false,
14275 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
14276 		cast(XEvent*) &xclient
14277 	);
14278 
14279 	/+
14280 	XChangeProperty(
14281 		display,
14282 		window.impl.window,
14283 		GetAtom!"_NET_WM_STATE"(display),
14284 		XA_ATOM,
14285 		32 /* bits */,
14286 		PropModeAppend,
14287 		&atom,
14288 		1);
14289 	+/
14290 }
14291 
14292 /// X-specific
14293 TrueColorImage getWindowNetWmIcon(Window window) {
14294 	auto display = XDisplayConnection.get;
14295 
14296 	auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
14297 
14298 	if (data.length > arch_ulong.sizeof * 2) {
14299 		auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
14300 		// these are an array of rgba images that we have to convert into pixmaps ourself
14301 
14302 		int width = cast(int) meta[0];
14303 		int height = cast(int) meta[1];
14304 
14305 		auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
14306 
14307 		static if(arch_ulong.sizeof == 4) {
14308 			bytes = bytes[0 .. width * height * 4];
14309 			alias imageData = bytes;
14310 		} else static if(arch_ulong.sizeof == 8) {
14311 			bytes = bytes[0 .. width * height * 8];
14312 			auto imageData = new ubyte[](4 * width * height);
14313 		} else static assert(0);
14314 
14315 
14316 
14317 		// this returns ARGB. Remember it is little-endian so
14318 		//                                         we have BGRA
14319 		// our thing uses RGBA, which in little endian, is ABGR
14320 		for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
14321 			auto r = bytes[idx + 2];
14322 			auto g = bytes[idx + 1];
14323 			auto b = bytes[idx + 0];
14324 			auto a = bytes[idx + 3];
14325 
14326 			imageData[idx2 + 0] = r;
14327 			imageData[idx2 + 1] = g;
14328 			imageData[idx2 + 2] = b;
14329 			imageData[idx2 + 3] = a;
14330 		}
14331 
14332 		return new TrueColorImage(width, height, imageData);
14333 	}
14334 
14335 	return null;
14336 }
14337 
14338 }
14339 
14340 
14341 void loadBinNameToWindowClassName () {
14342 	import core.stdc.stdlib : realloc;
14343 	version(linux) {
14344 		// args[0] MAY be empty, so we'll just use this
14345 		import core.sys.posix.unistd : readlink;
14346 		char[1024] ebuf = void; // 1KB should be enough for everyone!
14347 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
14348 		if (len < 1) return;
14349 	} else /*version(Windows)*/ {
14350 		import core.runtime : Runtime;
14351 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
14352 		auto ebuf = Runtime.args[0];
14353 		auto len = ebuf.length;
14354 	}
14355 	auto pos = len;
14356 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
14357 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
14358 	if (sdpyWindowClassStr is null) return; // oops
14359 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
14360 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
14361 }
14362 
14363 /++
14364 	An interface representing a font.
14365 
14366 	This is still MAJOR work in progress.
14367 +/
14368 interface DrawableFont {
14369 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
14370 }
14371 
14372 /++
14373 	Loads a true type font using [arsd.ttf]. That module must be compiled
14374 	in if you choose to use this function.
14375 
14376 	Be warned: this can be slow and memory hungry, especially on remote connections
14377 	to the X server.
14378 
14379 	This is still MAJOR work in progress.
14380 +/
14381 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
14382 	import arsd.ttf;
14383 	static class ArsdTtfFont : DrawableFont {
14384 		TtfFont font;
14385 		int size;
14386 		this(in ubyte[] data, int size) {
14387 			font = TtfFont(data);
14388 			this.size = size;
14389 		}
14390 
14391 		Sprite[string] cache;
14392 
14393 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
14394 			Sprite sprite = (text in cache) ? *(text in cache) : null;
14395 
14396 			auto fg = painter.impl._outlineColor;
14397 			auto bg = painter.impl._fillColor;
14398 
14399 			if(sprite is null) {
14400 				int width, height;
14401 				auto data = font.renderString(text, size, width, height);
14402 				auto image = new TrueColorImage(width, height);
14403 				int pos = 0;
14404 				foreach(y; 0 .. height)
14405 				foreach(x; 0 .. width) {
14406 					fg.a = data[0];
14407 					bg.a = 255;
14408 					auto color = alphaBlend(fg, bg);
14409 					image.imageData.bytes[pos++] = color.r;
14410 					image.imageData.bytes[pos++] = color.g;
14411 					image.imageData.bytes[pos++] = color.b;
14412 					image.imageData.bytes[pos++] = data[0];
14413 					data = data[1 .. $];
14414 				}
14415 				assert(data.length == 0);
14416 
14417 				sprite = new Sprite(painter.window, Image.fromMemoryImage(image));
14418 				cache[text.idup] = sprite;
14419 			}
14420 
14421 			sprite.drawAt(painter, upperLeft);
14422 		}
14423 	}
14424 
14425 	return new ArsdTtfFont(data, size);
14426 }
14427 
14428 class NotYetImplementedException : Exception {
14429 	this(string file = __FILE__, size_t line = __LINE__) {
14430 		super("Not yet implemented", file, line);
14431 	}
14432 }
14433 
14434 private alias scriptable = arsd_jsvar_compatible;