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