1 using System;
2 using System.Threading;
3 using System.IO;
4 using System.Collections;
5 using System.Unicode;
6 using cmsx.machine;
7
8 namespace cmsx.kernel
9 {
10 public delegate void ConsoleInputCallback();
11
12 public Kernel& GetKernel()
13 {
14 return Kernel.Instance();
15 }
16
17 public const ushort FOREGROUND_BLUE = 0x01u;
18 public const ushort FOREGROUND_GREEN = 0x02u;
19 public const ushort FOREGROUND_RED = 0x04u;
20 public const ushort FOREGROUND_INTENSITY = 0x08u;
21 public const ushort BACKGROUND_BLUE = 0x10u;
22 public const ushort BACKGROUND_GREEN = 0x20u;
23 public const ushort BACKGROUND_RED = 0x40u;
24 public const ushort BACKGROUND_INTENSITY = 0x80u;
25
26 public enum ConsoleColor : byte
27 {
28 black = 0u, darkBlue = 1u, darkGreen = 2u, darkCyan = 3u, darkRed = 4u, darkYellow = 6u, gray = 7u,
29 darkGray = 8u, blue = 9u, green = 10u, cyan = 11u, red = 12u, magenta = 13u, yellow = 14u, white = 15u
30 }
31
32 public string ConsoleColorName(ConsoleColor color)
33 {
34 switch (color)
35 {
36 case ConsoleColor.black: return "black";
37 case ConsoleColor.darkBlue: return "darkblue";
38 case ConsoleColor.darkGreen: return "dardgreen";
39 case ConsoleColor.darkCyan: return "dardcyan";
40 case ConsoleColor.darkRed: return "darkred";
41 case ConsoleColor.darkYellow: return "darkyellow";
42 case ConsoleColor.gray: return "gray";
43 case ConsoleColor.darkGray: return "darkgray";
44 case ConsoleColor.blue: return "blue";
45 case ConsoleColor.green: return "green";
46 case ConsoleColor.cyan: return "cyan";
47 case ConsoleColor.red: return "red";
48 case ConsoleColor.magenta: return "magenta";
49 case ConsoleColor.yellow: return "yellow";
50 case ConsoleColor.white: return "white";
51 }
52 return string();
53 }
54
55 public const uchar beginColors = cast<uchar>(0x100000u);
56 public const uchar endColors = cast<uchar>(0x10FFFDu);
57
58 public inline nothrow bool BeginColors(uchar c)
59 {
60 return (cast<uint>(c) & cast<uint>(beginColors)) == cast<uint>(beginColors);
61 }
62
63 public inline nothrow bool EndColors(uchar c)
64 {
65 return c == endColors;
66 }
67
68 public inline nothrow void GetColors(uchar c, ConsoleColor& foregroundColor, ConsoleColor& backgroundColor)
69 {
70 ushort x = cast<ushort>(cast<uint>(c));
71 foregroundColor = cast<ConsoleColor>(cast<byte>(x));
72 backgroundColor = cast<ConsoleColor>(cast<byte>(x >> 8u));
73 }
74
75 private bool consoleDriverExited = false;
76
77 public class ConsoleDriver
78 {
79 static ConsoleDriver() : instance(new ConsoleDriver())
80 {
81 }
82 public static ConsoleDriver& Instance()
83 {
84 return *instance;
85 }
86 private ConsoleDriver() :
87 consoleInputHandle(OsGetStdHandle(stdin)), consoleOutputHandle(OsGetStdHandle(stdout)), machine(GetMachine()),
88 pos(0), cursorPosX(0), cursorPosY(0), outputEndCursorPosX(0), outputEndCursorPosY(0), attributes(0u), obeyColorStrings(false), echo(true)
89 {
90 ConsoleInputCallback callback = ConsoleInputWaitingCallback;
91 ulong callBackValue = cast<ulong>(cast<void*>(callback));
92 bool retVal = OsRegisterConsoleCallback(consoleInputHandle, callBackValue);
93 if (!retVal)
94 {
95 Panic("Console driver callback initialization failed");
96 }
97 }
98 public void Stop()
99 {
100 consoleDriverExited = true;
101 consoleInputHandle = null;
102 }
103 public void GetDimensions(int& rows, int& cols)
104 {
105 GetDimensions();
106 rows = screenSizeY;
107 cols = screenSizeX;
108 }
109 public void GetDimensions()
110 {
111 if (!OsGetConsoleScreenBufferInfo(consoleOutputHandle, &cursorPosX, &cursorPosY, &screenSizeX, &screenSizeY, &attributes))
112 {
113 Panic("Console driver screen buffer info initialization failed");
114 }
115 }
116 public inline nothrow bool Eof() const
117 {
118 return eof;
119 }
120 public inline nothrow void ResetEof()
121 {
122 eof = false;
123 }
124 public inline nothrow bool Echo() const
125 {
126 return echo;
127 }
128 public inline nothrow void SetEcho(bool echo_)
129 {
130 echo = echo_;
131 }
132 public nothrow bool HasLine() const
133 {
134 return !lines.IsEmpty();
135 }
136 public nothrow string GetLine() const
137 {
138 return ToUtf8(lines.RemoveFirst());
139 }
140 public inline nothrow void* GetConsoleInputHandle()
141 {
142 return consoleInputHandle;
143 }
144 public inline nothrow void* GetConsoleOutputHandle()
145 {
146 return consoleOutputHandle;
147 }
148 public void GetConsoleColors(ConsoleColor& foregroundColor, ConsoleColor& backgroundColor)
149 {
150 foregroundColor = cast<ConsoleColor>(cast<byte>(attributes & 0x0Fu));
151 backgroundColor = cast<ConsoleColor>(cast<byte>((attributes >> 8u) & 0x0Fu));
152 }
153 public void SetConsoleColors(ConsoleColor foregroundColor, ConsoleColor backgroundColor)
154 {
155 ushort attrs = cast<ushort>(cast<byte>(foregroundColor)) | (cast<ushort>(cast<byte>(backgroundColor)) << 8u);
156
157 if (!OsSetConsoleTextAttribute(attrs))
158 {
159 Panic("Console driver could not set text attributes: WIN32 error " + ToString(OsGetLastError()));
160 }
161 attributes = attrs;
162 }
163 public void PushColors(ConsoleColor foregroundColor, ConsoleColor backgroundColor)
164 {
165 attributeStack.Push(attributes);
166 SetConsoleColors(foregroundColor, backgroundColor);
167 }
168 public void PopColors()
169 {
170 attributes = attributeStack.Pop();
171 OsSetConsoleTextAttribute(attributes);
172 }
173 public void BeginObeyColorStrings()
174 {
175 obeyColorStrings = true;
176 }
177 public void EndObeyColorStrings()
178 {
179 obeyColorStrings = false;
180 }
181 public void WriteToConsole(byte* buffer, long count)
182 {
183 ustring u;
184 for (long i = 0; i < count; ++i;)
185 {
186 byte x = buffer[i];
187 unicodeEngine.Put(x);
188 if (unicodeEngine.ResultReady())
189 {
190 uchar c = unicodeEngine.Get();
191 if (c != '\r')
192 {
193 u.Append(c);
194 }
195 }
196 }
197 if (!u.IsEmpty())
198 {
199 if (obeyColorStrings)
200 {
201 WriteWithColors(u);
202 }
203 else
204 {
205 Write(u);
206 }
207 }
208 }
209 private void Write(const ustring& u)
210 {
211 OsWriteConsole(consoleOutputHandle, u.Chars());
212 GetDimensions();
213 outputEndCursorPosX = cursorPosX;
214 outputEndCursorPosY = cursorPosY;
215 }
216 public void WriteWithColors(const ustring& u)
217 {
218 ustring buffer;
219 for (uchar c : u)
220 {
221 if (EndColors(c))
222 {
223 if (!buffer.IsEmpty())
224 {
225 Write(buffer);
226 buffer.Clear();
227 }
228 PopColors();
229 }
230 else if (BeginColors(c))
231 {
232 if (!buffer.IsEmpty())
233 {
234 Write(buffer);
235 buffer.Clear();
236 }
237 ConsoleColor foregroundColor;
238 ConsoleColor backgroundColor;
239 GetColors(c, foregroundColor, backgroundColor);
240 PushColors(foregroundColor, backgroundColor);
241 }
242 else
243 {
244 buffer.Append(c);
245 }
246 }
247 if (!buffer.IsEmpty())
248 {
249 Write(buffer);
250 }
251 }
252 public void KeyPressed(uchar c)
253 {
254 AddCharToInputBuffer(c);
255 machine.GetRegisters().SetInterrupt(KEYBOARD_BIT);
256 }
257 public void InterruptService()
258 {
259 if (Log())
260 {
261 LogMessage("con.interrupt", "begin");
262 }
263 GetDimensions();
264 outputEndCursorPosY = cursorPosY;
265 ustring chars = GetCharsFromInputBuffer();
266 for (uchar c : chars)
267 {
268 switch (c)
269 {
270 case cast<uchar>(keyEnter): HandleEnter(); break;
271 case cast<uchar>(keyControlD): HandleEof(); break;
272 case cast<uchar>(keyHome): HandleHome(); break;
273 case cast<uchar>(keyEnd): HandleEnd(); break;
274 case cast<uchar>(keyLeft): HandleLeft(); break;
275 case cast<uchar>(keyRight): HandleRight(); break;
276 case cast<uchar>(keyDel): HandleDelete(); break;
277 case cast<uchar>(keyBackspace): HandleBackspace(); break;
278 default: HandleChar(c); break;
279 }
280 }
281 if (Log())
282 {
283 LogMessage("con.interrupt", "end");
284 }
285 }
286 private void DeliverInputLine()
287 {
288 if (Log())
289 {
290 LogMessage("con.deliver", "begin");
291 }
292 Machine& machine = GetMachine();
293 Kernel& kernel = GetKernel();
294 Session* session = kernel.CurrentSession();
295 if (session->fgpid != -1)
296 {
297 ProcessTable& processTable = kernel.GetProcessTable();
298 if (Log())
299 {
300 LogMessage("con.deliver", "fgpid=" + ToString(session->fgpid));
301 }
302 cmsx.kernel.Process* reader = processTable.GetProcess(session->fgpid);
303 if (reader != null)
304 {
305 if (reader->state == cmsx.kernel.Process.State.asleep && reader->event == consoleInputEvent)
306 {
307 GlobalFileTable& fileTable = kernel.FileTable();
308 ConsoleInputFile* consoleInputFile = fileTable.GetConsoleInputFile();
309 if (!consoleInputFile->HasBufferedInput())
310 {
311 if (HasLine())
312 {
313 consoleInputFile->SetBufferedInput(GetLine());
314 }
315 }
316 if (Log())
317 {
318 LogMessage("con.deliver", "wakeup");
319 }
320 WakeUpProcess(processTable, reader);
321 if (processTable.GetRunning() == processTable.GetIdle())
322 {
323 if (Log())
324 {
325 LogMessage("con.interrupt", "schedule");
326 }
327 machine.GetRegisters().SetInterrupt(CLOCK_BIT);
328 }
329 }
330 }
331 else
332 {
333 if (Log())
334 {
335 LogMessage("con.deliver", "not.delivered");
336 }
337 }
338 }
339 if (Log())
340 {
341 LogMessage("con.deliver", "end");
342 }
343 }
344 private void HandleEnter()
345 {
346 line.Append('\n');
347 lines.Add(line);
348 line.Clear();
349 DeliverInputLine();
350 pos = 0;
351 ++cursorPosY;
352 SetCursorPos(0, cursorPosY);
353 outputEndCursorPosX = 0;
354 outputEndCursorPosY = cursorPosY;
355 }
356 private void HandleEof()
357 {
358 eof = true;
359 lines.Add(line);
360 line.Clear();
361 DeliverInputLine();
362 pos = 0;
363 ++cursorPosY;
364 SetCursorPos(0, cursorPosY);
365 outputEndCursorPosX = 0;
366 outputEndCursorPosY = cursorPosY;
367 }
368 private void HandleHome()
369 {
370 pos = 0;
371 if (echo)
372 {
373 SetCursorPos(outputEndCursorPosX, outputEndCursorPosY);
374 }
375 }
376 private void HandleEnd()
377 {
378 pos = cast<int>(line.Length());
379 if (echo)
380 {
381 SetCursorPos(outputEndCursorPosX + pos, outputEndCursorPosY);
382 }
383 }
384 private void HandleLeft()
385 {
386 if (pos > 0)
387 {
388 --pos;
389 if (echo)
390 {
391 SetCursorPos(outputEndCursorPosX + pos, outputEndCursorPosY);
392 }
393 }
394 }
395 private void HandleRight()
396 {
397 if (pos < line.Length())
398 {
399 ++pos;
400 if (echo)
401 {
402 SetCursorPos(outputEndCursorPosX + pos, outputEndCursorPosY);
403 }
404 }
405 }
406 private void HandleDelete()
407 {
408 if (pos < line.Length())
409 {
410 ++pos;
411 line = line.Substring(0, pos - 1) + line.Substring(pos);
412 --pos;
413 UpdateLine();
414 }
415 }
416 private void HandleBackspace()
417 {
418 if (pos > 0)
419 {
420 line = line.Substring(0, pos - 1) + line.Substring(pos);
421 --pos;
422 UpdateLine();
423 if (echo)
424 {
425 SetCursorPos(outputEndCursorPosX + pos, outputEndCursorPosY);
426 }
427 }
428 }
429 private void HandleChar(uchar c)
430 {
431 if (IsGraphic(c) && cursorPosX < screenSizeX - 1)
432 {
433 if (pos < line.Length())
434 {
435 line = line.Substring(0, pos) + ustring(c, 1) + line.Substring(pos);
436 }
437 else
438 {
439 line.Append(c);
440 }
441 ++pos;
442 UpdateLine();
443 if (echo)
444 {
445 SetCursorPos(outputEndCursorPosX + pos, outputEndCursorPosY);
446 }
447 }
448 }
449 private void AddCharToInputBuffer(uchar c)
450 {
451 LockGuard<Mutex> lock(inputBufferMutex);
452 inputBuffer.Append(c);
453 }
454 private ustring GetCharsFromInputBuffer()
455 {
456 LockGuard<Mutex> lock(inputBufferMutex);
457 ustring chars;
458 Swap(chars, inputBuffer);
459 return chars;
460 }
461 private void SetCursorPos(int x, int y)
462 {
463 cursorPosX = x;
464 cursorPosY = y;
465 OsSetConsoleCursorPosition(consoleOutputHandle, x, y);
466 }
467 private void UpdateLine()
468 {
469 if (!echo) return;
470 int prevCursorPosX = cursorPosX;
471 int prevCursorPosY = cursorPosY;
472 SetCursorPos(outputEndCursorPosX, outputEndCursorPosY);
473 ustring updateLine;
474 cursorPosX = outputEndCursorPosX;
475 cursorPosY = outputEndCursorPosY;
476 for (int i = 0; i < line.Length(); ++i;)
477 {
478 if (cursorPosX + i < screenSizeX)
479 {
480 updateLine.Append(line[i]);
481 }
482 else
483 {
484 OsWriteConsole(consoleOutputHandle, updateLine.Chars());
485 updateLine.Clear();
486 cursorPosX = 0;
487 ++cursorPosY;
488 }
489 }
490 if (!updateLine.IsEmpty())
491 {
492 OsWriteConsole(consoleOutputHandle, updateLine.Chars());
493 updateLine.Clear();
494 }
495 for (int i = cursorPosX; i < screenSizeX; ++i;)
496 {
497 updateLine.Append(' ');
498 }
499 OsWriteConsole(consoleOutputHandle, updateLine.Chars());
500 SetCursorPos(prevCursorPosX, prevCursorPosY);
501 }
502 private static UniquePtr<ConsoleDriver> instance;
503 private void* consoleInputHandle;
504 private void* consoleOutputHandle;
505 private Machine& machine;
506 private ustring inputBuffer;
507 private Mutex inputBufferMutex;
508 private ustring line;
509 private List<ustring> lines;
510 private bool eof;
511 private int pos;
512 private int cursorPosX;
513 private int cursorPosY;
514 private int outputEndCursorPosX;
515 private int outputEndCursorPosY;
516 private int screenSizeX;
517 private int screenSizeY;
518 private ushort attributes;
519 private Stack<ushort> attributeStack;
520 private UnicodeEngine unicodeEngine;
521 private bool obeyColorStrings;
522 private bool echo;
523 }
524
525 public void ConsoleInputWaitingCallback()
526 {
527 bool exited = consoleDriverExited;
528 if (exited) return;
529 ConsoleDriver& consoleDriver = GetConsoleDriver();
530 uchar c = '\0';
531 bool retVal = OsReadConsoleInput(consoleDriver.GetConsoleInputHandle(), &c);
532 if (retVal && c != '\0')
533 {
534 consoleDriver.KeyPressed(c);
535 }
536 }
537
538 public ConsoleDriver& GetConsoleDriver()
539 {
540 return ConsoleDriver.Instance();
541 }
542 }