1 /* 2 * LICENSE: GPL - See COPYING in the top level directory 3 * PROJECT: ReactOS Console Server DLL 4 * FILE: win32ss/user/winsrv/consrv/lineinput.c 5 * PURPOSE: Console line input functions 6 * PROGRAMMERS: Jeffrey Morlan 7 */ 8 9 /* INCLUDES *******************************************************************/ 10 11 #include "consrv.h" 12 #include "popup.h" 13 14 #define NDEBUG 15 #include <debug.h> 16 17 18 BOOLEAN 19 ConvertInputAnsiToUnicode(PCONSRV_CONSOLE Console, 20 PVOID Source, 21 USHORT SourceLength, 22 // BOOLEAN IsUnicode, 23 PWCHAR* Target, 24 PUSHORT TargetLength); 25 BOOLEAN 26 ConvertInputUnicodeToAnsi(PCONSRV_CONSOLE Console, 27 PVOID Source, 28 USHORT SourceLength, 29 // BOOLEAN IsAnsi, 30 PCHAR/* * */ Target, 31 /*P*/USHORT TargetLength); 32 33 34 VOID 35 HistoryAddEntry(PCONSRV_CONSOLE Console, 36 PUNICODE_STRING ExeName, 37 PUNICODE_STRING Entry); 38 BOOL 39 HistoryRecallHistory(PCONSRV_CONSOLE Console, 40 PUNICODE_STRING ExeName, 41 INT Offset, 42 PUNICODE_STRING Entry); 43 VOID 44 HistoryGetCurrentEntry(PCONSRV_CONSOLE Console, 45 PUNICODE_STRING ExeName, 46 PUNICODE_STRING Entry); 47 VOID 48 HistoryDeleteCurrentBuffer(PCONSRV_CONSOLE Console, 49 PUNICODE_STRING ExeName); 50 BOOL 51 HistoryFindEntryByPrefix(PCONSRV_CONSOLE Console, 52 PUNICODE_STRING ExeName, 53 PUNICODE_STRING Prefix, 54 PUNICODE_STRING Entry); 55 56 57 /* PRIVATE FUNCTIONS **********************************************************/ 58 59 static VOID 60 LineInputSetPos(PCONSRV_CONSOLE Console, 61 UINT Pos) 62 { 63 if (Pos != Console->LinePos && (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT)) 64 { 65 PCONSOLE_SCREEN_BUFFER Buffer = Console->ActiveBuffer; 66 SHORT OldCursorX = Buffer->CursorPosition.X; 67 SHORT OldCursorY = Buffer->CursorPosition.Y; 68 INT XY = OldCursorY * Buffer->ScreenBufferSize.X + OldCursorX; 69 70 XY += (Pos - Console->LinePos); 71 if (XY < 0) 72 XY = 0; 73 else if (XY >= Buffer->ScreenBufferSize.Y * Buffer->ScreenBufferSize.X) 74 XY = Buffer->ScreenBufferSize.Y * Buffer->ScreenBufferSize.X - 1; 75 76 Buffer->CursorPosition.X = XY % Buffer->ScreenBufferSize.X; 77 Buffer->CursorPosition.Y = XY / Buffer->ScreenBufferSize.X; 78 TermSetScreenInfo(Console, Buffer, OldCursorX, OldCursorY); 79 } 80 81 Console->LinePos = Pos; 82 } 83 84 static VOID 85 LineInputEdit(PCONSRV_CONSOLE Console, 86 UINT NumToDelete, 87 UINT NumToInsert, 88 PWCHAR Insertion) 89 { 90 PTEXTMODE_SCREEN_BUFFER ActiveBuffer; 91 UINT Pos = Console->LinePos; 92 UINT NewSize = Console->LineSize - NumToDelete + NumToInsert; 93 UINT i; 94 95 if (GetType(Console->ActiveBuffer) != TEXTMODE_BUFFER) return; 96 ActiveBuffer = (PTEXTMODE_SCREEN_BUFFER)Console->ActiveBuffer; 97 98 /* Make sure there is always enough room for ending \r\n */ 99 if (NewSize + 2 > Console->LineMaxSize) 100 return; 101 102 memmove(&Console->LineBuffer[Pos + NumToInsert], 103 &Console->LineBuffer[Pos + NumToDelete], 104 (Console->LineSize - (Pos + NumToDelete)) * sizeof(WCHAR)); 105 memcpy(&Console->LineBuffer[Pos], Insertion, NumToInsert * sizeof(WCHAR)); 106 107 if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT) 108 { 109 for (i = Pos; i < NewSize; i++) 110 { 111 TermWriteStream(Console, ActiveBuffer, &Console->LineBuffer[i], 1, TRUE); 112 } 113 for (; i < Console->LineSize; i++) 114 { 115 TermWriteStream(Console, ActiveBuffer, L" ", 1, TRUE); 116 } 117 Console->LinePos = i; 118 } 119 120 Console->LineSize = NewSize; 121 LineInputSetPos(Console, Pos + NumToInsert); 122 } 123 124 static VOID 125 LineInputRecallHistory(PCONSRV_CONSOLE Console, 126 PUNICODE_STRING ExeName, 127 INT Offset) 128 { 129 #if 0 130 PHISTORY_BUFFER Hist = HistoryCurrentBuffer(Console, ExeName); 131 UINT Position = 0; 132 133 if (!Hist || Hist->NumEntries == 0) return; 134 135 Position = Hist->Position + Offset; 136 Position = min(max(Position, 0), Hist->NumEntries - 1); 137 Hist->Position = Position; 138 139 LineInputSetPos(Console, 0); 140 LineInputEdit(Console, Console->LineSize, 141 Hist->Entries[Hist->Position].Length / sizeof(WCHAR), 142 Hist->Entries[Hist->Position].Buffer); 143 144 #else 145 146 UNICODE_STRING Entry; 147 148 if (!HistoryRecallHistory(Console, ExeName, Offset, &Entry)) return; 149 150 LineInputSetPos(Console, 0); 151 LineInputEdit(Console, Console->LineSize, 152 Entry.Length / sizeof(WCHAR), 153 Entry.Buffer); 154 #endif 155 } 156 157 158 // TESTS!! 159 PPOPUP_WINDOW Popup = NULL; 160 161 PPOPUP_WINDOW 162 HistoryDisplayCurrentHistory(PCONSRV_CONSOLE Console, 163 PUNICODE_STRING ExeName); 164 165 VOID 166 LineInputKeyDown(PCONSRV_CONSOLE Console, 167 PUNICODE_STRING ExeName, 168 KEY_EVENT_RECORD *KeyEvent) 169 { 170 UINT Pos = Console->LinePos; 171 UNICODE_STRING Entry; 172 173 /* 174 * First, deal with control keys... 175 */ 176 177 switch (KeyEvent->wVirtualKeyCode) 178 { 179 case VK_ESCAPE: 180 { 181 /* Clear the entire line */ 182 LineInputSetPos(Console, 0); 183 LineInputEdit(Console, Console->LineSize, 0, NULL); 184 185 // TESTS!! 186 if (Popup) 187 { 188 DestroyPopupWindow(Popup); 189 Popup = NULL; 190 } 191 return; 192 } 193 194 case VK_HOME: 195 { 196 /* Move to start of line. With CTRL, erase everything left of cursor. */ 197 LineInputSetPos(Console, 0); 198 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 199 LineInputEdit(Console, Pos, 0, NULL); 200 return; 201 } 202 203 case VK_END: 204 { 205 /* Move to end of line. With CTRL, erase everything right of cursor. */ 206 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 207 LineInputEdit(Console, Console->LineSize - Pos, 0, NULL); 208 else 209 LineInputSetPos(Console, Console->LineSize); 210 return; 211 } 212 213 case VK_LEFT: 214 { 215 /* Move to the left. With CTRL, move to beginning of previous word. */ 216 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 217 { 218 while (Pos > 0 && Console->LineBuffer[Pos - 1] == L' ') Pos--; 219 while (Pos > 0 && Console->LineBuffer[Pos - 1] != L' ') Pos--; 220 } 221 else 222 { 223 Pos -= (Pos > 0); 224 } 225 LineInputSetPos(Console, Pos); 226 return; 227 } 228 229 case VK_RIGHT: 230 case VK_F1: 231 { 232 /* Move to the right. With CTRL, move to beginning of next word. */ 233 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 234 { 235 while (Pos < Console->LineSize && Console->LineBuffer[Pos] != L' ') Pos++; 236 while (Pos < Console->LineSize && Console->LineBuffer[Pos] == L' ') Pos++; 237 LineInputSetPos(Console, Pos); 238 } 239 else 240 { 241 /* Recall one character (but don't overwrite current line) */ 242 HistoryGetCurrentEntry(Console, ExeName, &Entry); 243 if (Pos < Console->LineSize) 244 LineInputSetPos(Console, Pos + 1); 245 else if (Pos * sizeof(WCHAR) < Entry.Length) 246 LineInputEdit(Console, 0, 1, &Entry.Buffer[Pos]); 247 } 248 return; 249 } 250 251 case VK_INSERT: 252 { 253 /* Toggle between insert and overstrike */ 254 Console->LineInsertToggle = !Console->LineInsertToggle; 255 TermSetCursorInfo(Console, Console->ActiveBuffer); 256 return; 257 } 258 259 case VK_DELETE: 260 { 261 /* Remove one character to right of cursor */ 262 if (Pos != Console->LineSize) 263 LineInputEdit(Console, 1, 0, NULL); 264 return; 265 } 266 267 case VK_PRIOR: 268 { 269 /* Recall the first history entry */ 270 LineInputRecallHistory(Console, ExeName, -((WORD)-1)); 271 return; 272 } 273 274 case VK_NEXT: 275 { 276 /* Recall the last history entry */ 277 LineInputRecallHistory(Console, ExeName, +((WORD)-1)); 278 return; 279 } 280 281 case VK_UP: 282 case VK_F5: 283 { 284 /* 285 * Recall the previous history entry. On first time, actually recall 286 * the current (usually last) entry; on subsequent times go back. 287 */ 288 LineInputRecallHistory(Console, ExeName, Console->LineUpPressed ? -1 : 0); 289 Console->LineUpPressed = TRUE; 290 return; 291 } 292 293 case VK_DOWN: 294 { 295 /* Recall the next history entry */ 296 LineInputRecallHistory(Console, ExeName, +1); 297 return; 298 } 299 300 case VK_F3: 301 { 302 /* Recall the remainder of the current history entry */ 303 HistoryGetCurrentEntry(Console, ExeName, &Entry); 304 if (Pos * sizeof(WCHAR) < Entry.Length) 305 { 306 UINT InsertSize = (Entry.Length / sizeof(WCHAR) - Pos); 307 UINT DeleteSize = min(Console->LineSize - Pos, InsertSize); 308 LineInputEdit(Console, DeleteSize, InsertSize, &Entry.Buffer[Pos]); 309 } 310 return; 311 } 312 313 case VK_F6: 314 { 315 /* Insert a ^Z character */ 316 KeyEvent->uChar.UnicodeChar = 26; 317 break; 318 } 319 320 case VK_F7: 321 { 322 if (KeyEvent->dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) 323 HistoryDeleteCurrentBuffer(Console, ExeName); 324 else 325 { 326 if (Popup) DestroyPopupWindow(Popup); 327 Popup = HistoryDisplayCurrentHistory(Console, ExeName); 328 } 329 return; 330 } 331 332 case VK_F8: 333 { 334 UNICODE_STRING EntryFound; 335 336 Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR) 337 Entry.Buffer = Console->LineBuffer; 338 339 if (HistoryFindEntryByPrefix(Console, ExeName, &Entry, &EntryFound)) 340 { 341 LineInputEdit(Console, Console->LineSize - Pos, 342 EntryFound.Length / sizeof(WCHAR) - Pos, 343 &EntryFound.Buffer[Pos]); 344 /* Cursor stays where it was */ 345 LineInputSetPos(Console, Pos); 346 } 347 348 return; 349 } 350 #if 0 351 { 352 PHISTORY_BUFFER Hist; 353 INT HistPos; 354 355 /* Search for history entries starting with input */ 356 Hist = HistoryCurrentBuffer(Console, ExeName); 357 if (!Hist || Hist->NumEntries == 0) return; 358 359 /* 360 * Like Up/F5, on first time start from current (usually last) entry, 361 * but on subsequent times start at previous entry. 362 */ 363 if (Console->LineUpPressed) 364 Hist->Position = (Hist->Position ? Hist->Position : Hist->NumEntries) - 1; 365 Console->LineUpPressed = TRUE; 366 367 Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR) 368 Entry.Buffer = Console->LineBuffer; 369 370 /* 371 * Keep going backwards, even wrapping around to the end, 372 * until we get back to starting point. 373 */ 374 HistPos = Hist->Position; 375 do 376 { 377 if (RtlPrefixUnicodeString(&Entry, &Hist->Entries[HistPos], FALSE)) 378 { 379 Hist->Position = HistPos; 380 LineInputEdit(Console, Console->LineSize - Pos, 381 Hist->Entries[HistPos].Length / sizeof(WCHAR) - Pos, 382 &Hist->Entries[HistPos].Buffer[Pos]); 383 /* Cursor stays where it was */ 384 LineInputSetPos(Console, Pos); 385 return; 386 } 387 if (--HistPos < 0) HistPos += Hist->NumEntries; 388 } while (HistPos != Hist->Position); 389 390 return; 391 } 392 #endif 393 394 return; 395 } 396 397 398 /* 399 * OK, we deal with normal keys, we can continue... 400 */ 401 402 if (KeyEvent->uChar.UnicodeChar == L'\b' && (GetConsoleInputBufferMode(Console) & ENABLE_PROCESSED_INPUT)) 403 { 404 /* 405 * Backspace handling - if processed input enabled then we handle it 406 * here, otherwise we treat it like a normal character. 407 */ 408 if (Pos > 0) 409 { 410 LineInputSetPos(Console, Pos - 1); 411 LineInputEdit(Console, 1, 0, NULL); 412 } 413 } 414 else if (KeyEvent->uChar.UnicodeChar == L'\r') 415 { 416 /* 417 * Only add a history entry if console echo is enabled. This ensures 418 * that anything sent to the console when echo is disabled (e.g. 419 * binary data, or secrets like passwords...) does not remain stored 420 * in memory. 421 */ 422 if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT) 423 { 424 Entry.Length = Entry.MaximumLength = Console->LineSize * sizeof(WCHAR); 425 Entry.Buffer = Console->LineBuffer; 426 HistoryAddEntry(Console, ExeName, &Entry); 427 } 428 429 /* TODO: Expand aliases */ 430 DPRINT1("TODO: Expand aliases\n"); 431 432 LineInputSetPos(Console, Console->LineSize); 433 Console->LineBuffer[Console->LineSize++] = L'\r'; 434 if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT) 435 { 436 if (GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER) 437 { 438 TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\r", 1, TRUE); 439 } 440 } 441 442 /* 443 * Add \n if processed input. There should usually be room for it, 444 * but an exception to the rule exists: the buffer could have been 445 * pre-filled with LineMaxSize - 1 characters. 446 */ 447 if ((GetConsoleInputBufferMode(Console) & ENABLE_PROCESSED_INPUT) && 448 Console->LineSize < Console->LineMaxSize) 449 { 450 Console->LineBuffer[Console->LineSize++] = L'\n'; 451 if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT) 452 { 453 if (GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER) 454 { 455 TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\n", 1, TRUE); 456 } 457 } 458 } 459 Console->LineComplete = TRUE; 460 Console->LinePos = 0; 461 } 462 else if (KeyEvent->uChar.UnicodeChar != L'\0') 463 { 464 if (KeyEvent->uChar.UnicodeChar < 0x20 && 465 Console->LineWakeupMask & (1 << KeyEvent->uChar.UnicodeChar)) 466 { 467 /* Control key client wants to handle itself (e.g. for tab completion) */ 468 Console->LineBuffer[Console->LineSize++] = L' '; 469 Console->LineBuffer[Console->LinePos] = KeyEvent->uChar.UnicodeChar; 470 Console->LineComplete = TRUE; 471 Console->LinePos = 0; 472 } 473 else 474 { 475 /* Normal character */ 476 BOOL Overstrike = !Console->LineInsertToggle && (Console->LinePos != Console->LineSize); 477 DPRINT("Overstrike = %s\n", Overstrike ? "true" : "false"); 478 LineInputEdit(Console, (Overstrike ? 1 : 0), 1, &KeyEvent->uChar.UnicodeChar); 479 } 480 } 481 } 482 483 484 /* PUBLIC SERVER APIS *********************************************************/ 485 486 /* EOF */ 487