Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1185 lines
31 KiB

  1. /*****************************************************************************
  2. *
  3. * sdview.cpp
  4. *
  5. * Lame SD Viewer app.
  6. *
  7. *****************************************************************************/
  8. #include "sdview.h"
  9. HINSTANCE g_hinst;
  10. HCURSOR g_hcurWait;
  11. HCURSOR g_hcurArrow;
  12. HCURSOR g_hcurAppStarting;
  13. LONG g_lThreads;
  14. UINT g_wShowWindow;
  15. CGlobals GlobalSettings;
  16. /*****************************************************************************
  17. *
  18. * Stubs - will be filled in with goodies eventually
  19. *
  20. *****************************************************************************/
  21. DWORD CALLBACK CFileOut_ThreadProc(LPVOID lpParameter)
  22. {
  23. MessageBox(NULL, RECAST(LPTSTR, lpParameter), TEXT("fileout"), MB_OK);
  24. return EndThreadTask(0);
  25. }
  26. #if 0
  27. DWORD CALLBACK COpened_ThreadProc(LPVOID lpParameter)
  28. {
  29. MessageBox(NULL, RECAST(LPTSTR, lpParameter), TEXT("opened"), MB_OK);
  30. return EndThreadTask(0);
  31. }
  32. #endif
  33. /*****************************************************************************
  34. *
  35. * Eschew the C runtime. Also, bonus-initialize memory to zero.
  36. *
  37. *****************************************************************************/
  38. void * __cdecl operator new(size_t cb)
  39. {
  40. return RECAST(LPVOID, LocalAlloc(LPTR, cb));
  41. }
  42. void __cdecl operator delete(void *pv)
  43. {
  44. LocalFree(RECAST(HLOCAL, pv));
  45. }
  46. int __cdecl _purecall(void)
  47. {
  48. return 0;
  49. }
  50. /*****************************************************************************
  51. *
  52. * Assertion goo
  53. *
  54. *****************************************************************************/
  55. #ifdef DEBUG
  56. void AssertFailed(char *psz, char *pszFile, int iLine)
  57. {
  58. static BOOL fAsserting = FALSE;
  59. if (!fAsserting) {
  60. fAsserting = TRUE;
  61. String strTitle(TEXT("Assertion failed - "));
  62. strTitle << pszFile << TEXT(" - line ") << iLine;
  63. MessageBox(NULL, psz, strTitle, MB_OK);
  64. fAsserting = FALSE;
  65. }
  66. }
  67. #endif
  68. /*****************************************************************************
  69. *
  70. * LaunchThreadTask
  71. *
  72. *****************************************************************************/
  73. BOOL LaunchThreadTask(LPTHREAD_START_ROUTINE pfn, LPCTSTR pszArgs)
  74. {
  75. BOOL fSuccess = FALSE;
  76. LPTSTR psz = StrDup(pszArgs);
  77. if (psz) {
  78. InterlockedIncrement(&g_lThreads);
  79. if (_QueueUserWorkItem(pfn, CCAST(LPTSTR, psz), WT_EXECUTELONGFUNCTION)) {
  80. fSuccess = TRUE;
  81. } else {
  82. LocalFree(psz);
  83. InterlockedDecrement(&g_lThreads);
  84. }
  85. }
  86. return fSuccess;
  87. }
  88. /*****************************************************************************
  89. *
  90. * EndThreadTask
  91. *
  92. * When a task finishes, exit with "return EndThreadTask(dwExitCode)".
  93. * This decrements the count of active thread tasks and terminates
  94. * the process if this is the last one.
  95. *
  96. *****************************************************************************/
  97. DWORD
  98. EndThreadTask(DWORD dwExitCode)
  99. {
  100. if (InterlockedDecrement(&g_lThreads) <= 0) {
  101. ExitProcess(dwExitCode);
  102. }
  103. return dwExitCode;
  104. }
  105. /*****************************************************************************
  106. *
  107. * Listview stuff
  108. *
  109. *****************************************************************************/
  110. int ListView_GetCurSel(HWND hwnd)
  111. {
  112. return ListView_GetNextItem(hwnd, -1, LVNI_FOCUSED);
  113. }
  114. void ListView_SetCurSel(HWND hwnd, int iIndex)
  115. {
  116. ListView_SetItemState(hwnd, iIndex,
  117. LVIS_SELECTED | LVIS_FOCUSED,
  118. LVIS_SELECTED | LVIS_FOCUSED);
  119. }
  120. int ListView_GetSubItemText(HWND hwnd, int iItem, int iSubItem, LPTSTR pszBuf, int cch)
  121. {
  122. LVITEM lvi;
  123. lvi.iSubItem = iSubItem;
  124. lvi.pszText= pszBuf;
  125. lvi.cchTextMax = cch;
  126. return (int)::SendMessage(hwnd, LVM_GETITEMTEXT, iItem, RECAST(LPARAM, &lvi));
  127. }
  128. void ChangeTabsToSpaces(LPTSTR psz)
  129. {
  130. while ((psz = StrChr(psz, TEXT('\t'))) != NULL) *psz = TEXT(' ');
  131. }
  132. /*****************************************************************************
  133. *
  134. * LoadPopupMenu
  135. *
  136. *****************************************************************************/
  137. HMENU LoadPopupMenu(LPCTSTR pszMenu)
  138. {
  139. HMENU hmenuParent = LoadMenu(g_hinst, pszMenu);
  140. if (hmenuParent) {
  141. HMENU hmenuPopup = GetSubMenu(hmenuParent, 0);
  142. RemoveMenu(hmenuParent, 0, MF_BYPOSITION);
  143. DestroyMenu(hmenuParent);
  144. return hmenuPopup;
  145. } else {
  146. return NULL;
  147. }
  148. }
  149. /*****************************************************************************
  150. *
  151. * EnableDisableOrRemoveMenuItem
  152. *
  153. * Enable, disable or remove, accordingly.
  154. *
  155. *****************************************************************************/
  156. void EnableDisableOrRemoveMenuItem(HMENU hmenu, UINT id, BOOL fEnable, BOOL fDelete)
  157. {
  158. if (fEnable) {
  159. EnableMenuItem(hmenu, id, MF_BYCOMMAND | MF_ENABLED);
  160. } else if (fDelete) {
  161. DeleteMenu(hmenu, id, MF_BYCOMMAND);
  162. } else {
  163. EnableMenuItem(hmenu, id, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
  164. }
  165. }
  166. /*****************************************************************************
  167. *
  168. * MakeMenuPretty
  169. *
  170. * Remove separators at the top and at the bottom, and collapse
  171. * multiple consecutive separators.
  172. *
  173. *****************************************************************************/
  174. void MakeMenuPretty(HMENU hmenu)
  175. {
  176. BOOL fPrevSep = TRUE;
  177. int iCount = GetMenuItemCount(hmenu);
  178. for (int iItem = 0; iItem < iCount; iItem++) {
  179. UINT uiState = GetMenuState(hmenu, 0, MF_BYPOSITION);
  180. if (uiState & MF_SEPARATOR) {
  181. if (fPrevSep) {
  182. DeleteMenu(hmenu, iItem, MF_BYPOSITION);
  183. iCount--;
  184. iItem--; // Will be incremented by loop control
  185. }
  186. fPrevSep = TRUE;
  187. } else {
  188. fPrevSep = FALSE;
  189. }
  190. }
  191. if (iCount && fPrevSep) {
  192. DeleteMenu(hmenu, iCount - 1, MF_BYPOSITION);
  193. }
  194. }
  195. /*****************************************************************************
  196. *
  197. * JiggleMouse
  198. *
  199. *
  200. * Jiggle the mouse to force a cursor recomputation.
  201. *
  202. *****************************************************************************/
  203. void JiggleMouse()
  204. {
  205. POINT pt;
  206. if (GetCursorPos(&pt)) {
  207. SetCursorPos(pt.x, pt.y);
  208. }
  209. }
  210. /*****************************************************************************
  211. *
  212. * BGTask
  213. *
  214. *****************************************************************************/
  215. BGTask::~BGTask()
  216. {
  217. if (_hDone) {
  218. /*
  219. * Theoretically we don't need to pump messages because
  220. * we destroyed all the windows we created so our thread
  221. * should be clear of any windows. Except that Cicero will
  222. * secretly create a window on our thread, so we have
  223. * to pump messages anyway...
  224. */
  225. while (MsgWaitForMultipleObjects(1, &_hDone, FALSE,
  226. INFINITE, QS_ALLINPUT) == WAIT_OBJECT_0+1) {
  227. MSG msg;
  228. while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
  229. TranslateMessage(&msg);
  230. DispatchMessage(&msg);
  231. }
  232. }
  233. CloseHandle(_hDone);
  234. }
  235. }
  236. BOOL BGTask::BGStartTask(LPTHREAD_START_ROUTINE pfn, LPVOID Context)
  237. {
  238. ASSERT(!_fPending);
  239. if (BGConstructed()) {
  240. /*
  241. * Must reset before queueing the work item to avoid a race where
  242. * the work item completes before we return from the Queue call.
  243. */
  244. ResetEvent(_hDone);
  245. _fPending = QueueUserWorkItem(pfn, Context, WT_EXECUTELONGFUNCTION);
  246. if (_fPending) {
  247. JiggleMouse();
  248. } else {
  249. BGEndTask(); // pretend task completed (because it never started)
  250. }
  251. }
  252. return _fPending;
  253. }
  254. void BGTask::BGEndTask()
  255. {
  256. SetEvent(_hDone);
  257. _fPending = FALSE;
  258. JiggleMouse();
  259. }
  260. LRESULT BGTask::BGFilterSetCursor(LRESULT lres)
  261. {
  262. if (BGTaskPending()) {
  263. if (GetCursor() == g_hcurArrow) {
  264. SetCursor(g_hcurAppStarting);
  265. lres = TRUE;
  266. }
  267. }
  268. return lres;
  269. }
  270. /*****************************************************************************
  271. *
  272. * PremungeFileSpec
  273. *
  274. * Due to complex view specifications this can be led astray when "..."
  275. * gets involved. As a workaround (HACK!) we change "..." to "???",
  276. * do the mapping, then map back.
  277. *
  278. * We choose "???" because it has so many magical properties...
  279. *
  280. * - not a valid filename, so cannot match a local file specification.
  281. * - not a valid Source Depot wildcard, so cannot go wild on the server,
  282. * - not a single question mark, which SD treats as equivalent to "help".
  283. * - same length as "..." so can be updated in place.
  284. *
  285. * Any revision specifiers remain attached to the string.
  286. *
  287. *****************************************************************************/
  288. void _ChangeTo(LPTSTR psz, LPCTSTR pszFrom, LPCTSTR pszTo)
  289. {
  290. ASSERT(lstrlen(pszFrom) == lstrlen(pszTo));
  291. while ((psz = StrStr(psz, pszFrom)) != NULL) {
  292. memcpy(psz, pszTo, lstrlen(pszTo) * sizeof(pszTo[0]));
  293. }
  294. }
  295. void PremungeFilespec(LPTSTR psz)
  296. {
  297. _ChangeTo(psz, TEXT("..."), TEXT("???"));
  298. }
  299. void PostmungeFilespec(LPTSTR psz)
  300. {
  301. _ChangeTo(psz, TEXT("???"), TEXT("..."));
  302. }
  303. /*****************************************************************************
  304. *
  305. * MapToXPath
  306. *
  307. *****************************************************************************/
  308. BOOL MapToXPath(LPCTSTR pszSD, String& strOut, MAPTOX X)
  309. {
  310. if (X == MAPTOX_DEPOT) {
  311. //
  312. // Early-out: Is it already a full depot path?
  313. //
  314. if (pszSD[0] == TEXT('/')) {
  315. strOut = pszSD;
  316. return TRUE;
  317. }
  318. }
  319. //
  320. // Borrow strOut to compose the query string.
  321. //
  322. Substring ssPath;
  323. strOut.Reset();
  324. if (Parse(TEXT("$p"), pszSD, &ssPath) && ssPath.Length() > 0) {
  325. strOut << ssPath;
  326. } else {
  327. return FALSE;
  328. }
  329. PremungeFilespec(strOut);
  330. String str;
  331. str << TEXT("where ") << QuoteSpaces(strOut);
  332. WaitCursor wait;
  333. SDChildProcess proc(str);
  334. IOBuffer buf(proc.Handle());
  335. while (buf.NextLine(str)) {
  336. str.Chomp();
  337. Substring rgss[3];
  338. if (rgss[2].SetStart(Parse(TEXT("$P $P "), str, rgss))) {
  339. PostmungeFilespec(str);
  340. rgss[2].SetEnd(str + str.Length());
  341. strOut.Reset();
  342. strOut << rgss[X] << ssPath._pszMax;
  343. return TRUE;
  344. }
  345. }
  346. return FALSE;
  347. }
  348. /*****************************************************************************
  349. *
  350. * MapToLocalPath
  351. *
  352. * MapToXPath does most of the work, but then we have to do some
  353. * magic munging if we are running from a fake directory.
  354. *
  355. *****************************************************************************/
  356. BOOL MapToLocalPath(LPCTSTR pszSD, String& strOut)
  357. {
  358. BOOL fSuccess = MapToXPath(pszSD, strOut, MAPTOX_LOCAL);
  359. if (fSuccess && !GlobalSettings.GetFakeDir().IsEmpty()) {
  360. if (strOut.BufferLength() < MAX_PATH) {
  361. if (!strOut.Grow(MAX_PATH - strOut.BufferLength())) {
  362. return FALSE; // Out of memory
  363. }
  364. }
  365. LPCTSTR pszRest = strOut + lstrlen(GlobalSettings.GetFakeDir());
  366. if (*pszRest == TEXT('\\')) {
  367. pszRest++;
  368. }
  369. PathCombine(strOut.Buffer(), GlobalSettings.GetLocalRoot(), pszRest);
  370. fSuccess = TRUE;
  371. }
  372. return fSuccess;
  373. }
  374. /*****************************************************************************
  375. *
  376. * SpawnProcess
  377. *
  378. *****************************************************************************/
  379. BOOL SpawnProcess(LPTSTR pszCommand)
  380. {
  381. STARTUPINFO si = { 0 };
  382. PROCESS_INFORMATION pi;
  383. BOOL fSuccess = CreateProcess(NULL, pszCommand, NULL, NULL, FALSE, 0,
  384. NULL, NULL, &si, &pi);
  385. if (fSuccess) {
  386. CloseHandle(pi.hThread);
  387. CloseHandle(pi.hProcess);
  388. }
  389. return fSuccess;
  390. }
  391. /*****************************************************************************
  392. *
  393. * WindiffChangelist
  394. *
  395. *****************************************************************************/
  396. void WindiffChangelist(int iChange)
  397. {
  398. if (iChange > 0) {
  399. String str;
  400. str << TEXT("windiff.exe -ld") << iChange;
  401. SpawnProcess(str);
  402. }
  403. }
  404. /*****************************************************************************
  405. *
  406. * WindiffOneChange
  407. *
  408. *****************************************************************************/
  409. void WindiffOneChange(LPTSTR pszPath)
  410. {
  411. Substring rgss[2];
  412. if (Parse(TEXT("$P#$d$e"), pszPath, rgss)) {
  413. String str;
  414. str << TEXT("windiff.exe ");
  415. rgss[0].Finalize();
  416. int iVersion = StrToInt(rgss[1].Start());
  417. if (iVersion > 1) {
  418. /* Edit is easy */
  419. str << QuoteSpaces(rgss[0].Start()) << TEXT("#") << (iVersion - 1);
  420. } else {
  421. /* Add uses NUL as the base file */
  422. str << TEXT("NUL");
  423. }
  424. str << TEXT(' ');
  425. str << QuoteSpaces(rgss[0].Start()) << TEXT("#") << iVersion;
  426. SpawnProcess(str);
  427. }
  428. }
  429. /*****************************************************************************
  430. *
  431. * ParseBugNumber
  432. *
  433. * See if there's a bug number in there.
  434. *
  435. * Digits at the beginning - bug number.
  436. * Digits after a space or punctuation mark - bug number.
  437. * Digits after the word "bug" or the letter "B" - bug number.
  438. *
  439. * A valid bug number must begin with a nonzero digit.
  440. *
  441. *****************************************************************************/
  442. int ParseBugNumber(LPCTSTR psz)
  443. {
  444. Substring ss;
  445. LPCTSTR pszStart = psz;
  446. while (*psz) {
  447. if (IsDigit(*psz)) {
  448. if (*psz == TEXT('0')) {
  449. // Nope, cannot begin with zero
  450. } else if (psz == pszStart) {
  451. return StrToInt(psz); // woo-hoo!
  452. } else switch (psz[-1]) {
  453. case 'B':
  454. case 'g':
  455. case 'G':
  456. return StrToInt(psz); // Comes after a B or a G
  457. default:
  458. if (!IsAlpha(psz[-1])) {
  459. return StrToInt(psz); // Comes after a space or punctuation
  460. }
  461. }
  462. // Phooey, a digit string beginning with 0; not a bug.
  463. while (IsDigit(*psz)) psz++;
  464. } else {
  465. psz++;
  466. }
  467. }
  468. return 0;
  469. }
  470. /*****************************************************************************
  471. *
  472. * ParseBugNumberFromSubItem
  473. *
  474. * Sometimes we use this just to parse regular numbers since regular
  475. * numbers pass the Bug Number Test.
  476. *
  477. *****************************************************************************/
  478. int ParseBugNumberFromSubItem(HWND hwnd, int iItem, int iSubItem)
  479. {
  480. TCHAR sz[MAX_PATH];
  481. sz[0] = TEXT('\0');
  482. if (iItem >= 0) {
  483. ListView_GetSubItemText(hwnd, iItem, iSubItem, sz, ARRAYSIZE(sz));
  484. }
  485. return ParseBugNumber(sz);
  486. }
  487. /*****************************************************************************
  488. *
  489. * AdjustBugMenu
  490. *
  491. *****************************************************************************/
  492. inline void _TrimAtTab(LPTSTR psz)
  493. {
  494. psz = StrChr(psz, TEXT('\t'));
  495. if (psz) *psz = TEXT('\0');
  496. }
  497. void AdjustBugMenu(HMENU hmenu, int iBug, BOOL fContextMenu)
  498. {
  499. TCHAR sz[MAX_PATH];
  500. String str;
  501. if (iBug) {
  502. str << StringResource(IDS_VIEWBUG_FORMAT);
  503. wnsprintf(sz, ARRAYSIZE(sz), str, iBug);
  504. if (fContextMenu) {
  505. _TrimAtTab(sz);
  506. }
  507. ModifyMenu(hmenu, IDM_VIEWBUG, MF_BYCOMMAND, IDM_VIEWBUG, sz);
  508. } else {
  509. str << StringResource(IDS_VIEWBUG_NONE);
  510. ModifyMenu(hmenu, IDM_VIEWBUG, MF_BYCOMMAND, IDM_VIEWBUG, str);
  511. }
  512. EnableDisableOrRemoveMenuItem(hmenu, IDM_VIEWBUG, iBug, fContextMenu);
  513. }
  514. /*****************************************************************************
  515. *
  516. * OpenBugWindow
  517. *
  518. *****************************************************************************/
  519. void OpenBugWindow(HWND hwnd, int iBug)
  520. {
  521. String str;
  522. GlobalSettings.FormatBugUrl(str, iBug);
  523. LPCTSTR pszArgs = PathGetArgs(str);
  524. PathRemoveArgs(str);
  525. PathUnquoteSpaces(str);
  526. _AllowSetForegroundWindow(-1);
  527. ShellExecute(hwnd, NULL, str, pszArgs, 0, SW_NORMAL);
  528. }
  529. /*****************************************************************************
  530. *
  531. * SetClipboardText
  532. *
  533. *****************************************************************************/
  534. #ifdef UNICODE
  535. #define CF_TSTR CF_UNICODETEXT
  536. #else
  537. #define CF_TSTR CF_TEXT
  538. #endif
  539. void SetClipboardText(HWND hwnd, LPCTSTR psz)
  540. {
  541. if (OpenClipboard(hwnd)) {
  542. EmptyClipboard();
  543. int cch = lstrlen(psz) + 1;
  544. HGLOBAL hglob = GlobalAlloc(GMEM_MOVEABLE, cch * sizeof(*psz));
  545. if (hglob) {
  546. LPTSTR pszCopy = RECAST(LPTSTR, GlobalLock(hglob));
  547. if (pszCopy) {
  548. lstrcpy(pszCopy, psz);
  549. GlobalUnlock(hglob);
  550. if (SetClipboardData(CF_TSTR, hglob)) {
  551. hglob = NULL; // ownership transfer
  552. }
  553. }
  554. if (hglob) {
  555. GlobalFree(hglob);
  556. }
  557. }
  558. CloseClipboard();
  559. }
  560. }
  561. /*****************************************************************************
  562. *
  563. * ContainsWildcards
  564. *
  565. * The SD wildcards are
  566. *
  567. * * (asterisk)
  568. * ... (ellipsis)
  569. * %n (percent sign followed by anything)
  570. * (null) (null string -- shorthand for "//...")
  571. *
  572. *****************************************************************************/
  573. BOOL ContainsWildcards(LPCTSTR psz)
  574. {
  575. if (*psz == TEXT('#') || *psz == TEXT('@') || *psz == TEXT('\0')) {
  576. return TRUE; // Null string wildcard
  577. }
  578. for (; *psz; psz++) {
  579. if (*psz == TEXT('*') || *psz == TEXT('%')) {
  580. return TRUE;
  581. }
  582. if (psz[0] == TEXT('.') && psz[1] == TEXT('.') && psz[2] == TEXT('.')) {
  583. return TRUE;
  584. }
  585. }
  586. return FALSE;
  587. }
  588. /*****************************************************************************
  589. *
  590. * Downlevel support
  591. *
  592. *****************************************************************************/
  593. #ifdef SUPPORT_DOWNLEVEL
  594. /*
  595. * If there is no thread pool, then chew an entire thread.
  596. */
  597. BOOL WINAPI
  598. Emulate_QueueUserWorkItem(LPTHREAD_START_ROUTINE pfn, LPVOID Context, ULONG Flags)
  599. {
  600. DWORD dwId;
  601. HANDLE hThread = CreateThread(NULL, 0, pfn, Context, 0, &dwId);
  602. if (hThread) {
  603. CloseHandle(hThread);
  604. return TRUE;
  605. }
  606. return FALSE;
  607. }
  608. BOOL WINAPI
  609. Emulate_AllowSetForegroundWindow(DWORD dwProcessId)
  610. {
  611. return FALSE;
  612. }
  613. QUEUEUSERWORKITEM _QueueUserWorkItem;
  614. ALLOWSETFOREGROUNDWINDOW _AllowSetForegroundWindow;
  615. template<class T>
  616. T GetProcFromModule(LPCTSTR pszModule, LPCSTR pszProc, T Default)
  617. {
  618. T t;
  619. HMODULE hmod = GetModuleHandle(pszModule);
  620. if (pszModule) {
  621. t = RECAST(T, GetProcAddress(hmod, pszProc));
  622. if (!t) {
  623. t = Default;
  624. }
  625. } else {
  626. t = Default;
  627. }
  628. return t;
  629. }
  630. #define GetProc(mod, fn) \
  631. _##fn = GetProcFromModule(TEXT(mod), #fn, Emulate_##fn)
  632. void InitDownlevel()
  633. {
  634. GetProc("KERNEL32", QueueUserWorkItem);
  635. GetProc("USER32", AllowSetForegroundWindow);
  636. }
  637. #undef GetProc
  638. #else
  639. #define InitDownlevel()
  640. #endif
  641. /*****************************************************************************
  642. *
  643. * Main program stuff
  644. *
  645. *****************************************************************************/
  646. LONG GetDllVersion(LPCTSTR pszDll)
  647. {
  648. HINSTANCE hinst = LoadLibrary(pszDll);
  649. DWORD dwVersion = 0;
  650. if (hinst) {
  651. DLLGETVERSIONPROC DllGetVersion;
  652. DllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(hinst, "DllGetVersion");
  653. if (DllGetVersion) {
  654. DLLVERSIONINFO dvi;
  655. dvi.cbSize = sizeof(dvi);
  656. if (SUCCEEDED(DllGetVersion(&dvi))) {
  657. dwVersion = MAKELONG(dvi.dwMinorVersion, dvi.dwMajorVersion);
  658. }
  659. }
  660. // Leak the DLL - we're going to use him anyway
  661. }
  662. return dwVersion;
  663. }
  664. /*****************************************************************************
  665. *
  666. * Globals
  667. *
  668. *****************************************************************************/
  669. BOOL InitGlobals()
  670. {
  671. g_hinst = GetModuleHandle(0);
  672. g_hcurWait = LoadCursor(NULL, IDC_WAIT);
  673. g_hcurArrow = LoadCursor(NULL, IDC_ARROW);
  674. g_hcurAppStarting = LoadCursor(NULL, IDC_APPSTARTING);
  675. if (GetDllVersion(TEXT("Comctl32.dll")) < MAKELONG(71, 4) ||
  676. GetDllVersion(TEXT("Shlwapi.dll")) < MAKELONG(71, 4)) {
  677. TCHAR sz[MAX_PATH];
  678. LoadString(g_hinst, IDS_IE4, sz, ARRAYSIZE(sz));
  679. //$$//BUGBUG// MessageBox(NULL, sz, g_szTitle, MB_OK);
  680. return FALSE;
  681. }
  682. InitDownlevel();
  683. InitCommonControls();
  684. /*
  685. * Get the SW_ flag for the first window.
  686. */
  687. STARTUPINFOA si;
  688. si.cb = sizeof(si);
  689. si.dwFlags = 0;
  690. GetStartupInfoA(&si);
  691. if (si.dwFlags & STARTF_USESHOWWINDOW) {
  692. g_wShowWindow = si.wShowWindow;
  693. } else {
  694. g_wShowWindow = SW_SHOWDEFAULT;
  695. }
  696. return TRUE;
  697. }
  698. void TermGlobals()
  699. {
  700. }
  701. /*****************************************************************************
  702. *
  703. * Help
  704. *
  705. *****************************************************************************/
  706. void Help(HWND hwnd, LPCTSTR pszAnchor)
  707. {
  708. TCHAR szSelf[MAX_PATH];
  709. GetModuleFileName(g_hinst, szSelf, ARRAYSIZE(szSelf));
  710. String str;
  711. str << TEXT("res://") << szSelf << TEXT("/tips.htm");
  712. if (pszAnchor) {
  713. str << pszAnchor;
  714. }
  715. _AllowSetForegroundWindow(-1);
  716. ShellExecute(hwnd, NULL, str, 0, 0, SW_NORMAL);
  717. }
  718. /*****************************************************************************
  719. *
  720. * CGlobals::Initialize
  721. *
  722. *****************************************************************************/
  723. void CGlobals::Initialize()
  724. {
  725. /*
  726. * The order of these three steps is important.
  727. *
  728. * - We have to get the path before we can call sd.
  729. *
  730. * - We need the "sd info" in order to determine what
  731. * the proper fake directory is.
  732. */
  733. _InitSdPath();
  734. _InitInfo();
  735. _InitFakeDir();
  736. _InitServerVersion();
  737. _InitBugPage();
  738. }
  739. /*****************************************************************************
  740. *
  741. * CGlobals::_InitSdPath
  742. *
  743. * The environment variable "SD" provides the path to the program to use.
  744. * The default is "sd", but for debugging, you can set it to "fakesd",
  745. * or if you're using that other company's product, you might even want
  746. * to set it to that other company's program...
  747. *
  748. *****************************************************************************/
  749. void CGlobals::_InitSdPath()
  750. {
  751. TCHAR szSd[MAX_PATH];
  752. LPTSTR pszSdExe;
  753. DWORD cb = GetEnvironmentVariable(TEXT("SD"), szSd, ARRAYSIZE(szSd));
  754. if (cb == 0 || cb > ARRAYSIZE(szSd)) {
  755. pszSdExe = TEXT("SD.EXE"); // Default value
  756. } else {
  757. pszSdExe = szSd;
  758. }
  759. cb = SearchPath(NULL, pszSdExe, TEXT(".exe"), ARRAYSIZE(_szSd), _szSd, NULL);
  760. if (cb == 0 || cb > ARRAYSIZE(_szSd)) {
  761. /*
  762. * Not found on path, eek! Just use sd.exe and wait for the
  763. * fireworks.
  764. */
  765. lstrcpyn(_szSd, TEXT("SD.EXE"), ARRAYSIZE(_szSd));
  766. }
  767. }
  768. /*****************************************************************************
  769. *
  770. * CGlobals::_InitInfo
  771. *
  772. * Collect the results of the "sd info" command.
  773. *
  774. *****************************************************************************/
  775. void CGlobals::_InitInfo()
  776. {
  777. static const LPCTSTR s_rgpsz[] = {
  778. TEXT("User name: "),
  779. TEXT("Client name: "),
  780. TEXT("Client root: "),
  781. TEXT("Current directory: "),
  782. TEXT("Server version: "),
  783. };
  784. COMPILETIME_ASSERT(ARRAYSIZE(s_rgpsz) == ARRAYSIZE(_rgpszSettings));
  785. WaitCursor wait;
  786. SDChildProcess proc(TEXT("info"));
  787. IOBuffer buf(proc.Handle());
  788. String str;
  789. while (buf.NextLine(str)) {
  790. str.Chomp();
  791. int i;
  792. for (i = 0; i < ARRAYSIZE(s_rgpsz); i++) {
  793. LPTSTR pszRest = Parse(s_rgpsz[i], str, NULL);
  794. if (pszRest) {
  795. _rgpszSettings[i] = pszRest;
  796. }
  797. }
  798. }
  799. }
  800. /*****************************************************************************
  801. *
  802. * CGlobals::_InitFakeDir
  803. *
  804. * See if the user is borrowing another person's enlistment.
  805. * If so, then virtualize the directory (by walking the tree
  806. * looking for an sd.ini file) to keep sd happy.
  807. *
  808. * DO NOT WHINE if anything is wrong. Magical resolution of
  809. * borrowed directories is just a nicety.
  810. *
  811. *****************************************************************************/
  812. void CGlobals::_InitFakeDir()
  813. {
  814. /*
  815. * If the client root is not a prefix of the current directory,
  816. * then cook up a virtual current directory that will keep sd happy.
  817. */
  818. _StringCache& pszClientRoot = _rgpszSettings[SETTING_CLIENTROOT];
  819. _StringCache& pszLocalDir = _rgpszSettings[SETTING_LOCALDIR];
  820. if (!pszClientRoot.IsEmpty() && !pszLocalDir.IsEmpty() &&
  821. !PathIsPrefix(pszClientRoot, pszLocalDir)) {
  822. TCHAR szDir[MAX_PATH];
  823. TCHAR szOriginalDir[MAX_PATH];
  824. TCHAR szSdIni[MAX_PATH];
  825. szDir[0] = TEXT('\0');
  826. GetCurrentDirectory(ARRAYSIZE(szDir), szDir);
  827. if (!szDir[0]) return; // Freaky
  828. lstrcpyn(szOriginalDir, szDir, ARRAYSIZE(szOriginalDir));
  829. do {
  830. PathCombine(szSdIni, szDir, TEXT("sd.ini"));
  831. if (PathFileExists(szSdIni)) {
  832. _pszLocalRoot = szDir;
  833. //
  834. // Now work from the root back to the current directory.
  835. //
  836. LPTSTR pszSuffix = szOriginalDir + lstrlen(szDir);
  837. if (pszSuffix[0] == TEXT('\\')) {
  838. pszSuffix++;
  839. }
  840. PathCombine(szSdIni, _rgpszSettings[SETTING_CLIENTROOT], pszSuffix);
  841. _pszFakeDir = szSdIni;
  842. break;
  843. }
  844. } while (PathRemoveFileSpec(szDir));
  845. }
  846. }
  847. /*****************************************************************************
  848. *
  849. * CGlobals::_InitServerVersion
  850. *
  851. *****************************************************************************/
  852. void CGlobals::_InitServerVersion()
  853. {
  854. Substring rgss[5];
  855. if (Parse(TEXT("$w $d.$d.$d.$d"), _rgpszSettings[SETTING_SERVERVERSION], rgss)) {
  856. for (int i = 0; i < VERSION_MAX; i++) {
  857. _rguiVer[i] = StrToInt(rgss[1+i].Start());
  858. }
  859. }
  860. }
  861. /*****************************************************************************
  862. *
  863. * CGlobals::_InitBugPage
  864. *
  865. *****************************************************************************/
  866. void CGlobals::_InitBugPage()
  867. {
  868. TCHAR szRaid[MAX_PATH];
  869. DWORD cb = GetEnvironmentVariable(TEXT("SDVRAID"), szRaid, ARRAYSIZE(szRaid));
  870. if (cb == 0 || cb > ARRAYSIZE(szRaid)) {
  871. LoadString(g_hinst, IDS_DEFAULT_BUGPAGE, szRaid, ARRAYSIZE(szRaid));
  872. }
  873. LPTSTR pszSharp = StrChr(szRaid, TEXT('#'));
  874. if (pszSharp) {
  875. *pszSharp++ = TEXT('\0');
  876. }
  877. _pszBugPagePre = szRaid;
  878. _pszBugPagePost = pszSharp;
  879. }
  880. /*****************************************************************************
  881. *
  882. * CommandLineParser
  883. *
  884. *****************************************************************************/
  885. class CommandLineParser
  886. {
  887. public:
  888. CommandLineParser() : _tok(GetCommandLine()) {}
  889. BOOL ParseCommandLine();
  890. void Invoke();
  891. private:
  892. BOOL ParseMetaParam();
  893. BOOL TokenWithUndo();
  894. void UndoToken() { _tok.Restart(_pszUndo); }
  895. private:
  896. Tokenizer _tok;
  897. LPCTSTR _pszUndo;
  898. LPTHREAD_START_ROUTINE _pfn;
  899. String _str;
  900. };
  901. BOOL CommandLineParser::TokenWithUndo()
  902. {
  903. _pszUndo = _tok.Unparsed();
  904. return _tok.Token(_str);
  905. }
  906. BOOL CommandLineParser::ParseMetaParam()
  907. {
  908. switch (_str[2]) {
  909. case TEXT('s'):
  910. if (_str[3] == TEXT('\0')) {
  911. _tok.Token(_str);
  912. GlobalSettings.SetSdOpts(_str);
  913. } else {
  914. GlobalSettings.SetSdOpts(_str+3);
  915. }
  916. break;
  917. case TEXT('#'):
  918. switch (_str[3]) {
  919. case TEXT('+'):
  920. case TEXT('\0'):
  921. GlobalSettings.SetChurn(TRUE);
  922. break;
  923. case TEXT('-'):
  924. GlobalSettings.SetChurn(FALSE);
  925. break;
  926. default:
  927. return FALSE;
  928. }
  929. break;
  930. default:
  931. return FALSE;
  932. }
  933. return TRUE;
  934. }
  935. BOOL CommandLineParser::ParseCommandLine()
  936. {
  937. _tok.Token(_str); // Throw away program name
  938. /*
  939. * First collect the meta-parameters. These begin with two dashes.
  940. */
  941. while (TokenWithUndo()) {
  942. if (_str[0] == TEXT('-') && _str[1] == TEXT('-')) {
  943. if (!ParseMetaParam()) {
  944. return FALSE;
  945. }
  946. } else {
  947. break;
  948. }
  949. }
  950. /*
  951. * Next thing had better be a command!
  952. */
  953. if (lstrcmpi(_str, TEXT("changes")) == 0) {
  954. _pfn = CChanges_ThreadProc;
  955. } else if (lstrcmpi(_str, TEXT("describe")) == 0) {
  956. _pfn = CDescribe_ThreadProc;
  957. } else if (lstrcmpi(_str, TEXT("filelog")) == 0) {
  958. _pfn = CFileLog_ThreadProc;
  959. } else if (lstrcmpi(_str, TEXT("fileout")) == 0) {
  960. _pfn = CFileOut_ThreadProc;
  961. } else if (lstrcmpi(_str, TEXT("opened")) == 0) {
  962. _pfn = COpened_ThreadProc;
  963. } else {
  964. /*
  965. * Eek! Must use psychic powers!
  966. */
  967. Substring ss;
  968. if (_str[0] == TEXT('\0')) {
  969. /*
  970. * If no args, then it's "changes".
  971. */
  972. _pfn = CChanges_ThreadProc;
  973. } else if (_str[0] == TEXT('-')) {
  974. /*
  975. * If it begins with a dash, then it's "changes".
  976. */
  977. _pfn = CChanges_ThreadProc;
  978. } else if (Parse(TEXT("$d$e"), _str, &ss)) {
  979. /*
  980. * If first word is all digits, then it's "describe".
  981. */
  982. _pfn = CDescribe_ThreadProc;
  983. } else if (_tok.Finished() && !ContainsWildcards(_str)) {
  984. /*
  985. * If only one argument that contains no wildcards,
  986. * then it's "filelog".
  987. */
  988. _pfn = CFileLog_ThreadProc;
  989. } else {
  990. /*
  991. * If all else fails, assume "changes".
  992. */
  993. _pfn = CChanges_ThreadProc;
  994. }
  995. UndoToken(); /* Undo all the tokens we accidentally ate */
  996. }
  997. return TRUE;
  998. }
  999. void CommandLineParser::Invoke()
  1000. {
  1001. LPTSTR psz = StrDup(_tok.Unparsed());
  1002. if (psz) {
  1003. InterlockedIncrement(&g_lThreads);
  1004. ExitThread(_pfn(psz));
  1005. }
  1006. }
  1007. /*****************************************************************************
  1008. *
  1009. * Entry
  1010. *
  1011. * Program entry point.
  1012. *
  1013. *****************************************************************************/
  1014. EXTERN_C void PASCAL
  1015. Entry(void)
  1016. {
  1017. if (InitGlobals()) {
  1018. CommandLineParser parse;
  1019. if (!parse.ParseCommandLine()) {
  1020. Help(NULL, NULL);
  1021. } else {
  1022. GlobalSettings.Initialize();
  1023. parse.Invoke();
  1024. }
  1025. }
  1026. ExitProcess(0);
  1027. }