[CONSRV]
[reactos.git] / win32ss / user / consrv / frontends / gui / guiterm.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Console Server DLL
4 * FILE: win32ss/user/consrv/frontends/gui/guiterm.c
5 * PURPOSE: GUI Terminal Front-End
6 * PROGRAMMERS: Gé van Geldorp
7 * Johannes Anderwald
8 * Jeffrey Morlan
9 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
10 */
11
12 /* INCLUDES *******************************************************************/
13
14 #include "consrv.h"
15 #include "include/conio.h"
16 #include "include/console.h"
17 #include "include/settings.h"
18 #include "guiterm.h"
19 #include "guisettings.h"
20 #include "resource.h"
21
22 #include <windowsx.h>
23
24 #define NDEBUG
25 #include <debug.h>
26
27 /* GUI Console Window Class name */
28 #define GUI_CONSOLE_WINDOW_CLASS L"ConsoleWindowClass"
29
30 #ifndef WM_APP
31 #define WM_APP 0x8000
32 #endif
33 #define PM_CREATE_CONSOLE (WM_APP + 1)
34 #define PM_DESTROY_CONSOLE (WM_APP + 2)
35 #define PM_CONSOLE_BEEP (WM_APP + 3)
36 #define PM_CONSOLE_SET_TITLE (WM_APP + 4)
37
38
39 /* Not defined in any header file */
40 // extern VOID WINAPI PrivateCsrssManualGuiCheck(LONG Check);
41 // From win32ss/user/win32csr/dllmain.c
42 VOID
43 WINAPI
44 PrivateCsrssManualGuiCheck(LONG Check)
45 {
46 NtUserCallOneParam(Check, ONEPARAM_ROUTINE_CSRSS_GUICHECK);
47 }
48
49 /* GLOBALS ********************************************************************/
50
51 /**************************************************************\
52 \** Define the Console Leader Process for the console window **/
53 #define GWLP_CONSOLEWND_ALLOC (2 * sizeof(LONG_PTR))
54 #define GWLP_CONSOLE_LEADER_PID 0
55 #define GWLP_CONSOLE_LEADER_TID 4
56
57 #define SetConsoleWndConsoleLeaderCID(GuiData) \
58 do { \
59 PCONSOLE_PROCESS_DATA ProcessData; \
60 CLIENT_ID ConsoleLeaderCID; \
61 ProcessData = CONTAINING_RECORD((GuiData)->Console->ProcessList.Blink, \
62 CONSOLE_PROCESS_DATA, \
63 ConsoleLink); \
64 ConsoleLeaderCID = ProcessData->Process->ClientId; \
65 SetWindowLongPtrW((GuiData)->hWindow, GWLP_CONSOLE_LEADER_PID, (LONG_PTR)(ConsoleLeaderCID.UniqueProcess)); \
66 SetWindowLongPtrW((GuiData)->hWindow, GWLP_CONSOLE_LEADER_TID, (LONG_PTR)(ConsoleLeaderCID.UniqueThread )); \
67 } while (0)
68 /**************************************************************/
69
70 static BOOL ConsInitialized = FALSE;
71 static HICON ghDefaultIcon = NULL;
72 static HICON ghDefaultIconSm = NULL;
73 static HCURSOR ghDefaultCursor = NULL;
74 static HWND NotifyWnd = NULL;
75
76 typedef struct _GUICONSOLE_MENUITEM
77 {
78 UINT uID;
79 const struct _GUICONSOLE_MENUITEM *SubMenu;
80 WORD wCmdID;
81 } GUICONSOLE_MENUITEM, *PGUICONSOLE_MENUITEM;
82
83 static const GUICONSOLE_MENUITEM GuiConsoleEditMenuItems[] =
84 {
85 { IDS_MARK, NULL, ID_SYSTEM_EDIT_MARK },
86 { IDS_COPY, NULL, ID_SYSTEM_EDIT_COPY },
87 { IDS_PASTE, NULL, ID_SYSTEM_EDIT_PASTE },
88 { IDS_SELECTALL, NULL, ID_SYSTEM_EDIT_SELECTALL },
89 { IDS_SCROLL, NULL, ID_SYSTEM_EDIT_SCROLL },
90 { IDS_FIND, NULL, ID_SYSTEM_EDIT_FIND },
91
92 { 0, NULL, 0 } /* End of list */
93 };
94
95 static const GUICONSOLE_MENUITEM GuiConsoleMainMenuItems[] =
96 {
97 { IDS_EDIT, GuiConsoleEditMenuItems, 0 },
98 { IDS_DEFAULTS, NULL, ID_SYSTEM_DEFAULTS },
99 { IDS_PROPERTIES, NULL, ID_SYSTEM_PROPERTIES },
100
101 { 0, NULL, 0 } /* End of list */
102 };
103
104 /*
105 * Default 16-color palette for foreground and background
106 * (corresponding flags in comments).
107 */
108 const COLORREF s_Colors[16] =
109 {
110 RGB(0, 0, 0), // (Black)
111 RGB(0, 0, 128), // BLUE
112 RGB(0, 128, 0), // GREEN
113 RGB(0, 128, 128), // BLUE | GREEN
114 RGB(128, 0, 0), // RED
115 RGB(128, 0, 128), // BLUE | RED
116 RGB(128, 128, 0), // GREEN | RED
117 RGB(192, 192, 192), // BLUE | GREEN | RED
118
119 RGB(128, 128, 128), // (Grey) INTENSITY
120 RGB(0, 0, 255), // BLUE | INTENSITY
121 RGB(0, 255, 0), // GREEN | INTENSITY
122 RGB(0, 255, 255), // BLUE | GREEN | INTENSITY
123 RGB(255, 0, 0), // RED | INTENSITY
124 RGB(255, 0, 255), // BLUE | RED | INTENSITY
125 RGB(255, 255, 0), // GREEN | RED | INTENSITY
126 RGB(255, 255, 255) // BLUE | GREEN | RED | INTENSITY
127 };
128
129 /* FUNCTIONS ******************************************************************/
130
131 static VOID
132 GuiConsoleAppendMenuItems(HMENU hMenu,
133 const GUICONSOLE_MENUITEM *Items)
134 {
135 UINT i = 0;
136 WCHAR szMenuString[255];
137 HMENU hSubMenu;
138
139 do
140 {
141 if (Items[i].uID != (UINT)-1)
142 {
143 if (LoadStringW(ConSrvDllInstance,
144 Items[i].uID,
145 szMenuString,
146 sizeof(szMenuString) / sizeof(szMenuString[0])) > 0)
147 {
148 if (Items[i].SubMenu != NULL)
149 {
150 hSubMenu = CreatePopupMenu();
151 if (hSubMenu != NULL)
152 {
153 GuiConsoleAppendMenuItems(hSubMenu,
154 Items[i].SubMenu);
155
156 if (!AppendMenuW(hMenu,
157 MF_STRING | MF_POPUP,
158 (UINT_PTR)hSubMenu,
159 szMenuString))
160 {
161 DestroyMenu(hSubMenu);
162 }
163 }
164 }
165 else
166 {
167 AppendMenuW(hMenu,
168 MF_STRING,
169 Items[i].wCmdID,
170 szMenuString);
171 }
172 }
173 }
174 else
175 {
176 AppendMenuW(hMenu,
177 MF_SEPARATOR,
178 0,
179 NULL);
180 }
181 i++;
182 } while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].wCmdID == 0));
183 }
184
185 static VOID
186 GuiConsoleCreateSysMenu(HWND hWnd)
187 {
188 HMENU hMenu;
189 hMenu = GetSystemMenu(hWnd, FALSE);
190 if (hMenu != NULL)
191 {
192 GuiConsoleAppendMenuItems(hMenu, GuiConsoleMainMenuItems);
193 DrawMenuBar(hWnd);
194 }
195 }
196
197
198 static VOID
199 GuiConsoleCopy(PGUI_CONSOLE_DATA GuiData);
200 static VOID
201 GuiConsolePaste(PGUI_CONSOLE_DATA GuiData);
202 static VOID
203 GuiConsoleUpdateSelection(PCONSOLE Console, PCOORD coord);
204 static VOID WINAPI
205 GuiDrawRegion(PCONSOLE Console, SMALL_RECT* Region);
206 static VOID
207 GuiConsoleResizeWindow(PGUI_CONSOLE_DATA GuiData);
208
209
210 static LRESULT
211 GuiConsoleHandleSysMenuCommand(PGUI_CONSOLE_DATA GuiData, WPARAM wParam, LPARAM lParam)
212 {
213 LRESULT Ret = TRUE;
214 PCONSOLE Console = GuiData->Console;
215
216 if (!ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE))
217 {
218 Ret = FALSE;
219 goto Quit;
220 }
221
222 switch (wParam)
223 {
224 case ID_SYSTEM_EDIT_MARK:
225 {
226 LPWSTR WindowTitle = NULL;
227 SIZE_T Length = 0;
228
229 Console->dwSelectionCursor.X = 0;
230 Console->dwSelectionCursor.Y = 0;
231 Console->Selection.dwSelectionAnchor = Console->dwSelectionCursor;
232 Console->Selection.dwFlags |= CONSOLE_SELECTION_IN_PROGRESS;
233 GuiConsoleUpdateSelection(Console, &Console->Selection.dwSelectionAnchor);
234
235 Length = Console->Title.Length + sizeof(L"Mark - ")/sizeof(WCHAR) + 1;
236 WindowTitle = RtlAllocateHeap(ConSrvHeap, 0, Length * sizeof(WCHAR));
237 wcscpy(WindowTitle, L"Mark - ");
238 wcscat(WindowTitle, Console->Title.Buffer);
239 SetWindowText(GuiData->hWindow, WindowTitle);
240 RtlFreeHeap(ConSrvHeap, 0, WindowTitle);
241
242 break;
243 }
244
245 case ID_SYSTEM_EDIT_COPY:
246 GuiConsoleCopy(GuiData);
247 break;
248
249 case ID_SYSTEM_EDIT_PASTE:
250 GuiConsolePaste(GuiData);
251 break;
252
253 case ID_SYSTEM_EDIT_SELECTALL:
254 {
255 Console->Selection.dwSelectionAnchor.X = 0;
256 Console->Selection.dwSelectionAnchor.Y = 0;
257 Console->dwSelectionCursor.X = Console->ConsoleSize.X - 1;
258 Console->dwSelectionCursor.Y = Console->ConsoleSize.Y - 1;
259 GuiConsoleUpdateSelection(Console, &Console->dwSelectionCursor);
260 break;
261 }
262
263 case ID_SYSTEM_EDIT_SCROLL:
264 DPRINT1("Scrolling is not handled yet\n");
265 break;
266
267 case ID_SYSTEM_EDIT_FIND:
268 DPRINT1("Finding is not handled yet\n");
269 break;
270
271 case ID_SYSTEM_DEFAULTS:
272 GuiConsoleShowConsoleProperties(GuiData, TRUE);
273 break;
274
275 case ID_SYSTEM_PROPERTIES:
276 GuiConsoleShowConsoleProperties(GuiData, FALSE);
277 break;
278
279 default:
280 Ret = FALSE;
281 break;
282 }
283
284 LeaveCriticalSection(&Console->Lock);
285
286 Quit:
287 if (!Ret)
288 Ret = DefWindowProcW(GuiData->hWindow, WM_SYSCOMMAND, wParam, lParam);
289
290 return Ret;
291 }
292
293 static PGUI_CONSOLE_DATA
294 GuiGetGuiData(HWND hWnd)
295 {
296 /* This function ensures that the console pointer is not NULL */
297 PGUI_CONSOLE_DATA GuiData = (PGUI_CONSOLE_DATA)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
298 return ( ((GuiData == NULL) || (GuiData->hWindow == hWnd && GuiData->Console != NULL)) ? GuiData : NULL );
299 }
300
301 VOID
302 GuiConsoleMoveWindow(PGUI_CONSOLE_DATA GuiData)
303 {
304 /* Move the window if needed (not positioned by the system) */
305 if (!GuiData->GuiInfo.AutoPosition)
306 {
307 SetWindowPos(GuiData->hWindow,
308 NULL,
309 GuiData->GuiInfo.WindowOrigin.x,
310 GuiData->GuiInfo.WindowOrigin.y,
311 0,
312 0,
313 SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
314 }
315 }
316
317 static VOID
318 GuiConsoleResizeWindow(PGUI_CONSOLE_DATA GuiData)
319 {
320 PCONSOLE Console = GuiData->Console;
321 SCROLLINFO sInfo;
322
323 DWORD Width = Console->ConsoleSize.X * GuiData->CharWidth +
324 2 * (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXEDGE));
325 DWORD Height = Console->ConsoleSize.Y * GuiData->CharHeight +
326 2 * (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYEDGE)) + GetSystemMetrics(SM_CYCAPTION);
327
328 /* Set scrollbar sizes */
329 sInfo.cbSize = sizeof(SCROLLINFO);
330 sInfo.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
331 sInfo.nMin = 0;
332 if (Console->ActiveBuffer->ScreenBufferSize.Y > Console->ConsoleSize.Y)
333 {
334 sInfo.nMax = Console->ActiveBuffer->ScreenBufferSize.Y - 1;
335 sInfo.nPage = Console->ConsoleSize.Y;
336 sInfo.nPos = Console->ActiveBuffer->ShowY;
337 SetScrollInfo(GuiData->hWindow, SB_VERT, &sInfo, TRUE);
338 Width += GetSystemMetrics(SM_CXVSCROLL);
339 ShowScrollBar(GuiData->hWindow, SB_VERT, TRUE);
340 }
341 else
342 {
343 ShowScrollBar(GuiData->hWindow, SB_VERT, FALSE);
344 }
345
346 if (Console->ActiveBuffer->ScreenBufferSize.X > Console->ConsoleSize.X)
347 {
348 sInfo.nMax = Console->ActiveBuffer->ScreenBufferSize.X - 1;
349 sInfo.nPage = Console->ConsoleSize.X;
350 sInfo.nPos = Console->ActiveBuffer->ShowX;
351 SetScrollInfo(GuiData->hWindow, SB_HORZ, &sInfo, TRUE);
352 Height += GetSystemMetrics(SM_CYHSCROLL);
353 ShowScrollBar(GuiData->hWindow, SB_HORZ, TRUE);
354 }
355 else
356 {
357 ShowScrollBar(GuiData->hWindow, SB_HORZ, FALSE);
358 }
359
360 /* Resize the window */
361 SetWindowPos(GuiData->hWindow, NULL, 0, 0, Width, Height,
362 SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
363 }
364
365 static BOOL
366 GuiConsoleHandleNcCreate(HWND hWnd, LPCREATESTRUCTW Create)
367 {
368 PGUI_CONSOLE_DATA GuiData = (PGUI_CONSOLE_DATA)Create->lpCreateParams;
369 PCONSOLE Console;
370 HDC Dc;
371 HFONT OldFont;
372 TEXTMETRICW Metrics;
373 SIZE CharSize;
374
375 DPRINT("GuiConsoleHandleNcCreate\n");
376
377 if (NULL == GuiData)
378 {
379 DPRINT1("GuiConsoleNcCreate: No GUI data\n");
380 return FALSE;
381 }
382
383 Console = GuiData->Console;
384
385 GuiData->hWindow = hWnd;
386
387 GuiData->Font = CreateFontW(LOWORD(GuiData->GuiInfo.FontSize),
388 0, // HIWORD(GuiData->GuiInfo.FontSize),
389 0,
390 TA_BASELINE,
391 GuiData->GuiInfo.FontWeight,
392 FALSE,
393 FALSE,
394 FALSE,
395 OEM_CHARSET,
396 OUT_DEFAULT_PRECIS,
397 CLIP_DEFAULT_PRECIS,
398 NONANTIALIASED_QUALITY,
399 FIXED_PITCH | GuiData->GuiInfo.FontFamily /* FF_DONTCARE */,
400 GuiData->GuiInfo.FaceName);
401
402 if (NULL == GuiData->Font)
403 {
404 DPRINT1("GuiConsoleNcCreate: CreateFont failed\n");
405 GuiData->hWindow = NULL;
406 SetEvent(GuiData->hGuiInitEvent);
407 return FALSE;
408 }
409 Dc = GetDC(GuiData->hWindow);
410 if (NULL == Dc)
411 {
412 DPRINT1("GuiConsoleNcCreate: GetDC failed\n");
413 DeleteObject(GuiData->Font);
414 GuiData->hWindow = NULL;
415 SetEvent(GuiData->hGuiInitEvent);
416 return FALSE;
417 }
418 OldFont = SelectObject(Dc, GuiData->Font);
419 if (NULL == OldFont)
420 {
421 DPRINT1("GuiConsoleNcCreate: SelectObject failed\n");
422 ReleaseDC(GuiData->hWindow, Dc);
423 DeleteObject(GuiData->Font);
424 GuiData->hWindow = NULL;
425 SetEvent(GuiData->hGuiInitEvent);
426 return FALSE;
427 }
428 if (!GetTextMetricsW(Dc, &Metrics))
429 {
430 DPRINT1("GuiConsoleNcCreate: GetTextMetrics failed\n");
431 SelectObject(Dc, OldFont);
432 ReleaseDC(GuiData->hWindow, Dc);
433 DeleteObject(GuiData->Font);
434 GuiData->hWindow = NULL;
435 SetEvent(GuiData->hGuiInitEvent);
436 return FALSE;
437 }
438 GuiData->CharWidth = Metrics.tmMaxCharWidth;
439 GuiData->CharHeight = Metrics.tmHeight + Metrics.tmExternalLeading;
440
441 /* Measure real char width more precisely if possible. */
442 if (GetTextExtentPoint32W(Dc, L"R", 1, &CharSize))
443 GuiData->CharWidth = CharSize.cx;
444
445 SelectObject(Dc, OldFont);
446
447 ReleaseDC(GuiData->hWindow, Dc);
448
449 // FIXME: Keep these instructions here ? ///////////////////////////////////
450 Console->ActiveBuffer->CursorBlinkOn = TRUE;
451 Console->ActiveBuffer->ForceCursorOff = FALSE;
452 ////////////////////////////////////////////////////////////////////////////
453
454 SetWindowLongPtrW(GuiData->hWindow, GWLP_USERDATA, (DWORD_PTR)GuiData);
455
456 SetTimer(GuiData->hWindow, CONGUI_UPDATE_TIMER, CONGUI_UPDATE_TIME, NULL);
457 GuiConsoleCreateSysMenu(GuiData->hWindow);
458
459 DPRINT("GuiConsoleHandleNcCreate - setting start event\n");
460 SetEvent(GuiData->hGuiInitEvent);
461
462 return (BOOL)DefWindowProcW(GuiData->hWindow, WM_NCCREATE, 0, (LPARAM)Create);
463 }
464
465 static VOID
466 SmallRectToRect(PGUI_CONSOLE_DATA GuiData, PRECT Rect, PSMALL_RECT SmallRect)
467 {
468 PCONSOLE Console = GuiData->Console;
469 PCONSOLE_SCREEN_BUFFER Buffer = Console->ActiveBuffer;
470
471 Rect->left = (SmallRect->Left - Buffer->ShowX) * GuiData->CharWidth;
472 Rect->top = (SmallRect->Top - Buffer->ShowY) * GuiData->CharHeight;
473 Rect->right = (SmallRect->Right + 1 - Buffer->ShowX) * GuiData->CharWidth;
474 Rect->bottom = (SmallRect->Bottom + 1 - Buffer->ShowY) * GuiData->CharHeight;
475 }
476
477 static VOID
478 GuiConsoleUpdateSelection(PCONSOLE Console, PCOORD coord)
479 {
480 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
481 RECT oldRect, newRect;
482
483 SmallRectToRect(GuiData, &oldRect, &Console->Selection.srSelection);
484
485 if (coord != NULL)
486 {
487 SMALL_RECT rc;
488 /* exchange left/top with right/bottom if required */
489 rc.Left = min(Console->Selection.dwSelectionAnchor.X, coord->X);
490 rc.Top = min(Console->Selection.dwSelectionAnchor.Y, coord->Y);
491 rc.Right = max(Console->Selection.dwSelectionAnchor.X, coord->X);
492 rc.Bottom = max(Console->Selection.dwSelectionAnchor.Y, coord->Y);
493
494 SmallRectToRect(GuiData, &newRect, &rc);
495
496 if (Console->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY)
497 {
498 if (memcmp(&rc, &Console->Selection.srSelection, sizeof(SMALL_RECT)) != 0)
499 {
500 HRGN rgn1, rgn2;
501
502 /* calculate the region that needs to be updated */
503 if ((rgn1 = CreateRectRgnIndirect(&oldRect)))
504 {
505 if ((rgn2 = CreateRectRgnIndirect(&newRect)))
506 {
507 if (CombineRgn(rgn1, rgn2, rgn1, RGN_XOR) != ERROR)
508 {
509 InvalidateRgn(GuiData->hWindow, rgn1, FALSE);
510 }
511 DeleteObject(rgn2);
512 }
513 DeleteObject(rgn1);
514 }
515 }
516 }
517 else
518 {
519 InvalidateRect(GuiData->hWindow, &newRect, FALSE);
520 }
521 Console->Selection.dwFlags |= CONSOLE_SELECTION_NOT_EMPTY;
522 Console->Selection.srSelection = rc;
523 Console->dwSelectionCursor = *coord;
524 ConioPause(Console, PAUSED_FROM_SELECTION);
525 }
526 else
527 {
528 /* clear the selection */
529 if (Console->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY)
530 {
531 InvalidateRect(GuiData->hWindow, &oldRect, FALSE);
532 }
533 Console->Selection.dwFlags = CONSOLE_NO_SELECTION;
534 ConioUnpause(Console, PAUSED_FROM_SELECTION);
535 }
536 }
537
538 static VOID
539 GuiConsolePaint(PCONSOLE Console,
540 PGUI_CONSOLE_DATA GuiData,
541 HDC hDC,
542 PRECT rc)
543 {
544 PCONSOLE_SCREEN_BUFFER Buff;
545 ULONG TopLine, BottomLine, LeftChar, RightChar;
546 ULONG Line, Char, Start;
547 PBYTE From;
548 PWCHAR To;
549 BYTE LastAttribute, Attribute;
550 ULONG CursorX, CursorY, CursorHeight;
551 HBRUSH CursorBrush, OldBrush;
552 HFONT OldFont;
553
554 Buff = Console->ActiveBuffer;
555
556 TopLine = rc->top / GuiData->CharHeight + Buff->ShowY;
557 BottomLine = (rc->bottom + (GuiData->CharHeight - 1)) / GuiData->CharHeight - 1 + Buff->ShowY;
558 LeftChar = rc->left / GuiData->CharWidth + Buff->ShowX;
559 RightChar = (rc->right + (GuiData->CharWidth - 1)) / GuiData->CharWidth - 1 + Buff->ShowX;
560 LastAttribute = ConioCoordToPointer(Buff, LeftChar, TopLine)[1];
561
562 SetTextColor(hDC, RGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
563 SetBkColor(hDC, RGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
564
565 if (BottomLine >= Buff->ScreenBufferSize.Y) BottomLine = Buff->ScreenBufferSize.Y - 1;
566 if (RightChar >= Buff->ScreenBufferSize.X) RightChar = Buff->ScreenBufferSize.X - 1;
567
568 OldFont = SelectObject(hDC, GuiData->Font);
569
570 for (Line = TopLine; Line <= BottomLine; Line++)
571 {
572 WCHAR LineBuffer[80];
573 From = ConioCoordToPointer(Buff, LeftChar, Line);
574 Start = LeftChar;
575 To = LineBuffer;
576
577 for (Char = LeftChar; Char <= RightChar; Char++)
578 {
579 if (*(From + 1) != LastAttribute || (Char - Start == sizeof(LineBuffer) / sizeof(WCHAR)))
580 {
581 TextOutW(hDC,
582 (Start - Buff->ShowX) * GuiData->CharWidth,
583 (Line - Buff->ShowY) * GuiData->CharHeight,
584 LineBuffer,
585 Char - Start);
586 Start = Char;
587 To = LineBuffer;
588 Attribute = *(From + 1);
589 if (Attribute != LastAttribute)
590 {
591 SetTextColor(hDC, RGBFromAttrib(Console, TextAttribFromAttrib(Attribute)));
592 SetBkColor(hDC, RGBFromAttrib(Console, BkgdAttribFromAttrib(Attribute)));
593 LastAttribute = Attribute;
594 }
595 }
596
597 MultiByteToWideChar(Console->OutputCodePage,
598 0, (PCHAR)From, 1, To, 1);
599 To++;
600 From += 2;
601 }
602
603 TextOutW(hDC,
604 (Start - Buff->ShowX) * GuiData->CharWidth,
605 (Line - Buff->ShowY) * GuiData->CharHeight,
606 LineBuffer,
607 RightChar - Start + 1);
608 }
609
610 if (Buff->CursorInfo.bVisible &&
611 Buff->CursorBlinkOn &&
612 !Buff->ForceCursorOff)
613 {
614 CursorX = Buff->CursorPosition.X;
615 CursorY = Buff->CursorPosition.Y;
616 if (LeftChar <= CursorX && CursorX <= RightChar &&
617 TopLine <= CursorY && CursorY <= BottomLine)
618 {
619 CursorHeight = ConioEffectiveCursorSize(Console, GuiData->CharHeight);
620 From = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y) + 1;
621
622 if (*From != DEFAULT_SCREEN_ATTRIB)
623 {
624 CursorBrush = CreateSolidBrush(RGBFromAttrib(Console, *From));
625 }
626 else
627 {
628 CursorBrush = CreateSolidBrush(RGBFromAttrib(Console, Buff->ScreenDefaultAttrib));
629 }
630
631 OldBrush = SelectObject(hDC, CursorBrush);
632 PatBlt(hDC,
633 (CursorX - Buff->ShowX) * GuiData->CharWidth,
634 (CursorY - Buff->ShowY) * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight),
635 GuiData->CharWidth,
636 CursorHeight,
637 PATCOPY);
638 SelectObject(hDC, OldBrush);
639 DeleteObject(CursorBrush);
640 }
641 }
642
643 SelectObject(hDC, OldFont);
644 }
645
646 static VOID
647 GuiConsoleHandlePaint(PGUI_CONSOLE_DATA GuiData)
648 {
649 BOOL Success = TRUE;
650 PCONSOLE Console = GuiData->Console;
651 HDC hDC;
652 PAINTSTRUCT ps;
653
654 if (!ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE))
655 {
656 Success = FALSE;
657 goto Quit;
658 }
659
660 if (Console->ActiveBuffer == NULL ||
661 Console->ActiveBuffer->Buffer == NULL)
662 {
663 goto Quit;
664 }
665
666 hDC = BeginPaint(GuiData->hWindow, &ps);
667 if (hDC != NULL &&
668 ps.rcPaint.left < ps.rcPaint.right &&
669 ps.rcPaint.top < ps.rcPaint.bottom)
670 {
671 EnterCriticalSection(&GuiData->Lock);
672
673 GuiConsolePaint(Console,
674 GuiData,
675 hDC,
676 &ps.rcPaint);
677
678 if (Console->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY)
679 {
680 RECT rc;
681 SmallRectToRect(GuiData, &rc, &Console->Selection.srSelection);
682
683 /* invert the selection */
684 if (IntersectRect(&rc,
685 &ps.rcPaint,
686 &rc))
687 {
688 PatBlt(hDC,
689 rc.left,
690 rc.top,
691 rc.right - rc.left,
692 rc.bottom - rc.top,
693 DSTINVERT);
694 }
695 }
696
697 LeaveCriticalSection(&GuiData->Lock);
698 }
699 EndPaint(GuiData->hWindow, &ps);
700
701 Quit:
702 if (Success)
703 LeaveCriticalSection(&Console->Lock);
704 else
705 DefWindowProcW(GuiData->hWindow, WM_PAINT, 0, 0);
706
707 return;
708 }
709
710 static VOID
711 GuiConsoleHandleKey(PGUI_CONSOLE_DATA GuiData, UINT msg, WPARAM wParam, LPARAM lParam)
712 {
713 PCONSOLE Console = GuiData->Console;
714 MSG Message;
715
716 if (!ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE)) return;
717
718 if ( (Console->Selection.dwFlags & CONSOLE_SELECTION_IN_PROGRESS) &&
719 ((Console->Selection.dwFlags & CONSOLE_MOUSE_SELECTION) == 0) &&
720 (Console->ActiveBuffer) )
721 {
722 BOOL Interpreted = FALSE;
723
724 /* Selection with keyboard */
725 if (msg == WM_KEYDOWN)
726 {
727 BOOL MajPressed = (GetKeyState(VK_SHIFT) & 0x8000);
728
729 switch (wParam)
730 {
731 case VK_LEFT:
732 {
733 Interpreted = TRUE;
734 if (Console->dwSelectionCursor.X > 0)
735 Console->dwSelectionCursor.X--;
736
737 break;
738 }
739
740 case VK_RIGHT:
741 {
742 Interpreted = TRUE;
743 if (Console->dwSelectionCursor.X < Console->ActiveBuffer->ScreenBufferSize.X - 1)
744 Console->dwSelectionCursor.X++;
745
746 break;
747 }
748
749 case VK_UP:
750 {
751 Interpreted = TRUE;
752 if (Console->dwSelectionCursor.Y > 0)
753 Console->dwSelectionCursor.Y--;
754
755 break;
756 }
757
758 case VK_DOWN:
759 {
760 Interpreted = TRUE;
761 if (Console->dwSelectionCursor.Y < Console->ActiveBuffer->ScreenBufferSize.Y - 1)
762 Console->dwSelectionCursor.Y++;
763
764 break;
765 }
766
767 case VK_HOME:
768 {
769 Interpreted = TRUE;
770 Console->dwSelectionCursor.X = 0;
771 Console->dwSelectionCursor.Y = 0;
772 break;
773 }
774
775 case VK_END:
776 {
777 Interpreted = TRUE;
778 Console->dwSelectionCursor.Y = Console->ActiveBuffer->ScreenBufferSize.Y - 1;
779 break;
780 }
781
782 case VK_PRIOR:
783 {
784 Interpreted = TRUE;
785 Console->dwSelectionCursor.Y -= Console->ConsoleSize.Y;
786 if (Console->dwSelectionCursor.Y < 0)
787 Console->dwSelectionCursor.Y = 0;
788
789 break;
790 }
791
792 case VK_NEXT:
793 {
794 Interpreted = TRUE;
795 Console->dwSelectionCursor.Y += Console->ConsoleSize.Y;
796 if (Console->dwSelectionCursor.Y >= Console->ActiveBuffer->ScreenBufferSize.Y)
797 Console->dwSelectionCursor.Y = Console->ActiveBuffer->ScreenBufferSize.Y - 1;
798
799 break;
800 }
801
802 default:
803 break;
804 }
805
806 if (Interpreted)
807 {
808 if (!MajPressed)
809 Console->Selection.dwSelectionAnchor = Console->dwSelectionCursor;
810
811 GuiConsoleUpdateSelection(Console, &Console->dwSelectionCursor);
812 }
813 }
814 }
815 else
816 {
817 Message.hwnd = GuiData->hWindow;
818 Message.message = msg;
819 Message.wParam = wParam;
820 Message.lParam = lParam;
821
822 if (msg == WM_KEYDOWN)
823 {
824 /* If we are in selection mode (with mouse), clear the selection */
825 GuiConsoleUpdateSelection(Console, NULL);
826 SetWindowText(GuiData->hWindow, Console->Title.Buffer);
827 }
828
829 ConioProcessKey(Console, &Message);
830 }
831
832 LeaveCriticalSection(&Console->Lock);
833 }
834
835 static VOID
836 GuiInvalidateCell(PCONSOLE Console, SHORT x, SHORT y)
837 {
838 SMALL_RECT CellRect = { x, y, x, y };
839 GuiDrawRegion(Console, &CellRect);
840 }
841
842 static VOID
843 GuiConsoleHandleTimer(PGUI_CONSOLE_DATA GuiData)
844 {
845 PCONSOLE Console = GuiData->Console;
846 PCONSOLE_SCREEN_BUFFER Buff;
847
848 SetTimer(GuiData->hWindow, CONGUI_UPDATE_TIMER, CURSOR_BLINK_TIME, NULL);
849
850 if (!ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE)) return;
851
852 Buff = Console->ActiveBuffer;
853 GuiInvalidateCell(Console, Buff->CursorPosition.X, Buff->CursorPosition.Y);
854 Buff->CursorBlinkOn = !Buff->CursorBlinkOn;
855
856 if ((GuiData->OldCursor.x != Buff->CursorPosition.X) || (GuiData->OldCursor.y != Buff->CursorPosition.Y))
857 {
858 SCROLLINFO xScroll;
859 int OldScrollX = -1, OldScrollY = -1;
860 int NewScrollX = -1, NewScrollY = -1;
861
862 xScroll.cbSize = sizeof(SCROLLINFO);
863 xScroll.fMask = SIF_POS;
864 // Capture the original position of the scroll bars and save them.
865 if (GetScrollInfo(GuiData->hWindow, SB_HORZ, &xScroll))OldScrollX = xScroll.nPos;
866 if (GetScrollInfo(GuiData->hWindow, SB_VERT, &xScroll))OldScrollY = xScroll.nPos;
867
868 // If we successfully got the info for the horizontal scrollbar
869 if (OldScrollX >= 0)
870 {
871 if ((Buff->CursorPosition.X < Buff->ShowX)||(Buff->CursorPosition.X >= (Buff->ShowX + Console->ConsoleSize.X)))
872 {
873 // Handle the horizontal scroll bar
874 if (Buff->CursorPosition.X >= Console->ConsoleSize.X) NewScrollX = Buff->CursorPosition.X - Console->ConsoleSize.X + 1;
875 else NewScrollX = 0;
876 }
877 else
878 {
879 NewScrollX = OldScrollX;
880 }
881 }
882 // If we successfully got the info for the vertical scrollbar
883 if (OldScrollY >= 0)
884 {
885 if ((Buff->CursorPosition.Y < Buff->ShowY) || (Buff->CursorPosition.Y >= (Buff->ShowY + Console->ConsoleSize.Y)))
886 {
887 // Handle the vertical scroll bar
888 if (Buff->CursorPosition.Y >= Console->ConsoleSize.Y) NewScrollY = Buff->CursorPosition.Y - Console->ConsoleSize.Y + 1;
889 else NewScrollY = 0;
890 }
891 else
892 {
893 NewScrollY = OldScrollY;
894 }
895 }
896
897 // Adjust scroll bars and refresh the window if the cursor has moved outside the visible area
898 // NOTE: OldScroll# and NewScroll# will both be -1 (initial value) if the info for the respective scrollbar
899 // was not obtained successfully in the previous steps. This means their difference is 0 (no scrolling)
900 // and their associated scrollbar is left alone.
901 if ((OldScrollX != NewScrollX) || (OldScrollY != NewScrollY))
902 {
903 Buff->ShowX = NewScrollX;
904 Buff->ShowY = NewScrollY;
905 ScrollWindowEx(GuiData->hWindow,
906 (OldScrollX - NewScrollX) * GuiData->CharWidth,
907 (OldScrollY - NewScrollY) * GuiData->CharHeight,
908 NULL,
909 NULL,
910 NULL,
911 NULL,
912 SW_INVALIDATE);
913 if (NewScrollX >= 0)
914 {
915 xScroll.nPos = NewScrollX;
916 SetScrollInfo(GuiData->hWindow, SB_HORZ, &xScroll, TRUE);
917 }
918 if (NewScrollY >= 0)
919 {
920 xScroll.nPos = NewScrollY;
921 SetScrollInfo(GuiData->hWindow, SB_VERT, &xScroll, TRUE);
922 }
923 UpdateWindow(GuiData->hWindow);
924 GuiData->OldCursor.x = Buff->CursorPosition.X;
925 GuiData->OldCursor.y = Buff->CursorPosition.Y;
926 }
927 }
928
929 LeaveCriticalSection(&Console->Lock);
930 }
931
932 static VOID
933 GuiConsoleHandleClose(PGUI_CONSOLE_DATA GuiData)
934 {
935 PCONSOLE Console = GuiData->Console;
936 if (!ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE)) return;
937
938 /*
939 * FIXME: Windows will wait up to 5 seconds for the thread to exit.
940 * We shouldn't wait here, though, since the console lock is entered.
941 * A copy of the thread list probably needs to be made.
942 */
943 ConSrvConsoleProcessCtrlEvent(Console, 0, CTRL_CLOSE_EVENT);
944
945 LeaveCriticalSection(&Console->Lock);
946 }
947
948 static LRESULT
949 GuiConsoleHandleNcDestroy(HWND hWnd)
950 {
951 // PGUI_CONSOLE_DATA GuiData;
952
953 KillTimer(hWnd, CONGUI_UPDATE_TIMER);
954 GetSystemMenu(hWnd, TRUE);
955
956 /* Free the GuiData registration */
957 SetWindowLongPtrW(hWnd, GWLP_USERDATA, (DWORD_PTR)NULL);
958 // GuiData->hWindow = NULL;
959
960 // return 0;
961 return DefWindowProcW(hWnd, WM_NCDESTROY, 0, 0);
962 }
963
964 static COORD
965 PointToCoord(PGUI_CONSOLE_DATA GuiData, LPARAM lParam)
966 {
967 PCONSOLE Console = GuiData->Console;
968 PCONSOLE_SCREEN_BUFFER Buffer = Console->ActiveBuffer;
969 COORD Coord;
970
971 Coord.X = Buffer->ShowX + ((SHORT)LOWORD(lParam) / (int)GuiData->CharWidth);
972 Coord.Y = Buffer->ShowY + ((SHORT)HIWORD(lParam) / (int)GuiData->CharHeight);
973
974 /* Clip coordinate to ensure it's inside buffer */
975 if (Coord.X < 0)
976 Coord.X = 0;
977 else if (Coord.X >= Buffer->ScreenBufferSize.X)
978 Coord.X = Buffer->ScreenBufferSize.X - 1;
979
980 if (Coord.Y < 0)
981 Coord.Y = 0;
982 else if (Coord.Y >= Buffer->ScreenBufferSize.Y)
983 Coord.Y = Buffer->ScreenBufferSize.Y - 1;
984
985 return Coord;
986 }
987
988 static LRESULT
989 GuiConsoleHandleMouse(PGUI_CONSOLE_DATA GuiData, UINT msg, WPARAM wParam, LPARAM lParam)
990 {
991 BOOL Err = FALSE;
992 PCONSOLE Console = GuiData->Console;
993
994 if (!ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE))
995 {
996 Err = TRUE;
997 goto Quit;
998 }
999
1000 if ( (Console->Selection.dwFlags & CONSOLE_SELECTION_IN_PROGRESS) ||
1001 (Console->QuickEdit) )
1002 {
1003 switch (msg)
1004 {
1005 case WM_LBUTTONDOWN:
1006 {
1007 LPWSTR WindowTitle = NULL;
1008 SIZE_T Length = 0;
1009
1010 Console->Selection.dwSelectionAnchor = PointToCoord(GuiData, lParam);
1011 SetCapture(GuiData->hWindow);
1012 Console->Selection.dwFlags |= CONSOLE_SELECTION_IN_PROGRESS | CONSOLE_MOUSE_SELECTION | CONSOLE_MOUSE_DOWN;
1013 GuiConsoleUpdateSelection(Console, &Console->Selection.dwSelectionAnchor);
1014
1015 Length = Console->Title.Length + sizeof(L"Selection - ")/sizeof(WCHAR) + 1;
1016 WindowTitle = RtlAllocateHeap(ConSrvHeap, 0, Length * sizeof(WCHAR));
1017 wcscpy(WindowTitle, L"Selection - ");
1018 wcscat(WindowTitle, Console->Title.Buffer);
1019 SetWindowText(GuiData->hWindow, WindowTitle);
1020 RtlFreeHeap(ConSrvHeap, 0, WindowTitle);
1021
1022 break;
1023 }
1024
1025 case WM_LBUTTONUP:
1026 {
1027 COORD c;
1028
1029 if (!(Console->Selection.dwFlags & CONSOLE_MOUSE_DOWN)) break;
1030
1031 c = PointToCoord(GuiData, lParam);
1032 Console->Selection.dwFlags &= ~CONSOLE_MOUSE_DOWN;
1033 GuiConsoleUpdateSelection(Console, &c);
1034 ReleaseCapture();
1035
1036 break;
1037 }
1038
1039 case WM_RBUTTONDOWN:
1040 {
1041 if (!(Console->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY))
1042 {
1043 GuiConsolePaste(GuiData);
1044 }
1045 else
1046 {
1047 GuiConsoleCopy(GuiData);
1048
1049 /* Clear the selection */
1050 GuiConsoleUpdateSelection(Console, NULL);
1051 SetWindowText(GuiData->hWindow, Console->Title.Buffer);
1052 }
1053
1054 break;
1055 }
1056
1057 case WM_MOUSEMOVE:
1058 {
1059 COORD c;
1060
1061 if (!(wParam & MK_LBUTTON)) break;
1062 if (!(Console->Selection.dwFlags & CONSOLE_MOUSE_DOWN)) break;
1063
1064 c = PointToCoord(GuiData, lParam); /* TODO: Scroll buffer to bring c into view */
1065 GuiConsoleUpdateSelection(Console, &c);
1066
1067 break;
1068 }
1069
1070 default:
1071 Err = TRUE;
1072 break;
1073 }
1074 }
1075 else if (Console->InputBuffer.Mode & ENABLE_MOUSE_INPUT)
1076 {
1077 INPUT_RECORD er;
1078 WORD wKeyState = GET_KEYSTATE_WPARAM(wParam);
1079 DWORD dwButtonState = 0;
1080 DWORD dwControlKeyState = 0;
1081 DWORD dwEventFlags = 0;
1082
1083 switch (msg)
1084 {
1085 case WM_LBUTTONDOWN:
1086 SetCapture(GuiData->hWindow);
1087 dwButtonState = FROM_LEFT_1ST_BUTTON_PRESSED;
1088 dwEventFlags = 0;
1089 break;
1090
1091 case WM_MBUTTONDOWN:
1092 SetCapture(GuiData->hWindow);
1093 dwButtonState = FROM_LEFT_2ND_BUTTON_PRESSED;
1094 dwEventFlags = 0;
1095 break;
1096
1097 case WM_RBUTTONDOWN:
1098 SetCapture(GuiData->hWindow);
1099 dwButtonState = RIGHTMOST_BUTTON_PRESSED;
1100 dwEventFlags = 0;
1101 break;
1102
1103 case WM_LBUTTONUP:
1104 ReleaseCapture();
1105 dwButtonState = 0;
1106 dwEventFlags = 0;
1107 break;
1108
1109 case WM_MBUTTONUP:
1110 ReleaseCapture();
1111 dwButtonState = 0;
1112 dwEventFlags = 0;
1113 break;
1114
1115 case WM_RBUTTONUP:
1116 ReleaseCapture();
1117 dwButtonState = 0;
1118 dwEventFlags = 0;
1119 break;
1120
1121 case WM_LBUTTONDBLCLK:
1122 dwButtonState = FROM_LEFT_1ST_BUTTON_PRESSED;
1123 dwEventFlags = DOUBLE_CLICK;
1124 break;
1125
1126 case WM_MBUTTONDBLCLK:
1127 dwButtonState = FROM_LEFT_2ND_BUTTON_PRESSED;
1128 dwEventFlags = DOUBLE_CLICK;
1129 break;
1130
1131 case WM_RBUTTONDBLCLK:
1132 dwButtonState = RIGHTMOST_BUTTON_PRESSED;
1133 dwEventFlags = DOUBLE_CLICK;
1134 break;
1135
1136 case WM_MOUSEMOVE:
1137 dwButtonState = 0;
1138 dwEventFlags = MOUSE_MOVED;
1139 break;
1140
1141 case WM_MOUSEWHEEL:
1142 dwButtonState = GET_WHEEL_DELTA_WPARAM(wParam) << 16;
1143 dwEventFlags = MOUSE_WHEELED;
1144 break;
1145
1146 case WM_MOUSEHWHEEL:
1147 dwButtonState = GET_WHEEL_DELTA_WPARAM(wParam) << 16;
1148 dwEventFlags = MOUSE_HWHEELED;
1149 break;
1150
1151 default:
1152 Err = TRUE;
1153 break;
1154 }
1155
1156 if (!Err)
1157 {
1158 if (wKeyState & MK_LBUTTON)
1159 dwButtonState |= FROM_LEFT_1ST_BUTTON_PRESSED;
1160 if (wKeyState & MK_MBUTTON)
1161 dwButtonState |= FROM_LEFT_2ND_BUTTON_PRESSED;
1162 if (wKeyState & MK_RBUTTON)
1163 dwButtonState |= RIGHTMOST_BUTTON_PRESSED;
1164
1165 if (GetKeyState(VK_RMENU) & 0x8000)
1166 dwControlKeyState |= RIGHT_ALT_PRESSED;
1167 if (GetKeyState(VK_LMENU) & 0x8000)
1168 dwControlKeyState |= LEFT_ALT_PRESSED;
1169 if (GetKeyState(VK_RCONTROL) & 0x8000)
1170 dwControlKeyState |= RIGHT_CTRL_PRESSED;
1171 if (GetKeyState(VK_LCONTROL) & 0x8000)
1172 dwControlKeyState |= LEFT_CTRL_PRESSED;
1173 if (GetKeyState(VK_SHIFT) & 0x8000)
1174 dwControlKeyState |= SHIFT_PRESSED;
1175 if (GetKeyState(VK_NUMLOCK) & 0x0001)
1176 dwControlKeyState |= NUMLOCK_ON;
1177 if (GetKeyState(VK_SCROLL) & 0x0001)
1178 dwControlKeyState |= SCROLLLOCK_ON;
1179 if (GetKeyState(VK_CAPITAL) & 0x0001)
1180 dwControlKeyState |= CAPSLOCK_ON;
1181 /* See WM_CHAR MSDN documentation for instance */
1182 if (lParam & 0x01000000)
1183 dwControlKeyState |= ENHANCED_KEY;
1184
1185 er.EventType = MOUSE_EVENT;
1186 er.Event.MouseEvent.dwMousePosition = PointToCoord(GuiData, lParam);
1187 er.Event.MouseEvent.dwButtonState = dwButtonState;
1188 er.Event.MouseEvent.dwControlKeyState = dwControlKeyState;
1189 er.Event.MouseEvent.dwEventFlags = dwEventFlags;
1190
1191 ConioProcessInputEvent(Console, &er);
1192 }
1193 }
1194 else
1195 {
1196 Err = TRUE;
1197 }
1198
1199 LeaveCriticalSection(&Console->Lock);
1200
1201 Quit:
1202 if (Err)
1203 return DefWindowProcW(GuiData->hWindow, msg, wParam, lParam);
1204 else
1205 return 0;
1206 }
1207
1208 static VOID
1209 GuiConsoleCopy(PGUI_CONSOLE_DATA GuiData)
1210 {
1211 PCONSOLE Console = GuiData->Console;
1212
1213 if (OpenClipboard(GuiData->hWindow) == TRUE)
1214 {
1215 HANDLE hData;
1216 PBYTE ptr;
1217 LPSTR data, dstPos;
1218 ULONG selWidth, selHeight;
1219 ULONG xPos, yPos, size;
1220
1221 selWidth = Console->Selection.srSelection.Right - Console->Selection.srSelection.Left + 1;
1222 selHeight = Console->Selection.srSelection.Bottom - Console->Selection.srSelection.Top + 1;
1223 DPRINT("Selection is (%d|%d) to (%d|%d)\n",
1224 Console->Selection.srSelection.Left,
1225 Console->Selection.srSelection.Top,
1226 Console->Selection.srSelection.Right,
1227 Console->Selection.srSelection.Bottom);
1228
1229 /* Basic size for one line and termination */
1230 size = selWidth + 1;
1231 if (selHeight > 0)
1232 {
1233 /* Multiple line selections have to get \r\n appended */
1234 size += ((selWidth + 2) * (selHeight - 1));
1235 }
1236
1237 /* Allocate memory, it will be passed to the system and may not be freed here */
1238 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
1239 if (hData == NULL)
1240 {
1241 CloseClipboard();
1242 return;
1243 }
1244 data = GlobalLock(hData);
1245 if (data == NULL)
1246 {
1247 CloseClipboard();
1248 return;
1249 }
1250
1251 DPRINT("Copying %dx%d selection\n", selWidth, selHeight);
1252 dstPos = data;
1253
1254 for (yPos = 0; yPos < selHeight; yPos++)
1255 {
1256 ptr = ConioCoordToPointer(Console->ActiveBuffer,
1257 Console->Selection.srSelection.Left,
1258 yPos + Console->Selection.srSelection.Top);
1259 /* Copy only the characters, leave attributes alone */
1260 for (xPos = 0; xPos < selWidth; xPos++)
1261 {
1262 dstPos[xPos] = ptr[xPos * 2];
1263 }
1264 dstPos += selWidth;
1265 if (yPos != (selHeight - 1))
1266 {
1267 strcat(data, "\r\n");
1268 dstPos += 2;
1269 }
1270 }
1271
1272 DPRINT("Setting data <%s> to clipboard\n", data);
1273 GlobalUnlock(hData);
1274
1275 EmptyClipboard();
1276 SetClipboardData(CF_TEXT, hData);
1277 CloseClipboard();
1278 }
1279 }
1280
1281 static VOID
1282 GuiConsolePaste(PGUI_CONSOLE_DATA GuiData)
1283 {
1284 PCONSOLE Console = GuiData->Console;
1285
1286 if (OpenClipboard(GuiData->hWindow) == TRUE)
1287 {
1288 HANDLE hData;
1289 LPSTR str;
1290 size_t len;
1291
1292 hData = GetClipboardData(CF_TEXT);
1293 if (hData == NULL)
1294 {
1295 CloseClipboard();
1296 return;
1297 }
1298
1299 str = GlobalLock(hData);
1300 if (str == NULL)
1301 {
1302 CloseClipboard();
1303 return;
1304 }
1305 DPRINT("Got data <%s> from clipboard\n", str);
1306 len = strlen(str);
1307
1308 // TODO: Push the text into the input buffer.
1309 ConioWriteConsole(Console, Console->ActiveBuffer, str, len, TRUE);
1310
1311 GlobalUnlock(hData);
1312 CloseClipboard();
1313 }
1314 }
1315
1316 static VOID
1317 GuiConsoleGetMinMaxInfo(PGUI_CONSOLE_DATA GuiData, PMINMAXINFO minMaxInfo)
1318 {
1319 PCONSOLE Console = GuiData->Console;
1320 DWORD windx, windy;
1321
1322 if (!ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE)) return;
1323
1324 windx = CONGUI_MIN_WIDTH * GuiData->CharWidth + 2 * (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXEDGE));
1325 windy = CONGUI_MIN_HEIGHT * GuiData->CharHeight + 2 * (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYEDGE)) + GetSystemMetrics(SM_CYCAPTION);
1326
1327 minMaxInfo->ptMinTrackSize.x = windx;
1328 minMaxInfo->ptMinTrackSize.y = windy;
1329
1330 windx = (Console->ActiveBuffer->ScreenBufferSize.X) * GuiData->CharWidth + 2 * (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXEDGE));
1331 windy = (Console->ActiveBuffer->ScreenBufferSize.Y) * GuiData->CharHeight + 2 * (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYEDGE)) + GetSystemMetrics(SM_CYCAPTION);
1332
1333 if (Console->ConsoleSize.X < Console->ActiveBuffer->ScreenBufferSize.X) windy += GetSystemMetrics(SM_CYHSCROLL); // window currently has a horizontal scrollbar
1334 if (Console->ConsoleSize.Y < Console->ActiveBuffer->ScreenBufferSize.Y) windx += GetSystemMetrics(SM_CXVSCROLL); // window currently has a vertical scrollbar
1335
1336 minMaxInfo->ptMaxTrackSize.x = windx;
1337 minMaxInfo->ptMaxTrackSize.y = windy;
1338
1339 LeaveCriticalSection(&Console->Lock);
1340 }
1341
1342 static VOID
1343 GuiConsoleResize(PGUI_CONSOLE_DATA GuiData, WPARAM wParam, LPARAM lParam)
1344 {
1345 PCONSOLE Console = GuiData->Console;
1346
1347 if (!ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE)) return;
1348
1349 if ((GuiData->WindowSizeLock == FALSE) &&
1350 (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED || wParam == SIZE_MINIMIZED))
1351 {
1352 PCONSOLE_SCREEN_BUFFER Buff = Console->ActiveBuffer;
1353 DWORD windx, windy, charx, chary;
1354
1355 GuiData->WindowSizeLock = TRUE;
1356
1357 windx = LOWORD(lParam);
1358 windy = HIWORD(lParam);
1359
1360 // Compensate for existing scroll bars (because lParam values do not accommodate scroll bar)
1361 if (Console->ConsoleSize.X < Buff->ScreenBufferSize.X) windy += GetSystemMetrics(SM_CYHSCROLL); // window currently has a horizontal scrollbar
1362 if (Console->ConsoleSize.Y < Buff->ScreenBufferSize.Y) windx += GetSystemMetrics(SM_CXVSCROLL); // window currently has a vertical scrollbar
1363
1364 charx = windx / GuiData->CharWidth;
1365 chary = windy / GuiData->CharHeight;
1366
1367 // Character alignment (round size up or down)
1368 if ((windx % GuiData->CharWidth) >= (GuiData->CharWidth / 2)) ++charx;
1369 if ((windy % GuiData->CharHeight) >= (GuiData->CharHeight / 2)) ++chary;
1370
1371 // Compensate for added scroll bars in new window
1372 if (charx < Buff->ScreenBufferSize.X)windy -= GetSystemMetrics(SM_CYHSCROLL); // new window will have a horizontal scroll bar
1373 if (chary < Buff->ScreenBufferSize.Y)windx -= GetSystemMetrics(SM_CXVSCROLL); // new window will have a vertical scroll bar
1374
1375 charx = windx / GuiData->CharWidth;
1376 chary = windy / GuiData->CharHeight;
1377
1378 // Character alignment (round size up or down)
1379 if ((windx % GuiData->CharWidth) >= (GuiData->CharWidth / 2)) ++charx;
1380 if ((windy % GuiData->CharHeight) >= (GuiData->CharHeight / 2)) ++chary;
1381
1382 // Resize window
1383 if ((charx != Console->ConsoleSize.X) || (chary != Console->ConsoleSize.Y))
1384 {
1385 Console->ConsoleSize.X = (charx <= Buff->ScreenBufferSize.X) ? charx : Buff->ScreenBufferSize.X;
1386 Console->ConsoleSize.Y = (chary <= Buff->ScreenBufferSize.Y) ? chary : Buff->ScreenBufferSize.Y;
1387 }
1388
1389 GuiConsoleResizeWindow(GuiData);
1390
1391 // Adjust the start of the visible area if we are attempting to show nonexistent areas
1392 if ((Buff->ScreenBufferSize.X - Buff->ShowX) < Console->ConsoleSize.X) Buff->ShowX = Buff->ScreenBufferSize.X - Console->ConsoleSize.X;
1393 if ((Buff->ScreenBufferSize.Y - Buff->ShowY) < Console->ConsoleSize.Y) Buff->ShowY = Buff->ScreenBufferSize.Y - Console->ConsoleSize.Y;
1394 InvalidateRect(GuiData->hWindow, NULL, TRUE);
1395
1396 GuiData->WindowSizeLock = FALSE;
1397 }
1398
1399 LeaveCriticalSection(&Console->Lock);
1400 }
1401
1402 /*
1403 // HACK: This functionality is standard for general scrollbars. Don't add it by hand.
1404
1405 VOID
1406 FASTCALL
1407 GuiConsoleHandleScrollbarMenu(VOID)
1408 {
1409 HMENU hMenu;
1410
1411 hMenu = CreatePopupMenu();
1412 if (hMenu == NULL)
1413 {
1414 DPRINT("CreatePopupMenu failed\n");
1415 return;
1416 }
1417
1418 //InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLHERE);
1419 //InsertItem(hMenu, MFT_SEPARATOR, MIIM_FTYPE, 0, NULL, -1);
1420 //InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLTOP);
1421 //InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLBOTTOM);
1422 //InsertItem(hMenu, MFT_SEPARATOR, MIIM_FTYPE, 0, NULL, -1);
1423 //InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLPAGE_UP);
1424 //InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLPAGE_DOWN);
1425 //InsertItem(hMenu, MFT_SEPARATOR, MIIM_FTYPE, 0, NULL, -1);
1426 //InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLUP);
1427 //InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLDOWN);
1428 }
1429 */
1430
1431 static LRESULT
1432 GuiConsoleHandleScroll(PGUI_CONSOLE_DATA GuiData, UINT uMsg, WPARAM wParam)
1433 {
1434 PCONSOLE Console = GuiData->Console;
1435 PCONSOLE_SCREEN_BUFFER Buff;
1436 SCROLLINFO sInfo;
1437 int fnBar;
1438 int old_pos, Maximum;
1439 PUSHORT pShowXY;
1440
1441 if (!ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE)) return 0;
1442
1443 Buff = Console->ActiveBuffer;
1444
1445 if (uMsg == WM_HSCROLL)
1446 {
1447 fnBar = SB_HORZ;
1448 Maximum = Buff->ScreenBufferSize.X - Console->ConsoleSize.X;
1449 pShowXY = &Buff->ShowX;
1450 }
1451 else
1452 {
1453 fnBar = SB_VERT;
1454 Maximum = Buff->ScreenBufferSize.Y - Console->ConsoleSize.Y;
1455 pShowXY = &Buff->ShowY;
1456 }
1457
1458 /* set scrollbar sizes */
1459 sInfo.cbSize = sizeof(SCROLLINFO);
1460 sInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE | SIF_TRACKPOS;
1461
1462 if (!GetScrollInfo(GuiData->hWindow, fnBar, &sInfo)) goto Quit;
1463
1464 old_pos = sInfo.nPos;
1465
1466 switch (LOWORD(wParam))
1467 {
1468 case SB_LINELEFT:
1469 sInfo.nPos -= 1;
1470 break;
1471
1472 case SB_LINERIGHT:
1473 sInfo.nPos += 1;
1474 break;
1475
1476 case SB_PAGELEFT:
1477 sInfo.nPos -= sInfo.nPage;
1478 break;
1479
1480 case SB_PAGERIGHT:
1481 sInfo.nPos += sInfo.nPage;
1482 break;
1483
1484 case SB_THUMBTRACK:
1485 sInfo.nPos = sInfo.nTrackPos;
1486 ConioPause(Console, PAUSED_FROM_SCROLLBAR);
1487 break;
1488
1489 case SB_THUMBPOSITION:
1490 ConioUnpause(Console, PAUSED_FROM_SCROLLBAR);
1491 break;
1492
1493 case SB_TOP:
1494 sInfo.nPos = sInfo.nMin;
1495 break;
1496
1497 case SB_BOTTOM:
1498 sInfo.nPos = sInfo.nMax;
1499 break;
1500
1501 default:
1502 break;
1503 }
1504
1505 sInfo.nPos = max(sInfo.nPos, 0);
1506 sInfo.nPos = min(sInfo.nPos, Maximum);
1507
1508 if (old_pos != sInfo.nPos)
1509 {
1510 USHORT OldX = Buff->ShowX;
1511 USHORT OldY = Buff->ShowY;
1512 *pShowXY = sInfo.nPos;
1513
1514 ScrollWindowEx(GuiData->hWindow,
1515 (OldX - Buff->ShowX) * GuiData->CharWidth,
1516 (OldY - Buff->ShowY) * GuiData->CharHeight,
1517 NULL,
1518 NULL,
1519 NULL,
1520 NULL,
1521 SW_INVALIDATE);
1522
1523 sInfo.fMask = SIF_POS;
1524 SetScrollInfo(GuiData->hWindow, fnBar, &sInfo, TRUE);
1525
1526 UpdateWindow(GuiData->hWindow);
1527 }
1528
1529 Quit:
1530 LeaveCriticalSection(&Console->Lock);
1531 return 0;
1532 }
1533
1534 static LRESULT CALLBACK
1535 GuiConsoleWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1536 {
1537 LRESULT Result = 0;
1538 PGUI_CONSOLE_DATA GuiData = NULL;
1539 PCONSOLE Console = NULL;
1540
1541 /*
1542 * - If it's the first time we create a window for the terminal,
1543 * just initialize it and return.
1544 *
1545 * - If we are destroying the window, just do it and return.
1546 */
1547 if (msg == WM_NCCREATE)
1548 {
1549 return (LRESULT)GuiConsoleHandleNcCreate(hWnd, (LPCREATESTRUCTW)lParam);
1550 }
1551 else if (msg == WM_NCDESTROY)
1552 {
1553 return GuiConsoleHandleNcDestroy(hWnd);
1554 }
1555
1556 /*
1557 * Now the terminal window is initialized.
1558 * Get the terminal data via the window's data.
1559 * If there is no data, just go away.
1560 */
1561 GuiData = GuiGetGuiData(hWnd);
1562 if (GuiData == NULL) return 0;
1563
1564 /*
1565 * Each helper function which needs the console
1566 * has to validate and lock it.
1567 */
1568
1569 /* We have a console, start message dispatching */
1570 switch (msg)
1571 {
1572 case WM_CLOSE:
1573 GuiConsoleHandleClose(GuiData);
1574 break;
1575
1576 case WM_PAINT:
1577 GuiConsoleHandlePaint(GuiData);
1578 break;
1579
1580 case WM_KEYDOWN:
1581 case WM_KEYUP:
1582 case WM_SYSKEYDOWN:
1583 case WM_SYSKEYUP:
1584 case WM_CHAR:
1585 {
1586 GuiConsoleHandleKey(GuiData, msg, wParam, lParam);
1587 break;
1588 }
1589
1590 case WM_TIMER:
1591 GuiConsoleHandleTimer(GuiData);
1592 break;
1593
1594 case WM_LBUTTONDOWN:
1595 case WM_MBUTTONDOWN:
1596 case WM_RBUTTONDOWN:
1597 case WM_LBUTTONUP:
1598 case WM_MBUTTONUP:
1599 case WM_RBUTTONUP:
1600 case WM_LBUTTONDBLCLK:
1601 case WM_MBUTTONDBLCLK:
1602 case WM_RBUTTONDBLCLK:
1603 case WM_MOUSEMOVE:
1604 case WM_MOUSEWHEEL:
1605 case WM_MOUSEHWHEEL:
1606 {
1607 Result = GuiConsoleHandleMouse(GuiData, msg, wParam, lParam);
1608 break;
1609 }
1610
1611 case WM_NCRBUTTONDOWN:
1612 {
1613 DPRINT("WM_NCRBUTTONDOWN\n");
1614 /*
1615 * HACK: !! Because, when we deal with WM_RBUTTON* and we do not
1616 * call after that DefWindowProc, on ReactOS, right-clicks on the
1617 * (non-client) application title-bar does not display the system
1618 * menu and does not trigger a WM_NCRBUTTONUP message too.
1619 * See: http://git.reactos.org/?p=reactos.git;a=blob;f=reactos/win32ss/user/user32/windows/defwnd.c;hb=HEAD#l1103
1620 * and line 1135 too.
1621 */
1622 if (DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam) == HTCAPTION)
1623 return DefWindowProcW(hWnd, WM_CONTEXTMENU, wParam, lParam);
1624 else
1625 goto Default;
1626 }
1627 // case WM_NCRBUTTONUP:
1628 // DPRINT1("WM_NCRBUTTONUP\n");
1629 // goto Default;
1630
1631 case WM_CONTEXTMENU:
1632 {
1633 if (DefWindowProcW(hWnd /*GuiData->hWindow*/, WM_NCHITTEST, 0, lParam) == HTCLIENT)
1634 {
1635 HMENU hMenu = CreatePopupMenu();
1636 if (hMenu != NULL)
1637 {
1638 GuiConsoleAppendMenuItems(hMenu, GuiConsoleEditMenuItems);
1639 TrackPopupMenuEx(hMenu,
1640 TPM_RIGHTBUTTON,
1641 GET_X_LPARAM(lParam),
1642 GET_Y_LPARAM(lParam),
1643 hWnd,
1644 NULL);
1645 DestroyMenu(hMenu);
1646 }
1647 break;
1648 }
1649 else
1650 {
1651 goto Default;
1652 }
1653 }
1654
1655 case WM_COMMAND:
1656 case WM_SYSCOMMAND:
1657 {
1658 Result = GuiConsoleHandleSysMenuCommand(GuiData, wParam, lParam);
1659 break;
1660 }
1661
1662 case WM_HSCROLL:
1663 case WM_VSCROLL:
1664 {
1665 Result = GuiConsoleHandleScroll(GuiData, msg, wParam);
1666 break;
1667 }
1668
1669 case WM_SETFOCUS:
1670 case WM_KILLFOCUS:
1671 {
1672 Console = GuiData->Console; // Not NULL because checked in GuiGetGuiData.
1673 if (ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE))
1674 {
1675 INPUT_RECORD er;
1676 er.EventType = FOCUS_EVENT;
1677 er.Event.FocusEvent.bSetFocus = (msg == WM_SETFOCUS);
1678 ConioProcessInputEvent(Console, &er);
1679
1680 LeaveCriticalSection(&Console->Lock);
1681 }
1682 break;
1683 }
1684
1685 case WM_GETMINMAXINFO:
1686 GuiConsoleGetMinMaxInfo(GuiData, (PMINMAXINFO)lParam);
1687 break;
1688
1689 case WM_SIZE:
1690 GuiConsoleResize(GuiData, wParam, lParam);
1691 break;
1692
1693 case PM_APPLY_CONSOLE_INFO:
1694 {
1695 Console = GuiData->Console; // Not NULL because checked in GuiGetGuiData.
1696 if (ConSrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE))
1697 {
1698 GuiApplyUserSettings(GuiData, (HANDLE)wParam, (BOOL)lParam);
1699 LeaveCriticalSection(&Console->Lock);
1700 }
1701 break;
1702 }
1703
1704 case PM_CONSOLE_BEEP:
1705 DPRINT1("Beep !!\n");
1706 Beep(800, 200);
1707 break;
1708
1709 // case PM_CONSOLE_SET_TITLE:
1710 // SetWindowText(GuiData->hWindow, GuiData->Console->Title.Buffer);
1711 // break;
1712
1713 default: Default:
1714 Result = DefWindowProcW(hWnd, msg, wParam, lParam);
1715 break;
1716 }
1717
1718 return Result;
1719 }
1720
1721
1722
1723 /******************************************************************************
1724 * GUI Terminal Initialization *
1725 ******************************************************************************/
1726
1727 static LRESULT CALLBACK
1728 GuiConsoleNotifyWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1729 {
1730 HWND NewWindow;
1731 LONG WindowCount;
1732 MSG Msg;
1733
1734 switch (msg)
1735 {
1736 case WM_CREATE:
1737 {
1738 SetWindowLongW(hWnd, GWL_USERDATA, 0);
1739 return 0;
1740 }
1741
1742 case PM_CREATE_CONSOLE:
1743 {
1744 PGUI_CONSOLE_DATA GuiData = (PGUI_CONSOLE_DATA)lParam;
1745 PCONSOLE Console = GuiData->Console;
1746
1747 NewWindow = CreateWindowExW(WS_EX_CLIENTEDGE,
1748 GUI_CONSOLE_WINDOW_CLASS,
1749 Console->Title.Buffer,
1750 WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
1751 CW_USEDEFAULT,
1752 CW_USEDEFAULT,
1753 CW_USEDEFAULT,
1754 CW_USEDEFAULT,
1755 NULL,
1756 NULL,
1757 ConSrvDllInstance,
1758 (PVOID)GuiData);
1759 if (NULL != NewWindow)
1760 {
1761 WindowCount = GetWindowLongW(hWnd, GWL_USERDATA);
1762 WindowCount++;
1763 SetWindowLongW(hWnd, GWL_USERDATA, WindowCount);
1764
1765 DPRINT("Set icons via PM_CREATE_CONSOLE\n");
1766 if (GuiData->hIcon == NULL)
1767 {
1768 DPRINT("Not really /o\\...\n");
1769 GuiData->hIcon = ghDefaultIcon;
1770 GuiData->hIconSm = ghDefaultIconSm;
1771 }
1772 else if (GuiData->hIcon != ghDefaultIcon)
1773 {
1774 DPRINT("Yes \\o/\n");
1775 SendMessageW(GuiData->hWindow, WM_SETICON, ICON_BIG, (LPARAM)GuiData->hIcon);
1776 SendMessageW(GuiData->hWindow, WM_SETICON, ICON_SMALL, (LPARAM)GuiData->hIconSm);
1777 }
1778
1779 /* Move and resize the window to the user's values */
1780 /* CAN WE DEADLOCK ?? */
1781 GuiConsoleMoveWindow(GuiData);
1782 GuiData->WindowSizeLock = TRUE;
1783 GuiConsoleResizeWindow(GuiData);
1784 GuiData->WindowSizeLock = FALSE;
1785
1786 // ShowWindow(NewWindow, (int)wParam);
1787 ShowWindowAsync(NewWindow, (int)wParam);
1788 DPRINT("Window showed\n");
1789 }
1790
1791 return (LRESULT)NewWindow;
1792 }
1793
1794 case PM_DESTROY_CONSOLE:
1795 {
1796 PGUI_CONSOLE_DATA GuiData = (PGUI_CONSOLE_DATA)lParam;
1797
1798 /*
1799 * Window creation is done using a PostMessage(), so it's possible
1800 * that the window that we want to destroy doesn't exist yet.
1801 * So first empty the message queue.
1802 */
1803 /*
1804 while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE))
1805 {
1806 TranslateMessage(&Msg);
1807 DispatchMessageW(&Msg);
1808 }*/
1809 while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE)) ;
1810
1811 if (GuiData->hWindow != NULL) /* && DestroyWindow(GuiData->hWindow) */
1812 {
1813 DestroyWindow(GuiData->hWindow);
1814
1815 WindowCount = GetWindowLongW(hWnd, GWL_USERDATA);
1816 WindowCount--;
1817 SetWindowLongW(hWnd, GWL_USERDATA, WindowCount);
1818 if (0 == WindowCount)
1819 {
1820 NotifyWnd = NULL;
1821 DestroyWindow(hWnd);
1822 DPRINT("CONSRV: Going to quit the Gui Thread!!\n");
1823 PostQuitMessage(0);
1824 }
1825 }
1826
1827 return 0;
1828 }
1829
1830 default:
1831 return DefWindowProcW(hWnd, msg, wParam, lParam);
1832 }
1833 }
1834
1835 static DWORD WINAPI
1836 GuiConsoleGuiThread(PVOID Data)
1837 {
1838 MSG msg;
1839 PHANDLE GraphicsStartupEvent = (PHANDLE)Data;
1840
1841 /*
1842 * This thread dispatches all the console notifications to the notify window.
1843 * It is common for all the console windows.
1844 */
1845
1846 PrivateCsrssManualGuiCheck(+1);
1847
1848 NotifyWnd = CreateWindowW(L"ConSrvCreateNotify",
1849 L"",
1850 WS_OVERLAPPEDWINDOW,
1851 CW_USEDEFAULT,
1852 CW_USEDEFAULT,
1853 CW_USEDEFAULT,
1854 CW_USEDEFAULT,
1855 NULL,
1856 NULL,
1857 ConSrvDllInstance,
1858 NULL);
1859 if (NULL == NotifyWnd)
1860 {
1861 PrivateCsrssManualGuiCheck(-1);
1862 SetEvent(*GraphicsStartupEvent);
1863 return 1;
1864 }
1865
1866 SetEvent(*GraphicsStartupEvent);
1867
1868 while (GetMessageW(&msg, NULL, 0, 0))
1869 {
1870 TranslateMessage(&msg);
1871 DispatchMessageW(&msg);
1872 }
1873
1874 DPRINT("CONSRV: Quit the Gui Thread!!\n");
1875 PrivateCsrssManualGuiCheck(-1);
1876
1877 return 1;
1878 }
1879
1880 static BOOL
1881 GuiInit(VOID)
1882 {
1883 WNDCLASSEXW wc;
1884 ATOM ConsoleClassAtom;
1885
1886 /* Exit if we were already initialized */
1887 // if (ConsInitialized) return TRUE;
1888
1889 /*
1890 * Initialize and register the different window classes, if needed.
1891 */
1892 if (!ConsInitialized)
1893 {
1894 /* Initialize the notification window class */
1895 wc.cbSize = sizeof(WNDCLASSEXW);
1896 wc.lpszClassName = L"ConSrvCreateNotify";
1897 wc.lpfnWndProc = GuiConsoleNotifyWndProc;
1898 wc.style = 0;
1899 wc.hInstance = ConSrvDllInstance;
1900 wc.hIcon = NULL;
1901 wc.hIconSm = NULL;
1902 wc.hCursor = NULL;
1903 wc.hbrBackground = NULL;
1904 wc.lpszMenuName = NULL;
1905 wc.cbClsExtra = 0;
1906 wc.cbWndExtra = 0;
1907 if (RegisterClassExW(&wc) == 0)
1908 {
1909 DPRINT1("Failed to register GUI notify wndproc\n");
1910 return FALSE;
1911 }
1912
1913 /* Initialize the console window class */
1914 ghDefaultIcon = LoadImageW(ConSrvDllInstance,
1915 MAKEINTRESOURCEW(IDI_TERMINAL),
1916 IMAGE_ICON,
1917 GetSystemMetrics(SM_CXICON),
1918 GetSystemMetrics(SM_CYICON),
1919 LR_SHARED);
1920 ghDefaultIconSm = LoadImageW(ConSrvDllInstance,
1921 MAKEINTRESOURCEW(IDI_TERMINAL),
1922 IMAGE_ICON,
1923 GetSystemMetrics(SM_CXSMICON),
1924 GetSystemMetrics(SM_CYSMICON),
1925 LR_SHARED);
1926 ghDefaultCursor = LoadCursorW(NULL, IDC_ARROW);
1927 wc.cbSize = sizeof(WNDCLASSEXW);
1928 wc.lpszClassName = GUI_CONSOLE_WINDOW_CLASS;
1929 wc.lpfnWndProc = GuiConsoleWndProc;
1930 wc.style = CS_DBLCLKS /* | CS_HREDRAW | CS_VREDRAW */;
1931 wc.hInstance = ConSrvDllInstance;
1932 wc.hIcon = ghDefaultIcon;
1933 wc.hIconSm = ghDefaultIconSm;
1934 wc.hCursor = ghDefaultCursor;
1935 wc.hbrBackground = CreateSolidBrush(RGB(0,0,0)); // FIXME: Use defaults from registry.
1936 wc.lpszMenuName = NULL;
1937 wc.cbClsExtra = 0;
1938 wc.cbWndExtra = GWLP_CONSOLEWND_ALLOC;
1939
1940 ConsoleClassAtom = RegisterClassExW(&wc);
1941 if (ConsoleClassAtom == 0)
1942 {
1943 DPRINT1("Failed to register GUI console wndproc\n");
1944 return FALSE;
1945 }
1946 else
1947 {
1948 NtUserConsoleControl(GuiConsoleWndClassAtom, &ConsoleClassAtom, sizeof(ATOM));
1949 }
1950
1951 ConsInitialized = TRUE;
1952 }
1953
1954 /*
1955 * Set-up the notification window
1956 */
1957 if (NULL == NotifyWnd)
1958 {
1959 HANDLE ThreadHandle;
1960 HANDLE GraphicsStartupEvent;
1961
1962 GraphicsStartupEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
1963 if (NULL == GraphicsStartupEvent) return FALSE;
1964
1965 ThreadHandle = CreateThread(NULL,
1966 0,
1967 GuiConsoleGuiThread,
1968 (PVOID)&GraphicsStartupEvent,
1969 0,
1970 NULL);
1971 if (NULL == ThreadHandle)
1972 {
1973 CloseHandle(GraphicsStartupEvent);
1974 DPRINT1("CONSRV: Failed to create graphics console thread. Expect problems\n");
1975 return FALSE;
1976 }
1977 SetThreadPriority(ThreadHandle, THREAD_PRIORITY_HIGHEST);
1978 CloseHandle(ThreadHandle);
1979
1980 WaitForSingleObject(GraphicsStartupEvent, INFINITE);
1981 CloseHandle(GraphicsStartupEvent);
1982
1983 if (NULL == NotifyWnd)
1984 {
1985 DPRINT1("CONSRV: Failed to create notification window.\n");
1986 return FALSE;
1987 }
1988 }
1989
1990 // ConsInitialized = TRUE;
1991
1992 return TRUE;
1993 }
1994
1995
1996
1997 /******************************************************************************
1998 * GUI Console Driver *
1999 ******************************************************************************/
2000
2001 static VOID WINAPI
2002 GuiCleanupConsole(PCONSOLE Console)
2003 {
2004 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
2005
2006 SendMessageW(NotifyWnd, PM_DESTROY_CONSOLE, 0, (LPARAM)GuiData);
2007
2008 DPRINT("Destroying icons !! - GuiData->hIcon = 0x%p ; ghDefaultIcon = 0x%p ; GuiData->hIconSm = 0x%p ; ghDefaultIconSm = 0x%p\n",
2009 GuiData->hIcon, ghDefaultIcon, GuiData->hIconSm, ghDefaultIconSm);
2010 if (GuiData->hIcon != NULL && GuiData->hIcon != ghDefaultIcon)
2011 {
2012 DPRINT("Destroy hIcon\n");
2013 DestroyIcon(GuiData->hIcon);
2014 }
2015 if (GuiData->hIconSm != NULL && GuiData->hIconSm != ghDefaultIconSm)
2016 {
2017 DPRINT("Destroy hIconSm\n");
2018 DestroyIcon(GuiData->hIconSm);
2019 }
2020
2021 Console->TermIFace.Data = NULL;
2022 DeleteCriticalSection(&GuiData->Lock);
2023 RtlFreeHeap(ConSrvHeap, 0, GuiData);
2024
2025 DPRINT("Quit GuiCleanupConsole\n");
2026 }
2027
2028 static VOID WINAPI
2029 GuiWriteStream(PCONSOLE Console, SMALL_RECT* Region, SHORT CursorStartX, SHORT CursorStartY,
2030 UINT ScrolledLines, CHAR *Buffer, UINT Length)
2031 {
2032 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
2033 PCONSOLE_SCREEN_BUFFER Buff = Console->ActiveBuffer;
2034 SHORT CursorEndX, CursorEndY;
2035 RECT ScrollRect;
2036
2037 if (NULL == GuiData || NULL == GuiData->hWindow)
2038 {
2039 return;
2040 }
2041
2042 if (0 != ScrolledLines)
2043 {
2044 ScrollRect.left = 0;
2045 ScrollRect.top = 0;
2046 ScrollRect.right = Console->ConsoleSize.X * GuiData->CharWidth;
2047 ScrollRect.bottom = Region->Top * GuiData->CharHeight;
2048
2049 ScrollWindowEx(GuiData->hWindow,
2050 0,
2051 -(int)(ScrolledLines * GuiData->CharHeight),
2052 &ScrollRect,
2053 NULL,
2054 NULL,
2055 NULL,
2056 SW_INVALIDATE);
2057 }
2058
2059 GuiDrawRegion(Console, Region);
2060
2061 if (CursorStartX < Region->Left || Region->Right < CursorStartX
2062 || CursorStartY < Region->Top || Region->Bottom < CursorStartY)
2063 {
2064 GuiInvalidateCell(Console, CursorStartX, CursorStartY);
2065 }
2066
2067 CursorEndX = Buff->CursorPosition.X;
2068 CursorEndY = Buff->CursorPosition.Y;
2069 if ((CursorEndX < Region->Left || Region->Right < CursorEndX
2070 || CursorEndY < Region->Top || Region->Bottom < CursorEndY)
2071 && (CursorEndX != CursorStartX || CursorEndY != CursorStartY))
2072 {
2073 GuiInvalidateCell(Console, CursorEndX, CursorEndY);
2074 }
2075
2076 // Set up the update timer (very short interval) - this is a "hack" for getting the OS to
2077 // repaint the window without having it just freeze up and stay on the screen permanently.
2078 Buff->CursorBlinkOn = TRUE;
2079 SetTimer(GuiData->hWindow, CONGUI_UPDATE_TIMER, CONGUI_UPDATE_TIME, NULL);
2080 }
2081
2082 static VOID WINAPI
2083 GuiDrawRegion(PCONSOLE Console, SMALL_RECT* Region)
2084 {
2085 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
2086 RECT RegionRect;
2087
2088 SmallRectToRect(GuiData, &RegionRect, Region);
2089 InvalidateRect(GuiData->hWindow, &RegionRect, FALSE);
2090 }
2091
2092 static BOOL WINAPI
2093 GuiSetCursorInfo(PCONSOLE Console, PCONSOLE_SCREEN_BUFFER Buff)
2094 {
2095 if (Console->ActiveBuffer == Buff)
2096 {
2097 GuiInvalidateCell(Console, Buff->CursorPosition.X, Buff->CursorPosition.Y);
2098 }
2099
2100 return TRUE;
2101 }
2102
2103 static BOOL WINAPI
2104 GuiSetScreenInfo(PCONSOLE Console, PCONSOLE_SCREEN_BUFFER Buff, SHORT OldCursorX, SHORT OldCursorY)
2105 {
2106 if (Console->ActiveBuffer == Buff)
2107 {
2108 /* Redraw char at old position (removes cursor) */
2109 GuiInvalidateCell(Console, OldCursorX, OldCursorY);
2110 /* Redraw char at new position (shows cursor) */
2111 GuiInvalidateCell(Console, Buff->CursorPosition.X, Buff->CursorPosition.Y);
2112 }
2113
2114 return TRUE;
2115 }
2116
2117 static BOOL WINAPI
2118 GuiUpdateScreenInfo(PCONSOLE Console, PCONSOLE_SCREEN_BUFFER Buff)
2119 {
2120 return TRUE;
2121 }
2122
2123 static BOOL WINAPI
2124 GuiIsBufferResizeSupported(PCONSOLE Console)
2125 {
2126 return TRUE;
2127 }
2128
2129 static VOID WINAPI
2130 GuiResizeTerminal(PCONSOLE Console)
2131 {
2132 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
2133
2134 /* Resize the window to the user's values */
2135 GuiData->WindowSizeLock = TRUE;
2136 GuiConsoleResizeWindow(GuiData);
2137 GuiData->WindowSizeLock = FALSE;
2138 }
2139
2140 static BOOL WINAPI
2141 GuiProcessKeyCallback(PCONSOLE Console, MSG* msg, BYTE KeyStateMenu, DWORD ShiftState, UINT VirtualKeyCode, BOOL Down)
2142 {
2143 if ((ShiftState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED) || KeyStateMenu & 0x80) &&
2144 (VirtualKeyCode == VK_ESCAPE || VirtualKeyCode == VK_TAB || VirtualKeyCode == VK_SPACE))
2145 {
2146 DefWindowProcW(msg->hwnd, msg->message, msg->wParam, msg->lParam);
2147 return TRUE;
2148 }
2149
2150 return FALSE;
2151 }
2152
2153 static VOID WINAPI
2154 GuiRefreshInternalInfo(PCONSOLE Console)
2155 {
2156 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
2157
2158 /* Update the console leader information held by the window */
2159 SetConsoleWndConsoleLeaderCID(GuiData);
2160 }
2161
2162 static VOID WINAPI
2163 GuiChangeTitle(PCONSOLE Console)
2164 {
2165 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
2166 // PostMessageW(GuiData->hWindow, PM_CONSOLE_SET_TITLE, 0, 0);
2167 SetWindowText(GuiData->hWindow, Console->Title.Buffer);
2168 }
2169
2170 static BOOL WINAPI
2171 GuiChangeIcon(PCONSOLE Console, HICON hWindowIcon)
2172 {
2173 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
2174 HICON hIcon, hIconSm;
2175
2176 if (hWindowIcon == NULL)
2177 {
2178 hIcon = ghDefaultIcon;
2179 hIconSm = ghDefaultIconSm;
2180 }
2181 else
2182 {
2183 hIcon = CopyIcon(hWindowIcon);
2184 hIconSm = CopyIcon(hWindowIcon);
2185 }
2186
2187 if (hIcon == NULL)
2188 {
2189 return FALSE;
2190 }
2191
2192 if (hIcon != GuiData->hIcon)
2193 {
2194 if (GuiData->hIcon != NULL && GuiData->hIcon != ghDefaultIcon)
2195 {
2196 DestroyIcon(GuiData->hIcon);
2197 }
2198 if (GuiData->hIconSm != NULL && GuiData->hIconSm != ghDefaultIconSm)
2199 {
2200 DestroyIcon(GuiData->hIconSm);
2201 }
2202
2203 GuiData->hIcon = hIcon;
2204 GuiData->hIconSm = hIconSm;
2205
2206 DPRINT("Set icons in GuiChangeIcon\n");
2207 PostMessageW(GuiData->hWindow, WM_SETICON, ICON_BIG, (LPARAM)GuiData->hIcon);
2208 PostMessageW(GuiData->hWindow, WM_SETICON, ICON_SMALL, (LPARAM)GuiData->hIconSm);
2209 }
2210
2211 return TRUE;
2212 }
2213
2214 static HWND WINAPI
2215 GuiGetConsoleWindowHandle(PCONSOLE Console)
2216 {
2217 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
2218 return GuiData->hWindow;
2219 }
2220
2221 static VOID WINAPI
2222 GuiGetLargestConsoleWindowSize(PCONSOLE Console, PCOORD pSize)
2223 {
2224 PGUI_CONSOLE_DATA GuiData = Console->TermIFace.Data;
2225 HWND hDesktop;
2226 RECT desktop;
2227 LONG width, height;
2228
2229 if (!pSize) return;
2230
2231 /*
2232 * This is one solution. Surely better solutions exist :
2233 * http://stackoverflow.com/questions/4631292/how-detect-current-screen-resolution
2234 * http://www.clearevo.com/blog/programming/2011/08/30/windows_c_c++_-_get_monitor_display_screen_size_in_pixels.html
2235 */
2236 hDesktop = GetDesktopWindow();
2237 if (!hDesktop) return;
2238
2239 GetWindowRect(hDesktop, &desktop);
2240
2241 width = desktop.right;
2242 height = desktop.bottom;
2243
2244 width -= (2 * (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXEDGE)));
2245 height -= (2 * (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYEDGE)) + GetSystemMetrics(SM_CYCAPTION));
2246
2247 if (width < 0) width = 0;
2248 if (height < 0) height = 0;
2249
2250 pSize->X = (SHORT)(width / GuiData->CharWidth );
2251 pSize->Y = (SHORT)(height / GuiData->CharHeight);
2252 }
2253
2254 static FRONTEND_VTBL GuiVtbl =
2255 {
2256 GuiCleanupConsole,
2257 GuiWriteStream,
2258 GuiDrawRegion,
2259 GuiSetCursorInfo,
2260 GuiSetScreenInfo,
2261 GuiUpdateScreenInfo,
2262 GuiIsBufferResizeSupported,
2263 GuiResizeTerminal,
2264 GuiProcessKeyCallback,
2265 GuiRefreshInternalInfo,
2266 GuiChangeTitle,
2267 GuiChangeIcon,
2268 GuiGetConsoleWindowHandle,
2269 GuiGetLargestConsoleWindowSize
2270 };
2271
2272 NTSTATUS FASTCALL
2273 GuiInitConsole(PCONSOLE Console,
2274 /*IN*/ PCONSOLE_START_INFO ConsoleStartInfo,
2275 PCONSOLE_INFO ConsoleInfo,
2276 DWORD ProcessId,
2277 LPCWSTR IconPath,
2278 INT IconIndex)
2279 {
2280 PGUI_CONSOLE_DATA GuiData;
2281 GUI_CONSOLE_INFO TermInfo;
2282 SIZE_T Length = 0;
2283
2284 if (Console == NULL || ConsoleInfo == NULL)
2285 return STATUS_INVALID_PARAMETER;
2286
2287 /* Initialize the GUI terminal emulator */
2288 if (!GuiInit()) return STATUS_UNSUCCESSFUL;
2289
2290 /* Initialize the console */
2291 Console->TermIFace.Vtbl = &GuiVtbl;
2292
2293 GuiData = RtlAllocateHeap(ConSrvHeap, HEAP_ZERO_MEMORY,
2294 sizeof(GUI_CONSOLE_DATA));
2295 if (!GuiData)
2296 {
2297 DPRINT1("CONSRV: Failed to create GUI_CONSOLE_DATA\n");
2298 return STATUS_UNSUCCESSFUL;
2299 }
2300 Console->TermIFace.Data = (PVOID)GuiData;
2301 GuiData->Console = Console;
2302 GuiData->hWindow = NULL;
2303
2304 InitializeCriticalSection(&GuiData->Lock);
2305
2306
2307 /*
2308 * Load the terminal settings
2309 */
2310
2311 /***********************************************
2312 * Adapted from ConSrvInitConsole in console.c *
2313 ***********************************************/
2314
2315 /* 1. Load the default settings */
2316 GuiConsoleGetDefaultSettings(&TermInfo, ProcessId);
2317
2318 /* 2. Load the remaining console settings via the registry. */
2319 if ((ConsoleStartInfo->dwStartupFlags & STARTF_TITLEISLINKNAME) == 0)
2320 {
2321 /* Load the terminal infos from the registry. */
2322 GuiConsoleReadUserSettings(&TermInfo, ConsoleInfo->ConsoleTitle, ProcessId);
2323
2324 /*
2325 * Now, update them with the properties the user might gave to us
2326 * via the STARTUPINFO structure before calling CreateProcess
2327 * (and which was transmitted via the ConsoleStartInfo structure).
2328 * We therefore overwrite the values read in the registry.
2329 */
2330 if (ConsoleStartInfo->dwStartupFlags & STARTF_USESHOWWINDOW)
2331 {
2332 TermInfo.ShowWindow = ConsoleStartInfo->ShowWindow;
2333 }
2334 if (ConsoleStartInfo->dwStartupFlags & STARTF_USEPOSITION)
2335 {
2336 TermInfo.AutoPosition = FALSE;
2337 TermInfo.WindowOrigin = ConsoleStartInfo->ConsoleWindowOrigin;
2338 }
2339 /*
2340 if (ConsoleStartInfo->dwStartupFlags & STARTF_RUNFULLSCREEN)
2341 {
2342 ConsoleInfo.FullScreen = TRUE;
2343 }
2344 */
2345 }
2346
2347
2348 /*
2349 * Set up the GUI data
2350 */
2351
2352 Length = min(wcslen(TermInfo.FaceName) + 1, LF_FACESIZE); // wcsnlen
2353 wcsncpy(GuiData->GuiInfo.FaceName, TermInfo.FaceName, LF_FACESIZE);
2354 GuiData->GuiInfo.FaceName[Length] = L'\0';
2355 GuiData->GuiInfo.FontFamily = TermInfo.FontFamily;
2356 GuiData->GuiInfo.FontSize = TermInfo.FontSize;
2357 GuiData->GuiInfo.FontWeight = TermInfo.FontWeight;
2358 GuiData->GuiInfo.UseRasterFonts = TermInfo.UseRasterFonts;
2359 GuiData->GuiInfo.ShowWindow = TermInfo.ShowWindow;
2360 GuiData->GuiInfo.AutoPosition = TermInfo.AutoPosition;
2361 GuiData->GuiInfo.WindowOrigin = TermInfo.WindowOrigin;
2362
2363 /* Initialize the icon handles to their default values */
2364 GuiData->hIcon = ghDefaultIcon;
2365 GuiData->hIconSm = ghDefaultIconSm;
2366
2367 /* Get the associated icon, if any */
2368 if (IconPath == NULL || *IconPath == L'\0')
2369 {
2370 IconPath = ConsoleStartInfo->AppPath;
2371 IconIndex = 0;
2372 }
2373 DPRINT("IconPath = %S ; IconIndex = %lu\n", (IconPath ? IconPath : L"n/a"), IconIndex);
2374 if (IconPath)
2375 {
2376 HICON hIcon = NULL, hIconSm = NULL;
2377 PrivateExtractIconExW(IconPath,
2378 IconIndex,
2379 &hIcon,
2380 &hIconSm,
2381 1);
2382 DPRINT("hIcon = 0x%p ; hIconSm = 0x%p\n", hIcon, hIconSm);
2383 if (hIcon != NULL)
2384 {
2385 DPRINT("Effectively set the icons\n");
2386 GuiData->hIcon = hIcon;
2387 GuiData->hIconSm = hIconSm;
2388 }
2389 }
2390
2391 /*
2392 * We need to wait until the GUI has been fully initialized
2393 * to retrieve custom settings i.e. WindowSize etc...
2394 * Ideally we could use SendNotifyMessage for this but its not
2395 * yet implemented.
2396 */
2397 GuiData->hGuiInitEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
2398
2399 /* Create the terminal window */
2400 PostMessageW(NotifyWnd, PM_CREATE_CONSOLE, GuiData->GuiInfo.ShowWindow, (LPARAM)GuiData);
2401
2402 /* Wait until initialization has finished */
2403 WaitForSingleObject(GuiData->hGuiInitEvent, INFINITE);
2404 DPRINT("OK we created the console window\n");
2405 CloseHandle(GuiData->hGuiInitEvent);
2406 GuiData->hGuiInitEvent = NULL;
2407
2408 /* Check whether we really succeeded in initializing the terminal window */
2409 if (GuiData->hWindow == NULL)
2410 {
2411 DPRINT("GuiInitConsole - We failed at creating a new terminal window\n");
2412 // ConioCleanupConsole(Console);
2413 GuiCleanupConsole(Console);
2414 return STATUS_UNSUCCESSFUL;
2415 }
2416
2417 return STATUS_SUCCESS;
2418 }
2419
2420 /* EOF */