2 // System.WindowsConsoleDriver
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2005 Novell, Inc. (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Runtime.InteropServices;
33 struct ConsoleCursorInfo {
39 public short EventType;
40 // This is KEY_EVENT_RECORD
42 public short RepeatCount;
43 public short VirtualKeyCode;
44 public short VirtualScanCode;
45 public char Character;
46 public int ControlKeyState;
53 public char Character;
54 public short Attributes;
61 public Coord (int x, int y)
74 public SmallRect (int left, int top, int right, int bottom)
78 Right = (short) right;
79 Bottom = (short) bottom;
83 struct ConsoleScreenBufferInfo {
85 public Coord CursorPosition;
86 public short Attribute;
87 public SmallRect Window;
88 public Coord MaxWindowSize;
97 unsafe class WindowsConsoleDriver : IConsoleDriver {
100 short defaultAttribute;
103 public WindowsConsoleDriver ()
105 outputHandle = GetStdHandle (Handles.STD_OUTPUT);
106 inputHandle = GetStdHandle (Handles.STD_INPUT);
107 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
108 GetConsoleScreenBufferInfo (outputHandle, out info);
109 defaultAttribute = info.Attribute; // Not sure about this...
113 // FOREGROUND_GREEN 2
115 // FOREGROUND_INTENSITY 8
116 // BACKGROUND_BLUE 16
117 // BACKGROUND_GREEN 32
119 // BACKGROUND_INTENSITY 128
120 static ConsoleColor GetForeground (short attr)
123 return (ConsoleColor) attr;
126 static ConsoleColor GetBackground (short attr)
130 return (ConsoleColor) attr;
133 static short GetAttrForeground (int attr, ConsoleColor color)
136 return (short) (attr | (int) color);
139 static short GetAttrBackground (int attr, ConsoleColor color)
142 int c = ((int) color) << 4;
143 return (short) (attr | c);
146 public ConsoleColor BackgroundColor {
148 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
149 GetConsoleScreenBufferInfo (outputHandle, out info);
150 return GetBackground (info.Attribute);
153 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
154 GetConsoleScreenBufferInfo (outputHandle, out info);
155 short attr = GetAttrBackground (info.Attribute, value);
156 SetConsoleTextAttribute (outputHandle, attr);
160 public int BufferHeight {
162 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
163 GetConsoleScreenBufferInfo (outputHandle, out info);
166 set { SetBufferSize (BufferWidth, value); }
169 public int BufferWidth {
171 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
172 GetConsoleScreenBufferInfo (outputHandle, out info);
175 set { SetBufferSize (value, BufferHeight); }
178 public bool CapsLock {
180 short state = GetKeyState (20); // VK_CAPITAL
181 return ((state & 1) == 1);
185 public int CursorLeft {
187 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
188 GetConsoleScreenBufferInfo (outputHandle, out info);
189 return info.CursorPosition.X;
191 set { SetCursorPosition (value, CursorTop); }
194 public int CursorSize {
196 ConsoleCursorInfo info = new ConsoleCursorInfo ();
197 GetConsoleCursorInfo (outputHandle, out info);
201 if (value < 1 || value > 100)
202 throw new ArgumentOutOfRangeException ("value");
204 ConsoleCursorInfo info = new ConsoleCursorInfo ();
205 GetConsoleCursorInfo (outputHandle, out info);
207 if (!SetConsoleCursorInfo (outputHandle, ref info))
208 throw new Exception ("SetConsoleCursorInfo failed");
212 public int CursorTop {
214 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
215 GetConsoleScreenBufferInfo (outputHandle, out info);
216 return info.CursorPosition.Y;
218 set { SetCursorPosition (CursorLeft, value); }
221 public bool CursorVisible {
223 ConsoleCursorInfo info = new ConsoleCursorInfo ();
224 GetConsoleCursorInfo (outputHandle, out info);
228 ConsoleCursorInfo info = new ConsoleCursorInfo ();
229 GetConsoleCursorInfo (outputHandle, out info);
230 if (info.Visible == value)
233 info.Visible = value;
234 if (!SetConsoleCursorInfo (outputHandle, ref info))
235 throw new Exception ("SetConsoleCursorInfo failed");
239 public ConsoleColor ForegroundColor {
241 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
242 GetConsoleScreenBufferInfo (outputHandle, out info);
243 return GetForeground (info.Attribute);
246 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
247 GetConsoleScreenBufferInfo (outputHandle, out info);
248 short attr = GetAttrForeground (info.Attribute, value);
249 SetConsoleTextAttribute (outputHandle, attr);
253 public bool KeyAvailable {
256 InputRecord record = new InputRecord ();
258 // Use GetNumberOfConsoleInputEvents and remove the while?
259 if (!PeekConsoleInput (inputHandle, out record, 1, out eventsRead))
260 throw new InvalidOperationException ("Error in PeekConsoleInput " +
261 Marshal.GetLastWin32Error ());
267 if (record.EventType == 1 && record.KeyDown)
270 if (!ReadConsoleInput (inputHandle, out record, 1, out eventsRead))
271 throw new InvalidOperationException ("Error in ReadConsoleInput " +
272 Marshal.GetLastWin32Error ());
277 public bool Initialized { // Not useful on windows, so false.
278 get { return false; }
281 public int LargestWindowHeight {
283 Coord coord = GetLargestConsoleWindowSize (outputHandle);
284 if (coord.X == 0 && coord.Y == 0)
285 throw new Exception ("GetLargestConsoleWindowSize" + Marshal.GetLastWin32Error ());
291 public int LargestWindowWidth {
293 Coord coord = GetLargestConsoleWindowSize (outputHandle);
294 if (coord.X == 0 && coord.Y == 0)
295 throw new Exception ("GetLargestConsoleWindowSize" + Marshal.GetLastWin32Error ());
301 public bool NumberLock {
303 short state = GetKeyState (144); // VK_NUMLOCK
304 return ((state & 1) == 1);
308 public string Title {
310 StringBuilder sb = new StringBuilder (1024); // hope this is enough
311 if (GetConsoleTitle (sb, 1024) == 0) {
313 sb = new StringBuilder (26001);
314 if (GetConsoleTitle (sb, 26000) == 0)
315 throw new Exception ("Got " + Marshal.GetLastWin32Error ());
318 return sb.ToString ();
322 throw new ArgumentNullException ("value");
324 if (!SetConsoleTitle (value))
325 throw new Exception ("Got " + Marshal.GetLastWin32Error ());
329 public bool TreatControlCAsInput {
332 if (!GetConsoleMode (outputHandle, out mode))
333 throw new Exception ("Failed in GetConsoleMode: " + Marshal.GetLastWin32Error ());
335 // ENABLE_PROCESSED_INPUT
336 return ((mode & 1) == 0);
341 if (!GetConsoleMode (outputHandle, out mode))
342 throw new Exception ("Failed in GetConsoleMode: " + Marshal.GetLastWin32Error ());
344 bool cAsInput = ((mode & 1) == 0);
345 if (cAsInput == value)
353 if (!SetConsoleMode (outputHandle, mode))
354 throw new Exception ("Failed in SetConsoleMode: " + Marshal.GetLastWin32Error ());
358 public int WindowHeight {
360 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
361 GetConsoleScreenBufferInfo (outputHandle, out info);
362 return info.Window.Bottom - info.Window.Top + 1;
364 set { SetWindowSize (WindowWidth, value); }
367 public int WindowLeft {
369 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
370 GetConsoleScreenBufferInfo (outputHandle, out info);
371 return info.Window.Left;
373 set { SetWindowPosition (value, WindowTop); }
376 public int WindowTop {
378 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
379 GetConsoleScreenBufferInfo (outputHandle, out info);
380 return info.Window.Top;
382 set { SetWindowPosition (WindowLeft, value); }
385 public int WindowWidth {
387 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
388 GetConsoleScreenBufferInfo (outputHandle, out info);
389 return info.Window.Right - info.Window.Left + 1;
391 set { SetWindowSize (value, WindowHeight); }
394 public void Beep (int frequency, int duration)
396 _Beep (frequency, duration);
401 Coord coord = new Coord ();
402 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
403 GetConsoleScreenBufferInfo (outputHandle, out info);
405 int size = info.Size.X * info.Size.Y;
407 FillConsoleOutputCharacter (outputHandle, ' ', size, coord, out written);
409 GetConsoleScreenBufferInfo (outputHandle, out info);
411 FillConsoleOutputAttribute (outputHandle, info.Attribute, size, coord, out written);
412 SetConsoleCursorPosition (outputHandle, coord);
415 public void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
416 int targetLeft, int targetTop, Char sourceChar,
417 ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
419 if (sourceForeColor < 0)
420 throw new ArgumentException ("Cannot be less than 0.", "sourceForeColor");
422 if (sourceBackColor < 0)
423 throw new ArgumentException ("Cannot be less than 0.", "sourceBackColor");
425 if (sourceWidth == 0 || sourceHeight == 0)
428 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
429 GetConsoleScreenBufferInfo (outputHandle, out info);
430 CharInfo [] buffer = new CharInfo [sourceWidth * sourceHeight];
431 Coord bsize = new Coord (sourceWidth, sourceHeight);
432 Coord bpos = new Coord (0, 0);
433 SmallRect region = new SmallRect (sourceLeft, sourceTop, sourceLeft + sourceWidth - 1, sourceTop + sourceHeight - 1);
434 fixed (void *ptr = &buffer [0]) {
435 if (!ReadConsoleOutput (outputHandle, ptr, bsize, bpos, ref region))
436 throw new ArgumentException (String.Empty, "Cannot read from the specified coordinates.");
440 short attr = GetAttrForeground (0, sourceForeColor);
441 attr = GetAttrBackground (attr, sourceBackColor);
442 bpos = new Coord (sourceLeft, sourceTop);
443 for (int i = 0; i < sourceHeight; i++, bpos.Y++) {
444 FillConsoleOutputCharacter (outputHandle, sourceChar, sourceWidth, bpos, out written);
445 FillConsoleOutputAttribute (outputHandle, attr, sourceWidth, bpos, out written);
448 bpos = new Coord (0, 0);
449 region = new SmallRect (targetLeft, targetTop, targetLeft + sourceWidth - 1, targetTop + sourceHeight - 1);
450 if (!WriteConsoleOutput (outputHandle, buffer, bsize, bpos, ref region))
451 throw new ArgumentException (String.Empty, "Cannot write to the specified coordinates.");
458 public string ReadLine ()
460 StringBuilder builder = new StringBuilder ();
463 ConsoleKeyInfo key = ReadKey (false);
464 char c = key.KeyChar;
467 builder.Append (key.KeyChar);
469 return builder.ToString ();
472 public ConsoleKeyInfo ReadKey (bool intercept)
475 InputRecord record = new InputRecord ();
477 if (!ReadConsoleInput (inputHandle, out record, 1, out eventsRead))
478 throw new InvalidOperationException ("Error in ReadConsoleInput " +
479 Marshal.GetLastWin32Error ());
480 } while (record.EventType != 1 && !record.KeyDown);
482 // RIGHT_ALT_PRESSED 1
483 // LEFT_ALT_PRESSED 2
484 // RIGHT_CTRL_PRESSED 4
485 // LEFT_CTRL_PRESSED 8
487 bool alt = ((record.ControlKeyState & 3) != 0);
488 bool ctrl = ((record.ControlKeyState & 12) != 0);
489 bool shift = ((record.ControlKeyState & 16) != 0);
490 return new ConsoleKeyInfo (record.Character, (ConsoleKey) record.VirtualKeyCode, shift, alt, ctrl);
493 public void ResetColor ()
495 SetConsoleTextAttribute (outputHandle, defaultAttribute);
498 public void SetBufferSize (int width, int height)
500 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
501 GetConsoleScreenBufferInfo (outputHandle, out info);
503 if (width - 1 > info.Window.Right)
504 throw new ArgumentOutOfRangeException ("width");
506 if (height - 1 > info.Window.Bottom)
507 throw new ArgumentOutOfRangeException ("height");
509 Coord coord = new Coord (width, height);
510 if (!SetConsoleScreenBufferSize (outputHandle, coord))
511 throw new ArgumentOutOfRangeException ("height/width", "Cannot be smaller than the window size.");
514 public void SetCursorPosition (int left, int top)
516 Coord coord = new Coord (left, top);
517 SetConsoleCursorPosition (outputHandle, coord);
520 public void SetWindowPosition (int left, int top)
522 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
523 GetConsoleScreenBufferInfo (outputHandle, out info);
524 SmallRect rect = info.Window;
525 rect.Left = (short) left;
526 rect.Top = (short) top;
527 if (!SetConsoleWindowInfo (outputHandle, true, ref rect))
528 throw new ArgumentOutOfRangeException ("left/top", "Windows error " + Marshal.GetLastWin32Error ());
531 public void SetWindowSize (int width, int height)
533 ConsoleScreenBufferInfo info = new ConsoleScreenBufferInfo ();
534 GetConsoleScreenBufferInfo (outputHandle, out info);
535 SmallRect rect = info.Window;
536 rect.Right = (short) (rect.Left + width - 1);
537 rect.Bottom = (short) (rect.Top + height - 1);
538 if (!SetConsoleWindowInfo (outputHandle, true, ref rect))
539 throw new ArgumentOutOfRangeException ("left/top", "Windows error " + Marshal.GetLastWin32Error ());
545 [DllImport ("kernel32.dll", EntryPoint="GetStdHandle", SetLastError=true, CharSet=CharSet.Unicode)]
546 extern static IntPtr GetStdHandle (Handles handle);
548 [DllImport ("kernel32.dll", EntryPoint="Beep", SetLastError=true, CharSet=CharSet.Unicode)]
549 extern static void _Beep (int frequency, int duration);
551 [DllImport ("kernel32.dll", EntryPoint="GetConsoleScreenBufferInfo", SetLastError=true, CharSet=CharSet.Unicode)]
552 extern static bool GetConsoleScreenBufferInfo (IntPtr handle, out ConsoleScreenBufferInfo info);
554 [DllImport ("kernel32.dll", EntryPoint="FillConsoleOutputCharacter", SetLastError=true, CharSet=CharSet.Unicode)]
555 extern static bool FillConsoleOutputCharacter (IntPtr handle, char c, int size, Coord coord, out int written);
557 [DllImport ("kernel32.dll", EntryPoint="FillConsoleOutputAttribute", SetLastError=true, CharSet=CharSet.Unicode)]
558 extern static bool FillConsoleOutputAttribute (IntPtr handle, short c, int size, Coord coord, out int written);
560 [DllImport ("kernel32.dll", EntryPoint="SetConsoleCursorPosition", SetLastError=true, CharSet=CharSet.Unicode)]
561 extern static bool SetConsoleCursorPosition (IntPtr handle, Coord coord);
563 [DllImport ("kernel32.dll", EntryPoint="SetConsoleTextAttribute", SetLastError=true, CharSet=CharSet.Unicode)]
564 extern static bool SetConsoleTextAttribute (IntPtr handle, short attribute);
566 [DllImport ("kernel32.dll", EntryPoint="SetConsoleScreenBufferSize", SetLastError=true, CharSet=CharSet.Unicode)]
567 extern static bool SetConsoleScreenBufferSize (IntPtr handle, Coord newSize);
569 [DllImport ("kernel32.dll", EntryPoint="SetConsoleWindowInfo", SetLastError=true, CharSet=CharSet.Unicode)]
570 extern static bool SetConsoleWindowInfo (IntPtr handle, bool absolute, ref SmallRect rect);
572 [DllImport ("kernel32.dll", EntryPoint="GetConsoleTitle", SetLastError=true, CharSet=CharSet.Unicode)]
573 extern static int GetConsoleTitle (StringBuilder sb, int size);
575 [DllImport ("kernel32.dll", EntryPoint="SetConsoleTitle", SetLastError=true, CharSet=CharSet.Unicode)]
576 extern static bool SetConsoleTitle (string title);
578 [DllImport ("kernel32.dll", EntryPoint="GetConsoleCursorInfo", SetLastError=true, CharSet=CharSet.Unicode)]
579 extern static bool GetConsoleCursorInfo (IntPtr handle, out ConsoleCursorInfo info);
581 [DllImport ("kernel32.dll", EntryPoint="SetConsoleCursorInfo", SetLastError=true, CharSet=CharSet.Unicode)]
582 extern static bool SetConsoleCursorInfo (IntPtr handle, ref ConsoleCursorInfo info);
584 [DllImport ("user32.dll", EntryPoint="GetKeyState", SetLastError=true, CharSet=CharSet.Unicode)]
585 extern static short GetKeyState (int virtKey);
587 [DllImport ("kernel32.dll", EntryPoint="GetConsoleMode", SetLastError=true, CharSet=CharSet.Unicode)]
588 extern static bool GetConsoleMode (IntPtr handle, out int mode);
590 [DllImport ("kernel32.dll", EntryPoint="SetConsoleMode", SetLastError=true, CharSet=CharSet.Unicode)]
591 extern static bool SetConsoleMode (IntPtr handle, int mode);
593 [DllImport ("kernel32.dll", EntryPoint="PeekConsoleInput", SetLastError=true, CharSet=CharSet.Unicode)]
594 extern static bool PeekConsoleInput (IntPtr handle, out InputRecord record, int length, out int eventsRead);
596 [DllImport ("kernel32.dll", EntryPoint="ReadConsoleInput", SetLastError=true, CharSet=CharSet.Unicode)]
597 extern static bool ReadConsoleInput (IntPtr handle, out InputRecord record, int length, out int nread);
599 [DllImport ("kernel32.dll", EntryPoint="GetLargestConsoleWindowSize", SetLastError=true, CharSet=CharSet.Unicode)]
600 extern static Coord GetLargestConsoleWindowSize (IntPtr handle);
602 [DllImport ("kernel32.dll", EntryPoint="ReadConsoleOutput", SetLastError=true, CharSet=CharSet.Unicode)]
603 extern static bool ReadConsoleOutput (IntPtr handle, void *buffer, Coord bsize, Coord bpos, ref SmallRect region);
605 [DllImport ("kernel32.dll", EntryPoint="WriteConsoleOutput", SetLastError=true, CharSet=CharSet.Unicode)]
606 extern static bool WriteConsoleOutput (IntPtr handle, CharInfo [] buffer, Coord bsize, Coord bpos, ref SmallRect region);