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