1 //#define NTDDI_VERSION NTDDI_WIN7
2 //#include <shobjidl.h>
3 #include <TCFoundation/TCDefines.h>
4 #include <windows.h>
5 #include <windowsx.h>
6 #include "ModelLoader.h"
7 #include "SSConfigure.h"
8 #include <TCFoundation/TCAutoreleasePool.h>
9 #include <TCFoundation/TCDictionary.h>
10 #include <TCFoundation/TCUserDefaults.h>
11 #include <TCFoundation/TCLocalStrings.h>
12 #include <TCFoundation/mystring.h>
13 #include <CUI/CUIWindow.h>
14 #include "SSPreview.h"
15 #include "Resource.h"
16 #include <LDLib/LDUserDefaultsKeys.h>
17 #include <stdio.h>
18 #include <ctime>
19 #include <cstdlib>
20 
21 //#include <TCFoundation/TCImage.h>
22 //#include <TCFoundation/TCJpegOptions.h>
23 
24 #if defined(_MSC_VER) && _MSC_VER >= 1400 && defined(_DEBUG)
25 #define new DEBUG_CLIENTBLOCK
26 #endif
27 
28 static bool debugToFile = false;
29 
30 typedef BOOL (__stdcall *PFNATTACHCONSOLE)(DWORD dwProcessId);
31 typedef HRESULT (__stdcall *PFNDLLREGISTERSERVER)(void);
32 
createConsole(void)33 void createConsole(void)
34 {
35 	if (AllocConsole())
36 	{
37 		COORD size = {80, 1000};
38 		SMALL_RECT rect = {0, 0, 79, 24};
39 //		HANDLE hOrigStdOut;
40 		HANDLE hStdOut;
41 		SECURITY_ATTRIBUTES securityAttributes;
42 
43 		securityAttributes.nLength = sizeof SECURITY_ATTRIBUTES;
44 		securityAttributes.lpSecurityDescriptor = NULL;
45 		securityAttributes.bInheritHandle = TRUE;
46 		hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
47 //		hStdOut = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
48 //			FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes,
49 //			CONSOLE_TEXTMODE_BUFFER, NULL);
50 		SetConsoleScreenBufferSize(hStdOut, size);
51 		SetConsoleWindowInfo(hStdOut, TRUE, &rect);
52 		SetConsoleActiveScreenBuffer(hStdOut);
53 //		SetStdHandle(STD_OUTPUT_HANDLE, hStdOut);
54 		freopen("CONOUT$", "w", stdout);
55 		freopen("CONIN$", "r", stdin);
56 	}
57 }
58 
isScreenSaver(void)59 bool isScreenSaver(void)
60 {
61 	UCSTR programName;
62 	bool retVal;
63 	UCSTR commandLine = NULL;
64 	UCSTR spot;
65 
66 	commandLine = copyString(GetCommandLine());
67 	if (commandLine[0] == '"')
68 	{
69 		if ((spot = ucstrchr(commandLine + 1, '"')) != NULL)
70 		{
71 			*spot = 0;
72 		}
73 		programName = commandLine + 1;
74 	}
75 	else
76 	{
77 		if ((spot = ucstrchr(commandLine, ' ')) != NULL)
78 		{
79 			*spot = 0;
80 		}
81 		programName = commandLine;
82 	}
83 	retVal = ucstrlen(programName) > 4 &&
84 		ucstrcasecmp(programName + ucstrlen(programName) - 4, _UC(".scr")) == 0;
85 	delete commandLine;
86 	return retVal;
87 }
88 
debugOut(char * fmt,...)89 void debugOut(char *fmt, ...)
90 {
91 	if (!debugToFile)
92 	{
93 		return;
94 	}
95 	static const char* userProfile = getenv("USERPROFILE");
96 	std::string debugPath = "C:\\LDViewDebug.txt";
97 	if (userProfile != NULL)
98 	{
99 		debugPath = std::string(userProfile) + "\\LDViewDebug.txt";
100 	}
101 	FILE* debugFile = ucfopen(debugPath.c_str(), "a+");
102 
103 	if (debugFile)
104 	{
105 		va_list marker;
106 
107 		std::time_t t = std::time(nullptr);
108 		char mbstr[100];
109 		if (std::strftime(mbstr, sizeof(mbstr), "%F %T", std::localtime(&t)))
110 		{
111 			fprintf(debugFile, "%s: ", mbstr);
112 		}
113 		va_start(marker, fmt);
114 		vfprintf(debugFile, fmt, marker);
115 		va_end(marker);
116 		fclose(debugFile);
117 	}
118 }
119 
mainLoop()120 int mainLoop()
121 {
122 	MSG msg;
123 	HACCEL hAccel;
124 	bool screenSaver = isScreenSaver();
125 //	DWORD startTickCount = GetTickCount();
126 
127 	hAccel = LoadAccelerators(CUIWindow::getLanguageModule(),
128 		MAKEINTRESOURCE(IDR_ACCELERATORS));
129 	while (1)
130 	{
131 		HWND parentWindow;
132 		HWND topParent;
133 		HWND newParent;
134 		DWORD tickCount = GetTickCount();
135 
136 		//debugOut("%d\n", tickCount);
137 /*
138 		if (tickCount > startTickCount + 3000 || tickCount < startTickCount)
139 		{
140 			OleUninitialize();
141 			return 0;
142 		}
143 */
144 		if (!GetMessage(&msg, NULL, 0, 0))
145 		{
146 			OleUninitialize();
147 			return (int)msg.wParam;
148 		}
149 #ifdef _DEBUG
150 //			_CrtDbgReport(_CRT_WARN, NULL, 0, NULL, "hWnd: 0x%6X msg: 0x%X\n", msg.hwnd, msg.message);
151 #endif // _DEBUG
152 		parentWindow = GetParent(msg.hwnd);
153 		topParent = msg.hwnd;
154 		while ((newParent = GetParent(topParent)) != NULL)
155 		{
156 			topParent = newParent;
157 		}
158 		if (screenSaver || !TranslateAccelerator(
159 		    topParent,     // handle to receiving window
160 			hAccel,        // handle to active accelerator table
161 			&msg))
162 		{
163 			if (!parentWindow || !IsDialogMessage(parentWindow, &msg))
164 			{
165 				TranslateMessage(&msg);
166 				DispatchMessage(&msg);
167 				if (screenSaver && msg.message == WM_DESTROY)
168 				{
169 					debugOut("WM_DESTROY\n", tickCount);
170 					PostQuitMessage(0);
171 				}
172 			}
173 		}
174 		TCAutoreleasePool::processReleases();
175 	}
176 }
177 
flushPreviewEvents(HWND hWindow)178 BOOL flushPreviewEvents(HWND hWindow)
179 {
180     // In preview mode, "pause" (enter a limited message loop) briefly
181     // before proceeding, so the display control panel knows to update itself.
182     BOOL waitForInputIdle = TRUE;
183 
184     // Post a message to mark the end of the initial group of window messages
185     PostMessage( hWindow, WM_USER, 0, 0 );
186 
187     MSG msg;
188     while( waitForInputIdle )
189     {
190         // If GetMessage returns FALSE, it's quitting time.
191         if( !GetMessage( &msg, NULL, 0, 0 ) )
192         {
193 			return TRUE;
194         }
195 
196         TranslateMessage( &msg );
197 		if (msg.message == WM_USER && msg.hwnd == hWindow)
198 		{
199 			waitForInputIdle = FALSE;
200 		}
201 		else
202 		{
203 			DispatchMessage( &msg );
204 		}
205     }
206 	return TRUE;
207 }
208 
screenSaverLoop(HINSTANCE,HWND hWindow)209 void screenSaverLoop(HINSTANCE /*hInstance*/, HWND hWindow)
210 {
211     MSG msg;
212 	BOOL bGotMsg;
213 
214     msg.message = WM_NULL;
215     while ( msg.message != WM_QUIT && msg.message != WM_DESTROY )
216     {
217         bGotMsg = PeekMessage( &msg, NULL, 0, 0, PM_REMOVE );
218         if( bGotMsg )
219         {
220             TranslateMessage( &msg );
221             DispatchMessage( &msg );
222 			debugOut("Message: 0x%08X\n", msg.message);
223         }
224         else
225         {
226 			debugOut("No Message\n");
227 			if (!GetParent(hWindow))
228 			{
229 				PostQuitMessage(0);
230 			}
231             Sleep(100);
232         }
233     }
234 /*
235     while( 1 )
236     {
237 		FILE* debugFile;
238         // If GetMessage returns FALSE, it's quitting time.
239         if( !GetMessage( &msg, NULL, 0, 0 ) )
240         {
241 			debugFile = ucfopen("LDViewDebug.txt", "a+");
242 
243 			if (debugFile)
244 			{
245 				fprintf(debugFile, "EXIT\n");
246 				fclose(debugFile);
247 			}
248             // Post the quit message to handle it later
249 			return;
250         }
251 
252         TranslateMessage( &msg );
253 		DispatchMessage( &msg );
254 		debugFile = ucfopen("LDViewDebug.txt", "a+");
255 		if (debugFile)
256 		{
257 			fprintf(debugFile, "Message: 0x%08X\n", msg.message);
258 			fclose(debugFile);
259 		}
260 		if (msg.message == WM_DESTROY)
261 		{
262 			debugFile = ucfopen("LDViewDebug.txt", "a+");
263 
264 			if (debugFile)
265 			{
266 				fprintf(debugFile, "WM_DESTROY\n");
267 				fclose(debugFile);
268 			}
269 			return;
270 //			PostQuitMessage(0);
271 		}
272     }
273 */
274 }
275 
doPreview(HINSTANCE hInstance,LPCSTR lpCmdLine)276 int doPreview(HINSTANCE hInstance, LPCSTR lpCmdLine)
277 {
278 	const char *spot = strchr(lpCmdLine, ' ');
279 
280 	debugOut("Command line: %s\n", lpCmdLine);
281 	if (spot)
282 	{
283 		long long parentWindowNum;
284 
285 		spot++;
286 		if (sscanf(spot, "%lld", &parentWindowNum) == 1)
287 		{
288 			HWND hParentWindow = (HWND)parentWindowNum;
289 			SSPreview* ssPreview = new SSPreview(hParentWindow, hInstance);
290 
291 			if (ssPreview->run())
292 			{
293 				return 0;
294 			}
295 			else
296 			{
297 				return 1;
298 			}
299 		}
300 	}
301 	return 1;
302 }
303 
loadLocalStrings(const char * filename,UINT resourceId,bool replace)304 static void loadLocalStrings(const char *filename, UINT resourceId, bool replace)
305 {
306 	HMODULE hModule = (HMODULE)CUIWindow::getLanguageModule();
307 	HRSRC hLocalStringsResource = FindResource(hModule,
308 		MAKEINTRESOURCE(resourceId),
309 		RT_RCDATA);
310 	bool done = false;
311 
312 	if (hLocalStringsResource)
313 	{
314 		HGLOBAL hLocalStrings = LoadResource(hModule, hLocalStringsResource);
315 
316 		if (hLocalStrings)
317 		{
318 			TCByte *data = (TCByte *)LockResource(hLocalStrings);
319 
320 			if (data)
321 			{
322 				DWORD length = SizeofResource(hModule, hLocalStringsResource);
323 
324 				if (length)
325 				{
326 					//char *localStrings = new char[length + 1];
327 
328 					//memcpy(localStrings, data, length);
329 					//localStrings[length] = 0;
330 					TCLocalStrings::setStringTable(data, length, replace);
331 					//delete localStrings;
332 					done = true;
333 				}
334 				UnlockResource(hLocalStrings);
335 			}
336 		}
337 	}
338 	if (!done)
339 	{
340 		TCLocalStrings::loadStringTable(filename, replace);
341 	}
342 }
343 
setupLocalStrings(void)344 static void setupLocalStrings(void)
345 {
346 	loadLocalStrings("LDViewMessages.ini", IDR_LOCAL_STRINGS, true);
347 	loadLocalStrings("LDExportMessages.ini", IDR_EXPORTER_STRINGS, false);
348 	//TCLocalStrings::dumpTable("C:\\Temp\\ST-ASCII.txt", "ASCII");
349 }
350 
loadLanguageModule(void)351 static void loadLanguageModule(void)
352 {
353 	// This function is something of a hack.  We want to force the language
354 	// module to load with certain pre-conditions.  The first is that prior to
355 	// going into this function, the app name was set to LDView.  The next is
356 	// that we want to change into the LDView directory, so that the
357 	// LoadLibrary call will find language modules in that directory, so that
358 	// if we're running as a screensaver, it will still find the language
359 	// modules.
360 	UCCHAR originalPath[1024];
361 	DWORD maxPath = COUNT_OF(originalPath);
362 	DWORD dirResult = GetCurrentDirectory(maxPath, originalPath);
363 	bool dirChange = false;
364 
365 	if (dirResult > 0 && dirResult <= maxPath)
366 	{
367 		UCSTR installPath =
368 			TCUserDefaults::stringForKeyUC(INSTALL_PATH_4_1_KEY, NULL, false);
369 
370 		if (installPath)
371 		{
372 			SetCurrentDirectory(installPath);
373 			delete[] installPath;
374 			dirChange = true;
375 		}
376 	}
377 	// The following forces CUIWindow to load (and cache) the language module.
378 	CUIWindow::getLanguageModule();
379 	if (dirChange)
380 	{
381 		SetCurrentDirectory(originalPath);
382 	}
383 }
384 
setupUserDefaults(LPCSTR lpCmdLine,bool screenSaver,bool removableDrive)385 static bool setupUserDefaults(
386 	LPCSTR lpCmdLine,
387 	bool screenSaver,
388 	bool removableDrive)
389 {
390 	char *appName = "Travis Cobbs/LDView";
391 	char *sessionName;
392 	bool retValue = true;
393 
394 	TCUserDefaults::setCommandLine(lpCmdLine);
395 	debugToFile = TCUserDefaults::boolForKey("DebugToFile", false, false);
396 	std::string haveStdOut =
397 		TCUserDefaults::commandLineStringForKey("HaveStdOut");
398 	if (!haveStdOut.empty())
399 	{
400 		if (std::strtol(haveStdOut.c_str(), NULL, 10) != 0)
401 		{
402 			runningWithConsole();
403 		}
404 	}
405 	// IniFile can be specified on the command line; if so, don't load a
406 	// different one.
407 	if (removableDrive && !TCUserDefaults::isIniFileSet())
408 	{
409 		if (!TCUserDefaults::setIniFile("LDView.ini"))
410 		{
411 			retValue = false;
412 		}
413 	}
414 	TCUserDefaults::setAppName(appName);
415 	// The language module needs to be loaded using LDView as the app name.  So
416 	// if we're running in screensaver mode, we'll take care of changing our
417 	// app name after that is done.
418 	loadLanguageModule();
419 	if (screenSaver)
420 	{
421 		UCSTR ldrawDir =
422 			TCUserDefaults::stringForKeyUC(LDRAWDIR_KEY, NULL, false);
423 
424 		appName = "Travis Cobbs/LDView Screen Saver";
425 		TCUserDefaults::setAppName(appName);
426 		if (ldrawDir)
427 		{
428 			TCUserDefaults::setStringForKey(ldrawDir, LDRAWDIR_KEY, false);
429 			delete ldrawDir;
430 		}
431 	}
432 #ifdef _DEBUG
433 	// Set the debug level before selecting a pref set.
434 	setDebugLevel((int)TCUserDefaults::longForKey(DEBUG_LEVEL_KEY, 1, false));
435 #endif // _DEBUG
436 	sessionName =
437 		TCUserDefaults::getSavedSessionNameFromKey(PREFERENCE_SET_KEY);
438 	if (sessionName && sessionName[0])
439 	{
440 		TCUserDefaults::setSessionName(sessionName, NULL, false);
441 	}
442 	delete sessionName;
443 	return retValue;
444 }
445 
isRemovableDrive(HINSTANCE hInstance)446 static bool isRemovableDrive(HINSTANCE hInstance)
447 {
448 	UCCHAR filename[2048];
449 
450 	if (GetModuleFileName(hInstance, filename, COUNT_OF(filename)) > 0)
451 	{
452 		if (isalpha(filename[0]) && filename[1] == ':')
453 		{
454 			UCCHAR driveRoot[4] = _UC("");
455 
456 			ucstrncpy(driveRoot, filename, 2);
457 			driveRoot[2] = '\\';
458 			if (GetDriveType(driveRoot) == DRIVE_REMOVABLE)
459 			{
460 				return true;
461 			}
462 		}
463 	}
464 	return false;
465 }
466 
467 //static void Win7JumpListStuff(void)
468 //{
469 //	HMODULE hShell32 = LoadLibrary("shell32.dll");
470 //
471 //	if (hShell32 != NULL)
472 //	{
473 //		typedef HRESULT (__stdcall *PSetCurrentProcessExplicitAppUserModelID)(PCWSTR AppID);
474 //		PSetCurrentProcessExplicitAppUserModelID
475 //			pSetCurrentProcessExplicitAppUserModelID =
476 //			(PSetCurrentProcessExplicitAppUserModelID)GetProcAddress(hShell32,
477 //			"SetCurrentProcessExplicitAppUserModelID");
478 //
479 //		if (pSetCurrentProcessExplicitAppUserModelID != NULL)
480 //		{
481 //			PCWSTR appID = L"Travis.Cobbs.LDView";
482 //			pSetCurrentProcessExplicitAppUserModelID(appID);
483 //			ICustomDestinationList *cdl;
484 //			// Using CLSID_DestinationList from the header file auto-links
485 //			// an incompatible copy of uuid.lib, causing link failure.
486 //			// "77f10cf0-3db5-4966-b520-b7c54fd35ed6"
487 //			IID CLSID_DestinationList = {
488 //				0x77f10cf0, 0x3db5, 0x4966,
489 //				{ 0xb5, 0x20, 0xb7, 0xc5, 0x4f, 0xd3, 0x5e, 0xd6 }
490 //			};
491 //			CoCreateInstance(CLSID_DestinationList,NULL,CLSCTX_INPROC_SERVER,
492 //				__uuidof(ICustomDestinationList),(void**)&cdl);
493 //			if (cdl != NULL)
494 //			{
495 //				cdl->SetAppID(appID);
496 //				// Recent Items begin list
497 //				UINT maxCount = 0;
498 //				IObjectArray* oa = 0;
499 //				HRESULT hr = cdl->BeginList(&maxCount,__uuidof(IObjectArray),(void**)&oa);
500 //
501 //				if (SUCCEEDED(hr) && oa != NULL)
502 //				{
503 //					// Add known category
504 //					hr = cdl->AppendKnownCategory(KDC_RECENT);
505 //					oa->Release();
506 //					//odl->Release();
507 //					//tb->Release();
508 //				}
509 //				cdl->Release();
510 //			}
511 //		}
512 //		FreeLibrary(hShell32);
513 //	}
514 //}
515 
516 #ifdef TC_NO_UNICODE
WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR lpCmdLine,int nCmdShow)517 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,
518 				   LPSTR lpCmdLine, int nCmdShow)
519 #else // TC_NO_UNICODE
520 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,
521 					LPWSTR lpCmdLine, int nCmdShow)
522 #endif // !TC_NO_UNICODE
523 {
524 #ifdef _DEBUG
525 //	MessageBox(NULL, _UC("Attach a debugger now..."), _UC("Debug"), MB_OK);
526 #endif
527 	ModelLoader* modelLoader;
528 	bool screenSaver = isScreenSaver();
529 	int retValue;
530 	STARTUPINFO startupInfo;
531 
532 	debugOut("Command Line: <<%ls>>\n", lpCmdLine);
533 	std::string utf8CmdLine;
534 	ucstringtoutf8(utf8CmdLine, lpCmdLine);
535 	bool udok = setupUserDefaults(utf8CmdLine.c_str(), screenSaver,
536 		isRemovableDrive(hInstance));
537 	debugOut("Command Line: <<%ls>>\n", lpCmdLine);
538 #ifdef _DEBUG
539 	int _debugFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
540 	_debugFlag |= _CRTDBG_LEAK_CHECK_DF;
541 	_CrtSetDbgFlag(_debugFlag);
542 	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
543 	if (!haveConsole())
544 	{
545 		createConsole();
546 	}
547 #endif // _DEBUG
548 	setupLocalStrings();
549 	if (TCUserDefaults::boolForKey(DEBUG_COMMAND_LINE_KEY, false, false))
550 	{
551 		ucstring message = _UC("Command Line:\n");
552 
553 		message += lpCmdLine;
554 		MessageBox(NULL, message.c_str(), _UC("LDView"), MB_OK);
555 	}
556 	if (!udok && !TCUserDefaults::longForKey("IniFailureShown", 0, 0))
557 	{
558 		UCCHAR message[2048];
559 		ucstring iniPath;
560 		utf8toucstring(iniPath, TCUserDefaults::getIniPath());
561 
562 		sucprintf(message, COUNT_OF(message),
563 			TCLocalStrings::get(_UC("IniFailure")), iniPath.c_str());
564 		CUIWindow::messageBoxUC(NULL, message, _UC("LDView"), MB_OK);
565 		TCUserDefaults::setLongForKey(1, "IniFailureShown", false);
566 	}
567 	if (screenSaver)
568 	{
569 		if (strncasecmp(utf8CmdLine.c_str(), "/p", 2) == 0 ||
570 			strncasecmp(utf8CmdLine.c_str(), "-p", 2) == 0 ||
571 			strncasecmp(utf8CmdLine.c_str(), "p", 1) == 0)
572 		{
573 			// preview mode
574 			int retValue = doPreview(hInstance, utf8CmdLine.c_str());
575 			return retValue;
576 		}
577 		if (strncasecmp(utf8CmdLine.c_str(), "/c", 2) == 0 ||
578 			strncasecmp(utf8CmdLine.c_str(), "-c", 2) == 0 ||
579 			strncasecmp(utf8CmdLine.c_str(), "c", 1) == 0 ||
580 			utf8CmdLine.empty())
581 		{
582 			SSConfigure *configure;
583 
584 			configure = new SSConfigure(hInstance);
585 #ifdef _DEBUG
586 			_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
587 			createConsole();
588 #endif // _DEBUG
589 			configure->run();
590 			// configure mode
591 			return 1;
592 		}
593 		// This shouldn't be necessary, but I've received a report of a whole
594 		// bunch of copies of the LDView screensaver running at once.  This
595 		// might not fix things entirely, but it will at least prevent it
596 		// from launching multiple times concurrently.
597 		CreateMutex(NULL, FALSE, _UC("LDView Screensaver"));
598 		if (GetLastError() == ERROR_ALREADY_EXISTS)
599 		{
600 			return 0;
601 		}
602 	}
603 #ifdef _LOG_PERFORMANCE
604 	LARGE_INTEGER frequency;
605 	if (QueryPerformanceFrequency(&frequency))
606 	{
607 		debugPrintf("Frequency: %I64d\n", frequency.QuadPart);
608 	}
609 #endif // _LOG_PERFORMANCE
610 	OleInitialize(NULL);
611 
612 	//Win7JumpListStuff();
613 
614 	modelLoader = new ModelLoader(CUIWindow::getLanguageModule(), nCmdShow,
615 		screenSaver);
616 	retValue = mainLoop();
617 	modelLoader->release();
618 	return retValue;
619 } // WinMain
620