PageRenderTime 57ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/sys/src/cmd/gs/src/dwsetup.cpp

https://bitbucket.org/rminnich/sysfromiso
C++ | 1309 lines | 1012 code | 141 blank | 156 comment | 189 complexity | a2123a245e9bccb1025b8d7ad86e5e98 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, MPL-2.0-no-copyleft-exception, Unlicense
  1. /* Copyright (C) 1999-2003, Ghostgum Software Pty Ltd. All rights reserved.
  2. This software is provided AS-IS with no warranty, either express or
  3. implied.
  4. This software is distributed under license and may not be copied,
  5. modified or distributed except as expressly authorized under the terms
  6. of the license contained in the file LICENSE in this distribution.
  7. For more information about licensing, please refer to
  8. http://www.ghostscript.com/licensing/. For information on
  9. commercial licensing, go to http://www.artifex.com/licensing/ or
  10. contact Artifex Software, Inc., 101 Lucas Valley Road #110,
  11. San Rafael, CA 94903, U.S.A., +1(415)492-9861.
  12. */
  13. // $Id: dwsetup.cpp,v 1.11 2005/03/04 21:58:55 ghostgum Exp $
  14. //
  15. //
  16. // This is the setup program for Win32 AFPL Ghostscript
  17. //
  18. // The starting point is a self extracting zip archive
  19. // with the following contents:
  20. // setupgs.exe
  21. // uninstgs.exe
  22. // filelist.txt (contains list of program files)
  23. // fontlist.txt (contains list of font files)
  24. // gs#.##\* (files listed in filelist.txt)
  25. // fonts\* (fonts listed in fontlist.txt)
  26. // This is the same as the zip file created by Aladdin Enterprises,
  27. // with the addition of setupgs.exe, uninstgs.exe, filelist.txt and
  28. // fontlist.txt.
  29. //
  30. // The first line of the files filelist.txt and fontlist.txt
  31. // contains the uninstall name to be used.
  32. // The second line contains name of the main directory where
  33. // uninstall log files are to be placed.
  34. // Subsequent lines contain files to be copied (but not directories).
  35. // For example, filelist.txt might contain:
  36. // AFPL Ghostscript 6.50
  37. // gs6.50
  38. // gs6.50\bin\gsdll32.dll
  39. // gs6.50\lib\gs_init.ps
  40. // The file fontlist.txt might contain:
  41. // AFPL Ghostscript Fonts
  42. // fonts
  43. // fonts\n019003l.pfb
  44. // fonts\n019023l.pfb
  45. //
  46. // The default install directory is c:\gs.
  47. // The default Start Menu Folder is Ghostscript.
  48. // These are set in the resources.
  49. // The setup program will create the following uninstall log files
  50. // c:\gs\gs#.##\uninstal.txt
  51. // c:\gs\fonts\uninstal.txt
  52. // The uninstall program (accessed through control panel) will not
  53. // remove directories nor will it remove itself.
  54. //
  55. // If the install directory is the same as the current file
  56. // location, no files will be copied, but the existence of each file
  57. // will be checked. This allows the archive to be unzipped, then
  58. // configured in its current location. Running the uninstall will not
  59. // remove uninstgs.exe, setupgs.exe, filelist.txt or fontlist.txt.
  60. #define STRICT
  61. #include <windows.h>
  62. #include <shellapi.h>
  63. #include <objbase.h>
  64. #include <shlobj.h>
  65. #include <stdio.h>
  66. #include <direct.h>
  67. #ifdef MAX_PATH
  68. #define MAXSTR MAX_PATH
  69. #else
  70. #define MAXSTR 256
  71. #endif
  72. #include "dwsetup.h"
  73. #include "dwinst.h"
  74. extern "C" {
  75. typedef HRESULT (WINAPI *PFN_SHGetFolderPath)(
  76. HWND hwndOwner,
  77. int nFolder,
  78. HANDLE hToken,
  79. DWORD dwFlags,
  80. LPSTR pszPath);
  81. typedef BOOL (WINAPI *PFN_SHGetSpecialFolderPath)(
  82. HWND hwndOwner,
  83. LPTSTR lpszPath,
  84. int nFolder,
  85. BOOL fCreate);
  86. }
  87. //#define DEBUG
  88. #define UNINSTALLPROG "uninstgs.exe"
  89. /////////////////////////////////
  90. // Globals
  91. CInstall cinst;
  92. // TRUE = Place Start Menu items in All Users.
  93. // FALSE = Current User
  94. BOOL g_bUseCommon;
  95. // TRUE = Destination is the same as Source, so don't copy files.
  96. BOOL g_bNoCopy;
  97. // Source directory, usually a temporary directory created by
  98. // unzip self extractor.
  99. CHAR g_szSourceDir[MAXSTR];
  100. // Target directory for program and fonts.
  101. // Default loaded from resources
  102. CHAR g_szTargetDir[MAXSTR];
  103. // Target Group for shortcut.
  104. // Default loaded from resources
  105. CHAR g_szTargetGroup[MAXSTR];
  106. // Setup application name, loaded from resources
  107. CHAR g_szAppName[MAXSTR];
  108. BOOL g_bInstallFonts = TRUE;
  109. BOOL g_bCJKFonts = FALSE;
  110. BOOL g_bAllUsers = FALSE;
  111. HWND g_hMain; // Main install dialog
  112. HWND g_hWndText; // Install log dialog
  113. HINSTANCE g_hInstance;
  114. // If a directory is listed on the command line, g_bBatch will
  115. // be TRUE and a silent install will occur.
  116. BOOL g_bBatch = FALSE;
  117. BOOL g_bQuit = FALSE; // TRUE = Get out of message loop.
  118. BOOL g_bError = FALSE; // TRUE = Install was not successful
  119. BOOL is_winnt = FALSE; // Disable "All Users" if not NT.
  120. #ifdef _WIN64
  121. #define DLGRETURN INT_PTR
  122. #else
  123. #define DLGRETURN BOOL
  124. #endif
  125. // Prototypes
  126. DLGRETURN CALLBACK MainDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  127. void gs_addmess_count(const char *str, int count);
  128. void gs_addmess(const char *str);
  129. void gs_addmess_update(void);
  130. BOOL init();
  131. BOOL install_all();
  132. BOOL install_prog();
  133. BOOL install_fonts();
  134. BOOL make_filelist(int argc, char *argv[]);
  135. int get_font_path(char *path, unsigned int pathlen);
  136. BOOL write_cidfmap(const char *gspath, const char *cidpath);
  137. BOOL GetProgramFiles(LPTSTR path);
  138. //////////////////////////////////////////////////////////////////////
  139. // Entry point
  140. //////////////////////////////////////////////////////////////////////
  141. int APIENTRY WinMain(HINSTANCE hInstance,
  142. HINSTANCE hPrevInstance,
  143. LPSTR lpCmdLine,
  144. int nCmdShow)
  145. {
  146. MSG msg;
  147. g_hInstance = hInstance;
  148. if (!init()) {
  149. MessageBox(HWND_DESKTOP, "Initialisation failed",
  150. g_szAppName, MB_OK);
  151. return 1;
  152. }
  153. if (!g_bBatch) {
  154. while (GetMessage(&msg, (HWND)NULL, 0, 0)) {
  155. if (!IsDialogMessage(g_hWndText, &msg) &&
  156. !IsDialogMessage(g_hMain, &msg)) {
  157. TranslateMessage(&msg);
  158. DispatchMessage(&msg);
  159. }
  160. }
  161. DestroyWindow(g_hMain);
  162. }
  163. return (g_bError ? 1 : 0);
  164. }
  165. //////////////////////////////////////////////////////////////////////
  166. // Text log window
  167. //////////////////////////////////////////////////////////////////////
  168. #define TWLENGTH 32768
  169. #define TWSCROLL 1024
  170. char twbuf[TWLENGTH];
  171. int twend;
  172. // Modeless Dialog Box
  173. DLGRETURN CALLBACK
  174. TextWinDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  175. {
  176. switch(message) {
  177. case WM_INITDIALOG:
  178. EnableWindow(g_hMain, FALSE);
  179. return TRUE;
  180. case WM_COMMAND:
  181. switch(LOWORD(wParam)) {
  182. case IDC_TEXTWIN_COPY:
  183. {HGLOBAL hglobal;
  184. LPSTR p;
  185. DWORD result;
  186. int start, end;
  187. result = SendDlgItemMessage(hwnd, IDC_TEXTWIN_MLE, EM_GETSEL, (WPARAM)0, (LPARAM)0);
  188. start = LOWORD(result);
  189. end = HIWORD(result);
  190. if (start == end) {
  191. start = 0;
  192. end = twend;
  193. }
  194. hglobal = GlobalAlloc(GHND | GMEM_SHARE, end-start+1);
  195. if (hglobal == (HGLOBAL)NULL) {
  196. MessageBeep(-1);
  197. return(FALSE);
  198. }
  199. p = (char *)GlobalLock(hglobal);
  200. if (p == (LPSTR)NULL) {
  201. MessageBeep(-1);
  202. return(FALSE);
  203. }
  204. lstrcpyn(p, twbuf+start, end-start);
  205. GlobalUnlock(hglobal);
  206. OpenClipboard(hwnd);
  207. EmptyClipboard();
  208. SetClipboardData(CF_TEXT, hglobal);
  209. CloseClipboard();
  210. }
  211. break;
  212. case IDCANCEL:
  213. g_bQuit = TRUE;
  214. DestroyWindow(hwnd);
  215. return TRUE;
  216. }
  217. break;
  218. case WM_CLOSE:
  219. DestroyWindow(hwnd);
  220. return TRUE;
  221. case WM_DESTROY:
  222. g_bQuit = TRUE;
  223. g_hWndText = (HWND)NULL;
  224. EnableWindow(g_hMain, TRUE);
  225. PostQuitMessage(0);
  226. break;
  227. }
  228. return FALSE;
  229. }
  230. // Add string to log window
  231. void
  232. gs_addmess_count(const char *str, int count)
  233. {
  234. const char *s;
  235. char *p;
  236. int i, lfcount;
  237. MSG msg;
  238. // we need to add \r after each \n, so count the \n's
  239. lfcount = 0;
  240. s = str;
  241. for (i=0; i<count; i++) {
  242. if (*s == '\n')
  243. lfcount++;
  244. s++;
  245. }
  246. if (count + lfcount >= TWSCROLL)
  247. return; // too large
  248. if (count + lfcount + twend >= TWLENGTH-1) {
  249. // scroll buffer
  250. twend -= TWSCROLL;
  251. memmove(twbuf, twbuf+TWSCROLL, twend);
  252. }
  253. p = twbuf+twend;
  254. for (i=0; i<count; i++) {
  255. if (*str == '\n') {
  256. *p++ = '\r';
  257. }
  258. *p++ = *str++;
  259. }
  260. twend += (count + lfcount);
  261. *(twbuf+twend) = '\0';
  262. // Update the dialog box
  263. if (g_bBatch)
  264. return;
  265. gs_addmess_update();
  266. while (PeekMessage(&msg, (HWND)NULL, 0, 0, PM_REMOVE)) {
  267. if (!IsDialogMessage(g_hWndText, &msg) &&
  268. !IsDialogMessage(g_hMain, &msg)) {
  269. TranslateMessage(&msg);
  270. DispatchMessage(&msg);
  271. }
  272. }
  273. }
  274. void
  275. gs_addmess(const char *str)
  276. {
  277. gs_addmess_count(str, lstrlen(str));
  278. }
  279. void
  280. gs_addmess_update(void)
  281. {
  282. HWND hwndmess = g_hWndText;
  283. if (g_bBatch)
  284. return;
  285. if (IsWindow(hwndmess)) {
  286. HWND hwndtext = GetDlgItem(hwndmess, IDC_TEXTWIN_MLE);
  287. DWORD linecount;
  288. SendMessage(hwndtext, WM_SETREDRAW, FALSE, 0);
  289. SetDlgItemText(hwndmess, IDC_TEXTWIN_MLE, twbuf);
  290. linecount = SendDlgItemMessage(hwndmess, IDC_TEXTWIN_MLE, EM_GETLINECOUNT, (WPARAM)0, (LPARAM)0);
  291. SendDlgItemMessage(hwndmess, IDC_TEXTWIN_MLE, EM_LINESCROLL, (WPARAM)0, (LPARAM)linecount-14);
  292. SendMessage(hwndtext, WM_SETREDRAW, TRUE, 0);
  293. InvalidateRect(hwndtext, (LPRECT)NULL, TRUE);
  294. UpdateWindow(hwndtext);
  295. }
  296. }
  297. //////////////////////////////////////////////////////////////////////
  298. // Browse dialog box
  299. //////////////////////////////////////////////////////////////////////
  300. // nasty GLOBALS
  301. char szFolderName[MAXSTR];
  302. char szDirName[MAXSTR];
  303. DLGRETURN CALLBACK
  304. DirDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  305. {
  306. WORD notify_message;
  307. switch(message) {
  308. case WM_INITDIALOG:
  309. DlgDirList(hwnd, szDirName, IDC_FILES, IDC_FOLDER,
  310. DDL_DRIVES | DDL_DIRECTORY);
  311. SetDlgItemText(hwnd, IDC_TARGET, szFolderName);
  312. return FALSE;
  313. case WM_COMMAND:
  314. notify_message = HIWORD(wParam);
  315. switch (LOWORD(wParam)) {
  316. case IDC_FILES:
  317. if (notify_message == LBN_DBLCLK) {
  318. CHAR szPath[MAXSTR];
  319. DlgDirSelectEx(hwnd, szPath, sizeof(szPath), IDC_FILES);
  320. DlgDirList(hwnd, szPath, IDC_FILES, IDC_FOLDER,
  321. DDL_DRIVES | DDL_DIRECTORY);
  322. }
  323. return FALSE;
  324. case IDOK:
  325. GetDlgItemText(hwnd, IDC_FOLDER, szDirName, sizeof(szDirName));
  326. GetDlgItemText(hwnd, IDC_TARGET, szFolderName, sizeof(szFolderName));
  327. EndDialog(hwnd, TRUE);
  328. return TRUE;
  329. case IDCANCEL:
  330. EndDialog(hwnd, FALSE);
  331. return TRUE;
  332. }
  333. return FALSE;
  334. }
  335. return FALSE;
  336. }
  337. //////////////////////////////////////////////////////////////////////
  338. // Initialisation and Main dialog box
  339. //////////////////////////////////////////////////////////////////////
  340. void
  341. message_box(const char *str)
  342. {
  343. MessageBox(HWND_DESKTOP, str, g_szAppName, MB_OK);
  344. }
  345. BOOL
  346. init()
  347. {
  348. DWORD dwVersion = GetVersion();
  349. // get source directory
  350. GetCurrentDirectory(sizeof(g_szSourceDir), g_szSourceDir);
  351. // load strings
  352. LoadString(g_hInstance, IDS_APPNAME, g_szAppName, sizeof(g_szAppName));
  353. LoadString(g_hInstance, IDS_TARGET_GROUP,
  354. g_szTargetGroup, sizeof(g_szTargetGroup));
  355. if (LOBYTE(LOWORD(dwVersion)) < 4) {
  356. MessageBox(HWND_DESKTOP,
  357. "This install program needs Windows 4.0 or later",
  358. g_szAppName, MB_OK);
  359. return FALSE;
  360. }
  361. if ( (HIWORD(dwVersion) & 0x8000) == 0)
  362. is_winnt = TRUE;
  363. cinst.SetMessageFunction(message_box);
  364. #define MAXCMDTOKENS 128
  365. int argc;
  366. LPSTR argv[MAXCMDTOKENS];
  367. LPSTR p;
  368. char command[256];
  369. char *args;
  370. char *d, *e;
  371. p = GetCommandLine();
  372. argc = 0;
  373. args = (char *)malloc(lstrlen(p)+1);
  374. if (args == (char *)NULL)
  375. return 1;
  376. // Parse command line handling quotes.
  377. d = args;
  378. while (*p) {
  379. // for each argument
  380. if (argc >= MAXCMDTOKENS - 1)
  381. break;
  382. e = d;
  383. while ((*p) && (*p != ' ')) {
  384. if (*p == '\042') {
  385. // Remove quotes, skipping over embedded spaces.
  386. // Doesn't handle embedded quotes.
  387. p++;
  388. while ((*p) && (*p != '\042'))
  389. *d++ =*p++;
  390. }
  391. else
  392. *d++ = *p;
  393. if (*p)
  394. p++;
  395. }
  396. *d++ = '\0';
  397. argv[argc++] = e;
  398. while ((*p) && (*p == ' '))
  399. p++; // Skip over trailing spaces
  400. }
  401. argv[argc] = NULL;
  402. if (strlen(argv[0]) == 0) {
  403. GetModuleFileName(g_hInstance, command, sizeof(command)-1);
  404. argv[0] = command;
  405. }
  406. if (argc > 2) {
  407. // Probably creating filelist.txt
  408. return make_filelist(argc, argv);
  409. }
  410. // check if batch mode requested
  411. // get location of target directory from command line as argv[1]
  412. if (argc == 2) {
  413. strncpy(g_szTargetDir, argv[1], sizeof(g_szTargetDir));
  414. g_bBatch = TRUE;
  415. if (is_winnt)
  416. g_bAllUsers = TRUE;
  417. }
  418. if (g_bBatch) {
  419. if (!install_all()) {
  420. // display log showing error
  421. g_bBatch = FALSE;
  422. g_hWndText = CreateDialogParam(g_hInstance,
  423. MAKEINTRESOURCE(IDD_TEXTWIN),
  424. (HWND)HWND_DESKTOP, TextWinDlgProc,
  425. (LPARAM)NULL);
  426. gs_addmess_update();
  427. }
  428. return TRUE;
  429. }
  430. // Interactive setup
  431. if (!GetProgramFiles(g_szTargetDir))
  432. strcpy(g_szTargetDir, "C:\\Program Files");
  433. strcat(g_szTargetDir, "\\");
  434. LoadString(g_hInstance, IDS_TARGET_DIR,
  435. g_szTargetDir+strlen(g_szTargetDir),
  436. sizeof(g_szTargetDir)-strlen(g_szTargetDir));
  437. // main dialog box
  438. g_hMain = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_MAIN), (HWND)NULL, MainDlgProc, (LPARAM)NULL);
  439. // centre dialog on screen
  440. int width = GetSystemMetrics(SM_CXFULLSCREEN);
  441. int height = GetSystemMetrics(SM_CYFULLSCREEN);
  442. RECT rect;
  443. GetWindowRect(g_hMain, &rect);
  444. MoveWindow(g_hMain, (width - (rect.right - rect.left))/2,
  445. (height - (rect.bottom - rect.top))/2,
  446. (rect.right - rect.left),
  447. (rect.bottom - rect.top), FALSE);
  448. // initialize targets
  449. cinst.SetMessageFunction(message_box);
  450. if (!cinst.Init(g_szSourceDir, "filelist.txt"))
  451. return FALSE;
  452. SetDlgItemText(g_hMain, IDC_TARGET_DIR, g_szTargetDir);
  453. SetDlgItemText(g_hMain, IDC_TARGET_GROUP, g_szTargetGroup);
  454. SetDlgItemText(g_hMain, IDC_PRODUCT_NAME, cinst.GetUninstallName());
  455. SendDlgItemMessage(g_hMain, IDC_INSTALL_FONTS, BM_SETCHECK, BST_CHECKED, 0);
  456. ShowWindow(g_hMain, SW_SHOWNORMAL);
  457. return (g_hMain != (HWND)NULL); /* success */
  458. }
  459. // Main Modeless Dialog Box
  460. DLGRETURN CALLBACK
  461. MainDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  462. {
  463. switch(message) {
  464. case WM_INITDIALOG:
  465. EnableWindow(GetDlgItem(hwnd, IDC_ALLUSERS), is_winnt);
  466. return TRUE;
  467. case WM_COMMAND:
  468. switch(LOWORD(wParam)) {
  469. case IDC_README:
  470. {
  471. char buf[MAXSTR];
  472. sprintf(buf, "%s\\%s\\doc\\Readme.htm", g_szSourceDir,
  473. cinst.GetMainDir());
  474. ShellExecute(hwnd, NULL, buf, NULL, g_szSourceDir,
  475. SW_SHOWNORMAL);
  476. }
  477. return TRUE;
  478. case IDC_BROWSE_DIR:
  479. { char dir[MAXSTR];
  480. char *p;
  481. GetDlgItemText(hwnd, IDC_TARGET_DIR, dir, sizeof(dir));
  482. strcpy(szDirName, dir);
  483. if ( (p = strrchr(szDirName, '\\')) != (char *)NULL ) {
  484. strcpy(szFolderName, p+1);
  485. if (p == szDirName+2)
  486. p++; // step over c:\ //
  487. *p = '\0';
  488. }
  489. else {
  490. strcpy(szDirName, "c:\\");
  491. strcpy(szFolderName, dir);
  492. }
  493. if (DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_DIRDLG),
  494. hwnd, DirDlgProc)) {
  495. strcpy(dir, szDirName);
  496. if (strlen(dir) && (dir[strlen(dir)-1] != '\\'))
  497. strcat(dir, "\\");
  498. strcat(dir, szFolderName);
  499. SetDlgItemText(hwnd, IDC_TARGET_DIR, dir);
  500. }
  501. }
  502. return TRUE;
  503. case IDC_BROWSE_GROUP:
  504. { char dir[MAXSTR];
  505. char programs[MAXSTR];
  506. char *p;
  507. GetDlgItemText(hwnd, IDC_TARGET_GROUP, dir, sizeof(dir));
  508. cinst.GetPrograms(
  509. SendDlgItemMessage(hwnd, IDC_ALLUSERS,
  510. BM_GETCHECK, 0, 0) == BST_CHECKED,
  511. programs, sizeof(programs));
  512. strcpy(szDirName, programs);
  513. strcpy(szFolderName, dir);
  514. if (DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_DIRDLG),
  515. hwnd, DirDlgProc)) {
  516. strcpy(dir, szFolderName);
  517. p = szDirName;
  518. if (strnicmp(szDirName, programs,
  519. strlen(programs)) == 0) {
  520. p += strlen(programs);
  521. if (*p == '\\')
  522. p++;
  523. strcpy(dir, p);
  524. if (strlen(dir) &&
  525. (dir[strlen(dir)-1] != '\\'))
  526. strcat(dir, "\\");
  527. strcat(dir, szFolderName);
  528. }
  529. SetDlgItemText(hwnd, IDC_TARGET_GROUP, dir);
  530. }
  531. }
  532. return TRUE;
  533. case IDCANCEL:
  534. PostQuitMessage(0);
  535. return TRUE;
  536. case IDC_INSTALL:
  537. GetDlgItemText(hwnd, IDC_TARGET_DIR,
  538. g_szTargetDir, sizeof(g_szTargetDir));
  539. GetDlgItemText(hwnd, IDC_TARGET_GROUP,
  540. g_szTargetGroup, sizeof(g_szTargetGroup));
  541. g_bInstallFonts = (SendDlgItemMessage(g_hMain,
  542. IDC_INSTALL_FONTS, BM_GETCHECK, 0, 0)
  543. == BST_CHECKED);
  544. g_bCJKFonts = (SendDlgItemMessage(g_hMain,
  545. IDC_CJK_FONTS, BM_GETCHECK, 0, 0)
  546. == BST_CHECKED);
  547. g_bAllUsers = (SendDlgItemMessage(hwnd,
  548. IDC_ALLUSERS, BM_GETCHECK, 0, 0
  549. ) == BST_CHECKED);
  550. // install log dialog box
  551. g_hWndText = CreateDialogParam(g_hInstance,
  552. MAKEINTRESOURCE(IDD_TEXTWIN),
  553. (HWND)hwnd, TextWinDlgProc, (LPARAM)NULL);
  554. EnableWindow(GetDlgItem(hwnd, IDC_INSTALL), FALSE);
  555. if (install_all())
  556. PostQuitMessage(0);
  557. return TRUE;
  558. default:
  559. return(FALSE);
  560. }
  561. case WM_CLOSE:
  562. PostQuitMessage(0);
  563. return TRUE;
  564. }
  565. return FALSE;
  566. }
  567. // install program and files
  568. BOOL
  569. install_all()
  570. {
  571. gs_addmess("Source Directory=");
  572. gs_addmess(g_szSourceDir);
  573. gs_addmess("\n");
  574. gs_addmess("Target Directory=");
  575. gs_addmess(g_szTargetDir);
  576. gs_addmess("\n");
  577. gs_addmess("Target Shell Folder=");
  578. gs_addmess(g_szTargetGroup);
  579. gs_addmess("\n");
  580. gs_addmess(g_bAllUsers ? " All users\n" : " Current user\n");
  581. if (stricmp(g_szSourceDir, g_szTargetDir) == 0) {
  582. // Don't copy files
  583. if (!g_bBatch)
  584. if (::MessageBox(g_hWndText, "Install location is the same as the current file location. No files will be copied.", g_szAppName, MB_OKCANCEL)
  585. != IDOK) {
  586. return FALSE;
  587. }
  588. g_bNoCopy = TRUE;
  589. }
  590. if (g_bQuit)
  591. return FALSE;
  592. if (!install_prog()) {
  593. cinst.CleanUp();
  594. g_bError = TRUE;
  595. return FALSE;
  596. }
  597. if (g_bInstallFonts && !install_fonts()) {
  598. cinst.CleanUp();
  599. g_bError = TRUE;
  600. return FALSE;
  601. }
  602. gs_addmess("Install successful\n");
  603. // show start menu folder
  604. if (!g_bBatch) {
  605. char szFolder[MAXSTR];
  606. szFolder[0] = '\0';
  607. cinst.GetPrograms(g_bAllUsers, szFolder, sizeof(szFolder));
  608. strcat(szFolder, "\\");
  609. strcat(szFolder, g_szTargetGroup);
  610. ShellExecute(HWND_DESKTOP, "open", szFolder,
  611. NULL, NULL, SW_SHOWNORMAL);
  612. }
  613. #ifdef DEBUG
  614. return FALSE;
  615. #endif
  616. return TRUE;
  617. }
  618. BOOL
  619. install_prog()
  620. {
  621. char *regkey1 = "AFPL Ghostscript";
  622. char regkey2[16];
  623. char szDLL[MAXSTR];
  624. char szLIB[MAXSTR+MAXSTR];
  625. char szProgram[MAXSTR];
  626. char szArguments[MAXSTR];
  627. char szDescription[MAXSTR];
  628. char szDotVersion[MAXSTR];
  629. char szPlatformSuffix[MAXSTR];
  630. const char *pSuffix = "";
  631. if (g_bQuit)
  632. return FALSE;
  633. cinst.SetMessageFunction(gs_addmess);
  634. cinst.SetTargetDir(g_szTargetDir);
  635. cinst.SetTargetGroup(g_szTargetGroup);
  636. cinst.SetAllUsers(g_bAllUsers);
  637. if (!cinst.Init(g_szSourceDir, "filelist.txt"))
  638. return FALSE;
  639. // Get GS version number
  640. gs_addmess("Installing Program...\n");
  641. int nGSversion = 0;
  642. const char *p = cinst.GetMainDir();
  643. while (*p && !isdigit(*p)) // skip over "gs" prefix
  644. p++;
  645. if (strlen(p) == 4)
  646. nGSversion = (p[0]-'0')*100 + (p[2]-'0')*10 + (p[3]-'0');
  647. else if (strlen(p) == 3)
  648. nGSversion = (p[0]-'0')*100 + (p[2]-'0')*10;
  649. strncpy(szDotVersion, p, sizeof(szDotVersion));
  650. strncpy(regkey2, szDotVersion, sizeof(regkey2));
  651. // copy files
  652. if (!cinst.InstallFiles(g_bNoCopy, &g_bQuit)) {
  653. gs_addmess("Program install failed\n");
  654. return FALSE;
  655. }
  656. if (g_bQuit)
  657. return FALSE;
  658. // write registry entries
  659. gs_addmess("Updating Registry\n");
  660. if (!cinst.UpdateRegistryBegin()) {
  661. gs_addmess("Failed to begin registry update\n");
  662. return FALSE;
  663. }
  664. if (!cinst.UpdateRegistryKey(regkey1, regkey2)) {
  665. gs_addmess("Failed to open/create registry application key\n");
  666. return FALSE;
  667. }
  668. strcpy(szDLL, g_szTargetDir);
  669. strcat(szDLL, "\\");
  670. strcat(szDLL, cinst.GetMainDir());
  671. strcat(szDLL, "\\bin\\gsdll32.dll");
  672. if (!cinst.UpdateRegistryValue(regkey1, regkey2, "GS_DLL", szDLL)) {
  673. gs_addmess("Failed to add registry value\n");
  674. return FALSE;
  675. }
  676. strcpy(szLIB, g_szTargetDir);
  677. strcat(szLIB, "\\");
  678. strcat(szLIB, cinst.GetMainDir());
  679. strcat(szLIB, "\\lib;");
  680. strcat(szLIB, g_szTargetDir);
  681. strcat(szLIB, "\\fonts;");
  682. strcat(szLIB, g_szTargetDir);
  683. strcat(szLIB, "\\");
  684. strcat(szLIB, cinst.GetMainDir());
  685. strcat(szLIB, "\\Resource");
  686. if (g_bCJKFonts) {
  687. strcat(szLIB, ";");
  688. get_font_path(szLIB+strlen(szLIB), sizeof(szLIB)-strlen(szLIB)-1);
  689. }
  690. if (!cinst.UpdateRegistryValue(regkey1, regkey2, "GS_LIB", szLIB)) {
  691. gs_addmess("Failed to add registry value\n");
  692. return FALSE;
  693. }
  694. if (!cinst.UpdateRegistryEnd()) {
  695. gs_addmess("Failed to end registry update\n");
  696. return FALSE;
  697. }
  698. if (g_bQuit)
  699. return FALSE;
  700. // Add Start Menu items
  701. gs_addmess("Adding Start Menu items\n");
  702. memset(szPlatformSuffix, 0, sizeof(szPlatformSuffix));
  703. if (GetProgramFiles(szPlatformSuffix)) {
  704. /* If ProgramFiles has a suffix like " (x86)" then use
  705. * it for Start menu entries to distinguish between
  706. * 32-bit and 64-bit programs.
  707. */
  708. for (pSuffix = szPlatformSuffix; *pSuffix; pSuffix++)
  709. if ((pSuffix[0] == ' ') && (pSuffix[1] == '('))
  710. break;
  711. }
  712. else {
  713. pSuffix = "";
  714. }
  715. if (!cinst.StartMenuBegin()) {
  716. gs_addmess("Failed to begin Start Menu update\n");
  717. return FALSE;
  718. }
  719. strcpy(szProgram, g_szTargetDir);
  720. strcat(szProgram, "\\");
  721. strcat(szProgram, cinst.GetMainDir());
  722. strcat(szProgram, "\\bin\\gswin32.exe");
  723. strcpy(szArguments, "\042-I");
  724. strcat(szArguments, szLIB);
  725. strcat(szArguments, "\042");
  726. sprintf(szDescription, "Ghostscript %s%s", szDotVersion, pSuffix);
  727. if (!cinst.StartMenuAdd(szDescription, szProgram, szArguments)) {
  728. gs_addmess("Failed to add Start Menu item\n");
  729. return FALSE;
  730. }
  731. strcpy(szProgram, g_szTargetDir);
  732. strcat(szProgram, "\\");
  733. strcat(szProgram, cinst.GetMainDir());
  734. strcat(szProgram, "\\doc\\Readme.htm");
  735. sprintf(szDescription, "Ghostscript Readme %s%s",
  736. szDotVersion, pSuffix);
  737. if (!cinst.StartMenuAdd(szDescription, szProgram, NULL)) {
  738. gs_addmess("Failed to add Start Menu item\n");
  739. return FALSE;
  740. }
  741. if (!cinst.StartMenuEnd()) {
  742. gs_addmess("Failed to end Start Menu update\n");
  743. return FALSE;
  744. }
  745. /* Create lib/cidfmap */
  746. if (g_bCJKFonts) {
  747. FILE *f;
  748. char szCIDFmap[MAXSTR];
  749. char szCIDFmap_bak[MAXSTR];
  750. char szGSPATH[MAXSTR];
  751. /* backup old cidfmap */
  752. strcpy(szCIDFmap, g_szTargetDir);
  753. strcat(szCIDFmap, "\\");
  754. strcat(szCIDFmap, cinst.GetMainDir());
  755. strcat(szCIDFmap, "\\lib\\cidfmap");
  756. strcpy(szCIDFmap_bak, szCIDFmap);
  757. strcat(szCIDFmap_bak, ".bak");
  758. gs_addmess("Backing up\n ");
  759. gs_addmess(szCIDFmap);
  760. gs_addmess("\nto\n ");
  761. gs_addmess(szCIDFmap_bak);
  762. gs_addmess("\n");
  763. rename(szCIDFmap, szCIDFmap_bak);
  764. /* mark backup for uninstall */
  765. cinst.AppendFileNew(szCIDFmap_bak);
  766. /* write new cidfmap */
  767. gs_addmess("Writing cidfmap\n ");
  768. gs_addmess(szCIDFmap);
  769. gs_addmess("\n");
  770. strcpy(szGSPATH, g_szTargetDir);
  771. strcat(szGSPATH, "\\");
  772. strcat(szGSPATH, cinst.GetMainDir());
  773. if (!write_cidfmap(szGSPATH, szCIDFmap)) {
  774. gs_addmess("Failed to write cidfmap\n");
  775. return FALSE;
  776. }
  777. }
  778. // consolidate logs into one uninstall file
  779. if (cinst.MakeLog()) {
  780. // add uninstall entry for "Add/Remove Programs"
  781. gs_addmess("Adding uninstall program\n");
  782. if (!cinst.WriteUninstall(UNINSTALLPROG, g_bNoCopy)) {
  783. gs_addmess("Failed to write uninstall entry\n");
  784. return FALSE;
  785. }
  786. }
  787. else {
  788. gs_addmess("Failed to write uninstall log\n");
  789. // If batch install, files might be on a server
  790. // in a write protected directory.
  791. // Don't return an error for batch install.
  792. if (g_bBatch)
  793. return TRUE;
  794. return FALSE;
  795. }
  796. gs_addmess("Program install successful\n");
  797. return TRUE;
  798. }
  799. BOOL
  800. install_fonts()
  801. {
  802. cinst.SetMessageFunction(gs_addmess);
  803. cinst.SetTargetDir(g_szTargetDir);
  804. cinst.SetTargetGroup(g_szTargetGroup);
  805. cinst.SetAllUsers(g_bAllUsers);
  806. if (!cinst.Init(g_szSourceDir, "fontlist.txt"))
  807. return FALSE;
  808. // copy files
  809. if (!cinst.InstallFiles(g_bNoCopy, &g_bQuit)) {
  810. gs_addmess("Font install failed\n");
  811. return FALSE;
  812. }
  813. if (g_bQuit)
  814. return FALSE;
  815. if (g_bNoCopy) {
  816. // Don't write uninstall log or entry
  817. // since we didn't copy any files.
  818. cinst.CleanUp();
  819. }
  820. else {
  821. // consolidate logs into one uninstall file
  822. if (cinst.MakeLog()) {
  823. // add uninstall entry for "Add/Remove Programs"
  824. gs_addmess("Adding uninstall program\n");
  825. if (!cinst.WriteUninstall(UNINSTALLPROG, g_bNoCopy)) {
  826. gs_addmess("Failed to write uninstall entry\n");
  827. return FALSE;
  828. }
  829. }
  830. else {
  831. gs_addmess("Failed to write uninstall log\n");
  832. // If batch install, files might be on a server
  833. // in a write protected directory.
  834. // Don't return an error for batch install.
  835. if (g_bBatch)
  836. return TRUE;
  837. return FALSE;
  838. }
  839. }
  840. gs_addmess("Font install successful\n");
  841. return TRUE;
  842. }
  843. //////////////////////////////////////////////////////////////////////
  844. // Create lib/cidfmap based on installed fonts
  845. //////////////////////////////////////////////////////////////////////
  846. /* Get the path to enumerate for fonts */
  847. int
  848. get_font_path(char *path, unsigned int pathlen)
  849. {
  850. int i;
  851. int len = GetWindowsDirectory(path, pathlen);
  852. if (len == 0)
  853. return -1;
  854. if (pathlen - strlen(path) < 8)
  855. return -1;
  856. strncat(path, "/fonts", pathlen - strlen(path) - 7);
  857. for (i = strlen(path)-1; i >= 0; i--)
  858. if (path[i] == '\\')
  859. path[i] = '/';
  860. return len;
  861. }
  862. BOOL write_cidfmap(const char *gspath, const char *cidpath)
  863. {
  864. char fontpath[MAXSTR];
  865. char buf[4*MAXSTR];
  866. STARTUPINFO siStartInfo;
  867. PROCESS_INFORMATION piProcInfo;
  868. get_font_path(fontpath, sizeof(fontpath)-1);
  869. strcpy(buf, "\042");
  870. strcat(buf, gspath);
  871. strcat(buf, "\\bin\\gswin32c.exe\042 -q -dBATCH \042-sFONTDIR=");
  872. strcat(buf, fontpath);
  873. strcat(buf, "\042 \042");
  874. strcat(buf, "-sCIDFMAP=");
  875. strcat(buf, cidpath);
  876. strcat(buf, "\042 \042");
  877. strcat(buf, gspath);
  878. strcat(buf, "\\lib\\mkcidfm.ps\042");
  879. siStartInfo.cb = sizeof(STARTUPINFO);
  880. siStartInfo.lpReserved = NULL;
  881. siStartInfo.lpDesktop = NULL;
  882. siStartInfo.lpTitle = NULL; /* use executable name as title */
  883. siStartInfo.dwX = siStartInfo.dwY = CW_USEDEFAULT; /* ignored */
  884. siStartInfo.dwXSize = siStartInfo.dwYSize = CW_USEDEFAULT; /* ignored */
  885. siStartInfo.dwXCountChars = 80;
  886. siStartInfo.dwYCountChars = 25;
  887. siStartInfo.dwFillAttribute = 0; /* ignored */
  888. siStartInfo.dwFlags = STARTF_USESHOWWINDOW;
  889. siStartInfo.wShowWindow = SW_HIDE;
  890. siStartInfo.cbReserved2 = 0;
  891. siStartInfo.lpReserved2 = NULL;
  892. siStartInfo.hStdInput = NULL;
  893. siStartInfo.hStdOutput = NULL;
  894. siStartInfo.hStdError = NULL;
  895. /* Create the child process. */
  896. if (!CreateProcess(NULL,
  897. (char *)buf, /* command line */
  898. NULL, /* process security attributes */
  899. NULL, /* primary thread security attributes */
  900. FALSE, /* handles are not inherited */
  901. 0, /* creation flags */
  902. NULL, /* environment */
  903. NULL, /* use parent's current directory */
  904. &siStartInfo, /* STARTUPINFO pointer */
  905. &piProcInfo)) /* receives PROCESS_INFORMATION */
  906. return FALSE;
  907. /* We don't care if ghostscript fails, so just return */
  908. CloseHandle(piProcInfo.hProcess);
  909. CloseHandle(piProcInfo.hThread);
  910. return TRUE;
  911. }
  912. //////////////////////////////////////////////////////////////////////
  913. // Create file list
  914. //////////////////////////////////////////////////////////////////////
  915. FILE *fList;
  916. typedef int (*PFN_dodir)(const char *name);
  917. /* Called once for each directory */
  918. int
  919. dodir(const char *filename)
  920. {
  921. return 0;
  922. }
  923. /* Called once for each file */
  924. int
  925. dofile(const char *filename)
  926. {
  927. if (fList != (FILE *)NULL) {
  928. fputs(filename, fList);
  929. fputs("\n", fList);
  930. }
  931. return 0;
  932. }
  933. /* Walk through directory 'path', calling dodir() for given directory
  934. * and dofile() for each file.
  935. * If recurse=1, recurse into subdirectories, calling dodir() for
  936. * each directory.
  937. */
  938. int
  939. dirwalk(char *path, int recurse, PFN_dodir dodir, PFN_dodir dofile)
  940. {
  941. WIN32_FIND_DATA find_data;
  942. HANDLE find_handle;
  943. char pattern[MAXSTR]; /* orig pattern + modified pattern */
  944. char base[MAXSTR];
  945. char name[MAXSTR];
  946. BOOL bMore = TRUE;
  947. char *p;
  948. if (path) {
  949. strcpy(pattern, path);
  950. if (strlen(pattern) != 0) {
  951. p = pattern + strlen(pattern) -1;
  952. if (*p == '\\')
  953. *p = '\0'; // truncate trailing backslash
  954. }
  955. strcpy(base, pattern);
  956. if (strchr(base, '*') != NULL) {
  957. // wildcard already included
  958. // truncate it from the base path
  959. if ( (p = strrchr(base, '\\')) != NULL )
  960. *(++p) = '\0';
  961. }
  962. else if (isalpha(pattern[0]) &&
  963. pattern[1]==':' && pattern[2]=='\0') {
  964. strcat(pattern, "\\*"); // search entire disk
  965. strcat(base, "\\");
  966. }
  967. else {
  968. // wildcard NOT included
  969. // check to see if path is a directory
  970. find_handle = FindFirstFile(pattern, &find_data);
  971. if (find_handle != INVALID_HANDLE_VALUE) {
  972. FindClose(find_handle);
  973. if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  974. strcat(pattern, "\\*"); // yes, search files
  975. strcat(base, "\\");
  976. }
  977. else {
  978. dofile(path); // no, return just this file
  979. return 0;
  980. }
  981. }
  982. else
  983. return 1; // path invalid
  984. }
  985. }
  986. else {
  987. base[0] = '\0';
  988. strcpy(pattern, "*");
  989. }
  990. find_handle = FindFirstFile(pattern, &find_data);
  991. if (find_handle == INVALID_HANDLE_VALUE)
  992. return 1;
  993. while (bMore) {
  994. strcpy(name, base);
  995. strcat(name, find_data.cFileName);
  996. if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  997. if ( strcmp(find_data.cFileName, ".") &&
  998. strcmp(find_data.cFileName, "..") ) {
  999. dodir(name);
  1000. if (recurse)
  1001. dirwalk(name, recurse, dodir, dofile);
  1002. }
  1003. }
  1004. else {
  1005. dofile(name);
  1006. }
  1007. bMore = FindNextFile(find_handle, &find_data);
  1008. }
  1009. FindClose(find_handle);
  1010. return 0;
  1011. }
  1012. // This is used when creating a file list.
  1013. BOOL make_filelist(int argc, char *argv[])
  1014. {
  1015. char *title = NULL;
  1016. char *dir = NULL;
  1017. char *list = NULL;
  1018. int i;
  1019. g_bBatch = TRUE; // Don't run message loop
  1020. for (i=1; i<argc; i++) {
  1021. if (strcmp(argv[i], "-title") == 0) {
  1022. i++;
  1023. title = argv[i];
  1024. }
  1025. else if (strcmp(argv[i], "-dir") == 0) {
  1026. i++;
  1027. dir = argv[i];
  1028. }
  1029. else if (strcmp(argv[i], "-list") == 0) {
  1030. i++;
  1031. list = argv[i];
  1032. }
  1033. else {
  1034. if ((title == NULL) || (strlen(title) == 0) ||
  1035. (dir == NULL) || (strlen(dir) == 0) ||
  1036. (list == NULL) || (strlen(list) == 0)) {
  1037. message_box("Usage: setupgs -title \042AFPL Ghostscript #.##\042 -dir \042gs#.##\042 -list \042filelist.txt\042 spec1 spec2 specn\n");
  1038. return FALSE;
  1039. }
  1040. if (fList == (FILE *)NULL) {
  1041. if ( (fList = fopen(list, "w")) == (FILE *)NULL ) {
  1042. message_box("Can't write list file\n");
  1043. return FALSE;
  1044. }
  1045. fputs(title, fList);
  1046. fputs("\n", fList);
  1047. fputs(dir, fList);
  1048. fputs("\n", fList);
  1049. }
  1050. if (argv[i][0] == '@') {
  1051. // Use @filename with list of files/directories
  1052. // to avoid DOS command line limit
  1053. FILE *f;
  1054. char buf[MAXSTR];
  1055. int j;
  1056. if ( (f = fopen(&(argv[i][1]), "r")) != (FILE *)NULL) {
  1057. while (fgets(buf, sizeof(buf), f)) {
  1058. // remove trailing newline and spaces
  1059. while ( ((j = strlen(buf)-1) >= 0) &&
  1060. ((buf[j] == '\n') || (buf[j] == ' ')) )
  1061. buf[j] = '\0';
  1062. dirwalk(buf, TRUE, &dodir, &dofile);
  1063. }
  1064. fclose(f);
  1065. }
  1066. else {
  1067. wsprintf(buf, "Can't open @ file \042%s\042",
  1068. &argv[i][1]);
  1069. message_box(buf);
  1070. }
  1071. }
  1072. else
  1073. dirwalk(argv[i], TRUE, &dodir, &dofile);
  1074. }
  1075. }
  1076. if (fList != (FILE *)NULL) {
  1077. fclose(fList);
  1078. fList = NULL;
  1079. }
  1080. return TRUE;
  1081. }
  1082. //////////////////////////////////////////////////////////////////////
  1083. #ifndef CSIDL_PROGRAM_FILES
  1084. #define CSIDL_PROGRAM_FILES 0x0026
  1085. #endif
  1086. #ifndef CSIDL_FLAG_CREATE
  1087. #define CSIDL_FLAG_CREATE 0x8000
  1088. #endif
  1089. #ifndef SHGFP_TYPE_CURRENT
  1090. #define SHGFP_TYPE_CURRENT 0
  1091. #endif
  1092. BOOL
  1093. GetProgramFiles(LPTSTR path)
  1094. {
  1095. PFN_SHGetSpecialFolderPath PSHGetSpecialFolderPath = NULL;
  1096. PFN_SHGetFolderPath PSHGetFolderPath = NULL;
  1097. HMODULE hModuleShell32 = NULL;
  1098. HMODULE hModuleShfolder = NULL;
  1099. BOOL fOk = FALSE;
  1100. hModuleShfolder = LoadLibrary("shfolder.dll");
  1101. hModuleShell32 = LoadLibrary("shell32.dll");
  1102. if (hModuleShfolder) {
  1103. PSHGetFolderPath = (PFN_SHGetFolderPath)
  1104. GetProcAddress(hModuleShfolder, "SHGetFolderPathA");
  1105. if (PSHGetFolderPath) {
  1106. fOk = (PSHGetFolderPath(HWND_DESKTOP,
  1107. CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE,
  1108. NULL, SHGFP_TYPE_CURRENT, path) == S_OK);
  1109. }
  1110. }
  1111. if (!fOk && hModuleShell32) {
  1112. PSHGetFolderPath = (PFN_SHGetFolderPath)
  1113. GetProcAddress(hModuleShell32, "SHGetFolderPathA");
  1114. if (PSHGetFolderPath) {
  1115. fOk = (PSHGetFolderPath(HWND_DESKTOP,
  1116. CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE,
  1117. NULL, SHGFP_TYPE_CURRENT, path) == S_OK);
  1118. }
  1119. }
  1120. if (!fOk && hModuleShell32) {
  1121. PSHGetSpecialFolderPath = (PFN_SHGetSpecialFolderPath)
  1122. GetProcAddress(hModuleShell32, "SHGetSpecialFolderPathA");
  1123. if (PSHGetSpecialFolderPath) {
  1124. fOk = PSHGetSpecialFolderPath(HWND_DESKTOP, path,
  1125. CSIDL_PROGRAM_FILES, TRUE);
  1126. }
  1127. }
  1128. if (!fOk) {
  1129. /* If all else fails (probably Win95), try the registry */
  1130. LONG rc;
  1131. HKEY hkey;
  1132. DWORD cbData;
  1133. DWORD keytype;
  1134. rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  1135. "SOFTWARE\\Microsoft\\Windows\\CurrentVersion", 0, KEY_READ, &hkey);
  1136. if (rc == ERROR_SUCCESS) {
  1137. cbData = MAX_PATH;
  1138. keytype = REG_SZ;
  1139. if (rc == ERROR_SUCCESS)
  1140. rc = RegQueryValueEx(hkey, "ProgramFilesDir", 0, &keytype,
  1141. (LPBYTE)path, &cbData);
  1142. RegCloseKey(hkey);
  1143. }
  1144. fOk = (rc == ERROR_SUCCESS);
  1145. }
  1146. return fOk;
  1147. }
  1148. //////////////////////////////////////////////////////////////////////