2 * LICENSE: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Console Server DLL
4 * FILE: win32ss/user/consrv/tuiconsole.c
5 * PURPOSE: Console line input functions
6 * PROGRAMMERS: Jeffrey Morlan
9 /* INCLUDES ******************************************************************/
17 typedef struct tagHISTORY_BUFFER
23 PUNICODE_STRING Entries
;
24 UNICODE_STRING ExeName
;
25 } HISTORY_BUFFER
, *PHISTORY_BUFFER
;
27 /* FUNCTIONS *****************************************************************/
29 static PHISTORY_BUFFER
30 HistoryCurrentBuffer(PCSRSS_CONSOLE Console
)
32 /* TODO: use actual EXE name sent from process that called ReadConsole */
33 UNICODE_STRING ExeName
= { 14, 14, L
"cmd.exe" };
34 PLIST_ENTRY Entry
= Console
->HistoryBuffers
.Flink
;
37 for (; Entry
!= &Console
->HistoryBuffers
; Entry
= Entry
->Flink
)
39 Hist
= CONTAINING_RECORD(Entry
, HISTORY_BUFFER
, ListEntry
);
40 if (RtlEqualUnicodeString(&ExeName
, &Hist
->ExeName
, FALSE
))
44 /* Couldn't find the buffer, create a new one */
45 Hist
= HeapAlloc(ConSrvHeap
, 0, sizeof(HISTORY_BUFFER
) + ExeName
.Length
);
48 Hist
->MaxEntries
= Console
->HistoryBufferSize
;
50 Hist
->Entries
= HeapAlloc(ConSrvHeap
, 0, Hist
->MaxEntries
* sizeof(UNICODE_STRING
));
53 HeapFree(ConSrvHeap
, 0, Hist
);
56 Hist
->ExeName
.Length
= Hist
->ExeName
.MaximumLength
= ExeName
.Length
;
57 Hist
->ExeName
.Buffer
= (PWCHAR
)(Hist
+ 1);
58 memcpy(Hist
->ExeName
.Buffer
, ExeName
.Buffer
, ExeName
.Length
);
59 InsertHeadList(&Console
->HistoryBuffers
, &Hist
->ListEntry
);
64 HistoryAddEntry(PCSRSS_CONSOLE Console
)
66 UNICODE_STRING NewEntry
;
70 NewEntry
.Length
= NewEntry
.MaximumLength
= Console
->LineSize
* sizeof(WCHAR
);
71 NewEntry
.Buffer
= Console
->LineBuffer
;
73 if (!(Hist
= HistoryCurrentBuffer(Console
)))
76 /* Don't add blank or duplicate entries */
77 if (NewEntry
.Length
== 0 || Hist
->MaxEntries
== 0 ||
78 (Hist
->NumEntries
> 0 &&
79 RtlEqualUnicodeString(&Hist
->Entries
[Hist
->NumEntries
- 1], &NewEntry
, FALSE
)))
84 if (Console
->HistoryNoDup
)
86 /* Check if this line has been entered before */
87 for (i
= Hist
->NumEntries
- 1; i
>= 0; i
--)
89 if (RtlEqualUnicodeString(&Hist
->Entries
[i
], &NewEntry
, FALSE
))
91 /* Just rotate the list to bring this entry to the end */
92 NewEntry
= Hist
->Entries
[i
];
93 memmove(&Hist
->Entries
[i
], &Hist
->Entries
[i
+ 1],
94 (Hist
->NumEntries
- (i
+ 1)) * sizeof(UNICODE_STRING
));
95 Hist
->Entries
[Hist
->NumEntries
- 1] = NewEntry
;
96 Hist
->Position
= Hist
->NumEntries
- 1;
102 if (Hist
->NumEntries
== Hist
->MaxEntries
)
104 /* List is full, remove oldest entry */
105 RtlFreeUnicodeString(&Hist
->Entries
[0]);
106 memmove(&Hist
->Entries
[0], &Hist
->Entries
[1],
107 --Hist
->NumEntries
* sizeof(UNICODE_STRING
));
110 if (NT_SUCCESS(RtlDuplicateUnicodeString(0, &NewEntry
, &Hist
->Entries
[Hist
->NumEntries
])))
112 Hist
->Position
= Hist
->NumEntries
- 1;
116 HistoryGetCurrentEntry(PCSRSS_CONSOLE Console
, PUNICODE_STRING Entry
)
118 PHISTORY_BUFFER Hist
;
119 if (!(Hist
= HistoryCurrentBuffer(Console
)) || Hist
->NumEntries
== 0)
122 *Entry
= Hist
->Entries
[Hist
->Position
];
125 static PHISTORY_BUFFER
126 HistoryFindBuffer(PCSRSS_CONSOLE Console
, PUNICODE_STRING ExeName
)
128 PLIST_ENTRY Entry
= Console
->HistoryBuffers
.Flink
;
129 while (Entry
!= &Console
->HistoryBuffers
)
131 /* For the history APIs, the caller is allowed to give only part of the name */
132 PHISTORY_BUFFER Hist
= CONTAINING_RECORD(Entry
, HISTORY_BUFFER
, ListEntry
);
133 if (RtlPrefixUnicodeString(ExeName
, &Hist
->ExeName
, TRUE
))
135 Entry
= Entry
->Flink
;
141 HistoryDeleteBuffer(PHISTORY_BUFFER Hist
)
145 while (Hist
->NumEntries
!= 0)
146 RtlFreeUnicodeString(&Hist
->Entries
[--Hist
->NumEntries
]);
147 HeapFree(ConSrvHeap
, 0, Hist
->Entries
);
148 RemoveEntryList(&Hist
->ListEntry
);
149 HeapFree(ConSrvHeap
, 0, Hist
);
152 CSR_API(SrvGetConsoleCommandHistoryLength
)
154 PCSRSS_GET_COMMAND_HISTORY_LENGTH GetCommandHistoryLength
= &((PCONSOLE_API_MESSAGE
)ApiMessage
)->Data
.GetCommandHistoryLength
;
155 PCSR_PROCESS Process
= CsrGetClientThread()->Process
;
156 PCSRSS_CONSOLE Console
;
158 PHISTORY_BUFFER Hist
;
162 if (!Win32CsrValidateBuffer(Process
,
163 GetCommandHistoryLength
->ExeName
.Buffer
,
164 GetCommandHistoryLength
->ExeName
.Length
, 1))
166 return STATUS_ACCESS_VIOLATION
;
169 Status
= ConioConsoleFromProcessData(ConsoleGetPerProcessData(Process
), &Console
);
170 if (NT_SUCCESS(Status
))
172 Hist
= HistoryFindBuffer(Console
, &GetCommandHistoryLength
->ExeName
);
175 for (i
= 0; i
< Hist
->NumEntries
; i
++)
176 Length
+= Hist
->Entries
[i
].Length
+ sizeof(WCHAR
);
178 GetCommandHistoryLength
->Length
= Length
;
179 ConioUnlockConsole(Console
);
184 CSR_API(SrvGetConsoleCommandHistory
)
186 PCSRSS_GET_COMMAND_HISTORY GetCommandHistory
= &((PCONSOLE_API_MESSAGE
)ApiMessage
)->Data
.GetCommandHistory
;
187 PCSR_PROCESS Process
= CsrGetClientThread()->Process
;
188 PCSRSS_CONSOLE Console
;
190 PHISTORY_BUFFER Hist
;
191 PBYTE Buffer
= (PBYTE
)GetCommandHistory
->History
;
192 ULONG BufferSize
= GetCommandHistory
->Length
;
195 if (!Win32CsrValidateBuffer(Process
, Buffer
, BufferSize
, 1) ||
196 !Win32CsrValidateBuffer(Process
,
197 GetCommandHistory
->ExeName
.Buffer
,
198 GetCommandHistory
->ExeName
.Length
, 1))
200 return STATUS_ACCESS_VIOLATION
;
203 Status
= ConioConsoleFromProcessData(ConsoleGetPerProcessData(Process
), &Console
);
204 if (NT_SUCCESS(Status
))
206 Hist
= HistoryFindBuffer(Console
, &GetCommandHistory
->ExeName
);
209 for (i
= 0; i
< Hist
->NumEntries
; i
++)
211 if (BufferSize
< (Hist
->Entries
[i
].Length
+ sizeof(WCHAR
)))
213 Status
= STATUS_BUFFER_OVERFLOW
;
216 memcpy(Buffer
, Hist
->Entries
[i
].Buffer
, Hist
->Entries
[i
].Length
);
217 Buffer
+= Hist
->Entries
[i
].Length
;
218 *(PWCHAR
)Buffer
= L
'\0';
219 Buffer
+= sizeof(WCHAR
);
222 GetCommandHistory
->Length
= Buffer
- (PBYTE
)GetCommandHistory
->History
;
223 ConioUnlockConsole(Console
);
228 CSR_API(SrvExpungeConsoleCommandHistory
)
230 PCSRSS_EXPUNGE_COMMAND_HISTORY ExpungeCommandHistory
= &((PCONSOLE_API_MESSAGE
)ApiMessage
)->Data
.ExpungeCommandHistory
;
231 PCSR_PROCESS Process
= CsrGetClientThread()->Process
;
232 PCSRSS_CONSOLE Console
;
233 PHISTORY_BUFFER Hist
;
236 if (!Win32CsrValidateBuffer(Process
,
237 ExpungeCommandHistory
->ExeName
.Buffer
,
238 ExpungeCommandHistory
->ExeName
.Length
, 1))
240 return STATUS_ACCESS_VIOLATION
;
243 Status
= ConioConsoleFromProcessData(ConsoleGetPerProcessData(Process
), &Console
);
244 if (NT_SUCCESS(Status
))
246 Hist
= HistoryFindBuffer(Console
, &ExpungeCommandHistory
->ExeName
);
247 HistoryDeleteBuffer(Hist
);
248 ConioUnlockConsole(Console
);
253 CSR_API(SrvSetConsoleNumberOfCommands
)
255 PCSRSS_SET_HISTORY_NUMBER_COMMANDS SetHistoryNumberCommands
= &((PCONSOLE_API_MESSAGE
)ApiMessage
)->Data
.SetHistoryNumberCommands
;
256 PCSR_PROCESS Process
= CsrGetClientThread()->Process
;
257 PCSRSS_CONSOLE Console
;
258 PHISTORY_BUFFER Hist
;
260 UINT MaxEntries
= SetHistoryNumberCommands
->NumCommands
;
261 PUNICODE_STRING OldEntryList
, NewEntryList
;
263 if (!Win32CsrValidateBuffer(Process
,
264 SetHistoryNumberCommands
->ExeName
.Buffer
,
265 SetHistoryNumberCommands
->ExeName
.Length
, 1))
267 return STATUS_ACCESS_VIOLATION
;
270 Status
= ConioConsoleFromProcessData(ConsoleGetPerProcessData(Process
), &Console
);
271 if (NT_SUCCESS(Status
))
273 Hist
= HistoryFindBuffer(Console
, &SetHistoryNumberCommands
->ExeName
);
276 OldEntryList
= Hist
->Entries
;
277 NewEntryList
= HeapAlloc(ConSrvHeap
, 0,
278 MaxEntries
* sizeof(UNICODE_STRING
));
281 Status
= STATUS_NO_MEMORY
;
285 /* If necessary, shrink by removing oldest entries */
286 for (; Hist
->NumEntries
> MaxEntries
; Hist
->NumEntries
--)
288 RtlFreeUnicodeString(Hist
->Entries
++);
289 Hist
->Position
+= (Hist
->Position
== 0);
292 Hist
->MaxEntries
= MaxEntries
;
293 Hist
->Entries
= memcpy(NewEntryList
, Hist
->Entries
,
294 Hist
->NumEntries
* sizeof(UNICODE_STRING
));
295 HeapFree(ConSrvHeap
, 0, OldEntryList
);
298 ConioUnlockConsole(Console
);
303 CSR_API(SrvGetConsoleHistory
)
305 PCSRSS_HISTORY_INFO HistoryInfoRequest
= &((PCONSOLE_API_MESSAGE
)ApiMessage
)->Data
.HistoryInfoRequest
;
306 PCSRSS_CONSOLE Console
;
307 NTSTATUS Status
= ConioConsoleFromProcessData(ConsoleGetPerProcessData(CsrGetClientThread()->Process
), &Console
);
308 if (NT_SUCCESS(Status
))
310 HistoryInfoRequest
->HistoryBufferSize
= Console
->HistoryBufferSize
;
311 HistoryInfoRequest
->NumberOfHistoryBuffers
= Console
->NumberOfHistoryBuffers
;
312 HistoryInfoRequest
->dwFlags
= Console
->HistoryNoDup
;
313 ConioUnlockConsole(Console
);
318 CSR_API(SrvSetConsoleHistory
)
320 PCSRSS_HISTORY_INFO HistoryInfoRequest
= &((PCONSOLE_API_MESSAGE
)ApiMessage
)->Data
.HistoryInfoRequest
;
321 PCSRSS_CONSOLE Console
;
322 NTSTATUS Status
= ConioConsoleFromProcessData(ConsoleGetPerProcessData(CsrGetClientThread()->Process
), &Console
);
323 if (NT_SUCCESS(Status
))
325 Console
->HistoryBufferSize
= HistoryInfoRequest
->HistoryBufferSize
;
326 Console
->NumberOfHistoryBuffers
= HistoryInfoRequest
->NumberOfHistoryBuffers
;
327 Console
->HistoryNoDup
= HistoryInfoRequest
->dwFlags
& HISTORY_NO_DUP_FLAG
;
328 ConioUnlockConsole(Console
);
334 LineInputSetPos(PCSRSS_CONSOLE Console
, UINT Pos
)
336 if (Pos
!= Console
->LinePos
&& Console
->Mode
& ENABLE_ECHO_INPUT
)
338 PCSRSS_SCREEN_BUFFER Buffer
= Console
->ActiveBuffer
;
339 UINT OldCursorX
= Buffer
->CurrentX
;
340 UINT OldCursorY
= Buffer
->CurrentY
;
341 INT XY
= OldCursorY
* Buffer
->MaxX
+ OldCursorX
;
343 XY
+= (Pos
- Console
->LinePos
);
346 else if (XY
>= Buffer
->MaxY
* Buffer
->MaxX
)
347 XY
= Buffer
->MaxY
* Buffer
->MaxX
- 1;
349 Buffer
->CurrentX
= XY
% Buffer
->MaxX
;
350 Buffer
->CurrentY
= XY
/ Buffer
->MaxX
;
351 ConioSetScreenInfo(Console
, Buffer
, OldCursorX
, OldCursorY
);
354 Console
->LinePos
= Pos
;
358 LineInputEdit(PCSRSS_CONSOLE Console
, UINT NumToDelete
, UINT NumToInsert
, WCHAR
*Insertion
)
360 UINT Pos
= Console
->LinePos
;
361 UINT NewSize
= Console
->LineSize
- NumToDelete
+ NumToInsert
;
364 /* Make sure there's always enough room for ending \r\n */
365 if (NewSize
+ 2 > Console
->LineMaxSize
)
368 memmove(&Console
->LineBuffer
[Pos
+ NumToInsert
],
369 &Console
->LineBuffer
[Pos
+ NumToDelete
],
370 (Console
->LineSize
- (Pos
+ NumToDelete
)) * sizeof(WCHAR
));
371 memcpy(&Console
->LineBuffer
[Pos
], Insertion
, NumToInsert
* sizeof(WCHAR
));
373 if (Console
->Mode
& ENABLE_ECHO_INPUT
)
375 for (i
= Pos
; i
< NewSize
; i
++)
378 WideCharToMultiByte(Console
->OutputCodePage
, 0,
379 &Console
->LineBuffer
[i
], 1,
380 &AsciiChar
, 1, NULL
, NULL
);
381 ConioWriteConsole(Console
, Console
->ActiveBuffer
, &AsciiChar
, 1, TRUE
);
383 for (; i
< Console
->LineSize
; i
++)
385 ConioWriteConsole(Console
, Console
->ActiveBuffer
, " ", 1, TRUE
);
387 Console
->LinePos
= i
;
390 Console
->LineSize
= NewSize
;
391 LineInputSetPos(Console
, Pos
+ NumToInsert
);
395 LineInputRecallHistory(PCSRSS_CONSOLE Console
, INT Offset
)
397 PHISTORY_BUFFER Hist
;
399 if (!(Hist
= HistoryCurrentBuffer(Console
)) || Hist
->NumEntries
== 0)
402 Offset
+= Hist
->Position
;
403 Offset
= max(Offset
, 0);
404 Offset
= min(Offset
, Hist
->NumEntries
- 1);
405 Hist
->Position
= Offset
;
407 LineInputSetPos(Console
, 0);
408 LineInputEdit(Console
, Console
->LineSize
,
409 Hist
->Entries
[Offset
].Length
/ sizeof(WCHAR
),
410 Hist
->Entries
[Offset
].Buffer
);
414 LineInputKeyDown(PCSRSS_CONSOLE Console
, KEY_EVENT_RECORD
*KeyEvent
)
416 UINT Pos
= Console
->LinePos
;
417 PHISTORY_BUFFER Hist
;
418 UNICODE_STRING Entry
;
421 switch (KeyEvent
->wVirtualKeyCode
)
424 /* Clear entire line */
425 LineInputSetPos(Console
, 0);
426 LineInputEdit(Console
, Console
->LineSize
, 0, NULL
);
429 /* Move to start of line. With ctrl, erase everything left of cursor */
430 LineInputSetPos(Console
, 0);
431 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
432 LineInputEdit(Console
, Pos
, 0, NULL
);
435 /* Move to end of line. With ctrl, erase everything right of cursor */
436 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
437 LineInputEdit(Console
, Console
->LineSize
- Pos
, 0, NULL
);
439 LineInputSetPos(Console
, Console
->LineSize
);
442 /* Move left. With ctrl, move to beginning of previous word */
443 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
445 while (Pos
> 0 && Console
->LineBuffer
[Pos
- 1] == L
' ') Pos
--;
446 while (Pos
> 0 && Console
->LineBuffer
[Pos
- 1] != L
' ') Pos
--;
452 LineInputSetPos(Console
, Pos
);
456 /* Move right. With ctrl, move to beginning of next word */
457 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
459 while (Pos
< Console
->LineSize
&& Console
->LineBuffer
[Pos
] != L
' ') Pos
++;
460 while (Pos
< Console
->LineSize
&& Console
->LineBuffer
[Pos
] == L
' ') Pos
++;
461 LineInputSetPos(Console
, Pos
);
466 /* Recall one character (but don't overwrite current line) */
467 HistoryGetCurrentEntry(Console
, &Entry
);
468 if (Pos
< Console
->LineSize
)
469 LineInputSetPos(Console
, Pos
+ 1);
470 else if (Pos
* sizeof(WCHAR
) < Entry
.Length
)
471 LineInputEdit(Console
, 0, 1, &Entry
.Buffer
[Pos
]);
475 /* Toggle between insert and overstrike */
476 Console
->LineInsertToggle
= !Console
->LineInsertToggle
;
477 ConioSetCursorInfo(Console
, Console
->ActiveBuffer
);
480 /* Remove character to right of cursor */
481 if (Pos
!= Console
->LineSize
)
482 LineInputEdit(Console
, 1, 0, NULL
);
485 /* Recall first history entry */
486 LineInputRecallHistory(Console
, -((WORD
)-1));
489 /* Recall last history entry */
490 LineInputRecallHistory(Console
, +((WORD
)-1));
494 /* Recall previous history entry. On first time, actually recall the
495 * current (usually last) entry; on subsequent times go back. */
496 LineInputRecallHistory(Console
, Console
->LineUpPressed
? -1 : 0);
497 Console
->LineUpPressed
= TRUE
;
500 /* Recall next history entry */
501 LineInputRecallHistory(Console
, +1);
504 /* Recall remainder of current history entry */
505 HistoryGetCurrentEntry(Console
, &Entry
);
506 if (Pos
* sizeof(WCHAR
) < Entry
.Length
)
508 UINT InsertSize
= (Entry
.Length
/ sizeof(WCHAR
) - Pos
);
509 UINT DeleteSize
= min(Console
->LineSize
- Pos
, InsertSize
);
510 LineInputEdit(Console
, DeleteSize
, InsertSize
, &Entry
.Buffer
[Pos
]);
514 /* Insert a ^Z character */
515 KeyEvent
->uChar
.UnicodeChar
= 26;
518 if (KeyEvent
->dwControlKeyState
& (LEFT_ALT_PRESSED
| RIGHT_ALT_PRESSED
))
519 HistoryDeleteBuffer(HistoryCurrentBuffer(Console
));
522 /* Search for history entries starting with input. */
523 if (!(Hist
= HistoryCurrentBuffer(Console
)) || Hist
->NumEntries
== 0)
526 /* Like Up/F5, on first time start from current (usually last) entry,
527 * but on subsequent times start at previous entry. */
528 if (Console
->LineUpPressed
)
529 Hist
->Position
= (Hist
->Position
? Hist
->Position
: Hist
->NumEntries
) - 1;
530 Console
->LineUpPressed
= TRUE
;
532 Entry
.Length
= Console
->LinePos
* sizeof(WCHAR
);
533 Entry
.Buffer
= Console
->LineBuffer
;
535 /* Keep going backwards, even wrapping around to the end,
536 * until we get back to starting point */
537 HistPos
= Hist
->Position
;
540 if (RtlPrefixUnicodeString(&Entry
, &Hist
->Entries
[HistPos
], FALSE
))
542 Hist
->Position
= HistPos
;
543 LineInputEdit(Console
, Console
->LineSize
- Pos
,
544 Hist
->Entries
[HistPos
].Length
/ sizeof(WCHAR
) - Pos
,
545 &Hist
->Entries
[HistPos
].Buffer
[Pos
]);
546 /* Cursor stays where it was */
547 LineInputSetPos(Console
, Pos
);
550 if (--HistPos
< 0) HistPos
+= Hist
->NumEntries
;
551 } while (HistPos
!= Hist
->Position
);
555 if (KeyEvent
->uChar
.UnicodeChar
== L
'\b' && Console
->Mode
& ENABLE_PROCESSED_INPUT
)
557 /* backspace handling - if processed input enabled then we handle it here
558 * otherwise we treat it like a normal char. */
561 LineInputSetPos(Console
, Pos
- 1);
562 LineInputEdit(Console
, 1, 0, NULL
);
565 else if (KeyEvent
->uChar
.UnicodeChar
== L
'\r')
567 HistoryAddEntry(Console
);
569 /* TODO: Expand aliases */
571 LineInputSetPos(Console
, Console
->LineSize
);
572 Console
->LineBuffer
[Console
->LineSize
++] = L
'\r';
573 if (Console
->Mode
& ENABLE_ECHO_INPUT
)
574 ConioWriteConsole(Console
, Console
->ActiveBuffer
, "\r", 1, TRUE
);
576 /* Add \n if processed input. There should usually be room for it,
577 * but an exception to the rule exists: the buffer could have been
578 * pre-filled with LineMaxSize - 1 characters. */
579 if (Console
->Mode
& ENABLE_PROCESSED_INPUT
&&
580 Console
->LineSize
< Console
->LineMaxSize
)
582 Console
->LineBuffer
[Console
->LineSize
++] = L
'\n';
583 if (Console
->Mode
& ENABLE_ECHO_INPUT
)
584 ConioWriteConsole(Console
, Console
->ActiveBuffer
, "\n", 1, TRUE
);
586 Console
->LineComplete
= TRUE
;
587 Console
->LinePos
= 0;
589 else if (KeyEvent
->uChar
.UnicodeChar
!= L
'\0')
591 if (KeyEvent
->uChar
.UnicodeChar
< 0x20 &&
592 Console
->LineWakeupMask
& (1 << KeyEvent
->uChar
.UnicodeChar
))
594 /* Control key client wants to handle itself (e.g. for tab completion) */
595 Console
->LineBuffer
[Console
->LineSize
++] = L
' ';
596 Console
->LineBuffer
[Console
->LinePos
] = KeyEvent
->uChar
.UnicodeChar
;
597 Console
->LineComplete
= TRUE
;
598 Console
->LinePos
= 0;
602 /* Normal character */
603 BOOL Overstrike
= Console
->LineInsertToggle
&& Console
->LinePos
!= Console
->LineSize
;
604 LineInputEdit(Console
, Overstrike
, 1, &KeyEvent
->uChar
.UnicodeChar
);