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 #if 0 125 static VOID 126 LineInputRecallHistory(PCONSRV_CONSOLE Console, 127 PUNICODE_STRING ExeName, 128 INT Offset) 129 { 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 static VOID 146 LineInputRecallHistory(PCONSRV_CONSOLE Console, 147 PUNICODE_STRING ExeName, 148 INT Offset) 149 { 150 UNICODE_STRING Entry; 151 152 if (!HistoryRecallHistory(Console, ExeName, Offset, &Entry)) return; 153 154 LineInputSetPos(Console, 0); 155 LineInputEdit(Console, Console->LineSize, 156 Entry.Length / sizeof(WCHAR), 157 Entry.Buffer); 158 } 159 #endif 160 161 162 // TESTS!! 163 PPOPUP_WINDOW Popup = NULL; 164 165 PPOPUP_WINDOW 166 HistoryDisplayCurrentHistory(PCONSRV_CONSOLE Console, 167 PUNICODE_STRING ExeName); 168 169 VOID 170 LineInputKeyDown(PCONSRV_CONSOLE Console, 171 PUNICODE_STRING ExeName, 172 KEY_EVENT_RECORD *KeyEvent) 173 { 174 UINT Pos = Console->LinePos; 175 UNICODE_STRING Entry; 176 177 /* 178 * First, deal with control keys... 179 */ 180 181 switch (KeyEvent->wVirtualKeyCode) 182 { 183 case VK_ESCAPE: 184 { 185 /* Clear the entire line */ 186 LineInputSetPos(Console, 0); 187 LineInputEdit(Console, Console->LineSize, 0, NULL); 188 189 // TESTS!! 190 if (Popup) 191 { 192 DestroyPopupWindow(Popup); 193 Popup = NULL; 194 } 195 return; 196 } 197 198 case VK_HOME: 199 { 200 /* Move to start of line. With CTRL, erase everything left of cursor. */ 201 LineInputSetPos(Console, 0); 202 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 203 LineInputEdit(Console, Pos, 0, NULL); 204 return; 205 } 206 207 case VK_END: 208 { 209 /* Move to end of line. With CTRL, erase everything right of cursor. */ 210 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 211 LineInputEdit(Console, Console->LineSize - Pos, 0, NULL); 212 else 213 LineInputSetPos(Console, Console->LineSize); 214 return; 215 } 216 217 case VK_LEFT: 218 { 219 /* Move to the left. With CTRL, move to beginning of previous word. */ 220 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 221 { 222 while (Pos > 0 && Console->LineBuffer[Pos - 1] == L' ') Pos--; 223 while (Pos > 0 && Console->LineBuffer[Pos - 1] != L' ') Pos--; 224 } 225 else 226 { 227 Pos -= (Pos > 0); 228 } 229 LineInputSetPos(Console, Pos); 230 return; 231 } 232 233 case VK_RIGHT: 234 case VK_F1: 235 { 236 /* Move to the right. With CTRL, move to beginning of next word. */ 237 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 238 { 239 while (Pos < Console->LineSize && Console->LineBuffer[Pos] != L' ') Pos++; 240 while (Pos < Console->LineSize && Console->LineBuffer[Pos] == L' ') Pos++; 241 LineInputSetPos(Console, Pos); 242 } 243 else 244 { 245 /* Recall one character (but don't overwrite current line) */ 246 HistoryGetCurrentEntry(Console, ExeName, &Entry); 247 if (Pos < Console->LineSize) 248 LineInputSetPos(Console, Pos + 1); 249 else if (Pos * sizeof(WCHAR) < Entry.Length) 250 LineInputEdit(Console, 0, 1, &Entry.Buffer[Pos]); 251 } 252 return; 253 } 254 255 case VK_INSERT: 256 { 257 /* Toggle between insert and overstrike */ 258 Console->LineInsertToggle = !Console->LineInsertToggle; 259 TermSetCursorInfo(Console, Console->ActiveBuffer); 260 return; 261 } 262 263 case VK_DELETE: 264 { 265 /* Remove one character to right of cursor */ 266 if (Pos != Console->LineSize) 267 LineInputEdit(Console, 1, 0, NULL); 268 return; 269 } 270 271 case VK_PRIOR: 272 { 273 /* Recall the first history entry */ 274 LineInputRecallHistory(Console, ExeName, -((WORD)-1)); 275 return; 276 } 277 278 case VK_NEXT: 279 { 280 /* Recall the last history entry */ 281 LineInputRecallHistory(Console, ExeName, +((WORD)-1)); 282 return; 283 } 284 285 case VK_UP: 286 case VK_F5: 287 { 288 /* 289 * Recall the previous history entry. On first time, actually recall 290 * the current (usually last) entry; on subsequent times go back. 291 */ 292 LineInputRecallHistory(Console, ExeName, Console->LineUpPressed ? -1 : 0); 293 Console->LineUpPressed = TRUE; 294 return; 295 } 296 297 case VK_DOWN: 298 { 299 /* Recall the next history entry */ 300 LineInputRecallHistory(Console, ExeName, +1); 301 return; 302 } 303 304 case VK_F3: 305 { 306 /* Recall the remainder of the current history entry */ 307 HistoryGetCurrentEntry(Console, ExeName, &Entry); 308 if (Pos * sizeof(WCHAR) < Entry.Length) 309 { 310 UINT InsertSize = (Entry.Length / sizeof(WCHAR) - Pos); 311 UINT DeleteSize = min(Console->LineSize - Pos, InsertSize); 312 LineInputEdit(Console, DeleteSize, InsertSize, &Entry.Buffer[Pos]); 313 } 314 return; 315 } 316 317 case VK_F6: 318 { 319 /* Insert a ^Z character */ 320 KeyEvent->uChar.UnicodeChar = 26; 321 break; 322 } 323 324 case VK_F7: 325 { 326 if (KeyEvent->dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) 327 HistoryDeleteCurrentBuffer(Console, ExeName); 328 else 329 { 330 if (Popup) DestroyPopupWindow(Popup); 331 Popup = HistoryDisplayCurrentHistory(Console, ExeName); 332 } 333 return; 334 } 335 336 case VK_F8: 337 { 338 UNICODE_STRING EntryFound; 339 340 Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR) 341 Entry.Buffer = Console->LineBuffer; 342 343 if (HistoryFindEntryByPrefix(Console, ExeName, &Entry, &EntryFound)) 344 { 345 LineInputEdit(Console, Console->LineSize - Pos, 346 EntryFound.Length / sizeof(WCHAR) - Pos, 347 &EntryFound.Buffer[Pos]); 348 /* Cursor stays where it was */ 349 LineInputSetPos(Console, Pos); 350 } 351 352 return; 353 } 354 #if 0 355 { 356 PHISTORY_BUFFER Hist; 357 INT HistPos; 358 359 /* Search for history entries starting with input */ 360 Hist = HistoryCurrentBuffer(Console, ExeName); 361 if (!Hist || Hist->NumEntries == 0) return; 362 363 /* 364 * Like Up/F5, on first time start from current (usually last) entry, 365 * but on subsequent times start at previous entry. 366 */ 367 if (Console->LineUpPressed) 368 Hist->Position = (Hist->Position ? Hist->Position : Hist->NumEntries) - 1; 369 Console->LineUpPressed = TRUE; 370 371 Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR) 372 Entry.Buffer = Console->LineBuffer; 373 374 /* 375 * Keep going backwards, even wrapping around to the end, 376 * until we get back to starting point. 377 */ 378 HistPos = Hist->Position; 379 do 380 { 381 if (RtlPrefixUnicodeString(&Entry, &Hist->Entries[HistPos], FALSE)) 382 { 383 Hist->Position = HistPos; 384 LineInputEdit(Console, Console->LineSize - Pos, 385 Hist->Entries[HistPos].Length / sizeof(WCHAR) - Pos, 386 &Hist->Entries[HistPos].Buffer[Pos]); 387 /* Cursor stays where it was */ 388 LineInputSetPos(Console, Pos); 389 return; 390 } 391 if (--HistPos < 0) HistPos += Hist->NumEntries; 392 } while (HistPos != Hist->Position); 393 394 return; 395 } 396 #endif 397 398 return; 399 } 400 401 402 /* 403 * OK, we deal with normal keys, we can continue... 404 */ 405 406 if (KeyEvent->uChar.UnicodeChar == L'\b' && (GetConsoleInputBufferMode(Console) & ENABLE_PROCESSED_INPUT)) 407 { 408 /* 409 * Backspace handling - if processed input enabled then we handle it 410 * here, otherwise we treat it like a normal character. 411 */ 412 if (Pos > 0) 413 { 414 LineInputSetPos(Console, Pos - 1); 415 LineInputEdit(Console, 1, 0, NULL); 416 } 417 } 418 else if (KeyEvent->uChar.UnicodeChar == L'\r') 419 { 420 /* 421 * Only add a history entry if console echo is enabled. This ensures 422 * that anything sent to the console when echo is disabled (e.g. 423 * binary data, or secrets like passwords...) does not remain stored 424 * in memory. 425 */ 426 if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT) 427 { 428 Entry.Length = Entry.MaximumLength = Console->LineSize * sizeof(WCHAR); 429 Entry.Buffer = Console->LineBuffer; 430 HistoryAddEntry(Console, ExeName, &Entry); 431 } 432 433 /* TODO: Expand aliases */ 434 DPRINT1("TODO: Expand aliases\n"); 435 436 LineInputSetPos(Console, Console->LineSize); 437 Console->LineBuffer[Console->LineSize++] = L'\r'; 438 if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT) 439 { 440 if (GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER) 441 { 442 TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\r", 1, TRUE); 443 } 444 } 445 446 /* 447 * Add \n if processed input. There should usually be room for it, 448 * but an exception to the rule exists: the buffer could have been 449 * pre-filled with LineMaxSize - 1 characters. 450 */ 451 if ((GetConsoleInputBufferMode(Console) & ENABLE_PROCESSED_INPUT) && 452 Console->LineSize < Console->LineMaxSize) 453 { 454 Console->LineBuffer[Console->LineSize++] = L'\n'; 455 if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT) 456 { 457 if (GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER) 458 { 459 TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\n", 1, TRUE); 460 } 461 } 462 } 463 Console->LineComplete = TRUE; 464 Console->LinePos = 0; 465 } 466 else if (KeyEvent->uChar.UnicodeChar != L'\0') 467 { 468 if (KeyEvent->uChar.UnicodeChar < 0x20 && 469 Console->LineWakeupMask & (1 << KeyEvent->uChar.UnicodeChar)) 470 { 471 /* Control key client wants to handle itself (e.g. for tab completion) */ 472 Console->LineBuffer[Console->LineSize++] = L' '; 473 Console->LineBuffer[Console->LinePos] = KeyEvent->uChar.UnicodeChar; 474 Console->LineComplete = TRUE; 475 Console->LinePos = 0; 476 } 477 else 478 { 479 /* Normal character */ 480 BOOL Overstrike = !Console->LineInsertToggle && (Console->LinePos != Console->LineSize); 481 DPRINT("Overstrike = %s\n", Overstrike ? "true" : "false"); 482 LineInputEdit(Console, (Overstrike ? 1 : 0), 1, &KeyEvent->uChar.UnicodeChar); 483 } 484 } 485 } 486 487 488 /* PUBLIC SERVER APIS *********************************************************/ 489 490 /* EOF */ 491