1 ////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // Nestopia - NES/Famicom emulator written in C++
4 //
5 // Copyright (C) 2003-2008 Martin Freij
6 //
7 // This file is part of Nestopia.
8 //
9 // Nestopia is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // Nestopia is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with Nestopia; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 //
23 ////////////////////////////////////////////////////////////////////////////////////////
24 
25 #include "language/resource.h"
26 #include "NstResourceCursor.hpp"
27 #include "NstResourceVersion.hpp"
28 #include "NstApplicationException.hpp"
29 #include "NstApplicationLanguage.hpp"
30 #include <CommCtrl.h>
31 #include <ObjBase.h>
32 #include <Shlobj.h>
33 #include <Shellapi.h>
34 
35 
36 #if NST_MSVC
37 #pragma comment(lib,"comctl32")
38 #endif
39 
40 namespace Nestopia
41 {
42 	namespace Application
43 	{
44 		const wchar_t Instance::appClassName[] = L"Nestopia";
45 
46 		struct Instance::Global
47 		{
48 			Global();
49 
50 			struct Paths
51 			{
52 				explicit Paths(HINSTANCE);
53 
54 				Path exePath;
55 				Path wrkPath;
56 				Path configPath;//bg configuration files path
57 			};
58 
59 			struct Hooks
60 			{
61 				explicit Hooks(HINSTANCE);
62 				~Hooks();
63 
64 				void OnCreate(const CREATESTRUCT&,HWND);
65 				void OnDestroy(HWND);
66 
67 				static LRESULT CALLBACK CBTProc(int,WPARAM,LPARAM);
68 
69 				enum
70 				{
71 					CHILDREN_RESERVE = 8
72 				};
73 
74 				typedef Collection::Vector<HWND> Children;
75 
76 				HHOOK const handle;
77 				HWND window;
78 				Children children;
79 			};
80 
81 			const Paths paths;
82 			const Resource::Version version;
83 			Language language;
84 			Hooks hooks;
85 			IconStyle iconStyle;
86 		};
87 
88 		Instance::Global Instance::global;
89 		Instance::Events::Callbacks Instance::Events::callbacks;
90 
Paths(HINSTANCE const hInstance)91 		Instance::Global::Paths::Paths(HINSTANCE const hInstance)
92 		{
93 			uint length;
94 
95 			do
96 			{
97 				exePath.Reserve( exePath.Capacity() + 255 );
98 				length = ::GetModuleFileName( hInstance, exePath.Ptr(), exePath.Capacity() );
99 			}
100 			while (length == exePath.Capacity());
101 
102 			exePath.ShrinkTo( length );
103 			exePath.MakePretty( false );
104 			exePath.Defrag();
105 
106 			wrkPath = ".";
107 			wrkPath.MakeAbsolute( true );
108 			wrkPath.Defrag();
109 
110 
111 			configPath.Reserve(configPath.Capacity() + 255);//bg
112 
113 			//bg
114 			bool isLua = false;
115 
116 			//bg check -lua between command line options
117 			int nArgs;
118 			LPWSTR *szArglist = ::CommandLineToArgvW(::GetCommandLineW(), &nArgs);
119 			if( szArglist != NULL) {
120 				for(int i=0; i<nArgs; i++) {
121 					isLua = ::wcscmp(szArglist[i],L"-lua") == 0;
122 					if (isLua) break;
123 				}
124 
125 			   ::LocalFree(szArglist);
126 			}
127 
128 			//bg END check
129 
130 			//bg set configPath
131 			if(isLua &&
132 
133 					SUCCEEDED(SHGetFolderPath(NULL,
134 								 CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE,
135 								 NULL,
136 								 0,
137 								 configPath.Ptr()))) {
138 									 configPath.ShrinkTo( std::wcslen(configPath.Ptr()) );
139 									 configPath.MakePretty( false );
140 									 configPath.Append (L"\\Nestopia",9);
141 									 configPath.MakePretty( false );
142 									 configPath.Defrag();
143 									 ::CreateDirectory(configPath.Ptr(), NULL);
144 				}
145 
146 			else {
147 				::GetModuleFileName( hInstance, configPath.Ptr(), configPath.Capacity() );
148 				configPath.ShrinkTo(configPath.FindLastOf('\\',configPath.Capacity()));//bg fix issue #265
149 			};
150 
151 			configPath.Defrag();
152 		}
153 
Hooks(HINSTANCE const hInstance)154 		Instance::Global::Hooks::Hooks(HINSTANCE const hInstance)
155 		: handle(::SetWindowsHookEx( WH_CBT, CBTProc, hInstance, ::GetCurrentThreadId() ))
156 		{
157 			children.Reserve( CHILDREN_RESERVE );
158 		}
159 
~Hooks()160 		Instance::Global::Hooks::~Hooks()
161 		{
162 			if (handle)
163 				::UnhookWindowsHookEx( handle );
164 		}
165 
166 		#ifdef NST_MSVC_OPTIMIZE
167 		#pragma optimize("t", on)
168 		#endif
169 
OnCreate(const CREATESTRUCT & createStruct,HWND const hWnd)170 		void Instance::Global::Hooks::OnCreate(const CREATESTRUCT& createStruct,HWND const hWnd)
171 		{
172 			NST_ASSERT( hWnd );
173 
174 			if (window == createStruct.hwndParent || !window)
175 			{
176 				{
177 					static const wchar_t menuClassName[] = L"#32768"; // documented on MSDN
178 
179 					enum
180 					{
181 						MAX_LENGTH = NST_MAX( sizeof(array(appClassName)) - 1,
182                                      NST_MAX( sizeof(array(STATUSCLASSNAME)) - 1, sizeof(array(menuClassName)) - 1 ))
183 					};
184 
185 					String::Stack<MAX_LENGTH,wchar_t> name;
186 					name.ShrinkTo( ::GetClassName( hWnd, name.Ptr(), MAX_LENGTH+1 ) );
187 
188 					if (name.Equal( menuClassName ) || name.Equal( STATUSCLASSNAME ) || name.Equal( L"IME" ))
189 						return;
190 
191 					if (window)
192 					{
193 						children.PushBack( hWnd );
194 						Events::Signal( EVENT_SYSTEM_BUSY );
195 					}
196 					else if (name.Equal( appClassName ))
197 					{
198 						window = hWnd;
199 					}
200 					else
201 					{
202 						return;
203 					}
204 				}
205 
206 				const Events::WindowCreateParam param =
207 				{
208 					hWnd,
209 					createStruct.cx > 0 ? createStruct.cx : 0,
210 					createStruct.cy > 0 ? createStruct.cy : 0
211 				};
212 
213 				Events::Signal( EVENT_WINDOW_CREATE, &param );
214 			}
215 		}
216 
OnDestroy(HWND const hWnd)217 		void Instance::Global::Hooks::OnDestroy(HWND const hWnd)
218 		{
219 			NST_ASSERT( hWnd );
220 
221 			Children::Iterator const child = children.Find( hWnd );
222 
223 			if (child)
224 			{
225 				Events::Signal( EVENT_SYSTEM_BUSY );
226 			}
227 			else if (window != hWnd)
228 			{
229 				return;
230 			}
231 
232 			{
233 				const Events::WindowDestroyParam param = { hWnd };
234 				Events::Signal( EVENT_WINDOW_DESTROY, &param );
235 			}
236 
237 			if (child)
238 				children.Erase( child );
239 			else
240 				window = NULL;
241 		}
242 
CBTProc(const int nCode,const WPARAM wParam,const LPARAM lParam)243 		LRESULT CALLBACK Instance::Global::Hooks::CBTProc(const int nCode,const WPARAM wParam,const LPARAM lParam)
244 		{
245 			NST_COMPILE_ASSERT( HCBT_CREATEWND >= 0 && HCBT_DESTROYWND >= 0 );
246 
247 			switch (nCode)
248 			{
249 				case HCBT_CREATEWND:
250 
251 					NST_ASSERT( lParam );
252 
253 					if (const CREATESTRUCT* const createStruct = reinterpret_cast<const CBT_CREATEWND*>(lParam)->lpcs)
254 					{
255 						NST_VERIFY( wParam );
256 						global.hooks.OnCreate( *createStruct, reinterpret_cast<HWND>(wParam) );
257 					}
258 
259 					return 0;
260 
261 				case HCBT_DESTROYWND:
262 
263 					NST_VERIFY( wParam );
264 
265 					if (wParam)
266 						global.hooks.OnDestroy( reinterpret_cast<HWND>(wParam) );
267 
268 					return 0;
269 			}
270 
271 			return nCode < 0 ? ::CallNextHookEx( global.hooks.handle, nCode, wParam, lParam ) : 0;
272 		}
273 
274 		#ifdef NST_MSVC_OPTIMIZE
275 		#pragma optimize("", on)
276 		#endif
277 
Global()278 		Instance::Global::Global()
279 		:
280 		paths     ( ::GetModuleHandle(NULL) ),
281 		version   ( paths.exePath.Ptr(), Resource::Version::PRODUCT ),
282 		hooks     ( ::GetModuleHandle(NULL) ),
283 		iconStyle ( ICONSTYLE_NES )
284 		{
285 		}
286 
GetLanguage()287 		Instance::Language& Instance::GetLanguage()
288 		{
289 			return global.language;
290 		}
291 
GetVersion()292 		const String::Heap<char>& Instance::GetVersion()
293 		{
294 			return global.version;
295 		}
296 
GetExePath()297 		const Path& Instance::GetExePath()
298 		{
299 			return global.paths.exePath;
300 		}
301 
GetExePath(const GenericString file)302 		const Path Instance::GetExePath(const GenericString file)
303 		{
304 			return Path( global.paths.exePath.Directory(), file );
305 		}
306 
GetConfigPath(const GenericString file)307 		const Path Instance::GetConfigPath(const GenericString file)//bg
308 		{
309 			return Path( global.paths.configPath, file );
310 		}
311 
GetFullPath(const GenericString relPath)312 		const Path Instance::GetFullPath(const GenericString relPath)
313 		{
314 			Path curPath( "." );
315 			curPath.MakeAbsolute( true );
316 
317 			const BOOL succeeded = ::SetCurrentDirectory( global.paths.wrkPath.Ptr() );
318 
319 			Path wrkPath( relPath );
320 			wrkPath.MakeAbsolute();
321 
322 			if (succeeded)
323 				::SetCurrentDirectory( curPath.Ptr() );
324 
325 			return wrkPath;
326 		}
327 
GetLongPath(wcstring const shortPath)328 		const Path Instance::GetLongPath(wcstring const shortPath)
329 		{
330 			Path longPath;
331 
332 			longPath.Reserve( 255 );
333 			uint length = ::GetLongPathName( shortPath, longPath.Ptr(), longPath.Capacity() + 1 );
334 
335 			if (length > longPath.Capacity() + 1)
336 			{
337 				longPath.Reserve( length - 1 );
338 				length = ::GetLongPathName( shortPath, longPath.Ptr(), longPath.Capacity() + 1 );
339 			}
340 
341 			if (!length)
342 				return shortPath;
343 
344 			const wchar_t slashed = GenericString(shortPath).Back();
345 
346 			longPath.ShrinkTo( length );
347 			longPath.MakePretty( slashed == '\\' || slashed == '/' );
348 
349 			return longPath;
350 		}
351 
GetTmpPath(GenericString file)352 		const Path Instance::GetTmpPath(GenericString file)
353 		{
354 			Path path;
355 			path.Reserve( 255 );
356 
357 			if (uint length = ::GetTempPath( path.Capacity() + 1, path.Ptr() ))
358 			{
359 				if (length > path.Capacity() + 1)
360 				{
361 					path.Reserve( length - 1 );
362 					length = ::GetTempPath( path.Capacity() + 1, path.Ptr() );
363 				}
364 
365 				if (length)
366 					path = GetLongPath( path.Ptr() );
367 			}
368 
369 			if (path.Length())
370 				path.MakePretty( true );
371 			else
372 				path << ".\\";
373 
374 			if (file.Empty())
375 				file = L"nestopia.tmp";
376 
377 			path << file;
378 
379 			return path;
380 		}
381 
~Callbacks()382 		Instance::Events::Callbacks::~Callbacks()
383 		{
384 			NST_VERIFY( Empty() );
385 		}
386 
Add(const Callback & callback)387 		void Instance::Events::Add(const Callback& callback)
388 		{
389 			NST_ASSERT( bool(callback) && !callbacks.Find( callback ) );
390 			callbacks.PushBack( callback );
391 		}
392 
Signal(const Event event,const void * param)393 		void Instance::Events::Signal(const Event event,const void* param)
394 		{
395 			for (Callbacks::ConstIterator it(callbacks.Begin()), end(callbacks.End()); it != end; ++it)
396 				(*it)( event, param );
397 		}
398 
Remove(const void * const instance)399 		void Instance::Events::Remove(const void* const instance)
400 		{
401 			for (Callbacks::Iterator it(callbacks.Begin()); it != callbacks.End(); ++it)
402 			{
403 				if (it->VoidPtr() == instance)
404 				{
405 					callbacks.Erase( it );
406 					break;
407 				}
408 			}
409 		}
410 
GetMainWindow()411 		Window::Generic Instance::GetMainWindow()
412 		{
413 			return global.hooks.window;
414 		}
415 
NumChildWindows()416 		uint Instance::NumChildWindows()
417 		{
418 			return global.hooks.children.Size();
419 		}
420 
GetChildWindow(uint i)421 		Window::Generic Instance::GetChildWindow(uint i)
422 		{
423 			return global.hooks.children[i];
424 		}
425 
GetActiveWindow()426 		Window::Generic Instance::GetActiveWindow()
427 		{
428 			HWND hWnd = ::GetActiveWindow();
429 			return hWnd ? hWnd : global.hooks.window;
430 		}
431 
GetIconStyle()432 		Instance::IconStyle Instance::GetIconStyle()
433 		{
434 			return global.iconStyle;
435 		}
436 
SetIconStyle(IconStyle style)437 		void Instance::SetIconStyle(IconStyle style)
438 		{
439 			global.iconStyle = style;
440 		}
441 
ShowChildWindows(bool show)442 		void Instance::ShowChildWindows(bool show)
443 		{
444 			const uint state = (show ? SW_SHOWNA : SW_HIDE);
445 
446 			for (Global::Hooks::Children::ConstIterator it(global.hooks.children.Begin()), end(global.hooks.children.End()); it != end; ++it)
447 				::ShowWindow( *it, state );
448 		}
449 
Waiter()450 		Instance::Waiter::Waiter()
451 		: hCursor(::SetCursor(Resource::Cursor::GetWait())) {}
452 
Locker()453 		Instance::Locker::Locker()
454 		: hWnd(GetMainWindow()), enabled(::IsWindowEnabled(hWnd))
455 		{
456 			::EnableWindow( hWnd, false );
457 			::LockWindowUpdate( hWnd );
458 		}
459 
CheckInput(int vKey) const460 		bool Instance::Locker::CheckInput(int vKey) const
461 		{
462 			if (::GetAsyncKeyState( vKey ) & 0x8000U)
463 			{
464 				while (::GetAsyncKeyState( vKey ) & 0x8000U)
465 					::Sleep( 10 );
466 
467 				return true;
468 			}
469 
470 			return false;
471 		}
472 
~Locker()473 		Instance::Locker::~Locker()
474 		{
475 			::LockWindowUpdate( NULL );
476 			::EnableWindow( hWnd, enabled );
477 			for (MSG msg;::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE|PM_QS_INPUT ););
478 		}
479 
Instance()480 		Instance::Instance()
481 		{
482 			if (!static_cast<const Configuration&>(cfg)["preferences"]["application"]["allow-multiple-instances"].Yes())
483 			{
484 				::CreateMutex( NULL, true, L"Nestopia Mutex" );
485 
486 				if (::GetLastError() == ERROR_ALREADY_EXISTS)
487 				{
488 					if (Window::Generic window = Window::Generic::Find( appClassName ))
489 					{
490 						window.Activate();
491 
492 						if (const uint length = cfg.GetStartupFile().Length())
493 						{
494 							COPYDATASTRUCT cds;
495 
496 							cds.dwData = COPYDATA_OPENFILE_ID;
497 							cds.cbData = (length + 1) * sizeof(wchar_t);
498 							cds.lpData = const_cast<wchar_t*>(cfg.GetStartupFile().Ptr());
499 
500 							window.Send( WM_COPYDATA, 0, &cds );
501 						}
502 					}
503 
504 					throw Exception();
505 				}
506 			}
507 
508 			if (global.paths.exePath.Empty())
509 				throw Exception( L"unicows.dll file is missing!" );
510 
511 			global.language.Load( cfg );
512 
513 			if (global.hooks.handle == NULL)
514 				throw Exception( IDS_ERR_FAILED, L"SetWindowsHookEx()" );
515 
516 			if (FAILED(::CoInitializeEx( NULL, COINIT_APARTMENTTHREADED )))
517 				throw Exception( IDS_ERR_FAILED, L"CoInitializeEx()" );
518 
519 			Object::Pod<INITCOMMONCONTROLSEX> initCtrlEx;
520 
521 			initCtrlEx.dwSize = sizeof(initCtrlEx);
522 			initCtrlEx.dwICC = ICC_WIN95_CLASSES;
523 			::InitCommonControlsEx( &initCtrlEx );
524 		}
525 
~Instance()526 		Instance::~Instance()
527 		{
528 			::CoUninitialize();
529 		}
530 
Save()531 		void Instance::Save()
532 		{
533 			global.language.Save( cfg );
534 		}
535 	}
536 }
537