1 /++
2 	Module for supporting cursor and color manipulation on the console as well
3 	as full-featured real-time input.
4 
5 	The main interface for this module is the Terminal struct, which
6 	encapsulates the output functions and line-buffered input of the terminal, and
7 	RealTimeConsoleInput, which gives real time input.
8 	
9 	Creating an instance of these structs will perform console initialization. When the struct
10 	goes out of scope, any changes in console settings will be automatically reverted.
11 
12 	Note: on Posix, it traps SIGINT and translates it into an input event. You should
13 	keep your event loop moving and keep an eye open for this to exit cleanly; simply break
14 	your event loop upon receiving a UserInterruptionEvent. (Without
15 	the signal handler, ctrl+c can leave your terminal in a bizarre state.)
16 
17 	As a user, if you have to forcibly kill your program and the event doesn't work, there's still ctrl+\
18 
19 	On Mac Terminal btw, a lot of hacks are needed and mouse support doesn't work. Most functions basically
20 	work now though.
21 
22 	ROADMAP:
23 		* The CharacterEvent and NonCharacterKeyEvent types will be removed. Instead, use KeyboardEvent
24 		  on new programs.
25 
26 		* The ScrollbackBuffer will be expanded to be easier to use to partition your screen. It might even
27 		  handle input events of some sort. Its API may change.
28 
29 		* getline I want to be really easy to use both for code and end users. It will need multi-line support
30 		  eventually.
31 
32 		* I might add an expandable event loop and base level widget classes. This may be Linux-specific in places and may overlap with similar functionality in simpledisplay.d. If I can pull it off without a third module, I want them to be compatible with each other too so the two modules can be combined easily. (Currently, they are both compatible with my eventloop.d and can be easily combined through it, but that is a third module.)
33 
34 		* More advanced terminal features as functions, where available, like cursor changing and full-color functions.
35 
36 		* The module will eventually be renamed to `arsd.terminal`.
37 
38 		* More documentation.
39 
40 	WHAT I WON'T DO:
41 		* support everything under the sun. If it isn't default-installed on an OS I or significant number of other people
42 		  might actually use, and isn't written by me, I don't really care about it. This means the only supported terminals are:
43 
44 		  - xterm (and decently xterm compatible emulators like Konsole)
45 		  - Windows console
46 		  - rxvt (to a lesser extent)
47 		  - Linux console
48 		  - My terminal emulator family of applications <https://github.com/adamdruppe/terminal-emulator>
49 
50 		  Anything else is cool if it does work, but I don't want to go out of my way for it.
51 
52 		* Use other libraries, unless strictly optional. terminal.d is a stand-alone module by default and
53 		  always will be.
54 
55 		* Do a full TUI widget set. I might do some basics and lay a little groundwork, but a full TUI
56 		  is outside the scope of this module (unless I can do it really small.)
57 +/
58 module terminal;
59 
60 /*
61 	Widgets:
62 		tab widget
63 		scrollback buffer
64 		partitioned canvas
65 */
66 
67 // FIXME: ctrl+d eof on stdin
68 
69 // FIXME: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx
70 
71 version(Posix) {
72 	enum SIGWINCH = 28;
73 	__gshared bool windowSizeChanged = false;
74 	__gshared bool interrupted = false; /// you might periodically check this in a long operation and abort if it is set. Remember it is volatile. It is also sent through the input event loop via RealTimeConsoleInput
75 	__gshared bool hangedUp = false; /// similar to interrupted.
76 
77 	version(with_eventloop)
78 		struct SignalFired {}
79 
80 	extern(C)
81 	void sizeSignalHandler(int sigNumber) nothrow {
82 		windowSizeChanged = true;
83 		version(with_eventloop) {
84 			import arsd.eventloop;
85 			try
86 				send(SignalFired());
87 			catch(Exception) {}
88 		}
89 	}
90 	extern(C)
91 	void interruptSignalHandler(int sigNumber) nothrow {
92 		interrupted = true;
93 		version(with_eventloop) {
94 			import arsd.eventloop;
95 			try
96 				send(SignalFired());
97 			catch(Exception) {}
98 		}
99 	}
100 	extern(C)
101 	void hangupSignalHandler(int sigNumber) nothrow {
102 		hangedUp = true;
103 		version(with_eventloop) {
104 			import arsd.eventloop;
105 			try
106 				send(SignalFired());
107 			catch(Exception) {}
108 		}
109 	}
110 
111 }
112 
113 // parts of this were taken from Robik's ConsoleD
114 // https://github.com/robik/ConsoleD/blob/master/consoled.d
115 
116 // Uncomment this line to get a main() to demonstrate this module's
117 // capabilities.
118 //version = Demo
119 
120 version(Windows) {
121 	import core.sys.windows.windows;
122 	import std.string : toStringz;
123 	private {
124 		enum RED_BIT = 4;
125 		enum GREEN_BIT = 2;
126 		enum BLUE_BIT = 1;
127 	}
128 }
129 
130 version(Posix) {
131 	import core.sys.posix.termios;
132 	import core.sys.posix.unistd;
133 	import unix = core.sys.posix.unistd;
134 	import core.sys.posix.sys.types;
135 	import core.sys.posix.sys.time;
136 	import core.stdc.stdio;
137 	private {
138 		enum RED_BIT = 1;
139 		enum GREEN_BIT = 2;
140 		enum BLUE_BIT = 4;
141 	}
142 
143 	version(linux) {
144 		extern(C) int ioctl(int, int, ...);
145 		enum int TIOCGWINSZ = 0x5413;
146 	} else version(OSX) {
147 		import core.stdc.config;
148 		extern(C) int ioctl(int, c_ulong, ...);
149 		enum TIOCGWINSZ = 1074295912;
150 	} else static assert(0, "confirm the value of tiocgwinsz");
151 
152 	struct winsize {
153 		ushort ws_row;
154 		ushort ws_col;
155 		ushort ws_xpixel;
156 		ushort ws_ypixel;
157 	}
158 
159 	// I'm taking this from the minimal termcap from my Slackware box (which I use as my /etc/termcap) and just taking the most commonly used ones (for me anyway).
160 
161 	// this way we'll have some definitions for 99% of typical PC cases even without any help from the local operating system
162 
163 	enum string builtinTermcap = `
164 # Generic VT entry.
165 vg|vt-generic|Generic VT entries:\
166 	:bs:mi:ms:pt:xn:xo:it#8:\
167 	:RA=\E[?7l:SA=\E?7h:\
168 	:bl=^G:cr=^M:ta=^I:\
169 	:cm=\E[%i%d;%dH:\
170 	:le=^H:up=\E[A:do=\E[B:nd=\E[C:\
171 	:LE=\E[%dD:RI=\E[%dC:UP=\E[%dA:DO=\E[%dB:\
172 	:ho=\E[H:cl=\E[H\E[2J:ce=\E[K:cb=\E[1K:cd=\E[J:sf=\ED:sr=\EM:\
173 	:ct=\E[3g:st=\EH:\
174 	:cs=\E[%i%d;%dr:sc=\E7:rc=\E8:\
175 	:ei=\E[4l:ic=\E[@:IC=\E[%d@:al=\E[L:AL=\E[%dL:\
176 	:dc=\E[P:DC=\E[%dP:dl=\E[M:DL=\E[%dM:\
177 	:so=\E[7m:se=\E[m:us=\E[4m:ue=\E[m:\
178 	:mb=\E[5m:mh=\E[2m:md=\E[1m:mr=\E[7m:me=\E[m:\
179 	:sc=\E7:rc=\E8:kb=\177:\
180 	:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:
181 
182 
183 # Slackware 3.1 linux termcap entry (Sat Apr 27 23:03:58 CDT 1996):
184 lx|linux|console|con80x25|LINUX System Console:\
185         :do=^J:co#80:li#25:cl=\E[H\E[J:sf=\ED:sb=\EM:\
186         :le=^H:bs:am:cm=\E[%i%d;%dH:nd=\E[C:up=\E[A:\
187         :ce=\E[K:cd=\E[J:so=\E[7m:se=\E[27m:us=\E[36m:ue=\E[m:\
188         :md=\E[1m:mr=\E[7m:mb=\E[5m:me=\E[m:is=\E[1;25r\E[25;1H:\
189         :ll=\E[1;25r\E[25;1H:al=\E[L:dc=\E[P:dl=\E[M:\
190         :it#8:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:kb=^H:ti=\E[r\E[H:\
191         :ho=\E[H:kP=\E[5~:kN=\E[6~:kH=\E[4~:kh=\E[1~:kD=\E[3~:kI=\E[2~:\
192         :k1=\E[[A:k2=\E[[B:k3=\E[[C:k4=\E[[D:k5=\E[[E:k6=\E[17~:\
193 	:F1=\E[23~:F2=\E[24~:\
194         :k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:K1=\E[1~:K2=\E[5~:\
195         :K4=\E[4~:K5=\E[6~:\
196         :pt:sr=\EM:vt#3:xn:km:bl=^G:vi=\E[?25l:ve=\E[?25h:vs=\E[?25h:\
197         :sc=\E7:rc=\E8:cs=\E[%i%d;%dr:\
198         :r1=\Ec:r2=\Ec:r3=\Ec:
199 
200 # Some other, commonly used linux console entries.
201 lx|con80x28:co#80:li#28:tc=linux:
202 lx|con80x43:co#80:li#43:tc=linux:
203 lx|con80x50:co#80:li#50:tc=linux:
204 lx|con100x37:co#100:li#37:tc=linux:
205 lx|con100x40:co#100:li#40:tc=linux:
206 lx|con132x43:co#132:li#43:tc=linux:
207 
208 # vt102 - vt100 + insert line etc. VT102 does not have insert character.
209 v2|vt102|DEC vt102 compatible:\
210 	:co#80:li#24:\
211 	:ic@:IC@:\
212 	:is=\E[m\E[?1l\E>:\
213 	:rs=\E[m\E[?1l\E>:\
214 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
215 	:ks=:ke=:\
216 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:\
217 	:tc=vt-generic:
218 
219 # vt100 - really vt102 without insert line, insert char etc.
220 vt|vt100|DEC vt100 compatible:\
221 	:im@:mi@:al@:dl@:ic@:dc@:AL@:DL@:IC@:DC@:\
222 	:tc=vt102:
223 
224 
225 # Entry for an xterm. Insert mode has been disabled.
226 vs|xterm|xterm-color|xterm-256color|vs100|xterm terminal emulator (X Window System):\
227 	:am:bs:mi@:km:co#80:li#55:\
228 	:im@:ei@:\
229 	:cl=\E[H\E[J:\
230 	:ct=\E[3k:ue=\E[m:\
231 	:is=\E[m\E[?1l\E>:\
232 	:rs=\E[m\E[?1l\E>:\
233 	:vi=\E[?25l:ve=\E[?25h:\
234 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
235 	:kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\
236 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\E[15~:\
237 	:k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\
238 	:F1=\E[23~:F2=\E[24~:\
239 	:kh=\E[H:kH=\E[F:\
240 	:ks=:ke=:\
241 	:te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\
242 	:tc=vt-generic:
243 
244 
245 #rxvt, added by me
246 rxvt|rxvt-unicode:\
247 	:am:bs:mi@:km:co#80:li#55:\
248 	:im@:ei@:\
249 	:ct=\E[3k:ue=\E[m:\
250 	:is=\E[m\E[?1l\E>:\
251 	:rs=\E[m\E[?1l\E>:\
252 	:vi=\E[?25l:\
253 	:ve=\E[?25h:\
254 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
255 	:kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\
256 	:k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\
257 	:k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\
258 	:F1=\E[23~:F2=\E[24~:\
259 	:kh=\E[7~:kH=\E[8~:\
260 	:ks=:ke=:\
261 	:te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\
262 	:tc=vt-generic:
263 
264 
265 # Some other entries for the same xterm.
266 v2|xterms|vs100s|xterm small window:\
267 	:co#80:li#24:tc=xterm:
268 vb|xterm-bold|xterm with bold instead of underline:\
269 	:us=\E[1m:tc=xterm:
270 vi|xterm-ins|xterm with insert mode:\
271 	:mi:im=\E[4h:ei=\E[4l:tc=xterm:
272 
273 Eterm|Eterm Terminal Emulator (X11 Window System):\
274         :am:bw:eo:km:mi:ms:xn:xo:\
275         :co#80:it#8:li#24:lm#0:pa#64:Co#8:AF=\E[3%dm:AB=\E[4%dm:op=\E[39m\E[49m:\
276         :AL=\E[%dL:DC=\E[%dP:DL=\E[%dM:DO=\E[%dB:IC=\E[%d@:\
277         :K1=\E[7~:K2=\EOu:K3=\E[5~:K4=\E[8~:K5=\E[6~:LE=\E[%dD:\
278         :RI=\E[%dC:UP=\E[%dA:ae=^O:al=\E[L:as=^N:bl=^G:cd=\E[J:\
279         :ce=\E[K:cl=\E[H\E[2J:cm=\E[%i%d;%dH:cr=^M:\
280         :cs=\E[%i%d;%dr:ct=\E[3g:dc=\E[P:dl=\E[M:do=\E[B:\
281         :ec=\E[%dX:ei=\E[4l:ho=\E[H:i1=\E[?47l\E>\E[?1l:ic=\E[@:\
282         :im=\E[4h:is=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l:\
283         :k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\
284         :k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:kD=\E[3~:\
285         :kI=\E[2~:kN=\E[6~:kP=\E[5~:kb=^H:kd=\E[B:ke=:kh=\E[7~:\
286         :kl=\E[D:kr=\E[C:ks=:ku=\E[A:le=^H:mb=\E[5m:md=\E[1m:\
287         :me=\E[m\017:mr=\E[7m:nd=\E[C:rc=\E8:\
288         :sc=\E7:se=\E[27m:sf=^J:so=\E[7m:sr=\EM:st=\EH:ta=^I:\
289         :te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:ue=\E[24m:up=\E[A:\
290         :us=\E[4m:vb=\E[?5h\E[?5l:ve=\E[?25h:vi=\E[?25l:\
291         :ac=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~:
292 
293 # DOS terminal emulator such as Telix or TeleMate.
294 # This probably also works for the SCO console, though it's incomplete.
295 an|ansi|ansi-bbs|ANSI terminals (emulators):\
296 	:co#80:li#24:am:\
297 	:is=:rs=\Ec:kb=^H:\
298 	:as=\E[m:ae=:eA=:\
299 	:ac=0\333+\257,\256.\031-\030a\261f\370g\361j\331k\277l\332m\300n\305q\304t\264u\303v\301w\302x\263~\025:\
300 	:kD=\177:kH=\E[Y:kN=\E[U:kP=\E[V:kh=\E[H:\
301 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\EOT:\
302 	:k6=\EOU:k7=\EOV:k8=\EOW:k9=\EOX:k0=\EOY:\
303 	:tc=vt-generic:
304 
305 	`;
306 }
307 
308 enum Bright = 0x08;
309 
310 /// Defines the list of standard colors understood by Terminal.
311 enum Color : ushort {
312 	black = 0, /// .
313 	red = RED_BIT, /// .
314 	green = GREEN_BIT, /// .
315 	yellow = red | green, /// .
316 	blue = BLUE_BIT, /// .
317 	magenta = red | blue, /// .
318 	cyan = blue | green, /// .
319 	white = red | green | blue, /// .
320 	DEFAULT = 256,
321 }
322 
323 /// When capturing input, what events are you interested in?
324 ///
325 /// Note: these flags can be OR'd together to select more than one option at a time.
326 ///
327 /// Ctrl+C and other keyboard input is always captured, though it may be line buffered if you don't use raw.
328 /// The rationale for that is to ensure the Terminal destructor has a chance to run, since the terminal is a shared resource and should be put back before the program terminates.
329 enum ConsoleInputFlags {
330 	raw = 0, /// raw input returns keystrokes immediately, without line buffering
331 	echo = 1, /// do you want to automatically echo input back to the user?
332 	mouse = 2, /// capture mouse events
333 	paste = 4, /// capture paste events (note: without this, paste can come through as keystrokes)
334 	size = 8, /// window resize events
335 
336 	releasedKeys = 64, /// key release events. Not reliable on Posix.
337 
338 	allInputEvents = 8|4|2, /// subscribe to all input events. Note: in previous versions, this also returned release events. It no longer does, use allInputEventsWithRelease if you want them.
339 	allInputEventsWithRelease = allInputEvents|releasedKeys, /// subscribe to all input events, including (unreliable on Posix) key release events.
340 }
341 
342 /// Defines how terminal output should be handled.
343 enum ConsoleOutputType {
344 	linear = 0, /// do you want output to work one line at a time?
345 	cellular = 1, /// or do you want access to the terminal screen as a grid of characters?
346 	//truncatedCellular = 3, /// cellular, but instead of wrapping output to the next line automatically, it will truncate at the edges
347 
348 	minimalProcessing = 255, /// do the least possible work, skips most construction and desturction tasks. Only use if you know what you're doing here
349 }
350 
351 /// Some methods will try not to send unnecessary commands to the screen. You can override their judgement using a ForceOption parameter, if present
352 enum ForceOption {
353 	automatic = 0, /// automatically decide what to do (best, unless you know for sure it isn't right)
354 	neverSend = -1, /// never send the data. This will only update Terminal's internal state. Use with caution.
355 	alwaysSend = 1, /// always send the data, even if it doesn't seem necessary
356 }
357 
358 // we could do it with termcap too, getenv("TERMCAP") then split on : and replace \E with \033 and get the pieces
359 
360 /// Encapsulates the I/O capabilities of a terminal.
361 ///
362 /// Warning: do not write out escape sequences to the terminal. This won't work
363 /// on Windows and will confuse Terminal's internal state on Posix.
364 struct Terminal {
365 	@disable this();
366 	@disable this(this);
367 	private ConsoleOutputType type;
368 
369 	version(Posix) {
370 		private int fdOut;
371 		private int fdIn;
372 		private int[] delegate() getSizeOverride;
373 		void delegate(in void[]) _writeDelegate; // used to override the unix write() system call, set it magically
374 	}
375 
376 	version(Posix) {
377 		bool terminalInFamily(string[] terms...) {
378 			import std.process;
379 			import std.string;
380 			auto term = environment.get("TERM");
381 			foreach(t; terms)
382 				if(indexOf(term, t) != -1)
383 					return true;
384 
385 			return false;
386 		}
387 
388 		// This is a filthy hack because Terminal.app and OS X are garbage who don't
389 		// work the way they're advertised. I just have to best-guess hack and hope it
390 		// doesn't break anything else. (If you know a better way, let me know!)
391 		bool isMacTerminal() {
392 			import std.process;
393 			import std.string;
394 			auto term = environment.get("TERM");
395 			return term == "xterm-256color";
396 		}
397 
398 		static string[string] termcapDatabase;
399 		static void readTermcapFile(bool useBuiltinTermcap = false) {
400 			import std.file;
401 			import std.stdio;
402 			import std.string;
403 
404 			if(!exists("/etc/termcap"))
405 				useBuiltinTermcap = true;
406 
407 			string current;
408 
409 			void commitCurrentEntry() {
410 				if(current is null)
411 					return;
412 
413 				string names = current;
414 				auto idx = indexOf(names, ":");
415 				if(idx != -1)
416 					names = names[0 .. idx];
417 
418 				foreach(name; split(names, "|"))
419 					termcapDatabase[name] = current;
420 
421 				current = null;
422 			}
423 
424 			void handleTermcapLine(in char[] line) {
425 				if(line.length == 0) { // blank
426 					commitCurrentEntry();
427 					return; // continue
428 				}
429 				if(line[0] == '#') // comment
430 					return; // continue
431 				size_t termination = line.length;
432 				if(line[$-1] == '\\')
433 					termination--; // cut off the \\
434 				current ~= strip(line[0 .. termination]);
435 				// termcap entries must be on one logical line, so if it isn't continued, we know we're done
436 				if(line[$-1] != '\\')
437 					commitCurrentEntry();
438 			}
439 
440 			if(useBuiltinTermcap) {
441 				foreach(line; splitLines(builtinTermcap)) {
442 					handleTermcapLine(line);
443 				}
444 			} else {
445 				foreach(line; File("/etc/termcap").byLine()) {
446 					handleTermcapLine(line);
447 				}
448 			}
449 		}
450 
451 		static string getTermcapDatabase(string terminal) {
452 			import std.string;
453 
454 			if(termcapDatabase is null)
455 				readTermcapFile();
456 
457 			auto data = terminal in termcapDatabase;
458 			if(data is null)
459 				return null;
460 
461 			auto tc = *data;
462 			auto more = indexOf(tc, ":tc=");
463 			if(more != -1) {
464 				auto tcKey = tc[more + ":tc=".length .. $];
465 				auto end = indexOf(tcKey, ":");
466 				if(end != -1)
467 					tcKey = tcKey[0 .. end];
468 				tc = getTermcapDatabase(tcKey) ~ tc;
469 			}
470 
471 			return tc;
472 		}
473 
474 		string[string] termcap;
475 		void readTermcap() {
476 			import std.process;
477 			import std.string;
478 			import std.array;
479 
480 			string termcapData = environment.get("TERMCAP");
481 			if(termcapData.length == 0) {
482 				termcapData = getTermcapDatabase(environment.get("TERM"));
483 			}
484 
485 			auto e = replace(termcapData, "\\\n", "\n");
486 			termcap = null;
487 
488 			foreach(part; split(e, ":")) {
489 				// FIXME: handle numeric things too
490 
491 				auto things = split(part, "=");
492 				if(things.length)
493 					termcap[things[0]] =
494 						things.length > 1 ? things[1] : null;
495 			}
496 		}
497 
498 		string findSequenceInTermcap(in char[] sequenceIn) {
499 			char[10] sequenceBuffer;
500 			char[] sequence;
501 			if(sequenceIn.length > 0 && sequenceIn[0] == '\033') {
502 				if(!(sequenceIn.length < sequenceBuffer.length - 1))
503 					return null;
504 				sequenceBuffer[1 .. sequenceIn.length + 1] = sequenceIn[];
505 				sequenceBuffer[0] = '\\';
506 				sequenceBuffer[1] = 'E';
507 				sequence = sequenceBuffer[0 .. sequenceIn.length + 1];
508 			} else {
509 				sequence = sequenceBuffer[1 .. sequenceIn.length + 1];
510 			}
511 
512 			import std.array;
513 			foreach(k, v; termcap)
514 				if(v == sequence)
515 					return k;
516 			return null;
517 		}
518 
519 		string getTermcap(string key) {
520 			auto k = key in termcap;
521 			if(k !is null) return *k;
522 			return null;
523 		}
524 
525 		// Looks up a termcap item and tries to execute it. Returns false on failure
526 		bool doTermcap(T...)(string key, T t) {
527 			import std.conv;
528 			auto fs = getTermcap(key);
529 			if(fs is null)
530 				return false;
531 
532 			int swapNextTwo = 0;
533 
534 			R getArg(R)(int idx) {
535 				if(swapNextTwo == 2) {
536 					idx ++;
537 					swapNextTwo--;
538 				} else if(swapNextTwo == 1) {
539 					idx --;
540 					swapNextTwo--;
541 				}
542 
543 				foreach(i, arg; t) {
544 					if(i == idx)
545 						return to!R(arg);
546 				}
547 				assert(0, to!string(idx) ~ " is out of bounds working " ~ fs);
548 			}
549 
550 			char[256] buffer;
551 			int bufferPos = 0;
552 
553 			void addChar(char c) {
554 				import std.exception;
555 				enforce(bufferPos < buffer.length);
556 				buffer[bufferPos++] = c;
557 			}
558 
559 			void addString(in char[] c) {
560 				import std.exception;
561 				enforce(bufferPos + c.length < buffer.length);
562 				buffer[bufferPos .. bufferPos + c.length] = c[];
563 				bufferPos += c.length;
564 			}
565 
566 			void addInt(int c, int minSize) {
567 				import std.string;
568 				auto str = format("%0"~(minSize ? to!string(minSize) : "")~"d", c);
569 				addString(str);
570 			}
571 
572 			bool inPercent;
573 			int argPosition = 0;
574 			int incrementParams = 0;
575 			bool skipNext;
576 			bool nextIsChar;
577 			bool inBackslash;
578 
579 			foreach(char c; fs) {
580 				if(inBackslash) {
581 					if(c == 'E')
582 						addChar('\033');
583 					else
584 						addChar(c);
585 					inBackslash = false;
586 				} else if(nextIsChar) {
587 					if(skipNext)
588 						skipNext = false;
589 					else
590 						addChar(cast(char) (c + getArg!int(argPosition) + (incrementParams ? 1 : 0)));
591 					if(incrementParams) incrementParams--;
592 					argPosition++;
593 					inPercent = false;
594 				} else if(inPercent) {
595 					switch(c) {
596 						case '%':
597 							addChar('%');
598 							inPercent = false;
599 						break;
600 						case '2':
601 						case '3':
602 						case 'd':
603 							if(skipNext)
604 								skipNext = false;
605 							else
606 								addInt(getArg!int(argPosition) + (incrementParams ? 1 : 0),
607 									c == 'd' ? 0 : (c - '0')
608 								);
609 							if(incrementParams) incrementParams--;
610 							argPosition++;
611 							inPercent = false;
612 						break;
613 						case '.':
614 							if(skipNext)
615 								skipNext = false;
616 							else
617 								addChar(cast(char) (getArg!int(argPosition) + (incrementParams ? 1 : 0)));
618 							if(incrementParams) incrementParams--;
619 							argPosition++;
620 						break;
621 						case '+':
622 							nextIsChar = true;
623 							inPercent = false;
624 						break;
625 						case 'i':
626 							incrementParams = 2;
627 							inPercent = false;
628 						break;
629 						case 's':
630 							skipNext = true;
631 							inPercent = false;
632 						break;
633 						case 'b':
634 							argPosition--;
635 							inPercent = false;
636 						break;
637 						case 'r':
638 							swapNextTwo = 2;
639 							inPercent = false;
640 						break;
641 						// FIXME: there's more
642 						// http://www.gnu.org/software/termutils/manual/termcap-1.3/html_mono/termcap.html
643 
644 						default:
645 							assert(0, "not supported " ~ c);
646 					}
647 				} else {
648 					if(c == '%')
649 						inPercent = true;
650 					else if(c == '\\')
651 						inBackslash = true;
652 					else
653 						addChar(c);
654 				}
655 			}
656 
657 			writeStringRaw(buffer[0 .. bufferPos]);
658 			return true;
659 		}
660 	}
661 
662 	version(Posix)
663 	/**
664 	 * Constructs an instance of Terminal representing the capabilities of
665 	 * the current terminal.
666 	 *
667 	 * While it is possible to override the stdin+stdout file descriptors, remember
668 	 * that is not portable across platforms and be sure you know what you're doing.
669 	 *
670 	 * ditto on getSizeOverride. That's there so you can do something instead of ioctl.
671 	 */
672 	this(ConsoleOutputType type, int fdIn = 0, int fdOut = 1, int[] delegate() getSizeOverride = null) {
673 		this.fdIn = fdIn;
674 		this.fdOut = fdOut;
675 		this.getSizeOverride = getSizeOverride;
676 		this.type = type;
677 
678 		readTermcap();
679 
680 		if(type == ConsoleOutputType.minimalProcessing) {
681 			_suppressDestruction = true;
682 			return;
683 		}
684 
685 		if(type == ConsoleOutputType.cellular) {
686 			doTermcap("ti");
687 			clear();
688 			moveTo(0, 0, ForceOption.alwaysSend); // we need to know where the cursor is for some features to work, and moving it is easier than querying it
689 		}
690 
691 		if(terminalInFamily("xterm", "rxvt", "screen")) {
692 			writeStringRaw("\033[22;0t"); // save window title on a stack (support seems spotty, but it doesn't hurt to have it)
693 		}
694 	}
695 
696 	version(Windows) {
697 		HANDLE hConsole;
698 		CONSOLE_SCREEN_BUFFER_INFO originalSbi;
699 	}
700 
701 	version(Windows)
702 	/// ditto
703 	this(ConsoleOutputType type) {
704 		if(type == ConsoleOutputType.cellular) {
705 			hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, null, CONSOLE_TEXTMODE_BUFFER, null);
706 			if(hConsole == INVALID_HANDLE_VALUE) {
707 				import std.conv;
708 				throw new Exception(to!string(GetLastError()));
709 			}
710 
711 			SetConsoleActiveScreenBuffer(hConsole);
712 			/*
713 http://msdn.microsoft.com/en-us/library/windows/desktop/ms686125%28v=vs.85%29.aspx
714 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.aspx
715 			*/
716 			COORD size;
717 			/*
718 			CONSOLE_SCREEN_BUFFER_INFO sbi;
719 			GetConsoleScreenBufferInfo(hConsole, &sbi);
720 			size.X = cast(short) GetSystemMetrics(SM_CXMIN);
721 			size.Y = cast(short) GetSystemMetrics(SM_CYMIN);
722 			*/
723 
724 			// FIXME: this sucks, maybe i should just revert it. but there shouldn't be scrollbars in cellular mode
725 			//size.X = 80;
726 			//size.Y = 24;
727 			//SetConsoleScreenBufferSize(hConsole, size);
728 
729 			clear();
730 		} else {
731 			hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
732 		}
733 
734 		GetConsoleScreenBufferInfo(hConsole, &originalSbi);
735 	}
736 
737 	// only use this if you are sure you know what you want, since the terminal is a shared resource you generally really want to reset it to normal when you leave...
738 	bool _suppressDestruction;
739 
740 	version(Posix)
741 	~this() {
742 		if(_suppressDestruction) {
743 			flush();
744 			return;
745 		}
746 		if(type == ConsoleOutputType.cellular) {
747 			doTermcap("te");
748 		}
749 		if(terminalInFamily("xterm", "rxvt", "screen")) {
750 			writeStringRaw("\033[23;0t"); // restore window title from the stack
751 		}
752 		showCursor();
753 		reset();
754 		flush();
755 
756 		if(lineGetter !is null)
757 			lineGetter.dispose();
758 	}
759 
760 	version(Windows)
761 	~this() {
762 		flush(); // make sure user data is all flushed before resetting
763 		reset();
764 		showCursor();
765 
766 		if(lineGetter !is null)
767 			lineGetter.dispose();
768 
769 		auto stdo = GetStdHandle(STD_OUTPUT_HANDLE);
770 		SetConsoleActiveScreenBuffer(stdo);
771 		if(hConsole !is stdo)
772 			CloseHandle(hConsole);
773 	}
774 
775 	// lazily initialized and preserved between calls to getline for a bit of efficiency (only a bit)
776 	// and some history storage.
777 	LineGetter lineGetter;
778 
779 	int _currentForeground = Color.DEFAULT;
780 	int _currentBackground = Color.DEFAULT;
781 	bool reverseVideo = false;
782 
783 	/// Changes the current color. See enum Color for the values.
784 	void color(int foreground, int background, ForceOption force = ForceOption.automatic, bool reverseVideo = false) {
785 		if(force != ForceOption.neverSend) {
786 			version(Windows) {
787 				// assuming a dark background on windows, so LowContrast == dark which means the bit is NOT set on hardware
788 				/*
789 				foreground ^= LowContrast;
790 				background ^= LowContrast;
791 				*/
792 
793 				ushort setTof = cast(ushort) foreground;
794 				ushort setTob = cast(ushort) background;
795 
796 				// this isn't necessarily right but meh
797 				if(background == Color.DEFAULT)
798 					setTob = Color.black;
799 				if(foreground == Color.DEFAULT)
800 					setTof = Color.white;
801 
802 				if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) {
803 					flush(); // if we don't do this now, the buffering can screw up the colors...
804 					if(reverseVideo) {
805 						if(background == Color.DEFAULT)
806 							setTof = Color.black;
807 						else
808 							setTof = cast(ushort) background | (foreground & Bright);
809 
810 						if(background == Color.DEFAULT)
811 							setTob = Color.white;
812 						else
813 							setTob = cast(ushort) (foreground & ~Bright);
814 					}
815 					SetConsoleTextAttribute(
816 						hConsole,
817 						cast(ushort)((setTob << 4) | setTof));
818 				}
819 			} else {
820 				import std.process;
821 				// I started using this envvar for my text editor, but now use it elsewhere too
822 				// if we aren't set to dark, assume light
823 				/*
824 				if(getenv("ELVISBG") == "dark") {
825 					// LowContrast on dark bg menas
826 				} else {
827 					foreground ^= LowContrast;
828 					background ^= LowContrast;
829 				}
830 				*/
831 
832 				ushort setTof = cast(ushort) foreground & ~Bright;
833 				ushort setTob = cast(ushort) background & ~Bright;
834 
835 				if(foreground & Color.DEFAULT)
836 					setTof = 9; // ansi sequence for reset
837 				if(background == Color.DEFAULT)
838 					setTob = 9;
839 
840 				import std.string;
841 
842 				if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) {
843 					writeStringRaw(format("\033[%dm\033[3%dm\033[4%dm\033[%dm",
844 						(foreground != Color.DEFAULT && (foreground & Bright)) ? 1 : 0,
845 						cast(int) setTof,
846 						cast(int) setTob,
847 						reverseVideo ? 7 : 27
848 					));
849 				}
850 			}
851 		}
852 
853 		_currentForeground = foreground;
854 		_currentBackground = background;
855 		this.reverseVideo = reverseVideo;
856 	}
857 
858 	private bool _underlined = false;
859 
860 	/// Note: the Windows console does not support underlining
861 	void underline(bool set, ForceOption force = ForceOption.automatic) {
862 		if(set == _underlined && force != ForceOption.alwaysSend)
863 			return;
864 		version(Posix) {
865 			if(set)
866 				writeStringRaw("\033[4m");
867 			else
868 				writeStringRaw("\033[24m");
869 		}
870 		_underlined = set;
871 	}
872 	// FIXME: do I want to do bold and italic?
873 
874 	/// Returns the terminal to normal output colors
875 	void reset() {
876 		version(Windows)
877 			SetConsoleTextAttribute(
878 				hConsole,
879 				originalSbi.wAttributes);
880 		else
881 			writeStringRaw("\033[0m");
882 
883 		_underlined = false;
884 		_currentForeground = Color.DEFAULT;
885 		_currentBackground = Color.DEFAULT;
886 		reverseVideo = false;
887 	}
888 
889 	// FIXME: add moveRelative
890 
891 	/// The current x position of the output cursor. 0 == leftmost column
892 	@property int cursorX() {
893 		return _cursorX;
894 	}
895 
896 	/// The current y position of the output cursor. 0 == topmost row
897 	@property int cursorY() {
898 		return _cursorY;
899 	}
900 
901 	private int _cursorX;
902 	private int _cursorY;
903 
904 	/// Moves the output cursor to the given position. (0, 0) is the upper left corner of the screen. The force parameter can be used to force an update, even if Terminal doesn't think it is necessary
905 	void moveTo(int x, int y, ForceOption force = ForceOption.automatic) {
906 		if(force != ForceOption.neverSend && (force == ForceOption.alwaysSend || x != _cursorX || y != _cursorY)) {
907 			executeAutoHideCursor();
908 			version(Posix) {
909 				doTermcap("cm", y, x);
910 			} else version(Windows) {
911 
912 				flush(); // if we don't do this now, the buffering can screw up the position
913 				COORD coord = {cast(short) x, cast(short) y};
914 				SetConsoleCursorPosition(hConsole, coord);
915 			} else static assert(0);
916 		}
917 
918 		_cursorX = x;
919 		_cursorY = y;
920 	}
921 
922 	/// shows the cursor
923 	void showCursor() {
924 		version(Posix)
925 			doTermcap("ve");
926 		else {
927 			CONSOLE_CURSOR_INFO info;
928 			GetConsoleCursorInfo(hConsole, &info);
929 			info.bVisible = true;
930 			SetConsoleCursorInfo(hConsole, &info);
931 		}
932 	}
933 
934 	/// hides the cursor
935 	void hideCursor() {
936 		version(Posix) {
937 			doTermcap("vi");
938 		} else {
939 			CONSOLE_CURSOR_INFO info;
940 			GetConsoleCursorInfo(hConsole, &info);
941 			info.bVisible = false;
942 			SetConsoleCursorInfo(hConsole, &info);
943 		}
944 
945 	}
946 
947 	private bool autoHidingCursor;
948 	private bool autoHiddenCursor;
949 	// explicitly not publicly documented
950 	// Sets the cursor to automatically insert a hide command at the front of the output buffer iff it is moved.
951 	// Call autoShowCursor when you are done with the batch update.
952 	void autoHideCursor() {
953 		autoHidingCursor = true;
954 	}
955 
956 	private void executeAutoHideCursor() {
957 		if(autoHidingCursor) {
958 			version(Windows)
959 				hideCursor();
960 			else version(Posix) {
961 				// prepend the hide cursor command so it is the first thing flushed
962 				writeBuffer = "\033[?25l" ~ writeBuffer;
963 			}
964 
965 			autoHiddenCursor = true;
966 			autoHidingCursor = false; // already been done, don't insert the command again
967 		}
968 	}
969 
970 	// explicitly not publicly documented
971 	// Shows the cursor if it was automatically hidden by autoHideCursor and resets the internal auto hide state.
972 	void autoShowCursor() {
973 		if(autoHiddenCursor)
974 			showCursor();
975 
976 		autoHidingCursor = false;
977 		autoHiddenCursor = false;
978 	}
979 
980 	/*
981 	// alas this doesn't work due to a bunch of delegate context pointer and postblit problems
982 	// instead of using: auto input = terminal.captureInput(flags)
983 	// use: auto input = RealTimeConsoleInput(&terminal, flags);
984 	/// Gets real time input, disabling line buffering
985 	RealTimeConsoleInput captureInput(ConsoleInputFlags flags) {
986 		return RealTimeConsoleInput(&this, flags);
987 	}
988 	*/
989 
990 	/// Changes the terminal's title
991 	void setTitle(string t) {
992 		version(Windows) {
993 			SetConsoleTitleA(toStringz(t));
994 		} else {
995 			import std.string;
996 			if(terminalInFamily("xterm", "rxvt", "screen"))
997 				writeStringRaw(format("\033]0;%s\007", t));
998 		}
999 	}
1000 
1001 	/// Flushes your updates to the terminal.
1002 	/// It is important to call this when you are finished writing for now if you are using the version=with_eventloop
1003 	void flush() {
1004 		if(writeBuffer.length == 0)
1005 			return;
1006 
1007 		version(Posix) {
1008 			if(_writeDelegate !is null) {
1009 				_writeDelegate(writeBuffer);
1010 			} else {
1011 				ssize_t written;
1012 
1013 				while(writeBuffer.length) {
1014 					written = unix.write(this.fdOut, writeBuffer.ptr, writeBuffer.length);
1015 					if(written < 0)
1016 						throw new Exception("write failed for some reason");
1017 					writeBuffer = writeBuffer[written .. $];
1018 				}
1019 			}
1020 		} else version(Windows) {
1021 			import std.conv;
1022 			// FIXME: I'm not sure I'm actually happy with this allocation but
1023 			// it probably isn't a big deal. At least it has unicode support now.
1024 			wstring writeBufferw = to!wstring(writeBuffer);
1025 			while(writeBufferw.length) {
1026 				DWORD written;
1027 				WriteConsoleW(hConsole, writeBufferw.ptr, writeBufferw.length, &written, null);
1028 				writeBufferw = writeBufferw[written .. $];
1029 			}
1030 
1031 			writeBuffer = null;
1032 		}
1033 	}
1034 
1035 	int[] getSize() {
1036 		version(Windows) {
1037 			CONSOLE_SCREEN_BUFFER_INFO info;
1038 			GetConsoleScreenBufferInfo( hConsole, &info );
1039         
1040 			int cols, rows;
1041         
1042 			cols = (info.srWindow.Right - info.srWindow.Left + 1);
1043 			rows = (info.srWindow.Bottom - info.srWindow.Top + 1);
1044 
1045 			return [cols, rows];
1046 		} else {
1047 			if(getSizeOverride is null) {
1048 				winsize w;
1049 				ioctl(0, TIOCGWINSZ, &w);
1050 				return [w.ws_col, w.ws_row];
1051 			} else return getSizeOverride();
1052 		}
1053 	}
1054 
1055 	void updateSize() {
1056 		auto size = getSize();
1057 		_width = size[0];
1058 		_height = size[1];
1059 	}
1060 
1061 	private int _width;
1062 	private int _height;
1063 
1064 	/// The current width of the terminal (the number of columns)
1065 	@property int width() {
1066 		if(_width == 0 || _height == 0)
1067 			updateSize();
1068 		return _width;
1069 	}
1070 
1071 	/// The current height of the terminal (the number of rows)
1072 	@property int height() {
1073 		if(_width == 0 || _height == 0)
1074 			updateSize();
1075 		return _height;
1076 	}
1077 
1078 	/*
1079 	void write(T...)(T t) {
1080 		foreach(arg; t) {
1081 			writeStringRaw(to!string(arg));
1082 		}
1083 	}
1084 	*/
1085 
1086 	/// Writes to the terminal at the current cursor position.
1087 	void writef(T...)(string f, T t) {
1088 		import std.string;
1089 		writePrintableString(format(f, t));
1090 	}
1091 
1092 	/// ditto
1093 	void writefln(T...)(string f, T t) {
1094 		writef(f ~ "\n", t);
1095 	}
1096 
1097 	/// ditto
1098 	void write(T...)(T t) {
1099 		import std.conv;
1100 		string data;
1101 		foreach(arg; t) {
1102 			data ~= to!string(arg);
1103 		}
1104 
1105 		writePrintableString(data);
1106 	}
1107 
1108 	/// ditto
1109 	void writeln(T...)(T t) {
1110 		write(t, "\n");
1111 	}
1112 
1113 	/+
1114 	/// A combined moveTo and writef that puts the cursor back where it was before when it finishes the write.
1115 	/// Only works in cellular mode. 
1116 	/// Might give better performance than moveTo/writef because if the data to write matches the internal buffer, it skips sending anything (to override the buffer check, you can use moveTo and writePrintableString with ForceOption.alwaysSend)
1117 	void writefAt(T...)(int x, int y, string f, T t) {
1118 		import std.string;
1119 		auto toWrite = format(f, t);
1120 
1121 		auto oldX = _cursorX;
1122 		auto oldY = _cursorY;
1123 
1124 		writeAtWithoutReturn(x, y, toWrite);
1125 
1126 		moveTo(oldX, oldY);
1127 	}
1128 
1129 	void writeAtWithoutReturn(int x, int y, in char[] data) {
1130 		moveTo(x, y);
1131 		writeStringRaw(toWrite, ForceOption.alwaysSend);
1132 	}
1133 	+/
1134 
1135 	void writePrintableString(in char[] s, ForceOption force = ForceOption.automatic) {
1136 		// an escape character is going to mess things up. Actually any non-printable character could, but meh
1137 		// assert(s.indexOf("\033") == -1);
1138 
1139 		// tracking cursor position
1140 		foreach(ch; s) {
1141 			switch(ch) {
1142 				case '\n':
1143 					_cursorX = 0;
1144 					_cursorY++;
1145 				break;
1146 				case '\r':
1147 					_cursorX = 0;
1148 				break;
1149 				case '\t':
1150 					_cursorX ++;
1151 					_cursorX += _cursorX % 8; // FIXME: get the actual tabstop, if possible
1152 				break;
1153 				default:
1154 					if(ch <= 127) // way of only advancing once per dchar instead of per code unit
1155 						_cursorX++;
1156 			}
1157 
1158 			if(_wrapAround && _cursorX > width) {
1159 				_cursorX = 0;
1160 				_cursorY++;
1161 			}
1162 
1163 			if(_cursorY == height)
1164 				_cursorY--;
1165 
1166 			/+
1167 			auto index = getIndex(_cursorX, _cursorY);
1168 			if(data[index] != ch) {
1169 				data[index] = ch;
1170 			}
1171 			+/
1172 		}
1173 
1174 		writeStringRaw(s);
1175 	}
1176 
1177 	/* private */ bool _wrapAround = true;
1178 
1179 	deprecated alias writePrintableString writeString; /// use write() or writePrintableString instead
1180 
1181 	private string writeBuffer;
1182 
1183 	// you really, really shouldn't use this unless you know what you are doing
1184 	/*private*/ void writeStringRaw(in char[] s) {
1185 		// FIXME: make sure all the data is sent, check for errors
1186 		version(Posix) {
1187 			writeBuffer ~= s; // buffer it to do everything at once in flush() calls
1188 		} else version(Windows) {
1189 			writeBuffer ~= s;
1190 		} else static assert(0);
1191 	}
1192 
1193 	/// Clears the screen.
1194 	void clear() {
1195 		version(Posix) {
1196 			doTermcap("cl");
1197 		} else version(Windows) {
1198 			// http://support.microsoft.com/kb/99261
1199 			flush();
1200 
1201 			DWORD c;
1202 			CONSOLE_SCREEN_BUFFER_INFO csbi;
1203 			DWORD conSize;
1204 			GetConsoleScreenBufferInfo(hConsole, &csbi);
1205 			conSize = csbi.dwSize.X * csbi.dwSize.Y;
1206 			COORD coordScreen;
1207 			FillConsoleOutputCharacterA(hConsole, ' ', conSize, coordScreen, &c);
1208 			FillConsoleOutputAttribute(hConsole, csbi.wAttributes, conSize, coordScreen, &c);
1209 			moveTo(0, 0, ForceOption.alwaysSend);
1210 		}
1211 
1212 		_cursorX = 0;
1213 		_cursorY = 0;
1214 	}
1215 
1216 	/// gets a line, including user editing. Convenience method around the LineGetter class and RealTimeConsoleInput facilities - use them if you need more control.
1217 	/// You really shouldn't call this if stdin isn't actually a user-interactive terminal! So if you expect people to pipe data to your app, check for that or use something else.
1218 	// FIXME: add a method to make it easy to check if stdin is actually a tty and use other methods there.
1219 	string getline(string prompt = null) {
1220 		if(lineGetter is null)
1221 			lineGetter = new LineGetter(&this);
1222 		// since the struct might move (it shouldn't, this should be unmovable!) but since
1223 		// it technically might, I'm updating the pointer before using it just in case.
1224 		lineGetter.terminal = &this;
1225 
1226 		if(prompt !is null)
1227 			lineGetter.prompt = prompt;
1228 
1229 		auto input = RealTimeConsoleInput(&this, ConsoleInputFlags.raw);
1230 		auto line = lineGetter.getline(&input);
1231 
1232 		// lineGetter leaves us exactly where it was when the user hit enter, giving best
1233 		// flexibility to real-time input and cellular programs. The convenience function,
1234 		// however, wants to do what is right in most the simple cases, which is to actually
1235 		// print the line (echo would be enabled without RealTimeConsoleInput anyway and they
1236 		// did hit enter), so we'll do that here too.
1237 		writePrintableString("\n");
1238 
1239 		return line;
1240 	}
1241 
1242 }
1243 
1244 /+
1245 struct ConsoleBuffer {
1246 	int cursorX;
1247 	int cursorY;
1248 	int width;
1249 	int height;
1250 	dchar[] data;
1251 
1252 	void actualize(Terminal* t) {
1253 		auto writer = t.getBufferedWriter();
1254 
1255 		this.copyTo(&(t.onScreen));
1256 	}
1257 
1258 	void copyTo(ConsoleBuffer* buffer) {
1259 		buffer.cursorX = this.cursorX;
1260 		buffer.cursorY = this.cursorY;
1261 		buffer.width = this.width;
1262 		buffer.height = this.height;
1263 		buffer.data[] = this.data[];
1264 	}
1265 }
1266 +/
1267 
1268 /**
1269  * Encapsulates the stream of input events received from the terminal input.
1270  */
1271 struct RealTimeConsoleInput {
1272 	@disable this();
1273 	@disable this(this);
1274 
1275 	version(Posix) {
1276 		private int fdOut;
1277 		private int fdIn;
1278 		private sigaction_t oldSigWinch;
1279 		private sigaction_t oldSigIntr;
1280 		private sigaction_t oldHupIntr;
1281 		private termios old;
1282 		ubyte[128] hack;
1283 		// apparently termios isn't the size druntime thinks it is (at least on 32 bit, sometimes)....
1284 		// tcgetattr smashed other variables in here too that could create random problems
1285 		// so this hack is just to give some room for that to happen without destroying the rest of the world
1286 	}
1287 
1288 	version(Windows) {
1289 		private DWORD oldInput;
1290 		private DWORD oldOutput;
1291 		HANDLE inputHandle;
1292 	}
1293 
1294 	private ConsoleInputFlags flags;
1295 	private Terminal* terminal;
1296 	private void delegate()[] destructor;
1297 
1298 	/// To capture input, you need to provide a terminal and some flags.
1299 	public this(Terminal* terminal, ConsoleInputFlags flags) {
1300 		this.flags = flags;
1301 		this.terminal = terminal;
1302 
1303 		version(Windows) {
1304 			inputHandle = GetStdHandle(STD_INPUT_HANDLE);
1305 
1306 			GetConsoleMode(inputHandle, &oldInput);
1307 
1308 			DWORD mode = 0;
1309 			mode |= ENABLE_PROCESSED_INPUT /* 0x01 */; // this gives Ctrl+C which we probably want to be similar to linux
1310 			//if(flags & ConsoleInputFlags.size)
1311 			mode |= ENABLE_WINDOW_INPUT /* 0208 */; // gives size etc
1312 			if(flags & ConsoleInputFlags.echo)
1313 				mode |= ENABLE_ECHO_INPUT; // 0x4
1314 			if(flags & ConsoleInputFlags.mouse)
1315 				mode |= ENABLE_MOUSE_INPUT; // 0x10
1316 			// if(flags & ConsoleInputFlags.raw) // FIXME: maybe that should be a separate flag for ENABLE_LINE_INPUT
1317 
1318 			SetConsoleMode(inputHandle, mode);
1319 			destructor ~= { SetConsoleMode(inputHandle, oldInput); };
1320 
1321 
1322 			GetConsoleMode(terminal.hConsole, &oldOutput);
1323 			mode = 0;
1324 			// we want this to match linux too
1325 			mode |= ENABLE_PROCESSED_OUTPUT; /* 0x01 */
1326 			mode |= ENABLE_WRAP_AT_EOL_OUTPUT; /* 0x02 */
1327 			SetConsoleMode(terminal.hConsole, mode);
1328 			destructor ~= { SetConsoleMode(terminal.hConsole, oldOutput); };
1329 
1330 			// FIXME: change to UTF8 as well
1331 		}
1332 
1333 		version(Posix) {
1334 			this.fdIn = terminal.fdIn;
1335 			this.fdOut = terminal.fdOut;
1336 
1337 			if(fdIn != -1) {
1338 				tcgetattr(fdIn, &old);
1339 				auto n = old;
1340 
1341 				auto f = ICANON;
1342 				if(!(flags & ConsoleInputFlags.echo))
1343 					f |= ECHO;
1344 
1345 				n.c_lflag &= ~f;
1346 				tcsetattr(fdIn, TCSANOW, &n);
1347 			}
1348 
1349 			// some weird bug breaks this, https://github.com/robik/ConsoleD/issues/3
1350 			//destructor ~= { tcsetattr(fdIn, TCSANOW, &old); };
1351 
1352 			if(flags & ConsoleInputFlags.size) {
1353 				import core.sys.posix.signal;
1354 				sigaction_t n;
1355 				n.sa_handler = &sizeSignalHandler;
1356 				n.sa_mask = cast(sigset_t) 0;
1357 				n.sa_flags = 0;
1358 				sigaction(SIGWINCH, &n, &oldSigWinch);
1359 			}
1360 
1361 			{
1362 				import core.sys.posix.signal;
1363 				sigaction_t n;
1364 				n.sa_handler = &interruptSignalHandler;
1365 				n.sa_mask = cast(sigset_t) 0;
1366 				n.sa_flags = 0;
1367 				sigaction(SIGINT, &n, &oldSigIntr);
1368 			}
1369 
1370 			{
1371 				import core.sys.posix.signal;
1372 				sigaction_t n;
1373 				n.sa_handler = &hangupSignalHandler;
1374 				n.sa_mask = cast(sigset_t) 0;
1375 				n.sa_flags = 0;
1376 				sigaction(SIGHUP, &n, &oldHupIntr);
1377 			}
1378 
1379 
1380 
1381 			if(flags & ConsoleInputFlags.mouse) {
1382 				// basic button press+release notification
1383 
1384 				// FIXME: try to get maximum capabilities from all terminals
1385 				// right now this works well on xterm but rxvt isn't sending movements...
1386 
1387 				terminal.writeStringRaw("\033[?1000h");
1388 				destructor ~= { terminal.writeStringRaw("\033[?1000l"); };
1389 				// the MOUSE_HACK env var is for the case where I run screen
1390 				// but set TERM=xterm (which I do from putty). The 1003 mouse mode
1391 				// doesn't work there, breaking mouse support entirely. So by setting
1392 				// MOUSE_HACK=1002 it tells us to use the other mode for a fallback.
1393 				import std.process : environment;
1394 				if(terminal.terminalInFamily("xterm") && environment.get("MOUSE_HACK") != "1002") {
1395 					// this is vt200 mouse with full motion tracking, supported by xterm
1396 					terminal.writeStringRaw("\033[?1003h");
1397 					destructor ~= { terminal.writeStringRaw("\033[?1003l"); };
1398 				} else if(terminal.terminalInFamily("rxvt", "screen") || environment.get("MOUSE_HACK") == "1002") {
1399 					terminal.writeStringRaw("\033[?1002h"); // this is vt200 mouse with press/release and motion notification iff buttons are pressed
1400 					destructor ~= { terminal.writeStringRaw("\033[?1002l"); };
1401 				}
1402 			}
1403 			if(flags & ConsoleInputFlags.paste) {
1404 				if(terminal.terminalInFamily("xterm", "rxvt", "screen")) {
1405 					terminal.writeStringRaw("\033[?2004h"); // bracketed paste mode
1406 					destructor ~= { terminal.writeStringRaw("\033[?2004l"); };
1407 				}
1408 			}
1409 
1410 			// try to ensure the terminal is in UTF-8 mode
1411 			if(terminal.terminalInFamily("xterm", "screen", "linux") && !terminal.isMacTerminal()) {
1412 				terminal.writeStringRaw("\033%G");
1413 			}
1414 
1415 			terminal.flush();
1416 		}
1417 
1418 
1419 		version(with_eventloop) {
1420 			import arsd.eventloop;
1421 			version(Windows)
1422 				auto listenTo = inputHandle;
1423 			else version(Posix)
1424 				auto listenTo = this.fdIn;
1425 			else static assert(0, "idk about this OS");
1426 
1427 			version(Posix)
1428 			addListener(&signalFired);
1429 
1430 			if(listenTo != -1) {
1431 				addFileEventListeners(listenTo, &eventListener, null, null);
1432 				destructor ~= { removeFileEventListeners(listenTo); };
1433 			}
1434 			addOnIdle(&terminal.flush);
1435 			destructor ~= { removeOnIdle(&terminal.flush); };
1436 		}
1437 	}
1438 
1439 	version(with_eventloop) {
1440 		version(Posix)
1441 		void signalFired(SignalFired) {
1442 			if(interrupted) {
1443 				interrupted = false;
1444 				send(InputEvent(UserInterruptionEvent(), terminal));
1445 			}
1446 			if(windowSizeChanged)
1447 				send(checkWindowSizeChanged());
1448 			if(hangedUp) {
1449 				hangedUp = false;
1450 				send(InputEvent(HangupEvent(), terminal));
1451 			}
1452 		}
1453 
1454 		import arsd.eventloop;
1455 		void eventListener(OsFileHandle fd) {
1456 			auto queue = readNextEvents();
1457 			foreach(event; queue)
1458 				send(event);
1459 		}
1460 	}
1461 
1462 	~this() {
1463 		// the delegate thing doesn't actually work for this... for some reason
1464 		version(Posix)
1465 			if(fdIn != -1)
1466 				tcsetattr(fdIn, TCSANOW, &old);
1467 
1468 		version(Posix) {
1469 			if(flags & ConsoleInputFlags.size) {
1470 				// restoration
1471 				sigaction(SIGWINCH, &oldSigWinch, null);
1472 			}
1473 			sigaction(SIGINT, &oldSigIntr, null);
1474 			sigaction(SIGHUP, &oldHupIntr, null);
1475 		}
1476 
1477 		// we're just undoing everything the constructor did, in reverse order, same criteria
1478 		foreach_reverse(d; destructor)
1479 			d();
1480 	}
1481 
1482 	/**
1483 		Returns true if there iff getch() would not block.
1484 
1485 		WARNING: kbhit might consume input that would be ignored by getch. This
1486 		function is really only meant to be used in conjunction with getch. Typically,
1487 		you should use a full-fledged event loop if you want all kinds of input. kbhit+getch
1488 		are just for simple keyboard driven applications.
1489 	*/
1490 	bool kbhit() {
1491 		auto got = getch(true);
1492 
1493 		if(got == dchar.init)
1494 			return false;
1495 
1496 		getchBuffer = got;
1497 		return true;
1498 	}
1499 
1500 	/// Check for input, waiting no longer than the number of milliseconds
1501 	bool timedCheckForInput(int milliseconds) {
1502 		version(Windows) {
1503 			auto response = WaitForSingleObject(terminal.hConsole, milliseconds);
1504 			if(response  == 0)
1505 				return true; // the object is ready
1506 			return false;
1507 		} else version(Posix) {
1508 			if(fdIn == -1)
1509 				return false;
1510 
1511 			timeval tv;
1512 			tv.tv_sec = 0;
1513 			tv.tv_usec = milliseconds * 1000;
1514 
1515 			fd_set fs;
1516 			FD_ZERO(&fs);
1517 
1518 			FD_SET(fdIn, &fs);
1519 			select(fdIn + 1, &fs, null, null, &tv);
1520 
1521 			return FD_ISSET(fdIn, &fs);
1522 		}
1523 	}
1524 
1525 	private bool anyInput_internal() {
1526 		if(inputQueue.length || timedCheckForInput(0))
1527 			return true;
1528 		version(Posix)
1529 			if(interrupted || windowSizeChanged || hangedUp)
1530 				return true;
1531 		return false;
1532 	}
1533 
1534 	private dchar getchBuffer;
1535 
1536 	/// Get one key press from the terminal, discarding other
1537 	/// events in the process. Returns dchar.init upon receiving end-of-file.
1538 	///
1539 	/// Be aware that this may return non-character key events, like F1, F2, arrow keys, etc., as private use Unicode characters. Check them against KeyboardEvent.Key if you like.
1540 	dchar getch(bool nonblocking = false) {
1541 		if(getchBuffer != dchar.init) {
1542 			auto a = getchBuffer;
1543 			getchBuffer = dchar.init;
1544 			return a;
1545 		}
1546 
1547 		if(nonblocking && !anyInput_internal())
1548 			return dchar.init;
1549 
1550 		auto event = nextEvent();
1551 		while(event.type != InputEvent.Type.KeyboardEvent || event.keyboardEvent.pressed == false) {
1552 			if(event.type == InputEvent.Type.UserInterruptionEvent)
1553 				throw new UserInterruptionException();
1554 			if(event.type == InputEvent.Type.HangupEvent)
1555 				throw new HangupException();
1556 			if(event.type == InputEvent.Type.EndOfFileEvent)
1557 				return dchar.init;
1558 
1559 			if(nonblocking && !anyInput_internal())
1560 				return dchar.init;
1561 
1562 			event = nextEvent();
1563 		}
1564 		return event.keyboardEvent.which;
1565 	}
1566 
1567 	//char[128] inputBuffer;
1568 	//int inputBufferPosition;
1569 	version(Posix)
1570 	int nextRaw(bool interruptable = false) {
1571 		if(fdIn == -1)
1572 			return 0;
1573 
1574 		char[1] buf;
1575 		try_again:
1576 		auto ret = read(fdIn, buf.ptr, buf.length);
1577 		if(ret == 0)
1578 			return 0; // input closed
1579 		if(ret == -1) {
1580 			import core.stdc.errno;
1581 			if(errno == EINTR)
1582 				// interrupted by signal call, quite possibly resize or ctrl+c which we want to check for in the event loop
1583 				if(interruptable)
1584 					return -1;
1585 				else
1586 					goto try_again;
1587 			else
1588 				throw new Exception("read failed");
1589 		}
1590 
1591 		//terminal.writef("RAW READ: %d\n", buf[0]);
1592 
1593 		if(ret == 1)
1594 			return inputPrefilter ? inputPrefilter(buf[0]) : buf[0];
1595 		else
1596 			assert(0); // read too much, should be impossible
1597 	}
1598 
1599 	version(Posix)
1600 		int delegate(char) inputPrefilter;
1601 
1602 	version(Posix)
1603 	dchar nextChar(int starting) {
1604 		if(starting <= 127)
1605 			return cast(dchar) starting;
1606 		char[6] buffer;
1607 		int pos = 0;
1608 		buffer[pos++] = cast(char) starting;
1609 
1610 		// see the utf-8 encoding for details
1611 		int remaining = 0;
1612 		ubyte magic = starting & 0xff;
1613 		while(magic & 0b1000_000) {
1614 			remaining++;
1615 			magic <<= 1;
1616 		}
1617 
1618 		while(remaining && pos < buffer.length) {
1619 			buffer[pos++] = cast(char) nextRaw();
1620 			remaining--;
1621 		}
1622 
1623 		import std.utf;
1624 		size_t throwAway; // it insists on the index but we don't care
1625 		return decode(buffer[], throwAway);
1626 	}
1627 
1628 	InputEvent checkWindowSizeChanged() {
1629 		auto oldWidth = terminal.width;
1630 		auto oldHeight = terminal.height;
1631 		terminal.updateSize();
1632 		version(Posix)
1633 		windowSizeChanged = false;
1634 		return InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal);
1635 	}
1636 
1637 
1638 	// character event
1639 	// non-character key event
1640 	// paste event
1641 	// mouse event
1642 	// size event maybe, and if appropriate focus events
1643 
1644 	/// Returns the next event.
1645 	///
1646 	/// Experimental: It is also possible to integrate this into
1647 	/// a generic event loop, currently under -version=with_eventloop and it will
1648 	/// require the module arsd.eventloop (Linux only at this point)
1649 	InputEvent nextEvent() {
1650 		terminal.flush();
1651 		if(inputQueue.length) {
1652 			auto e = inputQueue[0];
1653 			inputQueue = inputQueue[1 .. $];
1654 			return e;
1655 		}
1656 
1657 		wait_for_more:
1658 		version(Posix)
1659 		if(interrupted) {
1660 			interrupted = false;
1661 			return InputEvent(UserInterruptionEvent(), terminal);
1662 		}
1663 
1664 		version(Posix)
1665 		if(hangedUp) {
1666 			hangedUp = false;
1667 			return InputEvent(HangupEvent(), terminal);
1668 		}
1669 
1670 		version(Posix)
1671 		if(windowSizeChanged) {
1672 			return checkWindowSizeChanged();
1673 		}
1674 
1675 		auto more = readNextEvents();
1676 		if(!more.length)
1677 			goto wait_for_more; // i used to do a loop (readNextEvents can read something, but it might be discarded by the input filter) but now it goto's above because readNextEvents might be interrupted by a SIGWINCH aka size event so we want to check that at least
1678 
1679 		assert(more.length);
1680 
1681 		auto e = more[0];
1682 		inputQueue = more[1 .. $];
1683 		return e;
1684 	}
1685 
1686 	InputEvent* peekNextEvent() {
1687 		if(inputQueue.length)
1688 			return &(inputQueue[0]);
1689 		return null;
1690 	}
1691 
1692 	enum InjectionPosition { head, tail }
1693 	void injectEvent(InputEvent ev, InjectionPosition where) {
1694 		final switch(where) {
1695 			case InjectionPosition.head:
1696 				inputQueue = ev ~ inputQueue;
1697 			break;
1698 			case InjectionPosition.tail:
1699 				inputQueue ~= ev;
1700 			break;
1701 		}
1702 	}
1703 
1704 	InputEvent[] inputQueue;
1705 
1706 	version(Windows)
1707 	InputEvent[] readNextEvents() {
1708 		terminal.flush(); // make sure all output is sent out before waiting for anything
1709 
1710 		INPUT_RECORD[32] buffer;
1711 		DWORD actuallyRead;
1712 			// FIXME: ReadConsoleInputW
1713 		auto success = ReadConsoleInputA(inputHandle, buffer.ptr, buffer.length, &actuallyRead);
1714 		if(success == 0)
1715 			throw new Exception("ReadConsoleInput");
1716 
1717 		InputEvent[] newEvents;
1718 		input_loop: foreach(record; buffer[0 .. actuallyRead]) {
1719 			switch(record.EventType) {
1720 				case KEY_EVENT:
1721 					auto ev = record.KeyEvent;
1722 					KeyboardEvent ke;
1723 					CharacterEvent e;
1724 					NonCharacterKeyEvent ne;
1725 
1726 					e.eventType = ev.bKeyDown ? CharacterEvent.Type.Pressed : CharacterEvent.Type.Released;
1727 					ne.eventType = ev.bKeyDown ? NonCharacterKeyEvent.Type.Pressed : NonCharacterKeyEvent.Type.Released;
1728 
1729 					ke.pressed = ev.bKeyDown ? true : false;
1730 
1731 					// only send released events when specifically requested
1732 					if(!(flags & ConsoleInputFlags.releasedKeys) && !ev.bKeyDown)
1733 						break;
1734 
1735 					e.modifierState = ev.dwControlKeyState;
1736 					ne.modifierState = ev.dwControlKeyState;
1737 					ke.modifierState = ev.dwControlKeyState;
1738 
1739 					if(ev.UnicodeChar) {
1740 						// new style event goes first
1741 						ke.which = cast(dchar) cast(wchar) ev.UnicodeChar;
1742 						newEvents ~= InputEvent(ke, terminal);
1743 
1744 						// old style event then follows as the fallback
1745 						e.character = cast(dchar) cast(wchar) ev.UnicodeChar;
1746 						newEvents ~= InputEvent(e, terminal);
1747 					} else {
1748 						// old style event
1749 						ne.key = cast(NonCharacterKeyEvent.Key) ev.wVirtualKeyCode;
1750 
1751 						// new style event. See comment on KeyboardEvent.Key
1752 						ke.which = cast(KeyboardEvent.Key) (ev.wVirtualKeyCode + 0xF0000);
1753 
1754 						// FIXME: make this better. the goal is to make sure the key code is a valid enum member
1755 						// Windows sends more keys than Unix and we're doing lowest common denominator here
1756 						foreach(member; __traits(allMembers, NonCharacterKeyEvent.Key))
1757 							if(__traits(getMember, NonCharacterKeyEvent.Key, member) == ne.key) {
1758 								newEvents ~= InputEvent(ke, terminal);
1759 								newEvents ~= InputEvent(ne, terminal);
1760 								break;
1761 							}
1762 					}
1763 				break;
1764 				case MOUSE_EVENT:
1765 					auto ev = record.MouseEvent;
1766 					MouseEvent e;
1767 
1768 					e.modifierState = ev.dwControlKeyState;
1769 					e.x = ev.dwMousePosition.X;
1770 					e.y = ev.dwMousePosition.Y;
1771 
1772 					switch(ev.dwEventFlags) {
1773 						case 0:
1774 							//press or release
1775 							e.eventType = MouseEvent.Type.Pressed;
1776 							static DWORD lastButtonState;
1777 							auto lastButtonState2 = lastButtonState;
1778 							e.buttons = ev.dwButtonState;
1779 							lastButtonState = e.buttons;
1780 
1781 							// this is sent on state change. if fewer buttons are pressed, it must mean released
1782 							if(cast(DWORD) e.buttons < lastButtonState2) {
1783 								e.eventType = MouseEvent.Type.Released;
1784 								// if last was 101 and now it is 100, then button far right was released
1785 								// so we flip the bits, ~100 == 011, then and them: 101 & 011 == 001, the
1786 								// button that was released
1787 								e.buttons = lastButtonState2 & ~e.buttons;
1788 							}
1789 						break;
1790 						case MOUSE_MOVED:
1791 							e.eventType = MouseEvent.Type.Moved;
1792 							e.buttons = ev.dwButtonState;
1793 						break;
1794 						case 0x0004/*MOUSE_WHEELED*/:
1795 							e.eventType = MouseEvent.Type.Pressed;
1796 							if(ev.dwButtonState > 0)
1797 								e.buttons = MouseEvent.Button.ScrollDown;
1798 							else
1799 								e.buttons = MouseEvent.Button.ScrollUp;
1800 						break;
1801 						default:
1802 							continue input_loop;
1803 					}
1804 
1805 					newEvents ~= InputEvent(e, terminal);
1806 				break;
1807 				case WINDOW_BUFFER_SIZE_EVENT:
1808 					auto ev = record.WindowBufferSizeEvent;
1809 					auto oldWidth = terminal.width;
1810 					auto oldHeight = terminal.height;
1811 					terminal._width = ev.dwSize.X;
1812 					terminal._height = ev.dwSize.Y;
1813 					newEvents ~= InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal);
1814 				break;
1815 				// FIXME: can we catch ctrl+c here too?
1816 				default:
1817 					// ignore
1818 			}
1819 		}
1820 
1821 		return newEvents;
1822 	}
1823 
1824 	version(Posix)
1825 	InputEvent[] readNextEvents() {
1826 		terminal.flush(); // make sure all output is sent out before we try to get input
1827 
1828 		// we want to starve the read, especially if we're called from an edge-triggered
1829 		// epoll (which might happen in version=with_eventloop.. impl detail there subject
1830 		// to change).
1831 		auto initial = readNextEventsHelper();
1832 
1833 		// lol this calls select() inside a function prolly called from epoll but meh,
1834 		// it is the simplest thing that can possibly work. The alternative would be
1835 		// doing non-blocking reads and buffering in the nextRaw function (not a bad idea
1836 		// btw, just a bit more of a hassle).
1837 		while(timedCheckForInput(0)) {
1838 			auto ne = readNextEventsHelper();
1839 			initial ~= ne;
1840 			foreach(n; ne)
1841 				if(n.type == InputEvent.Type.EndOfFileEvent)
1842 					return initial; // hit end of file, get out of here lest we infinite loop
1843 					// (select still returns info available even after we read end of file)
1844 		}
1845 		return initial;
1846 	}
1847 
1848 	// The helper reads just one actual event from the pipe...
1849 	version(Posix)
1850 	InputEvent[] readNextEventsHelper() {
1851 		InputEvent[] charPressAndRelease(dchar character) {
1852 			if((flags & ConsoleInputFlags.releasedKeys))
1853 				return [
1854 					// new style event
1855 					InputEvent(KeyboardEvent(true, character, 0), terminal),
1856 					InputEvent(KeyboardEvent(false, character, 0), terminal),
1857 					// old style event
1858 					InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0), terminal),
1859 					InputEvent(CharacterEvent(CharacterEvent.Type.Released, character, 0), terminal),
1860 				];
1861 			else return [
1862 				// new style event
1863 				InputEvent(KeyboardEvent(true, character, 0), terminal),
1864 				// old style event
1865 				InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0), terminal)
1866 			];
1867 		}
1868 		InputEvent[] keyPressAndRelease(NonCharacterKeyEvent.Key key, uint modifiers = 0) {
1869 			if((flags & ConsoleInputFlags.releasedKeys))
1870 				return [
1871 					// new style event FIXME: when the old events are removed, kill the +0xF0000 from here!
1872 					InputEvent(KeyboardEvent(true, cast(dchar)(key) + 0xF0000, modifiers), terminal),
1873 					InputEvent(KeyboardEvent(false, cast(dchar)(key) + 0xF0000, modifiers), terminal),
1874 					// old style event
1875 					InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers), terminal),
1876 					InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Released, key, modifiers), terminal),
1877 				];
1878 			else return [
1879 				// new style event FIXME: when the old events are removed, kill the +0xF0000 from here!
1880 				InputEvent(KeyboardEvent(true, cast(dchar)(key) + 0xF0000, modifiers), terminal),
1881 				// old style event
1882 				InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers), terminal)
1883 			];
1884 		}
1885 
1886 		char[30] sequenceBuffer;
1887 
1888 		// this assumes you just read "\033["
1889 		char[] readEscapeSequence(char[] sequence) {
1890 			int sequenceLength = 2;
1891 			sequence[0] = '\033';
1892 			sequence[1] = '[';
1893 
1894 			while(sequenceLength < sequence.length) {
1895 				auto n = nextRaw();
1896 				sequence[sequenceLength++] = cast(char) n;
1897 				// I think a [ is supposed to termiate a CSI sequence
1898 				// but the Linux console sends CSI[A for F1, so I'm
1899 				// hacking it to accept that too
1900 				if(n >= 0x40 && !(sequenceLength == 3 && n == '['))
1901 					break;
1902 			}
1903 
1904 			return sequence[0 .. sequenceLength];
1905 		}
1906 
1907 		InputEvent[] translateTermcapName(string cap) {
1908 			switch(cap) {
1909 				//case "k0":
1910 					//return keyPressAndRelease(NonCharacterKeyEvent.Key.F1);
1911 				case "k1":
1912 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F1);
1913 				case "k2":
1914 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F2);
1915 				case "k3":
1916 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F3);
1917 				case "k4":
1918 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F4);
1919 				case "k5":
1920 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F5);
1921 				case "k6":
1922 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F6);
1923 				case "k7":
1924 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F7);
1925 				case "k8":
1926 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F8);
1927 				case "k9":
1928 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F9);
1929 				case "k;":
1930 				case "k0":
1931 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F10);
1932 				case "F1":
1933 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F11);
1934 				case "F2":
1935 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F12);
1936 
1937 
1938 				case "kb":
1939 					return charPressAndRelease('\b');
1940 				case "kD":
1941 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete);
1942 
1943 				case "kd":
1944 				case "do":
1945 					return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow);
1946 				case "ku":
1947 				case "up":
1948 					return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow);
1949 				case "kl":
1950 					return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow);
1951 				case "kr":
1952 				case "nd":
1953 					return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow);
1954 
1955 				case "kN":
1956 				case "K5":
1957 					return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown);
1958 				case "kP":
1959 				case "K2":
1960 					return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp);
1961 
1962 				case "ho": // this might not be a key but my thing sometimes returns it... weird...
1963 				case "kh":
1964 				case "K1":
1965 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Home);
1966 				case "kH":
1967 					return keyPressAndRelease(NonCharacterKeyEvent.Key.End);
1968 				case "kI":
1969 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert);
1970 				default:
1971 					// don't know it, just ignore
1972 					//import std.stdio;
1973 					//writeln(cap);
1974 			}
1975 
1976 			return null;
1977 		}
1978 
1979 
1980 		InputEvent[] doEscapeSequence(in char[] sequence) {
1981 			switch(sequence) {
1982 				case "\033[200~":
1983 					// bracketed paste begin
1984 					// we want to keep reading until
1985 					// "\033[201~":
1986 					// and build a paste event out of it
1987 
1988 
1989 					string data;
1990 					for(;;) {
1991 						auto n = nextRaw();
1992 						if(n == '\033') {
1993 							n = nextRaw();
1994 							if(n == '[') {
1995 								auto esc = readEscapeSequence(sequenceBuffer);
1996 								if(esc == "\033[201~") {
1997 									// complete!
1998 									break;
1999 								} else {
2000 									// was something else apparently, but it is pasted, so keep it
2001 									data ~= esc;
2002 								}
2003 							} else {
2004 								data ~= '\033';
2005 								data ~= cast(char) n;
2006 							}
2007 						} else {
2008 							data ~= cast(char) n;
2009 						}
2010 					}
2011 					return [InputEvent(PasteEvent(data), terminal)];
2012 				case "\033[M":
2013 					// mouse event
2014 					auto buttonCode = nextRaw() - 32;
2015 						// nextChar is commented because i'm not using UTF-8 mouse mode
2016 						// cuz i don't think it is as widely supported
2017 					auto x = cast(int) (/*nextChar*/(nextRaw())) - 33; /* they encode value + 32, but make upper left 1,1. I want it to be 0,0 */
2018 					auto y = cast(int) (/*nextChar*/(nextRaw())) - 33; /* ditto */
2019 
2020 
2021 					bool isRelease = (buttonCode & 0b11) == 3;
2022 					int buttonNumber;
2023 					if(!isRelease) {
2024 						buttonNumber = (buttonCode & 0b11);
2025 						if(buttonCode & 64)
2026 							buttonNumber += 3; // button 4 and 5 are sent as like button 1 and 2, but code | 64
2027 							// so button 1 == button 4 here
2028 
2029 						// note: buttonNumber == 0 means button 1 at this point
2030 						buttonNumber++; // hence this
2031 
2032 
2033 						// apparently this considers middle to be button 2. but i want middle to be button 3.
2034 						if(buttonNumber == 2)
2035 							buttonNumber = 3;
2036 						else if(buttonNumber == 3)
2037 							buttonNumber = 2;
2038 					}
2039 
2040 					auto modifiers = buttonCode & (0b0001_1100);
2041 						// 4 == shift
2042 						// 8 == meta
2043 						// 16 == control
2044 
2045 					MouseEvent m;
2046 
2047 					if(buttonCode & 32)
2048 						m.eventType = MouseEvent.Type.Moved;
2049 					else
2050 						m.eventType = isRelease ? MouseEvent.Type.Released : MouseEvent.Type.Pressed;
2051 
2052 					// ugh, if no buttons are pressed, released and moved are indistinguishable...
2053 					// so we'll count the buttons down, and if we get a release
2054 					static int buttonsDown = 0;
2055 					if(!isRelease && buttonNumber <= 3) // exclude wheel "presses"...
2056 						buttonsDown++;
2057 
2058 					if(isRelease && m.eventType != MouseEvent.Type.Moved) {
2059 						if(buttonsDown)
2060 							buttonsDown--;
2061 						else // no buttons down, so this should be a motion instead..
2062 							m.eventType = MouseEvent.Type.Moved;
2063 					}
2064 
2065 
2066 					if(buttonNumber == 0)
2067 						m.buttons = 0; // we don't actually know :(
2068 					else
2069 						m.buttons = 1 << (buttonNumber - 1); // I prefer flags so that's how we do it
2070 					m.x = x;
2071 					m.y = y;
2072 					m.modifierState = modifiers;
2073 
2074 					return [InputEvent(m, terminal)];
2075 				default:
2076 					// look it up in the termcap key database
2077 					auto cap = terminal.findSequenceInTermcap(sequence);
2078 					if(cap !is null) {
2079 						return translateTermcapName(cap);
2080 					} else {
2081 						if(terminal.terminalInFamily("xterm")) {
2082 							import std.conv, std.string;
2083 							auto terminator = sequence[$ - 1];
2084 							auto parts = sequence[2 .. $ - 1].split(";");
2085 							// parts[0] and terminator tells us the key
2086 							// parts[1] tells us the modifierState
2087 
2088 							uint modifierState;
2089 
2090 							int modGot;
2091 							if(parts.length > 1)
2092 								modGot = to!int(parts[1]);
2093 							mod_switch: switch(modGot) {
2094 								case 2: modifierState |= ModifierState.shift; break;
2095 								case 3: modifierState |= ModifierState.alt; break;
2096 								case 4: modifierState |= ModifierState.shift | ModifierState.alt; break;
2097 								case 5: modifierState |= ModifierState.control; break;
2098 								case 6: modifierState |= ModifierState.shift | ModifierState.control; break;
2099 								case 7: modifierState |= ModifierState.alt | ModifierState.control; break;
2100 								case 8: modifierState |= ModifierState.shift | ModifierState.alt | ModifierState.control; break;
2101 								case 9:
2102 								..
2103 								case 16:
2104 									modifierState |= ModifierState.meta;
2105 									if(modGot != 9) {
2106 										modGot -= 8;
2107 										goto mod_switch;
2108 									}
2109 								break;
2110 
2111 								// this is an extension in my own terminal emulator
2112 								case 20:
2113 								..
2114 								case 36:
2115 									modifierState |= ModifierState.windows;
2116 									modGot -= 20;
2117 									goto mod_switch;
2118 								default:
2119 							}
2120 
2121 							switch(terminator) {
2122 								case 'A': return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow, modifierState);
2123 								case 'B': return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow, modifierState);
2124 								case 'C': return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow, modifierState);
2125 								case 'D': return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow, modifierState);
2126 
2127 								case 'H': return keyPressAndRelease(NonCharacterKeyEvent.Key.Home, modifierState);
2128 								case 'F': return keyPressAndRelease(NonCharacterKeyEvent.Key.End, modifierState);
2129 
2130 								case 'P': return keyPressAndRelease(NonCharacterKeyEvent.Key.F1, modifierState);
2131 								case 'Q': return keyPressAndRelease(NonCharacterKeyEvent.Key.F2, modifierState);
2132 								case 'R': return keyPressAndRelease(NonCharacterKeyEvent.Key.F3, modifierState);
2133 								case 'S': return keyPressAndRelease(NonCharacterKeyEvent.Key.F4, modifierState);
2134 
2135 								case '~': // others
2136 									switch(parts[0]) {
2137 										case "5": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp, modifierState);
2138 										case "6": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown, modifierState);
2139 										case "2": return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert, modifierState);
2140 										case "3": return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete, modifierState);
2141 
2142 										case "15": return keyPressAndRelease(NonCharacterKeyEvent.Key.F5, modifierState);
2143 										case "17": return keyPressAndRelease(NonCharacterKeyEvent.Key.F6, modifierState);
2144 										case "18": return keyPressAndRelease(NonCharacterKeyEvent.Key.F7, modifierState);
2145 										case "19": return keyPressAndRelease(NonCharacterKeyEvent.Key.F8, modifierState);
2146 										case "20": return keyPressAndRelease(NonCharacterKeyEvent.Key.F9, modifierState);
2147 										case "21": return keyPressAndRelease(NonCharacterKeyEvent.Key.F10, modifierState);
2148 										case "23": return keyPressAndRelease(NonCharacterKeyEvent.Key.F11, modifierState);
2149 										case "24": return keyPressAndRelease(NonCharacterKeyEvent.Key.F12, modifierState);
2150 										default:
2151 									}
2152 								break;
2153 
2154 								default:
2155 							}
2156 						} else if(terminal.terminalInFamily("rxvt")) {
2157 							// FIXME: figure these out. rxvt seems to just change the terminator while keeping the rest the same
2158 							// though it isn't consistent. ugh.
2159 						} else {
2160 							// maybe we could do more terminals, but linux doesn't even send it and screen just seems to pass through, so i don't think so; xterm prolly covers most them anyway
2161 							// so this space is semi-intentionally left blank
2162 						}
2163 					}
2164 			}
2165 
2166 			return null;
2167 		}
2168 
2169 		auto c = nextRaw(true);
2170 		if(c == -1)
2171 			return null; // interrupted; give back nothing so the other level can recheck signal flags
2172 		if(c == 0)
2173 			return [InputEvent(EndOfFileEvent(), terminal)];
2174 		if(c == '\033') {
2175 			if(timedCheckForInput(50)) {
2176 				// escape sequence
2177 				c = nextRaw();
2178 				if(c == '[') { // CSI, ends on anything >= 'A'
2179 					return doEscapeSequence(readEscapeSequence(sequenceBuffer));
2180 				} else if(c == 'O') {
2181 					// could be xterm function key
2182 					auto n = nextRaw();
2183 
2184 					char[3] thing;
2185 					thing[0] = '\033';
2186 					thing[1] = 'O';
2187 					thing[2] = cast(char) n;
2188 
2189 					auto cap = terminal.findSequenceInTermcap(thing);
2190 					if(cap is null) {
2191 						return charPressAndRelease('\033') ~
2192 							charPressAndRelease('O') ~
2193 							charPressAndRelease(thing[2]);
2194 					} else {
2195 						return translateTermcapName(cap);
2196 					}
2197 				} else {
2198 					// I don't know, probably unsupported terminal or just quick user input or something
2199 					return charPressAndRelease('\033') ~ charPressAndRelease(nextChar(c));
2200 				}
2201 			} else {
2202 				// user hit escape (or super slow escape sequence, but meh)
2203 				return keyPressAndRelease(NonCharacterKeyEvent.Key.escape);
2204 			}
2205 		} else {
2206 			// FIXME: what if it is neither? we should check the termcap
2207 			auto next = nextChar(c);
2208 			if(next == 127) // some terminals send 127 on the backspace. Let's normalize that.
2209 				next = '\b';
2210 			return charPressAndRelease(next);
2211 		}
2212 	}
2213 }
2214 
2215 /// The new style of keyboard event
2216 struct KeyboardEvent {
2217 	bool pressed;
2218 	dchar which;
2219 	uint modifierState;
2220 
2221 	bool isCharacter() {
2222 		return !(which >= Key.min && which <= Key.max);
2223 	}
2224 
2225 	// these match Windows virtual key codes numerically for simplicity of translation there
2226 	// but are plus a unicode private use area offset so i can cram them in the dchar
2227 	// http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
2228 	/// .
2229 	enum Key : dchar {
2230 		escape = 0x1b + 0xF0000, /// .
2231 		F1 = 0x70 + 0xF0000, /// .
2232 		F2 = 0x71 + 0xF0000, /// .
2233 		F3 = 0x72 + 0xF0000, /// .
2234 		F4 = 0x73 + 0xF0000, /// .
2235 		F5 = 0x74 + 0xF0000, /// .
2236 		F6 = 0x75 + 0xF0000, /// .
2237 		F7 = 0x76 + 0xF0000, /// .
2238 		F8 = 0x77 + 0xF0000, /// .
2239 		F9 = 0x78 + 0xF0000, /// .
2240 		F10 = 0x79 + 0xF0000, /// .
2241 		F11 = 0x7A + 0xF0000, /// .
2242 		F12 = 0x7B + 0xF0000, /// .
2243 		LeftArrow = 0x25 + 0xF0000, /// .
2244 		RightArrow = 0x27 + 0xF0000, /// .
2245 		UpArrow = 0x26 + 0xF0000, /// .
2246 		DownArrow = 0x28 + 0xF0000, /// .
2247 		Insert = 0x2d + 0xF0000, /// .
2248 		Delete = 0x2e + 0xF0000, /// .
2249 		Home = 0x24 + 0xF0000, /// .
2250 		End = 0x23 + 0xF0000, /// .
2251 		PageUp = 0x21 + 0xF0000, /// .
2252 		PageDown = 0x22 + 0xF0000, /// .
2253 	}
2254 
2255 
2256 }
2257 
2258 /// Input event for characters
2259 struct CharacterEvent {
2260 	/// .
2261 	enum Type {
2262 		Released, /// .
2263 		Pressed /// .
2264 	}
2265 
2266 	Type eventType; /// .
2267 	dchar character; /// .
2268 	uint modifierState; /// Don't depend on this to be available for character events
2269 }
2270 
2271 struct NonCharacterKeyEvent {
2272 	/// .
2273 	enum Type {
2274 		Released, /// .
2275 		Pressed /// .
2276 	}
2277 	Type eventType; /// .
2278 
2279 	// these match Windows virtual key codes numerically for simplicity of translation there
2280 	//http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
2281 	/// .
2282 	enum Key : int {
2283 		escape = 0x1b, /// .
2284 		F1 = 0x70, /// .
2285 		F2 = 0x71, /// .
2286 		F3 = 0x72, /// .
2287 		F4 = 0x73, /// .
2288 		F5 = 0x74, /// .
2289 		F6 = 0x75, /// .
2290 		F7 = 0x76, /// .
2291 		F8 = 0x77, /// .
2292 		F9 = 0x78, /// .
2293 		F10 = 0x79, /// .
2294 		F11 = 0x7A, /// .
2295 		F12 = 0x7B, /// .
2296 		LeftArrow = 0x25, /// .
2297 		RightArrow = 0x27, /// .
2298 		UpArrow = 0x26, /// .
2299 		DownArrow = 0x28, /// .
2300 		Insert = 0x2d, /// .
2301 		Delete = 0x2e, /// .
2302 		Home = 0x24, /// .
2303 		End = 0x23, /// .
2304 		PageUp = 0x21, /// .
2305 		PageDown = 0x22, /// .
2306 		}
2307 	Key key; /// .
2308 
2309 	uint modifierState; /// A mask of ModifierState. Always use by checking modifierState & ModifierState.something, the actual value differs across platforms
2310 
2311 }
2312 
2313 /// .
2314 struct PasteEvent {
2315 	string pastedText; /// .
2316 }
2317 
2318 /// .
2319 struct MouseEvent {
2320 	// these match simpledisplay.d numerically as well
2321 	/// .
2322 	enum Type {
2323 		Moved = 0, /// .
2324 		Pressed = 1, /// .
2325 		Released = 2, /// .
2326 		Clicked, /// .
2327 	}
2328 
2329 	Type eventType; /// .
2330 
2331 	// note: these should numerically match simpledisplay.d for maximum beauty in my other code
2332 	/// .
2333 	enum Button : uint {
2334 		None = 0, /// .
2335 		Left = 1, /// .
2336 		Middle = 4, /// .
2337 		Right = 2, /// .
2338 		ScrollUp = 8, /// .
2339 		ScrollDown = 16 /// .
2340 	}
2341 	uint buttons; /// A mask of Button
2342 	int x; /// 0 == left side
2343 	int y; /// 0 == top
2344 	uint modifierState; /// shift, ctrl, alt, meta, altgr. Not always available. Always check by using modifierState & ModifierState.something
2345 }
2346 
2347 /// .
2348 struct SizeChangedEvent {
2349 	int oldWidth;
2350 	int oldHeight;
2351 	int newWidth;
2352 	int newHeight;
2353 }
2354 
2355 /// the user hitting ctrl+c will send this
2356 /// You should drop what you're doing and perhaps exit when this happens.
2357 struct UserInterruptionEvent {}
2358 
2359 /// If the user hangs up (for example, closes the terminal emulator without exiting the app), this is sent.
2360 /// If you receive it, you should generally cleanly exit.
2361 struct HangupEvent {}
2362 
2363 /// Sent upon receiving end-of-file from stdin.
2364 struct EndOfFileEvent {}
2365 
2366 interface CustomEvent {}
2367 
2368 version(Windows)
2369 enum ModifierState : uint {
2370 	shift = 0x10,
2371 	control = 0x8 | 0x4, // 8 == left ctrl, 4 == right ctrl
2372 
2373 	// i'm not sure if the next two are available
2374 	alt = 2 | 1, //2 ==left alt, 1 == right alt
2375 
2376 	// FIXME: I don't think these are actually available
2377 	windows = 512,
2378 	meta = 4096, // FIXME sanity
2379 
2380 	// I don't think this is available on Linux....
2381 	scrollLock = 0x40,
2382 }
2383 else
2384 enum ModifierState : uint {
2385 	shift = 4,
2386 	alt = 2,
2387 	control = 16,
2388 	meta = 8,
2389 
2390 	windows = 512 // only available if you are using my terminal emulator; it isn't actually offered on standard linux ones
2391 }
2392 
2393 /// GetNextEvent returns this. Check the type, then use get to get the more detailed input
2394 struct InputEvent {
2395 	/// .
2396 	enum Type {
2397 		KeyboardEvent, ///.
2398 		CharacterEvent, ///.
2399 		NonCharacterKeyEvent, /// .
2400 		PasteEvent, /// The user pasted some text. Not always available, the pasted text might come as a series of character events instead.
2401 		MouseEvent, /// only sent if you subscribed to mouse events
2402 		SizeChangedEvent, /// only sent if you subscribed to size events
2403 		UserInterruptionEvent, /// the user hit ctrl+c
2404 		EndOfFileEvent, /// stdin has received an end of file
2405 		HangupEvent, /// the terminal hanged up - for example, if the user closed a terminal emulator
2406 		CustomEvent /// .
2407 	}
2408 
2409 	/// .
2410 	@property Type type() { return t; }
2411 
2412 	/// Returns a pointer to the terminal associated with this event.
2413 	/// (You can usually just ignore this as there's only one terminal typically.)
2414 	///
2415 	/// It may be null in the case of program-generated events;
2416 	@property Terminal* terminal() { return term; }
2417 
2418 	/// .
2419 	@property auto get(Type T)() {
2420 		if(type != T)
2421 			throw new Exception("Wrong event type");
2422 		static if(T == Type.CharacterEvent)
2423 			return characterEvent;
2424 		else static if(T == Type.KeyboardEvent)
2425 			return keyboardEvent;
2426 		else static if(T == Type.NonCharacterKeyEvent)
2427 			return nonCharacterKeyEvent;
2428 		else static if(T == Type.PasteEvent)
2429 			return pasteEvent;
2430 		else static if(T == Type.MouseEvent)
2431 			return mouseEvent;
2432 		else static if(T == Type.SizeChangedEvent)
2433 			return sizeChangedEvent;
2434 		else static if(T == Type.UserInterruptionEvent)
2435 			return userInterruptionEvent;
2436 		else static if(T == Type.EndOfFileEvent)
2437 			return endOfFileEvent;
2438 		else static if(T == Type.HangupEvent)
2439 			return hangupEvent;
2440 		else static if(T == Type.CustomEvent)
2441 			return customEvent;
2442 		else static assert(0, "Type " ~ T.stringof ~ " not added to the get function");
2443 	}
2444 
2445 	// custom event is public because otherwise there's no point at all
2446 	this(CustomEvent c, Terminal* p = null) {
2447 		t = Type.CustomEvent;
2448 		customEvent = c;
2449 	}
2450 
2451 	private {
2452 		this(CharacterEvent c, Terminal* p) {
2453 			t = Type.CharacterEvent;
2454 			characterEvent = c;
2455 		}
2456 		this(KeyboardEvent c, Terminal* p) {
2457 			t = Type.KeyboardEvent;
2458 			keyboardEvent = c;
2459 		}
2460 		this(NonCharacterKeyEvent c, Terminal* p) {
2461 			t = Type.NonCharacterKeyEvent;
2462 			nonCharacterKeyEvent = c;
2463 		}
2464 		this(PasteEvent c, Terminal* p) {
2465 			t = Type.PasteEvent;
2466 			pasteEvent = c;
2467 		}
2468 		this(MouseEvent c, Terminal* p) {
2469 			t = Type.MouseEvent;
2470 			mouseEvent = c;
2471 		}
2472 		this(SizeChangedEvent c, Terminal* p) {
2473 			t = Type.SizeChangedEvent;
2474 			sizeChangedEvent = c;
2475 		}
2476 		this(UserInterruptionEvent c, Terminal* p) {
2477 			t = Type.UserInterruptionEvent;
2478 			userInterruptionEvent = c;
2479 		}
2480 		this(HangupEvent c, Terminal* p) {
2481 			t = Type.HangupEvent;
2482 			hangupEvent = c;
2483 		}
2484 		this(EndOfFileEvent c, Terminal* p) {
2485 			t = Type.EndOfFileEvent;
2486 			endOfFileEvent = c;
2487 		}
2488 
2489 		Type t;
2490 		Terminal* term;
2491 
2492 		union {
2493 			KeyboardEvent keyboardEvent;
2494 			CharacterEvent characterEvent;
2495 			NonCharacterKeyEvent nonCharacterKeyEvent;
2496 			PasteEvent pasteEvent;
2497 			MouseEvent mouseEvent;
2498 			SizeChangedEvent sizeChangedEvent;
2499 			UserInterruptionEvent userInterruptionEvent;
2500 			HangupEvent hangupEvent;
2501 			EndOfFileEvent endOfFileEvent;
2502 			CustomEvent customEvent;
2503 		}
2504 	}
2505 }
2506 
2507 version(Demo)
2508 void main() {
2509 	auto terminal = Terminal(ConsoleOutputType.cellular);
2510 
2511 	//terminal.color(Color.DEFAULT, Color.DEFAULT);
2512 
2513 	//
2514 	///*
2515 	auto getter = new FileLineGetter(&terminal, "test");
2516 	getter.prompt = "> ";
2517 	getter.history = ["abcdefghijklmnopqrstuvwzyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"];
2518 	terminal.writeln("\n" ~ getter.getline());
2519 	terminal.writeln("\n" ~ getter.getline());
2520 	terminal.writeln("\n" ~ getter.getline());
2521 	getter.dispose();
2522 	//*/
2523 
2524 	terminal.writeln(terminal.getline());
2525 	terminal.writeln(terminal.getline());
2526 	terminal.writeln(terminal.getline());
2527 
2528 	//input.getch();
2529 
2530 	// return;
2531 	//
2532 
2533 	terminal.setTitle("Basic I/O");
2534 	auto input = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEvents);
2535 	terminal.color(Color.green | Bright, Color.black);
2536 
2537 	terminal.write("test some long string to see if it wraps or what because i dont really know what it is going to do so i just want to test i think it will wrap but gotta be sure lolololololololol");
2538 	terminal.writefln("%d %d", terminal.cursorX, terminal.cursorY);
2539 
2540 	int centerX = terminal.width / 2;
2541 	int centerY = terminal.height / 2;
2542 
2543 	bool timeToBreak = false;
2544 
2545 	void handleEvent(InputEvent event) {
2546 		terminal.writef("%s\n", event.type);
2547 		final switch(event.type) {
2548 			case InputEvent.Type.UserInterruptionEvent:
2549 			case InputEvent.Type.HangupEvent:
2550 			case InputEvent.Type.EndOfFileEvent:
2551 				timeToBreak = true;
2552 				version(with_eventloop) {
2553 					import arsd.eventloop;
2554 					exit();
2555 				}
2556 			break;
2557 			case InputEvent.Type.SizeChangedEvent:
2558 				auto ev = event.get!(InputEvent.Type.SizeChangedEvent);
2559 				terminal.writeln(ev);
2560 			break;
2561 			case InputEvent.Type.KeyboardEvent:
2562 				auto ev = event.get!(InputEvent.Type.KeyboardEvent);
2563 					terminal.writef("\t%s", ev);
2564 				terminal.writef(" (%s)", cast(KeyboardEvent.Key) ev.which);
2565 				terminal.writeln();
2566 				if(ev.which == 'Q') {
2567 					timeToBreak = true;
2568 					version(with_eventloop) {
2569 						import arsd.eventloop;
2570 						exit();
2571 					}
2572 				}
2573 
2574 				if(ev.which == 'C')
2575 					terminal.clear();
2576 			break;
2577 			case InputEvent.Type.CharacterEvent: // obsolete
2578 				auto ev = event.get!(InputEvent.Type.CharacterEvent);
2579 				terminal.writef("\t%s\n", ev);
2580 			break;
2581 			case InputEvent.Type.NonCharacterKeyEvent: // obsolete
2582 				terminal.writef("\t%s\n", event.get!(InputEvent.Type.NonCharacterKeyEvent));
2583 			break;
2584 			case InputEvent.Type.PasteEvent:
2585 				terminal.writef("\t%s\n", event.get!(InputEvent.Type.PasteEvent));
2586 			break;
2587 			case InputEvent.Type.MouseEvent:
2588 				terminal.writef("\t%s\n", event.get!(InputEvent.Type.MouseEvent));
2589 			break;
2590 			case InputEvent.Type.CustomEvent:
2591 			break;
2592 		}
2593 
2594 		terminal.writefln("%d %d", terminal.cursorX, terminal.cursorY);
2595 
2596 		/*
2597 		if(input.kbhit()) {
2598 			auto c = input.getch();
2599 			if(c == 'q' || c == 'Q')
2600 				break;
2601 			terminal.moveTo(centerX, centerY);
2602 			terminal.writef("%c", c);
2603 			terminal.flush();
2604 		}
2605 		usleep(10000);
2606 		*/
2607 	}
2608 
2609 	version(with_eventloop) {
2610 		import arsd.eventloop;
2611 		addListener(&handleEvent);
2612 		loop();
2613 	} else {
2614 		loop: while(true) {
2615 			auto event = input.nextEvent();
2616 			handleEvent(event);
2617 			if(timeToBreak)
2618 				break loop;
2619 		}
2620 	}
2621 }
2622 
2623 /**
2624 	FIXME: support lines that wrap
2625 	FIXME: better controls maybe
2626 
2627 	FIXME: support multi-line "lines" and some form of line continuation, both
2628 	       from the user (if permitted) and from the application, so like the user
2629 	       hits "class foo { \n" and the app says "that line needs continuation" automatically.
2630 
2631 	FIXME: fix lengths on prompt and suggestion
2632 
2633 	A note on history:
2634 
2635 	To save history, you must call LineGetter.dispose() when you're done with it.
2636 	History will not be automatically saved without that call!
2637 
2638 	The history saving and loading as a trivially encountered race condition: if you
2639 	open two programs that use the same one at the same time, the one that closes second
2640 	will overwrite any history changes the first closer saved.
2641 
2642 	GNU Getline does this too... and it actually kinda drives me nuts. But I don't know
2643 	what a good fix is except for doing a transactional commit straight to the file every
2644 	time and that seems like hitting the disk way too often.
2645 
2646 	We could also do like a history server like a database daemon that keeps the order
2647 	correct but I don't actually like that either because I kinda like different bashes
2648 	to have different history, I just don't like it all to get lost.
2649 
2650 	Regardless though, this isn't even used in bash anyway, so I don't think I care enough
2651 	to put that much effort into it. Just using separate files for separate tasks is good
2652 	enough I think.
2653 */
2654 class LineGetter {
2655 	/* A note on the assumeSafeAppends in here: since these buffers are private, we can be
2656 	   pretty sure that stomping isn't an issue, so I'm using this liberally to keep the
2657 	   append/realloc code simple and hopefully reasonably fast. */
2658 
2659 	// saved to file
2660 	string[] history;
2661 
2662 	// not saved
2663 	Terminal* terminal;
2664 	string historyFilename;
2665 
2666 	/// Make sure that the parent terminal struct remains in scope for the duration
2667 	/// of LineGetter's lifetime, as it does hold on to and use the passed pointer
2668 	/// throughout.
2669 	///
2670 	/// historyFilename will load and save an input history log to a particular folder.
2671 	/// Leaving it null will mean no file will be used and history will not be saved across sessions.
2672 	this(Terminal* tty, string historyFilename = null) {
2673 		this.terminal = tty;
2674 		this.historyFilename = historyFilename;
2675 
2676 		line.reserve(128);
2677 
2678 		if(historyFilename.length)
2679 			loadSettingsAndHistoryFromFile();
2680 
2681 		regularForeground = cast(Color) terminal._currentForeground;
2682 		background = cast(Color) terminal._currentBackground;
2683 		suggestionForeground = Color.blue;
2684 	}
2685 
2686 	/// Call this before letting LineGetter die so it can do any necessary
2687 	/// cleanup and save the updated history to a file.
2688 	void dispose() {
2689 		if(historyFilename.length)
2690 			saveSettingsAndHistoryToFile();
2691 	}
2692 
2693 	/// Override this to change the directory where history files are stored
2694 	///
2695 	/// Default is $HOME/.arsd-getline on linux and %APPDATA%/arsd-getline/ on Windows.
2696 	/* virtual */ string historyFileDirectory() {
2697 		version(Windows) {
2698 			char[1024] path;
2699 			// FIXME: this doesn't link because the crappy dmd lib doesn't have it
2700 			if(0) { // SHGetFolderPathA(null, CSIDL_APPDATA, null, 0, path.ptr) >= 0) {
2701 				import core.stdc.string;
2702 				return cast(string) path[0 .. strlen(path.ptr)] ~ "\\arsd-getline";
2703 			} else {
2704 				import std.process;
2705 				return environment["APPDATA"] ~ "\\arsd-getline";
2706 			}
2707 		} else version(Posix) {
2708 			import std.process;
2709 			return environment["HOME"] ~ "/.arsd-getline";
2710 		}
2711 	}
2712 
2713 	/// You can customize the colors here. You should set these after construction, but before
2714 	/// calling startGettingLine or getline.
2715 	Color suggestionForeground;
2716 	Color regularForeground; /// .
2717 	Color background; /// .
2718 	//bool reverseVideo;
2719 
2720 	/// Set this if you want a prompt to be drawn with the line. It does NOT support color in string.
2721 	string prompt;
2722 
2723 	/// Turn on auto suggest if you want a greyed thing of what tab
2724 	/// would be able to fill in as you type.
2725 	///
2726 	/// You might want to turn it off if generating a completion list is slow.
2727 	bool autoSuggest = true;
2728 
2729 
2730 	/// Override this if you don't want all lines added to the history.
2731 	/// You can return null to not add it at all, or you can transform it.
2732 	/* virtual */ string historyFilter(string candidate) {
2733 		return candidate;
2734 	}
2735 
2736 	/// You may override this to do nothing
2737 	/* virtual */ void saveSettingsAndHistoryToFile() {
2738 		import std.file;
2739 		if(!exists(historyFileDirectory))
2740 			mkdir(historyFileDirectory);
2741 		auto fn = historyPath();
2742 		import std.stdio;
2743 		auto file = File(fn, "wt");
2744 		foreach(item; history)
2745 			file.writeln(item);
2746 	}
2747 
2748 	private string historyPath() {
2749 		import std.path;
2750 		auto filename = historyFileDirectory() ~ dirSeparator ~ historyFilename ~ ".history";
2751 		return filename;
2752 	}
2753 
2754 	/// You may override this to do nothing
2755 	/* virtual */ void loadSettingsAndHistoryFromFile() {
2756 		import std.file;
2757 		history = null;
2758 		auto fn = historyPath();
2759 		if(exists(fn)) {
2760 			import std.stdio;
2761 			foreach(line; File(fn, "rt").byLine)
2762 				history ~= line.idup;
2763 
2764 		}
2765 	}
2766 
2767 	/**
2768 		Override this to provide tab completion. You may use the candidate
2769 		argument to filter the list, but you don't have to (LineGetter will
2770 		do it for you on the values you return).
2771 
2772 		Ideally, you wouldn't return more than about ten items since the list
2773 		gets difficult to use if it is too long.
2774 
2775 		Default is to provide recent command history as autocomplete.
2776 	*/
2777 	/* virtual */ protected string[] tabComplete(in dchar[] candidate) {
2778 		return history.length > 20 ? history[0 .. 20] : history;
2779 	}
2780 
2781 	private string[] filterTabCompleteList(string[] list) {
2782 		if(list.length == 0)
2783 			return list;
2784 
2785 		string[] f;
2786 		f.reserve(list.length);
2787 
2788 		foreach(item; list) {
2789 			import std.algorithm;
2790 			if(startsWith(item, line[0 .. cursorPosition]))
2791 				f ~= item;
2792 		}
2793 
2794 		return f;
2795 	}
2796 
2797 	/// Override this to provide a custom display of the tab completion list
2798 	protected void showTabCompleteList(string[] list) {
2799 		if(list.length) {
2800 			// FIXME: allow mouse clicking of an item, that would be cool
2801 
2802 			// FIXME: scroll
2803 			//if(terminal.type == ConsoleOutputType.linear) {
2804 				terminal.writeln();
2805 				foreach(item; list) {
2806 					terminal.color(suggestionForeground, background);
2807 					import std.utf;
2808 					auto idx = codeLength!char(line[0 .. cursorPosition]);
2809 					terminal.write("  ", item[0 .. idx]);
2810 					terminal.color(regularForeground, background);
2811 					terminal.writeln(item[idx .. $]);
2812 				}
2813 				updateCursorPosition();
2814 				redraw();
2815 			//}
2816 		}
2817 	}
2818 
2819 	/// One-call shop for the main workhorse
2820 	/// If you already have a RealTimeConsoleInput ready to go, you
2821 	/// should pass a pointer to yours here. Otherwise, LineGetter will
2822 	/// make its own.
2823 	public string getline(RealTimeConsoleInput* input = null) {
2824 		startGettingLine();
2825 		if(input is null) {
2826 			auto i = RealTimeConsoleInput(terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEvents);
2827 			while(workOnLine(i.nextEvent())) {}
2828 		} else
2829 			while(workOnLine(input.nextEvent())) {}
2830 		return finishGettingLine();
2831 	}
2832 
2833 	private int currentHistoryViewPosition = 0;
2834 	private dchar[] uncommittedHistoryCandidate;
2835 	void loadFromHistory(int howFarBack) {
2836 		if(howFarBack < 0)
2837 			howFarBack = 0;
2838 		if(howFarBack > history.length) // lol signed/unsigned comparison here means if i did this first, before howFarBack < 0, it would totally cycle around.
2839 			howFarBack = cast(int) history.length;
2840 		if(howFarBack == currentHistoryViewPosition)
2841 			return;
2842 		if(currentHistoryViewPosition == 0) {
2843 			// save the current line so we can down arrow back to it later
2844 			if(uncommittedHistoryCandidate.length < line.length) {
2845 				uncommittedHistoryCandidate.length = line.length;
2846 			}
2847 
2848 			uncommittedHistoryCandidate[0 .. line.length] = line[];
2849 			uncommittedHistoryCandidate = uncommittedHistoryCandidate[0 .. line.length];
2850 			uncommittedHistoryCandidate.assumeSafeAppend();
2851 		}
2852 
2853 		currentHistoryViewPosition = howFarBack;
2854 
2855 		if(howFarBack == 0) {
2856 			line.length = uncommittedHistoryCandidate.length;
2857 			line.assumeSafeAppend();
2858 			line[] = uncommittedHistoryCandidate[];
2859 		} else {
2860 			line = line[0 .. 0];
2861 			line.assumeSafeAppend();
2862 			foreach(dchar ch; history[$ - howFarBack])
2863 				line ~= ch;
2864 		}
2865 
2866 		cursorPosition = cast(int) line.length;
2867 		scrollToEnd();
2868 	}
2869 
2870 	bool insertMode = true;
2871 	bool multiLineMode = false;
2872 
2873 	private dchar[] line;
2874 	private int cursorPosition = 0;
2875 	private int horizontalScrollPosition = 0;
2876 
2877 	private void scrollToEnd() {
2878 		horizontalScrollPosition = (cast(int) line.length);
2879 		horizontalScrollPosition -= availableLineLength();
2880 		if(horizontalScrollPosition < 0)
2881 			horizontalScrollPosition = 0;
2882 	}
2883 
2884 	// used for redrawing the line in the right place
2885 	// and detecting mouse events on our line.
2886 	private int startOfLineX;
2887 	private int startOfLineY;
2888 
2889 	// private string[] cachedCompletionList;
2890 
2891 	// FIXME
2892 	// /// Note that this assumes the tab complete list won't change between actual
2893 	// /// presses of tab by the user. If you pass it a list, it will use it, but
2894 	// /// otherwise it will keep track of the last one to avoid calls to tabComplete.
2895 	private string suggestion(string[] list = null) {
2896 		import std.algorithm, std.utf;
2897 		auto relevantLineSection = line[0 .. cursorPosition];
2898 		// FIXME: see about caching the list if we easily can
2899 		if(list is null)
2900 			list = filterTabCompleteList(tabComplete(relevantLineSection));
2901 
2902 		if(list.length) {
2903 			string commonality = list[0];
2904 			foreach(item; list[1 .. $]) {
2905 				commonality = commonPrefix(commonality, item);
2906 			}
2907 
2908 			if(commonality.length) {
2909 				return commonality[codeLength!char(relevantLineSection) .. $];
2910 			}
2911 		}
2912 
2913 		return null;
2914 	}
2915 
2916 	/// Adds a character at the current position in the line. You can call this too if you hook events for hotkeys or something.
2917 	/// You'll probably want to call redraw() after adding chars.
2918 	void addChar(dchar ch) {
2919 		assert(cursorPosition >= 0 && cursorPosition <= line.length);
2920 		if(cursorPosition == line.length)
2921 			line ~= ch;
2922 		else {
2923 			assert(line.length);
2924 			if(insertMode) {
2925 				line ~= ' ';
2926 				for(int i = cast(int) line.length - 2; i >= cursorPosition; i --)
2927 					line[i + 1] = line[i];
2928 			}
2929 			line[cursorPosition] = ch;
2930 		}
2931 		cursorPosition++;
2932 
2933 		if(cursorPosition >= horizontalScrollPosition + availableLineLength())
2934 			horizontalScrollPosition++;
2935 	}
2936 
2937 	/// .
2938 	void addString(string s) {
2939 		// FIXME: this could be more efficient
2940 		// but does it matter? these lines aren't super long anyway. But then again a paste could be excessively long (prolly accidental, but still)
2941 		foreach(dchar ch; s)
2942 			addChar(ch);
2943 	}
2944 
2945 	/// Deletes the character at the current position in the line.
2946 	/// You'll probably want to call redraw() after deleting chars.
2947 	void deleteChar() {
2948 		if(cursorPosition == line.length)
2949 			return;
2950 		for(int i = cursorPosition; i < line.length - 1; i++)
2951 			line[i] = line[i + 1];
2952 		line = line[0 .. $-1];
2953 		line.assumeSafeAppend();
2954 	}
2955 
2956 	///
2957 	void deleteToEndOfLine() {
2958 		while(cursorPosition < line.length)
2959 			deleteChar();
2960 	}
2961 
2962 	int availableLineLength() {
2963 		return terminal.width - startOfLineX - cast(int) prompt.length - 1;
2964 	}
2965 
2966 	private int lastDrawLength = 0;
2967 	void redraw() {
2968 		terminal.moveTo(startOfLineX, startOfLineY);
2969 
2970 		auto lineLength = availableLineLength();
2971 		if(lineLength < 0)
2972 			throw new Exception("too narrow terminal to draw");
2973 
2974 		terminal.write(prompt);
2975 
2976 		auto towrite = line[horizontalScrollPosition .. $];
2977 		auto cursorPositionToDrawX = cursorPosition - horizontalScrollPosition;
2978 		auto cursorPositionToDrawY = 0;
2979 
2980 		if(towrite.length > lineLength) {
2981 			towrite = towrite[0 .. lineLength];
2982 		}
2983 
2984 		terminal.write(towrite);
2985 
2986 		lineLength -= towrite.length;
2987 
2988 		string suggestion;
2989 
2990 		if(lineLength >= 0) {
2991 			suggestion = ((cursorPosition == towrite.length) && autoSuggest) ? this.suggestion() : null;
2992 			if(suggestion.length) {
2993 				terminal.color(suggestionForeground, background);
2994 				terminal.write(suggestion);
2995 				terminal.color(regularForeground, background);
2996 			}
2997 		}
2998 
2999 		// FIXME: graphemes and utf-8 on suggestion/prompt
3000 		auto written = cast(int) (towrite.length + suggestion.length + prompt.length);
3001 
3002 		if(written < lastDrawLength)
3003 		foreach(i; written .. lastDrawLength)
3004 			terminal.write(" ");
3005 		lastDrawLength = written;
3006 
3007 		terminal.moveTo(startOfLineX + cursorPositionToDrawX + cast(int) prompt.length, startOfLineY + cursorPositionToDrawY);
3008 	}
3009 
3010 	/// Starts getting a new line. Call workOnLine and finishGettingLine afterward.
3011 	///
3012 	/// Make sure that you've flushed your input and output before calling this
3013 	/// function or else you might lose events or get exceptions from this.
3014 	void startGettingLine() {
3015 		// reset from any previous call first
3016 		cursorPosition = 0;
3017 		horizontalScrollPosition = 0;
3018 		justHitTab = false;
3019 		currentHistoryViewPosition = 0;
3020 		if(line.length) {
3021 			line = line[0 .. 0];
3022 			line.assumeSafeAppend();
3023 		}
3024 
3025 		updateCursorPosition();
3026 		terminal.showCursor();
3027 
3028 		lastDrawLength = availableLineLength();
3029 		redraw();
3030 	}
3031 
3032 	private void updateCursorPosition() {
3033 		terminal.flush();
3034 
3035 		// then get the current cursor position to start fresh
3036 		version(Windows) {
3037 			CONSOLE_SCREEN_BUFFER_INFO info;
3038 			GetConsoleScreenBufferInfo(terminal.hConsole, &info);
3039 			startOfLineX = info.dwCursorPosition.X;
3040 			startOfLineY = info.dwCursorPosition.Y;
3041 		} else {
3042 			// request current cursor position
3043 
3044 			// we have to turn off cooked mode to get this answer, otherwise it will all
3045 			// be messed up. (I hate unix terminals, the Windows way is so much easer.)
3046 
3047 			// We also can't use RealTimeConsoleInput here because it also does event loop stuff
3048 			// which would be broken by the child destructor :( (maybe that should be a FIXME)
3049 
3050 			ubyte[128] hack2;
3051 			termios old;
3052 			ubyte[128] hack;
3053 			tcgetattr(terminal.fdIn, &old);
3054 			auto n = old;
3055 			n.c_lflag &= ~(ICANON | ECHO);
3056 			tcsetattr(terminal.fdIn, TCSANOW, &n);
3057 			scope(exit)
3058 				tcsetattr(terminal.fdIn, TCSANOW, &old);
3059 
3060 
3061 			terminal.writeStringRaw("\033[6n");
3062 			terminal.flush();
3063 
3064 			import core.sys.posix.unistd;
3065 			// reading directly to bypass any buffering
3066 			ubyte[16] buffer;
3067 			auto len = read(terminal.fdIn, buffer.ptr, buffer.length);
3068 			if(len <= 0)
3069 				throw new Exception("Couldn't get cursor position to initialize get line");
3070 			auto got = buffer[0 .. len];
3071 			if(got.length < 6)
3072 				throw new Exception("not enough cursor reply answer");
3073 			if(got[0] != '\033' || got[1] != '[' || got[$-1] != 'R')
3074 				throw new Exception("wrong answer for cursor position");
3075 			auto gots = cast(char[]) got[2 .. $-1];
3076 
3077 			import std.conv;
3078 			import std.string;
3079 
3080 			auto pieces = split(gots, ";");
3081 			if(pieces.length != 2) throw new Exception("wtf wrong answer on cursor position");
3082 
3083 			startOfLineX = to!int(pieces[1]) - 1;
3084 			startOfLineY = to!int(pieces[0]) - 1;
3085 		}
3086 
3087 		// updating these too because I can with the more accurate info from above
3088 		terminal._cursorX = startOfLineX;
3089 		terminal._cursorY = startOfLineY;
3090 	}
3091 
3092 	private bool justHitTab;
3093 
3094 	/// for integrating into another event loop
3095 	/// you can pass individual events to this and
3096 	/// the line getter will work on it
3097 	///
3098 	/// returns false when there's nothing more to do
3099 	bool workOnLine(InputEvent e) {
3100 		switch(e.type) {
3101 			case InputEvent.Type.EndOfFileEvent:
3102 				justHitTab = false;
3103 				// FIXME: this should be distinct from an empty line when hit at the beginning
3104 				return false;
3105 			//break;
3106 			case InputEvent.Type.KeyboardEvent:
3107 				auto ev = e.keyboardEvent;
3108 				if(ev.pressed == false)
3109 					return true;
3110 				/* Insert the character (unless it is backspace, tab, or some other control char) */
3111 				auto ch = ev.which;
3112 				switch(ch) {
3113 					case 4: // ctrl+d will also send a newline-equivalent 
3114 					case '\r':
3115 					case '\n':
3116 						justHitTab = false;
3117 						return false;
3118 					case '\t':
3119 						auto relevantLineSection = line[0 .. cursorPosition];
3120 						auto possibilities = filterTabCompleteList(tabComplete(relevantLineSection));
3121 						import std.utf;
3122 
3123 						if(possibilities.length == 1) {
3124 							auto toFill = possibilities[0][codeLength!char(relevantLineSection) .. $];
3125 							if(toFill.length) {
3126 								addString(toFill);
3127 								redraw();
3128 							}
3129 							justHitTab = false;
3130 						} else {
3131 							if(justHitTab) {
3132 								justHitTab = false;
3133 								showTabCompleteList(possibilities);
3134 							} else {
3135 								justHitTab = true;
3136 								/* fill it in with as much commonality as there is amongst all the suggestions */
3137 								auto suggestion = this.suggestion(possibilities);
3138 								if(suggestion.length) {
3139 									addString(suggestion);
3140 									redraw();
3141 								}
3142 							}
3143 						}
3144 					break;
3145 					case '\b':
3146 						justHitTab = false;
3147 						if(cursorPosition) {
3148 							cursorPosition--;
3149 							for(int i = cursorPosition; i < line.length - 1; i++)
3150 								line[i] = line[i + 1];
3151 							line = line[0 .. $ - 1];
3152 							line.assumeSafeAppend();
3153 
3154 							if(!multiLineMode) {
3155 								if(horizontalScrollPosition > cursorPosition - 1)
3156 									horizontalScrollPosition = cursorPosition - 1 - availableLineLength();
3157 								if(horizontalScrollPosition < 0)
3158 									horizontalScrollPosition = 0;
3159 							}
3160 
3161 							redraw();
3162 						}
3163 					break;
3164 					case KeyboardEvent.Key.LeftArrow:
3165 						justHitTab = false;
3166 						if(cursorPosition)
3167 							cursorPosition--;
3168 						if(!multiLineMode) {
3169 							if(cursorPosition < horizontalScrollPosition)
3170 								horizontalScrollPosition--;
3171 						}
3172 
3173 						redraw();
3174 					break;
3175 					case KeyboardEvent.Key.RightArrow:
3176 						justHitTab = false;
3177 						if(cursorPosition < line.length)
3178 							cursorPosition++;
3179 						if(!multiLineMode) {
3180 							if(cursorPosition >= horizontalScrollPosition + availableLineLength())
3181 								horizontalScrollPosition++;
3182 						}
3183 
3184 						redraw();
3185 					break;
3186 					case KeyboardEvent.Key.UpArrow:
3187 						justHitTab = false;
3188 						loadFromHistory(currentHistoryViewPosition + 1);
3189 						redraw();
3190 					break;
3191 					case KeyboardEvent.Key.DownArrow:
3192 						justHitTab = false;
3193 						loadFromHistory(currentHistoryViewPosition - 1);
3194 						redraw();
3195 					break;
3196 					case KeyboardEvent.Key.PageUp:
3197 						justHitTab = false;
3198 						loadFromHistory(cast(int) history.length);
3199 						redraw();
3200 					break;
3201 					case KeyboardEvent.Key.PageDown:
3202 						justHitTab = false;
3203 						loadFromHistory(0);
3204 						redraw();
3205 					break;
3206 					case 1: // ctrl+a does home too in the emacs keybindings
3207 					case KeyboardEvent.Key.Home:
3208 						justHitTab = false;
3209 						cursorPosition = 0;
3210 						horizontalScrollPosition = 0;
3211 						redraw();
3212 					break;
3213 					case 5: // ctrl+e from emacs
3214 					case KeyboardEvent.Key.End:
3215 						justHitTab = false;
3216 						cursorPosition = cast(int) line.length;
3217 						scrollToEnd();
3218 						redraw();
3219 					break;
3220 					case KeyboardEvent.Key.Insert:
3221 						justHitTab = false;
3222 						insertMode = !insertMode;
3223 						// FIXME: indicate this on the UI somehow
3224 						// like change the cursor or something
3225 					break;
3226 					case KeyboardEvent.Key.Delete:
3227 						justHitTab = false;
3228 						if(ev.modifierState & ModifierState.control)
3229 							deleteToEndOfLine();
3230 						else
3231 							deleteChar();
3232 						redraw();
3233 					break;
3234 					case 11: // ctrl+k is delete to end of line from emacs
3235 						justHitTab = false;
3236 						deleteToEndOfLine();
3237 						redraw();
3238 					break;
3239 					default:
3240 						justHitTab = false;
3241 						if(e.keyboardEvent.isCharacter)
3242 							addChar(ch);
3243 						redraw();
3244 				}
3245 			break;
3246 			case InputEvent.Type.PasteEvent:
3247 				justHitTab = false;
3248 				addString(e.pasteEvent.pastedText);
3249 				redraw();
3250 			break;
3251 			case InputEvent.Type.MouseEvent:
3252 				/* Clicking with the mouse to move the cursor is so much easier than arrowing
3253 				   or even emacs/vi style movements much of the time, so I'ma support it. */
3254 
3255 				auto me = e.mouseEvent;
3256 				if(me.eventType == MouseEvent.Type.Pressed) {
3257 					if(me.buttons & MouseEvent.Button.Left) {
3258 						if(me.y == startOfLineY) {
3259 							// FIXME: prompt.length should be graphemes or at least code poitns
3260 							int p = me.x - startOfLineX - cast(int) prompt.length + horizontalScrollPosition;
3261 							if(p >= 0 && p < line.length) {
3262 								justHitTab = false;
3263 								cursorPosition = p;
3264 								redraw();
3265 							}
3266 						}
3267 					}
3268 				}
3269 			break;
3270 			case InputEvent.Type.SizeChangedEvent:
3271 				/* We'll adjust the bounding box. If you don't like this, handle SizeChangedEvent
3272 				   yourself and then don't pass it to this function. */
3273 				// FIXME
3274 			break;
3275 			case InputEvent.Type.UserInterruptionEvent:
3276 				/* I'll take this as canceling the line. */
3277 				throw new UserInterruptionException();
3278 			//break;
3279 			case InputEvent.Type.HangupEvent:
3280 				/* I'll take this as canceling the line. */
3281 				throw new HangupException();
3282 			//break;
3283 			default:
3284 				/* ignore. ideally it wouldn't be passed to us anyway! */
3285 		}
3286 
3287 		return true;
3288 	}
3289 
3290 	string finishGettingLine() {
3291 		import std.conv;
3292 		auto f = to!string(line);
3293 		auto history = historyFilter(f);
3294 		if(history !is null)
3295 			this.history ~= history;
3296 
3297 		// FIXME: we should hide the cursor if it was hidden in the call to startGettingLine
3298 		return f;
3299 	}
3300 }
3301 
3302 /// Adds default constructors that just forward to the superclass
3303 mixin template LineGetterConstructors() {
3304 	this(Terminal* tty, string historyFilename = null) {
3305 		super(tty, historyFilename);
3306 	}
3307 }
3308 
3309 /// This is a line getter that customizes the tab completion to
3310 /// fill in file names separated by spaces, like a command line thing.
3311 class FileLineGetter : LineGetter {
3312 	mixin LineGetterConstructors;
3313 
3314 	/// You can set this property to tell it where to search for the files
3315 	/// to complete.
3316 	string searchDirectory = ".";
3317 
3318 	override protected string[] tabComplete(in dchar[] candidate) {
3319 		import std.file, std.conv, std.algorithm, std.string;
3320 		const(dchar)[] soFar = candidate;
3321 		auto idx = candidate.lastIndexOf(" ");
3322 		if(idx != -1)
3323 			soFar = candidate[idx + 1 .. $];
3324 
3325 		string[] list;
3326 		foreach(string name; dirEntries(searchDirectory, SpanMode.breadth)) {
3327 			// try without the ./
3328 			if(startsWith(name[2..$], soFar))
3329 				list ~= text(candidate, name[searchDirectory.length + 1 + soFar.length .. $]);
3330 			else // and with
3331 			if(startsWith(name, soFar))
3332 				list ~= text(candidate, name[soFar.length .. $]);
3333 		}
3334 
3335 		return list;
3336 	}
3337 }
3338 
3339 version(Windows) {
3340 	// to get the directory for saving history in the line things
3341 	enum CSIDL_APPDATA = 26;
3342 	extern(Windows) HRESULT SHGetFolderPathA(HWND, int, HANDLE, DWORD, LPSTR);
3343 }
3344 
3345 
3346 
3347 
3348 
3349 /* Like getting a line, printing a lot of lines is kinda important too, so I'm including
3350    that widget here too. */
3351 
3352 
3353 struct ScrollbackBuffer {
3354 	this(string name) {
3355 		this.name = name;
3356 	}
3357 
3358 	void write(T...)(T t) {
3359 		import std.conv : text;
3360 		addComponent(text(t), foreground_, background_, null);
3361 	}
3362 
3363 	void writeln(T...)(T t) {
3364 		write(t, "\n");
3365 	}
3366 
3367 	void writef(T...)(string fmt, T t) {
3368 		import std.format: format;
3369 		write(format(fmt, t));
3370 	}
3371 
3372 	void writefln(T...)(string fmt, T t) {
3373 		writef(fmt, t, "\n");
3374 	}
3375 
3376 	void clear() {
3377 		lines = null;
3378 		clickRegions = null;
3379 		scrollbackPosition = 0;
3380 	}
3381 
3382 	int foreground_ = Color.DEFAULT, background_ = Color.DEFAULT;
3383 	void color(int foreground, int background) {
3384 		this.foreground_ = foreground;
3385 		this.background_ = background;
3386 	}
3387 
3388 	void addComponent(string text, int foreground, int background, bool delegate() onclick) {
3389 		if(lines.length == 0) {
3390 			addLine();
3391 		}
3392 		bool first = true;
3393 		import std.algorithm;
3394 		foreach(t; splitter(text, "\n")) {
3395 			if(!first) addLine();
3396 			first = false;
3397 			lines[$-1].components ~= LineComponent(t, foreground, background, onclick);
3398 		}
3399 	}
3400 
3401 	void addLine() {
3402 		lines ~= Line();
3403 		if(scrollbackPosition) // if the user is scrolling back, we want to keep them basically centered where they are
3404 			scrollbackPosition++;
3405 	}
3406 
3407 	void addLine(string line) {
3408 		lines ~= Line([LineComponent(line)]);
3409 		if(scrollbackPosition) // if the user is scrolling back, we want to keep them basically centered where they are
3410 			scrollbackPosition++;
3411 	}
3412 
3413 	void scrollUp(int lines = 1) {
3414 		scrollbackPosition += lines;
3415 		//if(scrollbackPosition >= this.lines.length)
3416 		//	scrollbackPosition = cast(int) this.lines.length - 1;
3417 	}
3418 
3419 	void scrollDown(int lines = 1) {
3420 		scrollbackPosition -= lines;
3421 		if(scrollbackPosition < 0)
3422 			scrollbackPosition = 0;
3423 	}
3424 
3425 
3426 
3427 	struct LineComponent {
3428 		string text;
3429 		int color = Color.DEFAULT;
3430 		int background = Color.DEFAULT;
3431 		bool delegate() onclick; // return true if you need to redraw
3432 	}
3433 
3434 	struct Line {
3435 		LineComponent[] components;
3436 		int length() {
3437 			int l = 0;
3438 			foreach(c; components)
3439 				l += c.text.length;
3440 			return l;
3441 		}
3442 	}
3443 
3444 	// FIXME: limit scrollback lines.length
3445 
3446 	Line[] lines;
3447 	string name;
3448 
3449 	int x, y, width, height;
3450 
3451 	int scrollbackPosition;
3452 
3453 	void drawInto(Terminal* terminal, in int x = 0, in int y = 0, int width = 0, int height = 0) {
3454 		if(lines.length == 0)
3455 			return;
3456 
3457 		if(width == 0)
3458 			width = terminal.width;
3459 		if(height == 0)
3460 			height = terminal.height;
3461 
3462 		this.x = x;
3463 		this.y = y;
3464 		this.width = width;
3465 		this.height = height;
3466 
3467 		/* We need to figure out how much is going to fit
3468 		   in a first pass, so we can figure out where to
3469 		   start drawing */
3470 
3471 		int remaining = height + scrollbackPosition;
3472 		int start = cast(int) lines.length;
3473 		int howMany = 0;
3474 
3475 		bool firstPartial = false;
3476 
3477 		static struct Idx {
3478 			size_t cidx;
3479 			size_t idx;
3480 		}
3481 
3482 		Idx firstPartialStartIndex;
3483 
3484 		// this is private so I know we can safe append
3485 		clickRegions.length = 0;
3486 		clickRegions.assumeSafeAppend();
3487 
3488 		// FIXME: should prolly handle \n and \r in here too.
3489 
3490 		// we'll work backwards to figure out how much will fit...
3491 		// this will give accurate per-line things even with changing width and wrapping
3492 		// while being generally efficient - we usually want to show the end of the list
3493 		// anyway; actually using the scrollback is a bit of an exceptional case.
3494 
3495 		// It could probably do this instead of on each redraw, on each resize or insertion.
3496 		// or at least cache between redraws until one of those invalidates it.
3497 		foreach_reverse(line; lines) {
3498 			int written = 0;
3499 			int brokenLineCount;
3500 			Idx[16] lineBreaksBuffer;
3501 			Idx[] lineBreaks = lineBreaksBuffer[];
3502 			comp_loop: foreach(cidx, component; line.components) {
3503 				auto towrite = component.text;
3504 				foreach(idx, dchar ch; towrite) {
3505 					if(written >= width) {
3506 						if(brokenLineCount == lineBreaks.length)
3507 							lineBreaks ~= Idx(cidx, idx);
3508 						else
3509 							lineBreaks[brokenLineCount] = Idx(cidx, idx);
3510 
3511 						brokenLineCount++;
3512 
3513 						written = 0;
3514 					}
3515 
3516 					if(ch == '\t')
3517 						written += 8; // FIXME
3518 					else
3519 						written++;
3520 				}
3521 			}
3522 
3523 			lineBreaks = lineBreaks[0 .. brokenLineCount];
3524 
3525 			foreach_reverse(lineBreak; lineBreaks) {
3526 				if(remaining == 1) {
3527 					firstPartial = true;
3528 					firstPartialStartIndex = lineBreak;
3529 					break;
3530 				} else {
3531 					remaining--;
3532 				}
3533 				if(remaining <= 0)
3534 					break;
3535 			}
3536 
3537 			remaining--;
3538 
3539 			start--;
3540 			howMany++;
3541 			if(remaining <= 0)
3542 				break;
3543 		}
3544 
3545 		// second pass: actually draw it
3546 		int linePos = remaining;
3547 
3548 		foreach(idx, line; lines[start .. start + howMany]) {
3549 			int written = 0;
3550 
3551 			if(linePos < 0) {
3552 				linePos++;
3553 				continue;
3554 			}
3555 		
3556 			terminal.moveTo(x, y + ((linePos >= 0) ? linePos : 0));
3557 
3558 			auto todo = line.components;
3559 
3560 			if(firstPartial) {
3561 				todo = todo[firstPartialStartIndex.cidx .. $];
3562 			}
3563 
3564 			foreach(ref component; todo) {
3565 				terminal.color(component.color, component.background);
3566 				auto towrite = component.text;
3567 
3568 				again:
3569 
3570 				if(linePos >= height)
3571 					break;
3572 
3573 				if(firstPartial) {
3574 					towrite = towrite[firstPartialStartIndex.idx .. $];
3575 					firstPartial = false;
3576 				}
3577 
3578 				foreach(idx, dchar ch; towrite) {
3579 					if(written >= width) {
3580 						clickRegions ~= ClickRegion(&component, terminal.cursorX, terminal.cursorY, written);
3581 						terminal.write(towrite[0 .. idx]);
3582 						towrite = towrite[idx .. $];
3583 						linePos++;
3584 						written = 0;
3585 						terminal.moveTo(x, y + linePos);
3586 						goto again;
3587 					}
3588 
3589 					if(ch == '\t')
3590 						written += 8; // FIXME
3591 					else
3592 						written++;
3593 				}
3594 
3595 				if(towrite.length) {
3596 					clickRegions ~= ClickRegion(&component, terminal.cursorX, terminal.cursorY, written);
3597 					terminal.write(towrite);
3598 				}
3599 			}
3600 
3601 			if(written < width) {
3602 				terminal.color(Color.DEFAULT, Color.DEFAULT);
3603 				foreach(i; written .. width)
3604 					terminal.write(" ");
3605 			}
3606 
3607 			linePos++;
3608 
3609 			if(linePos >= height)
3610 				break;
3611 		}
3612 
3613 		if(linePos < height) {
3614 			terminal.color(Color.DEFAULT, Color.DEFAULT);
3615 			foreach(i; linePos .. height) {
3616 				if(i >= 0 && i < height) {
3617 					terminal.moveTo(x, y + i);
3618 					foreach(w; 0 .. width)
3619 						terminal.write(" ");
3620 				}
3621 			}
3622 		}
3623 	}
3624 
3625 	private struct ClickRegion {
3626 		LineComponent* component;
3627 		int xStart;
3628 		int yStart;
3629 		int length;
3630 	}
3631 	private ClickRegion[] clickRegions;
3632 
3633 	/// Default event handling for this widget. Call this only after drawing it into a rectangle
3634 	/// and only if the event ought to be dispatched to it (which you determine however you want;
3635 	/// you could dispatch all events to it, or perhaps filter some out too)
3636 	///
3637 	/// Returns true if it should be redrawn
3638 	bool handleEvent(InputEvent e) {
3639 		final switch(e.type) {
3640 			case InputEvent.Type.KeyboardEvent:
3641 				auto ev = e.keyboardEvent;
3642 
3643 				switch(ev.which) {
3644 					case KeyboardEvent.Key.UpArrow:
3645 						scrollUp();
3646 						return true;
3647 					case KeyboardEvent.Key.DownArrow:
3648 						scrollDown();
3649 						return true;
3650 					case KeyboardEvent.Key.PageUp:
3651 						scrollUp(height);
3652 						return true;
3653 					case KeyboardEvent.Key.PageDown:
3654 						scrollDown(height);
3655 						return true;
3656 					default:
3657 						// ignore
3658 				}
3659 			break;
3660 			case InputEvent.Type.MouseEvent:
3661 				auto ev = e.mouseEvent;
3662 				if(ev.x >= x && ev.x < x + width && ev.y >= y && ev.y < y + height) {
3663 					// it is inside our box, so do something with it
3664 					auto mx = ev.x - x;
3665 					auto my = ev.y - y;
3666 
3667 					if(ev.eventType == MouseEvent.Type.Pressed) {
3668 						if(ev.buttons & MouseEvent.Button.Left) {
3669 							foreach(region; clickRegions)
3670 								if(ev.x >= region.xStart && ev.x < region.xStart + region.length && ev.y == region.yStart)
3671 									if(region.component.onclick !is null)
3672 										return region.component.onclick();
3673 						}
3674 						if(ev.buttons & MouseEvent.Button.ScrollUp) {
3675 							scrollUp();
3676 							return true;
3677 						}
3678 						if(ev.buttons & MouseEvent.Button.ScrollDown) {
3679 							scrollDown();
3680 							return true;
3681 						}
3682 					}
3683 				} else {
3684 					// outside our area, free to ignore
3685 				}
3686 			break;
3687 			case InputEvent.Type.SizeChangedEvent:
3688 				// (size changed might be but it needs to be handled at a higher level really anyway)
3689 				// though it will return true because it probably needs redrawing anyway.
3690 				return true;
3691 			case InputEvent.Type.UserInterruptionEvent:
3692 				throw new UserInterruptionException();
3693 			case InputEvent.Type.HangupEvent:
3694 				throw new HangupException();
3695 			case InputEvent.Type.EndOfFileEvent:
3696 				// ignore, not relevant to this
3697 			break;
3698 			case InputEvent.Type.CharacterEvent:
3699 			case InputEvent.Type.NonCharacterKeyEvent:
3700 				// obsolete, ignore them until they are removed
3701 			break;
3702 			case InputEvent.Type.CustomEvent:
3703 			case InputEvent.Type.PasteEvent:
3704 				// ignored, not relevant to us
3705 			break;
3706 		}
3707 
3708 		return false;
3709 	}
3710 }
3711 
3712 
3713 class UserInterruptionException : Exception {
3714 	this() { super("Ctrl+C"); }
3715 }
3716 class HangupException : Exception {
3717 	this() { super("Hup"); }
3718 }
3719 
3720 
3721 
3722 /*
3723 
3724 	// more efficient scrolling
3725 	http://msdn.microsoft.com/en-us/library/windows/desktop/ms685113%28v=vs.85%29.aspx
3726 	// and the unix sequences
3727 
3728 
3729 	rxvt documentation:
3730 	use this to finish the input magic for that
3731 
3732 
3733        For the keypad, use Shift to temporarily override Application-Keypad
3734        setting use Num_Lock to toggle Application-Keypad setting if Num_Lock
3735        is off, toggle Application-Keypad setting. Also note that values of
3736        Home, End, Delete may have been compiled differently on your system.
3737 
3738                          Normal       Shift         Control      Ctrl+Shift
3739        Tab               ^I           ESC [ Z       ^I           ESC [ Z
3740        BackSpace         ^H           ^?            ^?           ^?
3741        Find              ESC [ 1 ~    ESC [ 1 $     ESC [ 1 ^    ESC [ 1 @
3742        Insert            ESC [ 2 ~    paste         ESC [ 2 ^    ESC [ 2 @
3743        Execute           ESC [ 3 ~    ESC [ 3 $     ESC [ 3 ^    ESC [ 3 @
3744        Select            ESC [ 4 ~    ESC [ 4 $     ESC [ 4 ^    ESC [ 4 @
3745        Prior             ESC [ 5 ~    scroll-up     ESC [ 5 ^    ESC [ 5 @
3746        Next              ESC [ 6 ~    scroll-down   ESC [ 6 ^    ESC [ 6 @
3747        Home              ESC [ 7 ~    ESC [ 7 $     ESC [ 7 ^    ESC [ 7 @
3748        End               ESC [ 8 ~    ESC [ 8 $     ESC [ 8 ^    ESC [ 8 @
3749        Delete            ESC [ 3 ~    ESC [ 3 $     ESC [ 3 ^    ESC [ 3 @
3750        F1                ESC [ 11 ~   ESC [ 23 ~    ESC [ 11 ^   ESC [ 23 ^
3751        F2                ESC [ 12 ~   ESC [ 24 ~    ESC [ 12 ^   ESC [ 24 ^
3752        F3                ESC [ 13 ~   ESC [ 25 ~    ESC [ 13 ^   ESC [ 25 ^
3753        F4                ESC [ 14 ~   ESC [ 26 ~    ESC [ 14 ^   ESC [ 26 ^
3754        F5                ESC [ 15 ~   ESC [ 28 ~    ESC [ 15 ^   ESC [ 28 ^
3755        F6                ESC [ 17 ~   ESC [ 29 ~    ESC [ 17 ^   ESC [ 29 ^
3756        F7                ESC [ 18 ~   ESC [ 31 ~    ESC [ 18 ^   ESC [ 31 ^
3757        F8                ESC [ 19 ~   ESC [ 32 ~    ESC [ 19 ^   ESC [ 32 ^
3758        F9                ESC [ 20 ~   ESC [ 33 ~    ESC [ 20 ^   ESC [ 33 ^
3759        F10               ESC [ 21 ~   ESC [ 34 ~    ESC [ 21 ^   ESC [ 34 ^
3760        F11               ESC [ 23 ~   ESC [ 23 $    ESC [ 23 ^   ESC [ 23 @
3761        F12               ESC [ 24 ~   ESC [ 24 $    ESC [ 24 ^   ESC [ 24 @
3762        F13               ESC [ 25 ~   ESC [ 25 $    ESC [ 25 ^   ESC [ 25 @
3763        F14               ESC [ 26 ~   ESC [ 26 $    ESC [ 26 ^   ESC [ 26 @
3764        F15 (Help)        ESC [ 28 ~   ESC [ 28 $    ESC [ 28 ^   ESC [ 28 @
3765        F16 (Menu)        ESC [ 29 ~   ESC [ 29 $    ESC [ 29 ^   ESC [ 29 @
3766 
3767        F17               ESC [ 31 ~   ESC [ 31 $    ESC [ 31 ^   ESC [ 31 @
3768        F18               ESC [ 32 ~   ESC [ 32 $    ESC [ 32 ^   ESC [ 32 @
3769        F19               ESC [ 33 ~   ESC [ 33 $    ESC [ 33 ^   ESC [ 33 @
3770        F20               ESC [ 34 ~   ESC [ 34 $    ESC [ 34 ^   ESC [ 34 @
3771                                                                  Application
3772        Up                ESC [ A      ESC [ a       ESC O a      ESC O A
3773        Down              ESC [ B      ESC [ b       ESC O b      ESC O B
3774        Right             ESC [ C      ESC [ c       ESC O c      ESC O C
3775        Left              ESC [ D      ESC [ d       ESC O d      ESC O D
3776        KP_Enter          ^M                                      ESC O M
3777        KP_F1             ESC O P                                 ESC O P
3778        KP_F2             ESC O Q                                 ESC O Q
3779        KP_F3             ESC O R                                 ESC O R
3780        KP_F4             ESC O S                                 ESC O S
3781        XK_KP_Multiply    *                                       ESC O j
3782        XK_KP_Add         +                                       ESC O k
3783        XK_KP_Separator   ,                                       ESC O l
3784        XK_KP_Subtract    -                                       ESC O m
3785        XK_KP_Decimal     .                                       ESC O n
3786        XK_KP_Divide      /                                       ESC O o
3787        XK_KP_0           0                                       ESC O p
3788        XK_KP_1           1                                       ESC O q
3789        XK_KP_2           2                                       ESC O r
3790        XK_KP_3           3                                       ESC O s
3791        XK_KP_4           4                                       ESC O t
3792        XK_KP_5           5                                       ESC O u
3793        XK_KP_6           6                                       ESC O v
3794        XK_KP_7           7                                       ESC O w
3795        XK_KP_8           8                                       ESC O x
3796        XK_KP_9           9                                       ESC O y
3797 */
3798 
3799 version(Demo_kbhit)
3800 void main() {
3801 	auto terminal = Terminal(ConsoleOutputType.linear);
3802 	auto input = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw);
3803 
3804 	int a;
3805 	char ch = '.';
3806 	while(a < 1000) {
3807 		a++;
3808 		if(a % terminal.width == 0) {
3809 			terminal.write("\r");
3810 			if(ch == '.')
3811 				ch = ' ';
3812 			else
3813 				ch = '.';
3814 		}
3815 
3816 		if(input.kbhit())
3817 			terminal.write(input.getch());
3818 		else
3819 			terminal.write(ch);
3820 
3821 		terminal.flush();
3822 
3823 		import core.thread;
3824 		Thread.sleep(50.msecs);
3825 	}
3826 }