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, ¶m ); 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, ¶m ); 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