[USERINIT]: Addendum to r72821 + demote to WARNing the displayed message if the insta...
[reactos.git] / reactos / base / system / userinit / livecd.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Userinit Logon Application
4 * FILE: base/system/userinit/livecd.c
5 * PROGRAMMERS: Eric Kohl
6 */
7
8 #include "userinit.h"
9
10 HWND hList;
11 HWND hLocaleList;
12 BOOL bSpain = FALSE;
13
14 static VOID
15 InitImageInfo(PIMGINFO ImgInfo)
16 {
17 BITMAP bitmap;
18
19 ZeroMemory(ImgInfo, sizeof(*ImgInfo));
20
21 ImgInfo->hBitmap = LoadImageW(hInstance,
22 MAKEINTRESOURCEW(IDB_ROSLOGO),
23 IMAGE_BITMAP,
24 0,
25 0,
26 LR_DEFAULTCOLOR);
27
28 if (ImgInfo->hBitmap != NULL)
29 {
30 GetObject(ImgInfo->hBitmap, sizeof(bitmap), &bitmap);
31
32 ImgInfo->cxSource = bitmap.bmWidth;
33 ImgInfo->cySource = bitmap.bmHeight;
34 }
35 }
36
37
38 BOOL
39 IsLiveCD(VOID)
40 {
41 HKEY ControlKey = NULL;
42 LPWSTR SystemStartOptions = NULL;
43 LPWSTR CurrentOption, NextOption; /* Pointers into SystemStartOptions */
44 LONG rc;
45 BOOL ret = FALSE;
46
47 TRACE("IsLiveCD()\n");
48
49 rc = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
50 REGSTR_PATH_CURRENT_CONTROL_SET,
51 0,
52 KEY_QUERY_VALUE,
53 &ControlKey);
54 if (rc != ERROR_SUCCESS)
55 {
56 WARN("RegOpenKeyEx() failed with error %lu\n", rc);
57 goto cleanup;
58 }
59
60 rc = ReadRegSzKey(ControlKey, L"SystemStartOptions", &SystemStartOptions);
61 if (rc != ERROR_SUCCESS)
62 {
63 WARN("ReadRegSzKey() failed with error %lu\n", rc);
64 goto cleanup;
65 }
66
67 /* Check for CONSOLE switch in SystemStartOptions */
68 CurrentOption = SystemStartOptions;
69 while (CurrentOption)
70 {
71 NextOption = wcschr(CurrentOption, L' ');
72 if (NextOption)
73 *NextOption = L'\0';
74 if (_wcsicmp(CurrentOption, L"MININT") == 0)
75 {
76 TRACE("Found 'MININT' boot option\n");
77 ret = TRUE;
78 goto cleanup;
79 }
80 CurrentOption = NextOption ? NextOption + 1 : NULL;
81 }
82
83 cleanup:
84 if (ControlKey != NULL)
85 RegCloseKey(ControlKey);
86 HeapFree(GetProcessHeap(), 0, SystemStartOptions);
87
88 TRACE("IsLiveCD() returning %d\n", ret);
89
90 return ret;
91 }
92
93
94 static BOOL CALLBACK
95 LocalesEnumProc(LPTSTR lpLocale)
96 {
97 LCID lcid;
98 WCHAR lang[255];
99 INT index;
100 BOOL bNoShow = FALSE;
101
102 lcid = wcstoul(lpLocale, NULL, 16);
103
104 /* Display only languages with installed support */
105 if (!IsValidLocale(lcid, LCID_INSTALLED))
106 return TRUE;
107
108 if (lcid == MAKELCID(MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH), SORT_DEFAULT) ||
109 lcid == MAKELCID(MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_MODERN), SORT_DEFAULT))
110 {
111 if (bSpain == FALSE)
112 {
113 LoadStringW(hInstance, IDS_SPAIN, lang, ARRAYSIZE(lang));
114 bSpain = TRUE;
115 }
116 else
117 {
118 bNoShow = TRUE;
119 }
120 }
121 else
122 {
123 GetLocaleInfoW(lcid, LOCALE_SLANGUAGE, lang, ARRAYSIZE(lang));
124 }
125
126 if (bNoShow == FALSE)
127 {
128 index = SendMessageW(hList,
129 CB_ADDSTRING,
130 0,
131 (LPARAM)lang);
132
133 SendMessageW(hList,
134 CB_SETITEMDATA,
135 index,
136 (LPARAM)lcid);
137 }
138
139 return TRUE;
140 }
141
142
143 static VOID
144 CreateLanguagesList(HWND hwnd)
145 {
146 WCHAR langSel[255];
147
148 hList = hwnd;
149 bSpain = FALSE;
150 EnumSystemLocalesW(LocalesEnumProc, LCID_SUPPORTED);
151
152 /* Select current locale */
153 /* or should it be System and not user? */
154 GetLocaleInfoW(GetUserDefaultLCID(), LOCALE_SLANGUAGE, langSel, ARRAYSIZE(langSel));
155
156 SendMessageW(hList,
157 CB_SELECTSTRING,
158 -1,
159 (LPARAM)langSel);
160 }
161
162
163 static
164 BOOL
165 GetLayoutName(
166 LPCWSTR szLCID,
167 LPWSTR szName)
168 {
169 HKEY hKey;
170 DWORD dwBufLen;
171 WCHAR szBuf[MAX_PATH], szDispName[MAX_PATH], szIndex[MAX_PATH], szPath[MAX_PATH];
172 HANDLE hLib;
173 UINT i, j, k;
174
175 wsprintf(szBuf, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s", szLCID);
176
177 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCTSTR)szBuf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
178 {
179 dwBufLen = sizeof(szDispName);
180
181 if (RegQueryValueExW(hKey, L"Layout Display Name", NULL, NULL, (LPBYTE)szDispName, &dwBufLen) == ERROR_SUCCESS)
182 {
183 if (szDispName[0] == '@')
184 {
185 for (i = 0; i < wcslen(szDispName); i++)
186 {
187 if ((szDispName[i] == ',') && (szDispName[i + 1] == '-'))
188 {
189 for (j = i + 2, k = 0; j < wcslen(szDispName)+1; j++, k++)
190 {
191 szIndex[k] = szDispName[j];
192 }
193 szDispName[i - 1] = '\0';
194 break;
195 }
196 else
197 szDispName[i] = szDispName[i + 1];
198 }
199
200 if (ExpandEnvironmentStringsW(szDispName, szPath, ARRAYSIZE(szPath)))
201 {
202 hLib = LoadLibraryW(szPath);
203 if (hLib)
204 {
205 if (LoadStringW(hLib, _wtoi(szIndex), szPath, ARRAYSIZE(szPath)) != 0)
206 {
207 wcscpy(szName, szPath);
208 RegCloseKey(hKey);
209 return TRUE;
210 }
211 FreeLibrary(hLib);
212 }
213 }
214 }
215 }
216
217 dwBufLen = sizeof(szBuf);
218
219 if (RegQueryValueExW(hKey, L"Layout Text", NULL, NULL, (LPBYTE)szName, &dwBufLen) == ERROR_SUCCESS)
220 {
221 RegCloseKey(hKey);
222 return TRUE;
223 }
224 }
225
226 return FALSE;
227 }
228
229
230 static
231 VOID
232 SetKeyboardLayout(
233 HWND hwnd)
234 {
235 INT iCurSel;
236 ULONG ulLayoutId;
237 HKL hKl;
238 WCHAR szLayoutId[9];
239
240 iCurSel = SendMessageW(hwnd, CB_GETCURSEL, 0, 0);
241 if (iCurSel == CB_ERR)
242 return;
243
244 ulLayoutId = (ULONG)SendMessageW(hwnd, CB_GETITEMDATA, iCurSel, 0);
245 if (ulLayoutId == (ULONG)CB_ERR)
246 return;
247
248 swprintf(szLayoutId, L"%08lx", ulLayoutId);
249
250 hKl = LoadKeyboardLayoutW(szLayoutId, KLF_ACTIVATE | KLF_REPLACELANG | KLF_SETFORPROCESS);
251 SystemParametersInfoW(SPI_SETDEFAULTINPUTLANG, 0, &hKl, SPIF_SENDWININICHANGE);
252 }
253
254
255 static
256 VOID
257 SelectKeyboardForLanguage(
258 HWND hwnd,
259 LCID lcid)
260 {
261 INT i, nCount;
262 LCID LayoutId;
263
264 TRACE("LCID: %08lx\n", lcid);
265 TRACE("LangID: %04lx\n", LANGIDFROMLCID(lcid));
266
267 nCount = SendMessageW(hwnd, CB_GETCOUNT, 0, 0);
268
269 for (i = 0; i < nCount; i++)
270 {
271 LayoutId = (LCID)SendMessageW(hwnd, CB_GETITEMDATA, i, 0);
272 TRACE("Layout: %08lx\n", LayoutId);
273
274 if (LANGIDFROMLCID(LayoutId) == LANGIDFROMLCID(lcid))
275 {
276 TRACE("Found 1: %08lx --> %08lx\n", LayoutId, lcid);
277 SendMessageW(hwnd, CB_SETCURSEL, i, 0);
278 return;
279 }
280 }
281
282 for (i = 0; i < nCount; i++)
283 {
284 LayoutId = (LCID)SendMessageW(hwnd, CB_GETITEMDATA, i, 0);
285 TRACE("Layout: %08lx\n", LayoutId);
286
287 if (PRIMARYLANGID(LayoutId) == PRIMARYLANGID(lcid))
288 {
289 TRACE("Found 2: %08lx --> %08lx\n", LayoutId, lcid);
290 SendMessageW(hwnd, CB_SETCURSEL, i, 0);
291 return;
292 }
293 }
294
295 TRACE("No match found!\n");
296 }
297
298
299 static
300 VOID
301 CreateKeyboardLayoutList(
302 HWND hItemsList)
303 {
304 HKEY hKey;
305 WCHAR szLayoutId[9], szCurrentLayoutId[9];
306 WCHAR KeyName[MAX_PATH];
307 DWORD dwIndex = 0;
308 DWORD dwSize;
309 INT iIndex;
310 LONG lError;
311 ULONG ulLayoutId;
312
313 if (!GetKeyboardLayoutNameW(szCurrentLayoutId))
314 wcscpy(szCurrentLayoutId, L"00000409");
315
316 lError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
317 L"System\\CurrentControlSet\\Control\\Keyboard Layouts",
318 0,
319 KEY_ENUMERATE_SUB_KEYS,
320 &hKey);
321 if (lError != ERROR_SUCCESS)
322 return;
323
324 while (TRUE)
325 {
326 dwSize = ARRAYSIZE(szLayoutId);
327
328 lError = RegEnumKeyExW(hKey,
329 dwIndex,
330 szLayoutId,
331 &dwSize,
332 NULL,
333 NULL,
334 NULL,
335 NULL);
336 if (lError != ERROR_SUCCESS)
337 break;
338
339 GetLayoutName(szLayoutId, KeyName);
340
341 iIndex = (INT)SendMessageW(hItemsList, CB_ADDSTRING, 0, (LPARAM)KeyName);
342
343 ulLayoutId = wcstoul(szLayoutId, NULL, 16);
344 SendMessageW(hItemsList, CB_SETITEMDATA, iIndex, (LPARAM)ulLayoutId);
345
346 if (wcscmp(szLayoutId, szCurrentLayoutId) == 0)
347 {
348 SendMessageW(hItemsList, CB_SETCURSEL, (WPARAM)iIndex, (LPARAM)0);
349 }
350
351 dwIndex++;
352 }
353
354 RegCloseKey(hKey);
355 }
356
357
358 static
359 VOID
360 InitializeDefaultUserLocale(
361 PLCID pNewLcid)
362 {
363 WCHAR szBuffer[80];
364 PWSTR ptr;
365 HKEY hLocaleKey;
366 DWORD ret;
367 DWORD dwSize;
368 LCID lcid;
369 INT i;
370
371 struct {LCTYPE LCType; PWSTR pValue;} LocaleData[] = {
372 /* Number */
373 {LOCALE_SDECIMAL, L"sDecimal"},
374 {LOCALE_STHOUSAND, L"sThousand"},
375 {LOCALE_SNEGATIVESIGN, L"sNegativeSign"},
376 {LOCALE_SPOSITIVESIGN, L"sPositiveSign"},
377 {LOCALE_SGROUPING, L"sGrouping"},
378 {LOCALE_SLIST, L"sList"},
379 {LOCALE_SNATIVEDIGITS, L"sNativeDigits"},
380 {LOCALE_INEGNUMBER, L"iNegNumber"},
381 {LOCALE_IDIGITS, L"iDigits"},
382 {LOCALE_ILZERO, L"iLZero"},
383 {LOCALE_IMEASURE, L"iMeasure"},
384 {LOCALE_IDIGITSUBSTITUTION, L"NumShape"},
385
386 /* Currency */
387 {LOCALE_SCURRENCY, L"sCurrency"},
388 {LOCALE_SMONDECIMALSEP, L"sMonDecimalSep"},
389 {LOCALE_SMONTHOUSANDSEP, L"sMonThousandSep"},
390 {LOCALE_SMONGROUPING, L"sMonGrouping"},
391 {LOCALE_ICURRENCY, L"iCurrency"},
392 {LOCALE_INEGCURR, L"iNegCurr"},
393 {LOCALE_ICURRDIGITS, L"iCurrDigits"},
394
395 /* Time */
396 {LOCALE_STIMEFORMAT, L"sTimeFormat"},
397 {LOCALE_STIME, L"sTime"},
398 {LOCALE_S1159, L"s1159"},
399 {LOCALE_S2359, L"s2359"},
400 {LOCALE_ITIME, L"iTime"},
401 {LOCALE_ITIMEMARKPOSN, L"iTimePrefix"},
402 {LOCALE_ITLZERO, L"iTLZero"},
403
404 /* Date */
405 {LOCALE_SLONGDATE, L"sLongDate"},
406 {LOCALE_SSHORTDATE, L"sShortDate"},
407 {LOCALE_SDATE, L"sDate"},
408 {LOCALE_IFIRSTDAYOFWEEK, L"iFirstDayOfWeek"},
409 {LOCALE_IFIRSTWEEKOFYEAR, L"iFirstWeekOfYear"},
410 {LOCALE_IDATE, L"iDate"},
411 {LOCALE_ICALENDARTYPE, L"iCalendarType"},
412
413 /* Misc */
414 {LOCALE_SCOUNTRY, L"sCountry"},
415 {LOCALE_SLANGUAGE, L"sLanguage"},
416 {LOCALE_ICOUNTRY, L"iCountry"},
417 {0, NULL}};
418
419 ret = RegOpenKeyExW(HKEY_USERS,
420 L".DEFAULT\\Control Panel\\International",
421 0,
422 KEY_READ | KEY_WRITE,
423 &hLocaleKey);
424 if (ret != ERROR_SUCCESS)
425 {
426 return;
427 }
428
429 if (pNewLcid == NULL)
430 {
431 dwSize = 9 * sizeof(WCHAR);
432 ret = RegQueryValueExW(hLocaleKey,
433 L"Locale",
434 NULL,
435 NULL,
436 (PBYTE)szBuffer,
437 &dwSize);
438 if (ret != ERROR_SUCCESS)
439 goto done;
440
441 lcid = (LCID)wcstoul(szBuffer, &ptr, 16);
442 if (lcid == 0)
443 goto done;
444 }
445 else
446 {
447 lcid = *pNewLcid;
448
449 swprintf(szBuffer, L"%08lx", lcid);
450 RegSetValueExW(hLocaleKey,
451 L"Locale",
452 0,
453 REG_SZ,
454 (PBYTE)szBuffer,
455 (wcslen(szBuffer) + 1) * sizeof(WCHAR));
456 }
457
458 i = 0;
459 while (LocaleData[i].pValue != NULL)
460 {
461 if (GetLocaleInfoW(lcid,
462 LocaleData[i].LCType | LOCALE_NOUSEROVERRIDE,
463 szBuffer,
464 ARRAYSIZE(szBuffer)))
465 {
466 RegSetValueExW(hLocaleKey,
467 LocaleData[i].pValue,
468 0,
469 REG_SZ,
470 (PBYTE)szBuffer,
471 (wcslen(szBuffer) + 1) * sizeof(WCHAR));
472 }
473
474 i++;
475 }
476
477 done:
478 RegCloseKey(hLocaleKey);
479 }
480
481
482 VOID
483 CenterWindow(HWND hWnd)
484 {
485 HWND hWndParent;
486 RECT rcParent;
487 RECT rcWindow;
488
489 hWndParent = GetParent(hWnd);
490 if (hWndParent == NULL)
491 hWndParent = GetDesktopWindow();
492
493 GetWindowRect(hWndParent, &rcParent);
494 GetWindowRect(hWnd, &rcWindow);
495
496 SetWindowPos(hWnd,
497 HWND_TOP,
498 ((rcParent.right - rcParent.left) - (rcWindow.right - rcWindow.left)) / 2,
499 ((rcParent.bottom - rcParent.top) - (rcWindow.bottom - rcWindow.top)) / 2,
500 0,
501 0,
502 SWP_NOSIZE);
503 }
504
505
506 static
507 VOID
508 OnDrawItem(
509 LPDRAWITEMSTRUCT lpDrawItem,
510 PSTATE pState,
511 UINT uCtlID)
512 {
513 HDC hdcMem;
514 LONG left;
515
516 if (lpDrawItem->CtlID == uCtlID)
517 {
518 /* Position image in centre of dialog */
519 left = (lpDrawItem->rcItem.right - pState->ImageInfo.cxSource) / 2;
520
521 hdcMem = CreateCompatibleDC(lpDrawItem->hDC);
522 if (hdcMem != NULL)
523 {
524 SelectObject(hdcMem, pState->ImageInfo.hBitmap);
525 BitBlt(lpDrawItem->hDC,
526 left,
527 lpDrawItem->rcItem.top,
528 lpDrawItem->rcItem.right - lpDrawItem->rcItem.left,
529 lpDrawItem->rcItem.bottom - lpDrawItem->rcItem.top,
530 hdcMem,
531 0,
532 0,
533 SRCCOPY);
534 DeleteDC(hdcMem);
535 }
536 }
537 }
538
539
540 static
541 INT_PTR
542 CALLBACK
543 LocaleDlgProc(
544 HWND hwndDlg,
545 UINT uMsg,
546 WPARAM wParam,
547 LPARAM lParam)
548 {
549 PSTATE pState;
550
551 /* Retrieve pointer to the state */
552 pState = (PSTATE)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);
553
554 switch (uMsg)
555 {
556 case WM_INITDIALOG:
557 /* Save pointer to the global state */
558 pState = (PSTATE)lParam;
559 SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (DWORD_PTR)pState);
560
561 /* Center the dialog window */
562 CenterWindow(hwndDlg);
563
564 /* Fill the language and keyboard layout lists */
565 CreateLanguagesList(GetDlgItem(hwndDlg, IDC_LANGUAGELIST));
566 CreateKeyboardLayoutList(GetDlgItem(hwndDlg, IDC_LAYOUTLIST));
567
568 /* Disable the 'Cancel' button*/
569 EnableWindow(GetDlgItem(hwndDlg, IDCANCEL), FALSE);
570 return FALSE;
571
572 case WM_DRAWITEM:
573 OnDrawItem((LPDRAWITEMSTRUCT)lParam,
574 pState,
575 IDC_LOCALELOGO);
576 return TRUE;
577
578 case WM_COMMAND:
579 switch (LOWORD(wParam))
580 {
581 case IDC_LANGUAGELIST:
582 if (HIWORD(wParam) == CBN_SELCHANGE)
583 {
584 LCID NewLcid;
585 INT iCurSel;
586
587 iCurSel = SendDlgItemMessageW(hwndDlg,
588 IDC_LANGUAGELIST,
589 CB_GETCURSEL,
590 0,
591 0);
592 if (iCurSel == CB_ERR)
593 break;
594
595 NewLcid = SendDlgItemMessageW(hwndDlg,
596 IDC_LANGUAGELIST,
597 CB_GETITEMDATA,
598 iCurSel,
599 0);
600 if (NewLcid == (LCID)CB_ERR)
601 break;
602
603 TRACE("LCID: 0x%08lx\n", NewLcid);
604 SelectKeyboardForLanguage(GetDlgItem(hwndDlg, IDC_LAYOUTLIST),
605 NewLcid);
606 }
607 break;
608
609 case IDOK:
610 if (HIWORD(wParam) == BN_CLICKED)
611 {
612 LCID NewLcid;
613 INT iCurSel;
614
615 iCurSel = SendDlgItemMessageW(hwndDlg,
616 IDC_LANGUAGELIST,
617 CB_GETCURSEL,
618 0,
619 0);
620 if (iCurSel == CB_ERR)
621 break;
622
623 NewLcid = SendDlgItemMessageW(hwndDlg,
624 IDC_LANGUAGELIST,
625 CB_GETITEMDATA,
626 iCurSel,
627 0);
628 if (NewLcid == (LCID)CB_ERR)
629 break;
630
631 /* Set the locale for the current thread */
632 NtSetDefaultLocale(TRUE, NewLcid);
633
634 /* Store the locale setings in the registry */
635 InitializeDefaultUserLocale(&NewLcid);
636
637 SetKeyboardLayout(GetDlgItem(hwndDlg, IDC_LAYOUTLIST));
638
639 pState->NextPage = STARTPAGE;
640 EndDialog(hwndDlg, 0);
641 }
642 break;
643
644 default:
645 break;
646 }
647 break;
648
649 default:
650 break;
651 }
652
653 return FALSE;
654 }
655
656
657 static
658 INT_PTR
659 CALLBACK
660 StartDlgProc(
661 HWND hwndDlg,
662 UINT uMsg,
663 WPARAM wParam,
664 LPARAM lParam)
665 {
666 PSTATE pState;
667
668 /* Retrieve pointer to the state */
669 pState = (PSTATE)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);
670
671 switch (uMsg)
672 {
673 case WM_INITDIALOG:
674 /* Save pointer to the state */
675 pState = (PSTATE)lParam;
676 SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (DWORD_PTR)pState);
677
678 /* Center the dialog window */
679 CenterWindow(hwndDlg);
680
681 EnableWindow(GetDlgItem(hwndDlg, IDCANCEL), FALSE);
682 return FALSE;
683
684 case WM_DRAWITEM:
685 OnDrawItem((LPDRAWITEMSTRUCT)lParam,
686 pState,
687 IDC_STARTLOGO);
688 return TRUE;
689
690 case WM_COMMAND:
691 if (HIWORD(wParam) == BN_CLICKED)
692 {
693 switch (LOWORD(wParam))
694 {
695 case IDC_RUN:
696 pState->NextPage = DONE;
697 pState->Run = SHELL;
698 EndDialog(hwndDlg, 0);
699 break;
700
701 case IDC_INSTALL:
702 pState->NextPage = DONE;
703 pState->Run = INSTALLER;
704 EndDialog(hwndDlg, 0);
705 break;
706
707 case IDOK:
708 pState->NextPage = LOCALEPAGE;
709 EndDialog(hwndDlg, 0);
710 break;
711
712 default:
713 break;
714 }
715 }
716 break;
717
718 default:
719 break;
720 }
721
722 return FALSE;
723 }
724
725
726 VOID
727 RunLiveCD(
728 PSTATE pState)
729 {
730 InitImageInfo(&pState->ImageInfo);
731
732 while (pState->NextPage != DONE)
733 {
734 switch (pState->NextPage)
735 {
736 case LOCALEPAGE:
737 DialogBoxParamW(hInstance,
738 MAKEINTRESOURCEW(IDD_LOCALEPAGE),
739 NULL,
740 LocaleDlgProc,
741 (LPARAM)pState);
742 break;
743
744 case STARTPAGE:
745 DialogBoxParamW(hInstance,
746 MAKEINTRESOURCEW(IDD_STARTPAGE),
747 NULL,
748 StartDlgProc,
749 (LPARAM)pState);
750 break;
751
752 default:
753 break;
754 }
755 }
756
757 DeleteObject(pState->ImageInfo.hBitmap);
758 }
759
760 /* EOF */