Copy msimg32
[reactos.git] / rosapps / sysutils / ctm / ctm.c
1 /* Console Task Manager
2
3 ctm.c - main program file
4
5 Written by: Aleksey Bragin (aleksey@studiocerebral.com)
6
7 Most of the code dealing with getting system parameters is taken from
8 ReactOS Task Manager written by Brian Palmer (brianp@reactos.org)
9
10 History:
11 09 April 2003 - v0.1, fixed bugs, added features, ported to mingw
12 20 March 2003 - v0.03, works good under ReactOS, and allows process
13 killing
14 18 March 2003 - Initial version 0.01, doesn't work under RectOS
15
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation; either version 2 of the License, or
19 (at your option) any later version.
20
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
25
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
29
30
31 //#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows //headers
32 #include <windows.h>
33 #include <stdlib.h>
34 #include <malloc.h>
35 #include <memory.h>
36 #include <tchar.h>
37 #include <process.h>
38 #include <stdio.h>
39
40 #include <ddk/ntddk.h>
41 #include <epsapi.h>
42 #include <ntos/zwtypes.h>
43
44 #include "ctm.h"
45
46 #define MAX_PROC 17
47 #define TIMES
48
49 HANDLE hStdin;
50 HANDLE hStdout;
51
52 DWORD inConMode;
53 DWORD outConMode;
54
55 //PROCNTQSI NtQuerySystemInformation= NULL;
56
57 const int ProcPerScreen = 17; // 17 processess are displayed on one page
58 ULONG ProcessCountOld = 0;
59 ULONG ProcessCount = 0;
60
61 double dbIdleTime;
62 double dbKernelTime;
63 double dbSystemTime;
64 LARGE_INTEGER liOldIdleTime = {{0,0}};
65 double OldKernelTime = 0;
66 LARGE_INTEGER liOldSystemTime = {{0,0}};
67
68 PPERFDATA pPerfDataOld = NULL; // Older perf data (saved to establish delta values)
69 PPERFDATA pPerfData = NULL; // Most recent copy of perf data
70
71 int selection=0;
72 int scrolled=0; // offset from which process start showing
73
74 #define NEW_CONSOLE
75
76 void *PsaiMalloc(SIZE_T size) { return malloc(size); }
77 void *PsaiRealloc(void *ptr, SIZE_T size) { return realloc(ptr, size); }
78 void PsaiFree(void *ptr) { free(ptr); }
79
80 // Prototypes
81 unsigned int GetKeyPressed();
82
83 void GetInputOutputHandles()
84 {
85 #ifdef NEW_CONSOLE
86 HANDLE console = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
87 FILE_SHARE_READ | FILE_SHARE_WRITE,
88 0, CONSOLE_TEXTMODE_BUFFER, 0);
89
90 if (SetConsoleActiveScreenBuffer(console) == FALSE)
91 {
92 hStdin = GetStdHandle(STD_INPUT_HANDLE);
93 hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
94 }
95 else
96 {
97 hStdin = GetStdHandle(STD_INPUT_HANDLE);//console;
98 hStdout = console;
99 }
100 #else
101 hStdin = GetStdHandle(STD_INPUT_HANDLE);
102 hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
103 #endif
104 }
105
106 void RestoreConsole()
107 {
108 SetConsoleMode(hStdin, inConMode);
109 SetConsoleMode(hStdout, outConMode);
110
111 #ifdef NEW_CONSOLE
112 SetConsoleActiveScreenBuffer(GetStdHandle(STD_OUTPUT_HANDLE));
113 #endif
114 }
115
116 void DisplayScreen()
117 {
118 COORD pos;
119 TCHAR lpStr[80];
120 int posStr;
121 DWORD numChars;
122 int lines;
123 int idx;
124 static int first = 0;
125
126 if (first == 0)
127 {
128 // Header
129 pos.X = 2; pos.Y = 2;
130 _tcscpy(lpStr, _T("Console TaskManager v0.1 by Aleksey Bragin <aleksey@studiocerebral.com>"));
131 WriteConsoleOutputCharacter(hStdout, lpStr, _tcslen(lpStr), pos, &numChars);
132
133 pos.X = 2; pos.Y = 3;
134 _tcscpy(lpStr, _T("+-------------------------------+-------+-----+-----------+-------------+"));
135 WriteConsoleOutputCharacter(hStdout, lpStr, _tcslen(lpStr), pos, &numChars);
136
137 pos.X = 2; pos.Y = 4;
138 _tcscpy(lpStr, _T("| Image name | PID | CPU | Mem Usage | Page Faults |"));
139 WriteConsoleOutputCharacter(hStdout, lpStr, _tcslen(lpStr), pos, &numChars);
140
141 pos.X = 2; pos.Y = 5;
142 _tcscpy(lpStr, _T("+-------------------------------+-------+-----+-----------+-------------+"));
143 WriteConsoleOutputCharacter(hStdout, lpStr, _tcslen(lpStr), pos, &numChars);
144
145 // Footer
146 pos.X = 2; pos.Y = 23;
147 _tcscpy(lpStr, _T("+-------------------------------+-------+-----+-----------+-------------+"));
148 WriteConsoleOutputCharacter(hStdout, lpStr, _tcslen(lpStr), pos, &numChars);
149
150 // Menu
151 pos.X = 2; pos.Y = 24;
152 _tcscpy(lpStr, _T("Press: q - quit, k - kill process "));
153 WriteConsoleOutputCharacter(hStdout, lpStr, _tcslen(lpStr), pos, &numChars);
154
155 first = 1;
156 }
157
158 // Processess
159 lines = ProcessCount;
160 if (lines > MAX_PROC)
161 lines = MAX_PROC;
162 for (idx=0; idx<MAX_PROC; idx++)
163 {
164 int len, i;
165 TCHAR imgName[MAX_PATH];
166 TCHAR lpPid[8];
167 TCHAR lpCpu[6];
168 TCHAR lpMemUsg[12];
169 TCHAR lpPageFaults[15];
170 WORD wColor;
171
172 // data
173 // image name
174 if (idx < lines && scrolled + idx < ProcessCount)
175 {
176 #ifdef _UNICODE
177 len = wcslen(pPerfData[scrolled+idx].ImageName);
178 #else
179 WideCharToMultiByte(CP_ACP, 0, pPerfData[scrolled+idx].ImageName, -1,
180 imgName, MAX_PATH, NULL, NULL);
181 len = strlen(imgName);
182 #endif
183 if (len > 31)
184 {
185 len = 31;
186 }
187 #ifdef _UNICODE
188 wcsncpy(&lpStr[2], pPerfData[scrolled+idx].ImageName, len);
189 #else
190 strncpy(&lpStr[2], imgName, len);
191 #endif
192 }
193 else
194 {
195 len = 0;
196 }
197 if (len < 31)
198 {
199 _tcsncpy(&lpStr[2 + len], _T(" "), 31 - len);
200 }
201
202 // PID
203 if (idx < lines && scrolled + idx < ProcessCount)
204 {
205 _stprintf(lpPid, _T("%6ld "), pPerfData[scrolled+idx].ProcessId);
206 _tcsncpy(&lpStr[34], lpPid, 7);
207 }
208 else
209 {
210 _tcsncpy(&lpStr[34], _T(" "), 7);
211 }
212
213 // CPU
214 if (idx < lines && scrolled + idx < ProcessCount)
215 {
216 _stprintf(lpCpu, _T("%3d%% "), pPerfData[scrolled+idx].CPUUsage);
217 _tcsncpy(&lpStr[42], lpCpu, 5);
218 }
219 else
220 {
221 _tcsncpy(&lpStr[42], _T(" "), 5);
222 }
223
224 // Mem usage
225 if (idx < lines && scrolled + idx < ProcessCount)
226 {
227 _stprintf(lpMemUsg, _T("%6ld "), pPerfData[scrolled+idx].WorkingSetSizeBytes / 1024);
228 _tcsncpy(&lpStr[48], lpMemUsg, 11);
229 }
230 else
231 {
232 _tcsncpy(&lpStr[48], _T(" "), 11);
233 }
234
235 // Page Fault
236 if (idx < lines && scrolled + idx < ProcessCount)
237 {
238 _stprintf(lpPageFaults, _T("%12ld "), pPerfData[scrolled+idx].PageFaultCount);
239 _tcsncpy(&lpStr[60], lpPageFaults, 13);
240 }
241 else
242 {
243 _tcsncpy(&lpStr[60], _T(" "), 13);
244 }
245
246 // columns
247 lpStr[0] = _T(' ');
248 lpStr[1] = _T('|');
249 lpStr[33] = _T('|');
250 lpStr[41] = _T('|');
251 lpStr[47] = _T('|');
252 lpStr[59] = _T('|');
253 lpStr[73] = _T('|');
254 pos.X = 1; pos.Y = 6+idx;
255 WriteConsoleOutputCharacter(hStdout, lpStr, 74, pos, &numChars);
256
257 // Attributes now...
258 pos.X = 3; pos.Y = 6+idx;
259 if (selection == idx)
260 {
261 wColor = BACKGROUND_GREEN |
262 FOREGROUND_RED |
263 FOREGROUND_GREEN |
264 FOREGROUND_BLUE;
265 }
266 else
267 {
268 wColor = BACKGROUND_BLUE |
269 FOREGROUND_RED |
270 FOREGROUND_GREEN |
271 FOREGROUND_BLUE;
272 }
273
274 FillConsoleOutputAttribute(
275 hStdout, // screen buffer handle
276 wColor, // color to fill with
277 31, // number of cells to fill
278 pos, // first cell to write to
279 &numChars); // actual number written
280 }
281
282 return;
283 }
284
285 // returns TRUE if exiting
286 int ProcessKeys(int numEvents)
287 {
288 if ((ProcessCount-scrolled < 17) && (ProcessCount > 17))
289 scrolled = ProcessCount-17;
290
291 TCHAR key = GetKeyPressed(numEvents);
292 if (key == VK_Q)
293 return TRUE;
294 else if (key == VK_K)
295 {
296 // user wants to kill some process, get his acknowledgement
297 DWORD pId;
298 COORD pos;
299 TCHAR lpStr[100];
300
301 pos.X = 2; pos.Y = 24;
302 _tcscpy(lpStr, _T("Are you sure you want to kill this process? (y/n)"));
303 WriteConsoleOutputCharacter(hStdout, lpStr, _tcslen(lpStr), pos, &pId);
304
305 do {
306 GetNumberOfConsoleInputEvents(hStdin, &pId);
307 key = GetKeyPressed(pId);
308 } while (key == 0);
309
310 if (key == VK_Y)
311 {
312 HANDLE hProcess;
313 pId = pPerfData[selection+scrolled].ProcessId;
314 hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pId);
315
316 if (hProcess)
317 {
318 if (!TerminateProcess(hProcess, 0))
319 {
320 _tcscpy(lpStr, _T("Unable to terminate this process... "));
321 WriteConsoleOutputCharacter(hStdout, lpStr, _tcslen(lpStr), pos, &pId);
322 Sleep(1000);
323 }
324
325 CloseHandle(hProcess);
326 }
327 else
328 {
329 _stprintf(lpStr, _T("Unable to terminate process %3d (unable to OpenProcess) "), pId);
330 WriteConsoleOutputCharacter(hStdout, lpStr, _tcslen(lpStr), pos, &pId);
331 Sleep(1000);
332 }
333 }
334 }
335 else if (key == VK_UP)
336 {
337 if (selection > 0)
338 selection--;
339 else if ((selection == 0) && (scrolled > 0))
340 scrolled--;
341 }
342 else if (key == VK_DOWN)
343 {
344 if ((selection < MAX_PROC-1) && (selection < ProcessCount-1))
345 selection++;
346 else if ((selection == MAX_PROC-1) && (selection+scrolled < ProcessCount-1))
347 scrolled++;
348 }
349
350 return FALSE;
351 }
352
353 void PerfInit()
354 {
355 // NtQuerySystemInformation = //(PROCNTQSI)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), //"NtQuerySystemInformation");
356 }
357
358 void PerfDataRefresh()
359 {
360 LONG status;
361 ULONG ulSize;
362 LPBYTE pBuffer;
363 ULONG BufferSize;
364 ULONG Idx, Idx2;
365 HANDLE hProcess;
366 HANDLE hProcessToken;
367 PSYSTEM_PROCESSES pSPI;
368 PPERFDATA pPDOld;
369 TCHAR szTemp[MAX_PATH];
370 DWORD dwSize;
371 double CurrentKernelTime;
372 PSYSTEM_PROCESSORTIME_INFO SysProcessorTimeInfo;
373 SYSTEM_PERFORMANCE_INFORMATION SysPerfInfo;
374 SYSTEM_TIMEOFDAY_INFORMATION SysTimeInfo;
375
376 #ifdef TIMES
377 // Get new system time
378 status = NtQuerySystemInformation(SystemTimeInformation, &SysTimeInfo, sizeof(SysTimeInfo), 0);
379 if (status != NO_ERROR)
380 return;
381
382 // Get new CPU's idle time
383 status = NtQuerySystemInformation(SystemPerformanceInformation, &SysPerfInfo, sizeof(SysPerfInfo), NULL);
384 if (status != NO_ERROR)
385 return;
386 #endif
387 // Get processor information
388 SysProcessorTimeInfo = (PSYSTEM_PROCESSORTIME_INFO)malloc(sizeof(SYSTEM_PROCESSORTIME_INFO) * 1/*SystemBasicInfo.bKeNumberProcessors*/);
389 status = NtQuerySystemInformation(SystemProcessorTimes, SysProcessorTimeInfo, sizeof(SYSTEM_PROCESSORTIME_INFO) * 1/*SystemBasicInfo.bKeNumberProcessors*/, &ulSize);
390
391
392 // Get process information
393 PsaCaptureProcessesAndThreads((PSYSTEM_PROCESSES *)&pBuffer);
394
395 #ifdef TIMES
396 for (CurrentKernelTime=0, Idx=0; Idx<1/*SystemBasicInfo.bKeNumberProcessors*/; Idx++) {
397 CurrentKernelTime += Li2Double(SysProcessorTimeInfo[Idx].TotalProcessorTime);
398 CurrentKernelTime += Li2Double(SysProcessorTimeInfo[Idx].TotalDPCTime);
399 CurrentKernelTime += Li2Double(SysProcessorTimeInfo[Idx].TotalInterruptTime);
400 }
401
402 // If it's a first call - skip idle time calcs
403 if (liOldIdleTime.QuadPart != 0) {
404 // CurrentValue = NewValue - OldValue
405 dbIdleTime = Li2Double(SysPerfInfo.IdleTime) - Li2Double(liOldIdleTime);
406 dbKernelTime = CurrentKernelTime - OldKernelTime;
407 dbSystemTime = Li2Double(SysTimeInfo.CurrentTime) - Li2Double(liOldSystemTime);
408
409 // CurrentCpuIdle = IdleTime / SystemTime
410 dbIdleTime = dbIdleTime / dbSystemTime;
411 dbKernelTime = dbKernelTime / dbSystemTime;
412
413 // CurrentCpuUsage% = 100 - (CurrentCpuIdle * 100) / NumberOfProcessors
414 dbIdleTime = 100.0 - dbIdleTime * 100.0; /* / (double)SystemBasicInfo.bKeNumberProcessors;// + 0.5; */
415 dbKernelTime = 100.0 - dbKernelTime * 100.0; /* / (double)SystemBasicInfo.bKeNumberProcessors;// + 0.5; */
416 }
417
418 // Store new CPU's idle and system time
419 liOldIdleTime = SysPerfInfo.IdleTime;
420 liOldSystemTime = SysTimeInfo.CurrentTime;
421 OldKernelTime = CurrentKernelTime;
422 #endif
423
424 // Determine the process count
425 // We loop through the data we got from PsaCaptureProcessesAndThreads
426 // and count how many structures there are (until PsaWalkNextProcess
427 // returns NULL)
428 ProcessCountOld = ProcessCount;
429 ProcessCount = 0;
430 pSPI = PsaWalkFirstProcess((PSYSTEM_PROCESSES)pBuffer);
431 while (pSPI) {
432 ProcessCount++;
433 pSPI = PsaWalkNextProcess(pSPI);
434 }
435
436 // Now alloc a new PERFDATA array and fill in the data
437 if (pPerfDataOld) {
438 //delete[] pPerfDataOld;
439 free(pPerfDataOld);
440 }
441 pPerfDataOld = pPerfData;
442 //pPerfData = new PERFDATA[ProcessCount];
443 pPerfData = (PPERFDATA)malloc(sizeof(PERFDATA) * ProcessCount);
444 pSPI = PsaWalkFirstProcess((PSYSTEM_PROCESSES)pBuffer);
445 for (Idx=0; Idx<ProcessCount; Idx++) {
446 // Get the old perf data for this process (if any)
447 // so that we can establish delta values
448 pPDOld = NULL;
449 for (Idx2=0; Idx2<ProcessCountOld; Idx2++) {
450 if (pPerfDataOld[Idx2].ProcessId == pSPI->ProcessId) {
451 pPDOld = &pPerfDataOld[Idx2];
452 break;
453 }
454 }
455
456 // Clear out process perf data structure
457 memset(&pPerfData[Idx], 0, sizeof(PERFDATA));
458
459 if (pSPI->ProcessName.Buffer) {
460 wcsncpy(pPerfData[Idx].ImageName, pSPI->ProcessName.Buffer, pSPI->ProcessName.Length / sizeof(WCHAR));
461 pPerfData[Idx].ImageName[pSPI->ProcessName.Length / sizeof(WCHAR)] = 0;
462 }
463 else
464 wcscpy(pPerfData[Idx].ImageName, L"System Idle Process");
465
466 pPerfData[Idx].ProcessId = pSPI->ProcessId;
467
468 if (pPDOld) {
469 #ifdef TIMES
470 double CurTime = Li2Double(pSPI->KernelTime) + Li2Double(pSPI->UserTime);
471 double OldTime = Li2Double(pPDOld->KernelTime) + Li2Double(pPDOld->UserTime);
472 double CpuTime = (CurTime - OldTime) / dbSystemTime;
473 CpuTime = CpuTime * 100.0; /* / (double)SystemBasicInfo.bKeNumberProcessors;// + 0.5;*/
474
475 pPerfData[Idx].CPUUsage = (ULONG)CpuTime;
476 #else
477 pPerfData[Idx].CPUUsage = 0;
478 #endif
479 }
480
481 pPerfData[Idx].CPUTime.QuadPart = pSPI->UserTime.QuadPart + pSPI->KernelTime.QuadPart;
482 pPerfData[Idx].WorkingSetSizeBytes = pSPI->VmCounters.WorkingSetSize;
483 pPerfData[Idx].PeakWorkingSetSizeBytes = pSPI->VmCounters.PeakWorkingSetSize;
484 if (pPDOld)
485 pPerfData[Idx].WorkingSetSizeDelta = labs((LONG)pSPI->VmCounters.WorkingSetSize - (LONG)pPDOld->WorkingSetSizeBytes);
486 else
487 pPerfData[Idx].WorkingSetSizeDelta = 0;
488 pPerfData[Idx].PageFaultCount = pSPI->VmCounters.PageFaultCount;
489 if (pPDOld)
490 pPerfData[Idx].PageFaultCountDelta = labs((LONG)pSPI->VmCounters.PageFaultCount - (LONG)pPDOld->PageFaultCount);
491 else
492 pPerfData[Idx].PageFaultCountDelta = 0;
493 pPerfData[Idx].VirtualMemorySizeBytes = pSPI->VmCounters.VirtualSize;
494 pPerfData[Idx].PagedPoolUsagePages = pSPI->VmCounters.QuotaPagedPoolUsage;
495 pPerfData[Idx].NonPagedPoolUsagePages = pSPI->VmCounters.QuotaNonPagedPoolUsage;
496 pPerfData[Idx].BasePriority = pSPI->BasePriority;
497 pPerfData[Idx].HandleCount = pSPI->HandleCount;
498 pPerfData[Idx].ThreadCount = pSPI->ThreadCount;
499 //pPerfData[Idx].SessionId = pSPI->SessionId;
500
501 #ifdef EXTRA_INFO
502 hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pSPI->ProcessId);
503 if (hProcess) {
504 if (OpenProcessToken(hProcess, TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_IMPERSONATE, &hProcessToken)) {
505 ImpersonateLoggedOnUser(hProcessToken);
506 memset(szTemp, 0, sizeof(TCHAR[MAX_PATH]));
507 dwSize = MAX_PATH;
508 GetUserName(szTemp, &dwSize);
509 #ifndef UNICODE
510 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szTemp, -1, pPerfData[Idx].UserName, MAX_PATH);
511 /*
512 int MultiByteToWideChar(
513 UINT CodePage, // code page
514 DWORD dwFlags, // character-type options
515 LPCSTR lpMultiByteStr, // string to map
516 int cbMultiByte, // number of bytes in string
517 LPWSTR lpWideCharStr, // wide-character buffer
518 int cchWideChar // size of buffer
519 );
520 */
521 #endif
522 RevertToSelf();
523 CloseHandle(hProcessToken);
524 }
525 CloseHandle(hProcess);
526 }
527 #endif
528 #ifdef TIMES
529 pPerfData[Idx].UserTime.QuadPart = pSPI->UserTime.QuadPart;
530 pPerfData[Idx].KernelTime.QuadPart = pSPI->KernelTime.QuadPart;
531 #endif
532 pSPI = PsaWalkNextProcess(pSPI);
533 }
534 //delete[] pBuffer;
535 PsaFreeCapture(pBuffer);
536
537 free(SysProcessorTimeInfo);
538 }
539
540 // Code partly taken from slw32tty.c from mc/slang
541 unsigned int GetKeyPressed(int events)
542 {
543 long key;
544 DWORD bytesRead;
545 INPUT_RECORD record;
546 int i;
547
548
549 for (i=0; i<events; i++)
550 {
551 if (!ReadConsoleInput(hStdin, &record, 0, &bytesRead)) {
552 return 0;
553 }
554 if (!ReadConsoleInput(hStdin, &record, 1, &bytesRead)) {
555 return 0;
556 }
557
558 if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown)
559 return record.Event.KeyEvent.wVirtualKeyCode;//.uChar.AsciiChar;
560 }
561
562 return 0;
563 }
564
565
566 int main(int *argc, char **argv)
567 {
568 GetInputOutputHandles();
569
570 if (hStdin == INVALID_HANDLE_VALUE || hStdout == INVALID_HANDLE_VALUE)
571 {
572 printf("ctm: can't use console.");
573 return -1;
574 }
575
576 if (GetConsoleMode(hStdin, &inConMode) == 0)
577 {
578 printf("ctm: can't GetConsoleMode() for input console.");
579 return -1;
580 }
581
582 if (GetConsoleMode(hStdout, &outConMode) == 0)
583 {
584 printf("ctm: can't GetConsoleMode() for output console.");
585 return -1;
586 }
587
588 SetConsoleMode(hStdin, 0); //FIXME: Should check for error!
589 SetConsoleMode(hStdout, 0); //FIXME: Should check for error!
590
591 PerfInit();
592
593 while (1)
594 {
595 DWORD numEvents;
596
597 PerfDataRefresh();
598 DisplayScreen();
599
600 //WriteConsole(hStdin, " ", 1, &numEvents, NULL); // TODO: Make another way (this is ugly, I know)
601 #if 1
602 /* WaitForSingleObject for console handles is not implemented in ROS */
603 WaitForSingleObject(hStdin, 1000);
604 #endif
605
606 GetNumberOfConsoleInputEvents(hStdin, &numEvents);
607
608 if (numEvents > 0)
609 {
610 if (ProcessKeys(numEvents) == TRUE)
611 break;
612 }
613 #if 0
614 else
615 {
616 /* Should be removed, if WaitForSingleObject is implemented for console handles */
617 Sleep(40); // TODO: Should be done more efficient (might be another thread handling input/etc)*/
618 }
619 #endif
620 }
621
622 RestoreConsole();
623 return 0;
624 }