Reactos
at master 767 lines 22 kB view raw
1/* 2 * ReactOS kernel 3 * Copyright (C) 2002 ReactOS Team 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19/* 20 * COPYRIGHT: See COPYING in the top level directory 21 * PROJECT: ReactOS text-mode setup 22 * FILE: base/setup/usetup/console.c 23 * PURPOSE: Console support functions 24 * PROGRAMMER: 25 */ 26 27/* INCLUDES ******************************************************************/ 28 29#include <usetup.h> 30/* Blue Driver Header */ 31#include <blue/ntddblue.h> 32#include "keytrans.h" 33 34#define NDEBUG 35#include <debug.h> 36 37/* DATA **********************************************************************/ 38 39static BOOLEAN InputQueueEmpty; 40static BOOLEAN WaitForInput; 41static KEYBOARD_INPUT_DATA InputDataQueue; // Only one element! 42static IO_STATUS_BLOCK InputIosb; 43static UINT LastLoadedCodepage; 44 45/* FUNCTIONS *****************************************************************/ 46 47typedef struct _CONSOLE_CABINET_CONTEXT 48{ 49 CABINET_CONTEXT CabinetContext; 50 PVOID Data; 51 ULONG Size; 52} CONSOLE_CABINET_CONTEXT, *PCONSOLE_CABINET_CONTEXT; 53 54static PVOID 55ConsoleCreateFileHandler( 56 IN PCABINET_CONTEXT CabinetContext, 57 IN ULONG FileSize) 58{ 59 PCONSOLE_CABINET_CONTEXT ConsoleCabinetContext; 60 61 ConsoleCabinetContext = (PCONSOLE_CABINET_CONTEXT)CabinetContext; 62 ConsoleCabinetContext->Data = RtlAllocateHeap(ProcessHeap, 0, FileSize); 63 if (!ConsoleCabinetContext->Data) 64 { 65 DPRINT("Failed to allocate %d bytes\n", FileSize); 66 return NULL; 67 } 68 ConsoleCabinetContext->Size = FileSize; 69 return ConsoleCabinetContext->Data; 70} 71 72BOOL 73WINAPI 74AllocConsole(VOID) 75{ 76 NTSTATUS Status; 77 UNICODE_STRING ScreenName = RTL_CONSTANT_STRING(L"\\??\\BlueScreen"); 78 UNICODE_STRING KeyboardName = RTL_CONSTANT_STRING(L"\\Device\\KeyboardClass0"); 79 OBJECT_ATTRIBUTES ObjectAttributes; 80 IO_STATUS_BLOCK IoStatusBlock; 81 ULONG Enable; 82 83 /* Open the screen */ 84 InitializeObjectAttributes(&ObjectAttributes, 85 &ScreenName, 86 0, 87 NULL, 88 NULL); 89 Status = NtOpenFile(&StdOutput, 90 FILE_ALL_ACCESS, 91 &ObjectAttributes, 92 &IoStatusBlock, 93 FILE_OPEN, 94 FILE_SYNCHRONOUS_IO_ALERT); 95 if (!NT_SUCCESS(Status)) 96 return FALSE; 97 98 /* Enable it */ 99 Enable = TRUE; 100 Status = NtDeviceIoControlFile(StdOutput, 101 NULL, 102 NULL, 103 NULL, 104 &IoStatusBlock, 105 IOCTL_CONSOLE_RESET_SCREEN, 106 &Enable, 107 sizeof(Enable), 108 NULL, 109 0); 110 if (!NT_SUCCESS(Status)) 111 { 112 NtClose(StdOutput); 113 return FALSE; 114 } 115 116 /* Default to en-US output codepage */ 117 SetConsoleOutputCP(437); 118 119 /* Open the keyboard */ 120 InitializeObjectAttributes(&ObjectAttributes, 121 &KeyboardName, 122 0, 123 NULL, 124 NULL); 125 Status = NtOpenFile(&StdInput, 126 FILE_ALL_ACCESS, 127 &ObjectAttributes, 128 &IoStatusBlock, 129 FILE_OPEN, 130 0); 131 if (!NT_SUCCESS(Status)) 132 { 133 NtClose(StdOutput); 134 return FALSE; 135 } 136 137 /* Reset the queue state */ 138 InputQueueEmpty = TRUE; 139 WaitForInput = FALSE; 140 141 return TRUE; 142} 143 144 145BOOL 146WINAPI 147AttachConsole( 148 IN DWORD dwProcessId) 149{ 150 return FALSE; 151} 152 153 154BOOL 155WINAPI 156FreeConsole(VOID) 157{ 158 /* Reset the queue state */ 159 InputQueueEmpty = TRUE; 160 WaitForInput = FALSE; 161 162 if (StdInput != INVALID_HANDLE_VALUE) 163 NtClose(StdInput); 164 165 if (StdOutput != INVALID_HANDLE_VALUE) 166 NtClose(StdOutput); 167 168 return TRUE; 169} 170 171 172BOOL 173WINAPI 174WriteConsole( 175 IN HANDLE hConsoleOutput, 176 IN const VOID *lpBuffer, 177 IN DWORD nNumberOfCharsToWrite, 178 OUT LPDWORD lpNumberOfCharsWritten, 179 IN LPVOID lpReserved) 180{ 181 IO_STATUS_BLOCK IoStatusBlock; 182 NTSTATUS Status; 183 184 Status = NtWriteFile(hConsoleOutput, 185 NULL, 186 NULL, 187 NULL, 188 &IoStatusBlock, 189 (PVOID)lpBuffer, 190 nNumberOfCharsToWrite, 191 NULL, 192 NULL); 193 if (!NT_SUCCESS(Status)) 194 return FALSE; 195 196 *lpNumberOfCharsWritten = IoStatusBlock.Information; 197 return TRUE; 198} 199 200 201HANDLE 202WINAPI 203GetStdHandle( 204 IN DWORD nStdHandle) 205{ 206 switch (nStdHandle) 207 { 208 case STD_INPUT_HANDLE: 209 return StdInput; 210 case STD_OUTPUT_HANDLE: 211 return StdOutput; 212 default: 213 return INVALID_HANDLE_VALUE; 214 } 215} 216 217 218BOOL 219WINAPI 220FlushConsoleInputBuffer( 221 IN HANDLE hConsoleInput) 222{ 223 NTSTATUS Status; 224 LARGE_INTEGER Offset, Timeout; 225 IO_STATUS_BLOCK IoStatusBlock; 226 KEYBOARD_INPUT_DATA InputData; 227 228 /* Cancel any pending read */ 229 if (WaitForInput) 230 NtCancelIoFile(hConsoleInput, &IoStatusBlock); 231 232 /* Reset the queue state */ 233 InputQueueEmpty = TRUE; 234 WaitForInput = FALSE; 235 236 /* Flush the keyboard buffer */ 237 do 238 { 239 Offset.QuadPart = 0; 240 Status = NtReadFile(hConsoleInput, 241 NULL, 242 NULL, 243 NULL, 244 &IoStatusBlock, 245 &InputData, 246 sizeof(InputData), 247 &Offset, 248 NULL); 249 if (Status == STATUS_PENDING) 250 { 251 Timeout.QuadPart = -100; 252 Status = NtWaitForSingleObject(hConsoleInput, FALSE, &Timeout); 253 if (Status == STATUS_TIMEOUT) 254 { 255 NtCancelIoFile(hConsoleInput, &IoStatusBlock); 256 return TRUE; 257 } 258 } 259 } while (NT_SUCCESS(Status)); 260 return FALSE; 261} 262 263 264BOOL 265WINAPI 266PeekConsoleInput( 267 IN HANDLE hConsoleInput, 268 OUT PINPUT_RECORD lpBuffer, 269 IN DWORD nLength, 270 OUT LPDWORD lpNumberOfEventsRead) 271{ 272 NTSTATUS Status; 273 LARGE_INTEGER Offset, Timeout; 274 KEYBOARD_INPUT_DATA InputData; 275 276 if (InputQueueEmpty) 277 { 278 /* Read the keyboard for an event, without waiting */ 279 if (!WaitForInput) 280 { 281 Offset.QuadPart = 0; 282 Status = NtReadFile(hConsoleInput, 283 NULL, 284 NULL, 285 NULL, 286 &InputIosb, 287 &InputDataQueue, 288 sizeof(InputDataQueue), 289 &Offset, 290 NULL); 291 if (!NT_SUCCESS(Status)) 292 return FALSE; 293 if (Status == STATUS_PENDING) 294 { 295 /* No input yet, we will have to wait next time */ 296 *lpNumberOfEventsRead = 0; 297 WaitForInput = TRUE; 298 return TRUE; 299 } 300 } 301 else 302 { 303 /* 304 * We already tried to read from the keyboard and are 305 * waiting for data, check whether something showed up. 306 */ 307 Timeout.QuadPart = -100; // Wait just a little bit. 308 Status = NtWaitForSingleObject(hConsoleInput, FALSE, &Timeout); 309 if (Status == STATUS_TIMEOUT) 310 { 311 /* Nothing yet, continue waiting next time */ 312 *lpNumberOfEventsRead = 0; 313 WaitForInput = TRUE; 314 return TRUE; 315 } 316 WaitForInput = FALSE; 317 if (!NT_SUCCESS(Status)) 318 return FALSE; 319 } 320 321 /* We got something in the queue */ 322 InputQueueEmpty = FALSE; 323 WaitForInput = FALSE; 324 } 325 326 /* Fetch from the queue but keep it inside */ 327 InputData = InputDataQueue; 328 329 lpBuffer->EventType = KEY_EVENT; 330 Status = IntTranslateKey(hConsoleInput, &InputData, &lpBuffer->Event.KeyEvent); 331 if (!NT_SUCCESS(Status)) 332 return FALSE; 333 334 *lpNumberOfEventsRead = 1; 335 return TRUE; 336} 337 338 339BOOL 340WINAPI 341ReadConsoleInput( 342 IN HANDLE hConsoleInput, 343 OUT PINPUT_RECORD lpBuffer, 344 IN DWORD nLength, 345 OUT LPDWORD lpNumberOfEventsRead) 346{ 347 NTSTATUS Status; 348 LARGE_INTEGER Offset; 349 KEYBOARD_INPUT_DATA InputData; 350 351 if (InputQueueEmpty) 352 { 353 /* Read the keyboard and wait for an event, skipping the queue */ 354 if (!WaitForInput) 355 { 356 Offset.QuadPart = 0; 357 Status = NtReadFile(hConsoleInput, 358 NULL, 359 NULL, 360 NULL, 361 &InputIosb, 362 &InputDataQueue, 363 sizeof(InputDataQueue), 364 &Offset, 365 NULL); 366 if (Status == STATUS_PENDING) 367 { 368 /* Block and wait for input */ 369 WaitForInput = TRUE; 370 Status = NtWaitForSingleObject(hConsoleInput, FALSE, NULL); 371 WaitForInput = FALSE; 372 Status = InputIosb.Status; 373 } 374 if (!NT_SUCCESS(Status)) 375 return FALSE; 376 } 377 else 378 { 379 /* 380 * We already tried to read from the keyboard and are 381 * waiting for data, block and wait for input. 382 */ 383 Status = NtWaitForSingleObject(hConsoleInput, FALSE, NULL); 384 WaitForInput = FALSE; 385 Status = InputIosb.Status; 386 if (!NT_SUCCESS(Status)) 387 return FALSE; 388 } 389 } 390 391 /* Fetch from the queue and empty it */ 392 InputData = InputDataQueue; 393 InputQueueEmpty = TRUE; 394 395 lpBuffer->EventType = KEY_EVENT; 396 Status = IntTranslateKey(hConsoleInput, &InputData, &lpBuffer->Event.KeyEvent); 397 if (!NT_SUCCESS(Status)) 398 return FALSE; 399 400 *lpNumberOfEventsRead = 1; 401 return TRUE; 402} 403 404 405BOOL 406WINAPI 407WriteConsoleOutputCharacterA( 408 HANDLE hConsoleOutput, 409 IN LPCSTR lpCharacter, 410 IN DWORD nLength, 411 IN COORD dwWriteCoord, 412 OUT LPDWORD lpNumberOfCharsWritten) 413{ 414 IO_STATUS_BLOCK IoStatusBlock; 415 PCHAR Buffer; 416 COORD *pCoord; 417 PCHAR pText; 418 NTSTATUS Status; 419 420 Buffer = (CHAR*)RtlAllocateHeap(ProcessHeap, 421 0, 422 nLength + sizeof(COORD)); 423 pCoord = (COORD *)Buffer; 424 pText = (PCHAR)(pCoord + 1); 425 426 *pCoord = dwWriteCoord; 427 memcpy(pText, lpCharacter, nLength); 428 429 Status = NtDeviceIoControlFile(hConsoleOutput, 430 NULL, 431 NULL, 432 NULL, 433 &IoStatusBlock, 434 IOCTL_CONSOLE_WRITE_OUTPUT_CHARACTER, 435 NULL, 436 0, 437 Buffer, 438 nLength + sizeof(COORD)); 439 440 RtlFreeHeap(ProcessHeap, 0, Buffer); 441 if (!NT_SUCCESS(Status)) 442 return FALSE; 443 444 *lpNumberOfCharsWritten = IoStatusBlock.Information; 445 return TRUE; 446} 447 448 449BOOL 450WINAPI 451WriteConsoleOutputCharacterW( 452 HANDLE hConsoleOutput, 453 IN LPCWSTR lpCharacter, 454 IN DWORD nLength, 455 IN COORD dwWriteCoord, 456 OUT LPDWORD lpNumberOfCharsWritten) 457{ 458 IO_STATUS_BLOCK IoStatusBlock; 459 PCHAR Buffer; 460 COORD *pCoord; 461 PCHAR pText; 462 NTSTATUS Status; 463// ULONG i; 464 465 UNICODE_STRING UnicodeString; 466 OEM_STRING OemString; 467 ULONG OemLength; 468 469 UnicodeString.Length = nLength * sizeof(WCHAR); 470 UnicodeString.MaximumLength = nLength * sizeof(WCHAR); 471 UnicodeString.Buffer = (PWSTR)lpCharacter; 472 473 OemLength = RtlUnicodeStringToOemSize(&UnicodeString); 474 475 476 Buffer = (CHAR*)RtlAllocateHeap(ProcessHeap, 477 0, 478 OemLength + sizeof(COORD)); 479// nLength + sizeof(COORD)); 480 if (Buffer== NULL) 481 return FALSE; 482 483 pCoord = (COORD *)Buffer; 484 pText = (PCHAR)(pCoord + 1); 485 486 *pCoord = dwWriteCoord; 487 488 OemString.Length = 0; 489 OemString.MaximumLength = OemLength; 490 OemString.Buffer = pText; 491 492 Status = RtlUnicodeStringToOemString(&OemString, 493 &UnicodeString, 494 FALSE); 495 if (!NT_SUCCESS(Status)) 496 goto done; 497 498 /* FIXME: use real unicode->oem conversion */ 499// for (i = 0; i < nLength; i++) 500// pText[i] = (CHAR)lpCharacter[i]; 501 502 Status = NtDeviceIoControlFile(hConsoleOutput, 503 NULL, 504 NULL, 505 NULL, 506 &IoStatusBlock, 507 IOCTL_CONSOLE_WRITE_OUTPUT_CHARACTER, 508 NULL, 509 0, 510 Buffer, 511 nLength + sizeof(COORD)); 512 513done: 514 RtlFreeHeap(ProcessHeap, 0, Buffer); 515 if (!NT_SUCCESS(Status)) 516 return FALSE; 517 518 *lpNumberOfCharsWritten = IoStatusBlock.Information; 519 return TRUE; 520} 521 522 523BOOL 524WINAPI 525FillConsoleOutputAttribute( 526 IN HANDLE hConsoleOutput, 527 IN WORD wAttribute, 528 IN DWORD nLength, 529 IN COORD dwWriteCoord, 530 OUT LPDWORD lpNumberOfAttrsWritten) 531{ 532 IO_STATUS_BLOCK IoStatusBlock; 533 OUTPUT_ATTRIBUTE Buffer; 534 NTSTATUS Status; 535 536 Buffer.wAttribute = wAttribute; 537 Buffer.nLength = nLength; 538 Buffer.dwCoord = dwWriteCoord; 539 540 Status = NtDeviceIoControlFile(hConsoleOutput, 541 NULL, 542 NULL, 543 NULL, 544 &IoStatusBlock, 545 IOCTL_CONSOLE_FILL_OUTPUT_ATTRIBUTE, 546 &Buffer, 547 sizeof(OUTPUT_ATTRIBUTE), 548 &Buffer, 549 sizeof(OUTPUT_ATTRIBUTE)); 550 if (!NT_SUCCESS(Status)) 551 return FALSE; 552 553 *lpNumberOfAttrsWritten = Buffer.dwTransfered; 554 return TRUE; 555} 556 557 558BOOL 559WINAPI 560FillConsoleOutputCharacterA( 561 IN HANDLE hConsoleOutput, 562 IN CHAR cCharacter, 563 IN DWORD nLength, 564 IN COORD dwWriteCoord, 565 OUT LPDWORD lpNumberOfCharsWritten) 566{ 567 IO_STATUS_BLOCK IoStatusBlock; 568 OUTPUT_CHARACTER Buffer; 569 NTSTATUS Status; 570 571 Buffer.cCharacter = cCharacter; 572 Buffer.nLength = nLength; 573 Buffer.dwCoord = dwWriteCoord; 574 575 Status = NtDeviceIoControlFile(hConsoleOutput, 576 NULL, 577 NULL, 578 NULL, 579 &IoStatusBlock, 580 IOCTL_CONSOLE_FILL_OUTPUT_CHARACTER, 581 &Buffer, 582 sizeof(OUTPUT_CHARACTER), 583 &Buffer, 584 sizeof(OUTPUT_CHARACTER)); 585 if (!NT_SUCCESS(Status)) 586 return FALSE; 587 588 *lpNumberOfCharsWritten = Buffer.dwTransfered; 589 return TRUE; 590} 591 592 593BOOL 594WINAPI 595GetConsoleScreenBufferInfo( 596 IN HANDLE hConsoleOutput, 597 OUT PCONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo) 598{ 599 IO_STATUS_BLOCK IoStatusBlock; 600 NTSTATUS Status; 601 602 Status = NtDeviceIoControlFile(hConsoleOutput, 603 NULL, 604 NULL, 605 NULL, 606 &IoStatusBlock, 607 IOCTL_CONSOLE_GET_SCREEN_BUFFER_INFO, 608 NULL, 609 0, 610 lpConsoleScreenBufferInfo, 611 sizeof(CONSOLE_SCREEN_BUFFER_INFO)); 612 return NT_SUCCESS(Status); 613} 614 615 616BOOL 617WINAPI 618SetConsoleCursorInfo( 619 IN HANDLE hConsoleOutput, 620 IN const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo) 621{ 622 IO_STATUS_BLOCK IoStatusBlock; 623 NTSTATUS Status; 624 625 Status = NtDeviceIoControlFile(hConsoleOutput, 626 NULL, 627 NULL, 628 NULL, 629 &IoStatusBlock, 630 IOCTL_CONSOLE_SET_CURSOR_INFO, 631 (PCONSOLE_CURSOR_INFO)lpConsoleCursorInfo, 632 sizeof(CONSOLE_CURSOR_INFO), 633 NULL, 634 0); 635 return NT_SUCCESS(Status); 636} 637 638 639BOOL 640WINAPI 641SetConsoleCursorPosition( 642 IN HANDLE hConsoleOutput, 643 IN COORD dwCursorPosition) 644{ 645 CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo; 646 IO_STATUS_BLOCK IoStatusBlock; 647 NTSTATUS Status; 648 649 Status = GetConsoleScreenBufferInfo(hConsoleOutput, &ConsoleScreenBufferInfo); 650 if (!NT_SUCCESS(Status)) 651 return FALSE; 652 653 ConsoleScreenBufferInfo.dwCursorPosition.X = dwCursorPosition.X; 654 ConsoleScreenBufferInfo.dwCursorPosition.Y = dwCursorPosition.Y; 655 656 Status = NtDeviceIoControlFile(hConsoleOutput, 657 NULL, 658 NULL, 659 NULL, 660 &IoStatusBlock, 661 IOCTL_CONSOLE_SET_SCREEN_BUFFER_INFO, 662 &ConsoleScreenBufferInfo, 663 sizeof(CONSOLE_SCREEN_BUFFER_INFO), 664 NULL, 665 0); 666 return NT_SUCCESS(Status); 667} 668 669 670BOOL 671WINAPI 672SetConsoleTextAttribute( 673 IN HANDLE hConsoleOutput, 674 IN WORD wAttributes) 675{ 676 IO_STATUS_BLOCK IoStatusBlock; 677 NTSTATUS Status; 678 679 Status = NtDeviceIoControlFile(hConsoleOutput, 680 NULL, 681 NULL, 682 NULL, 683 &IoStatusBlock, 684 IOCTL_CONSOLE_SET_TEXT_ATTRIBUTE, 685 &wAttributes, 686 sizeof(USHORT), 687 NULL, 688 0); 689 return NT_SUCCESS(Status); 690} 691 692 693BOOL 694WINAPI 695SetConsoleOutputCP( 696 IN UINT wCodepage) 697{ 698 static PCWSTR FontFile = L"\\SystemRoot\\vgafonts.cab"; 699 WCHAR FontName[20]; 700 CONSOLE_CABINET_CONTEXT ConsoleCabinetContext; 701 PCABINET_CONTEXT CabinetContext = &ConsoleCabinetContext.CabinetContext; 702 CAB_SEARCH Search; 703 ULONG CabStatus; 704 HANDLE hConsoleOutput; 705 IO_STATUS_BLOCK IoStatusBlock; 706 NTSTATUS Status; 707 708 if (wCodepage == LastLoadedCodepage) 709 return TRUE; 710 711 hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); 712 713 CabinetInitialize(CabinetContext); 714 CabinetSetEventHandlers(CabinetContext, 715 NULL, NULL, NULL, ConsoleCreateFileHandler); 716 CabinetSetCabinetName(CabinetContext, FontFile); 717 718 CabStatus = CabinetOpen(CabinetContext); 719 if (CabStatus != CAB_STATUS_SUCCESS) 720 { 721 DPRINT("CabinetOpen('%S') returned 0x%08x\n", FontFile, CabStatus); 722 return FALSE; 723 } 724 725 RtlStringCbPrintfW(FontName, sizeof(FontName), 726 L"%u-8x8.bin", wCodepage); 727 CabStatus = CabinetFindFirst(CabinetContext, FontName, &Search); 728 if (CabStatus != CAB_STATUS_SUCCESS) 729 { 730 DPRINT("CabinetFindFirst('%S', '%S') returned 0x%08x\n", FontFile, FontName, CabStatus); 731 CabinetClose(CabinetContext); 732 return FALSE; 733 } 734 735 CabStatus = CabinetExtractFile(CabinetContext, &Search); 736 CabinetClose(CabinetContext); 737 if (CabStatus != CAB_STATUS_SUCCESS) 738 { 739 DPRINT("CabinetExtractFile('%S', '%S') returned 0x%08x\n", FontFile, FontName, CabStatus); 740 if (ConsoleCabinetContext.Data) 741 RtlFreeHeap(ProcessHeap, 0, ConsoleCabinetContext.Data); 742 return FALSE; 743 } 744 ASSERT(ConsoleCabinetContext.Data); 745 746 Status = NtDeviceIoControlFile(hConsoleOutput, 747 NULL, 748 NULL, 749 NULL, 750 &IoStatusBlock, 751 IOCTL_CONSOLE_LOADFONT, 752 ConsoleCabinetContext.Data, 753 ConsoleCabinetContext.Size, 754 NULL, 755 0); 756 757 RtlFreeHeap(ProcessHeap, 0, ConsoleCabinetContext.Data); 758 759 if (!NT_SUCCESS(Status)) 760 return FALSE; 761 762 LastLoadedCodepage = wCodepage; 763 return TRUE; 764} 765 766 767/* EOF */