mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
536 lines
12 KiB
C++
536 lines
12 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Nestopia - NES/Famicom emulator written in C++
|
|
//
|
|
// Copyright (C) 2003-2008 Martin Freij
|
|
//
|
|
// This file is part of Nestopia.
|
|
//
|
|
// Nestopia is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Nestopia is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Nestopia; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "language/resource.h"
|
|
#include "NstResourceCursor.hpp"
|
|
#include "NstResourceVersion.hpp"
|
|
#include "NstApplicationException.hpp"
|
|
#include "NstApplicationLanguage.hpp"
|
|
#include <CommCtrl.h>
|
|
#include <ObjBase.h>
|
|
#include <Shlobj.h>
|
|
#include <Shellapi.h>
|
|
|
|
|
|
#if NST_MSVC
|
|
#pragma comment(lib,"comctl32")
|
|
#endif
|
|
|
|
namespace Nestopia
|
|
{
|
|
namespace Application
|
|
{
|
|
const wchar_t Instance::appClassName[] = L"Nestopia";
|
|
|
|
struct Instance::Global
|
|
{
|
|
Global();
|
|
|
|
struct Paths
|
|
{
|
|
explicit Paths(HINSTANCE);
|
|
|
|
Path exePath;
|
|
Path wrkPath;
|
|
Path configPath;//bg configuration files path
|
|
};
|
|
|
|
struct Hooks
|
|
{
|
|
explicit Hooks(HINSTANCE);
|
|
~Hooks();
|
|
|
|
void OnCreate(const CREATESTRUCT&,HWND);
|
|
void OnDestroy(HWND);
|
|
|
|
static LRESULT CALLBACK CBTProc(int,WPARAM,LPARAM);
|
|
|
|
enum
|
|
{
|
|
CHILDREN_RESERVE = 8
|
|
};
|
|
|
|
typedef Collection::Vector<HWND> Children;
|
|
|
|
HHOOK const handle;
|
|
HWND window;
|
|
Children children;
|
|
};
|
|
|
|
const Paths paths;
|
|
const Resource::Version version;
|
|
Language language;
|
|
Hooks hooks;
|
|
IconStyle iconStyle;
|
|
};
|
|
|
|
Instance::Global Instance::global;
|
|
Instance::Events::Callbacks Instance::Events::callbacks;
|
|
|
|
Instance::Global::Paths::Paths(HINSTANCE const hInstance)
|
|
{
|
|
uint length;
|
|
|
|
do
|
|
{
|
|
exePath.Reserve( exePath.Capacity() + 255 );
|
|
length = ::GetModuleFileName( hInstance, exePath.Ptr(), exePath.Capacity() );
|
|
}
|
|
while (length == exePath.Capacity());
|
|
|
|
exePath.ShrinkTo( length );
|
|
exePath.MakePretty( false );
|
|
exePath.Defrag();
|
|
|
|
wrkPath = ".";
|
|
wrkPath.MakeAbsolute( true );
|
|
wrkPath.Defrag();
|
|
|
|
|
|
configPath.Reserve(configPath.Capacity() + 255);//bg
|
|
|
|
//bg
|
|
bool isLua = false;
|
|
|
|
//bg check -lua between command line options
|
|
int nArgs;
|
|
LPWSTR *szArglist = ::CommandLineToArgvW(::GetCommandLineW(), &nArgs);
|
|
if( szArglist != NULL) {
|
|
for(int i=0; i<nArgs; i++) {
|
|
isLua = ::wcscmp(szArglist[i],L"-lua") == 0;
|
|
if (isLua) break;
|
|
}
|
|
|
|
::LocalFree(szArglist);
|
|
}
|
|
|
|
//bg END check
|
|
|
|
//bg set configPath
|
|
if(isLua &&
|
|
|
|
SUCCEEDED(SHGetFolderPath(NULL,
|
|
CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE,
|
|
NULL,
|
|
0,
|
|
configPath.Ptr()))) {
|
|
configPath.ShrinkTo( std::wcslen(configPath.Ptr()) );
|
|
configPath.MakePretty( false );
|
|
configPath.Append (L"\\Nestopia",9);
|
|
configPath.MakePretty( false );
|
|
configPath.Defrag();
|
|
::CreateDirectory(configPath.Ptr(), NULL);
|
|
}
|
|
|
|
else {
|
|
::GetModuleFileName( hInstance, configPath.Ptr(), configPath.Capacity() );
|
|
configPath.ShrinkTo(configPath.FindLastOf('\\',configPath.Capacity()));//bg fix issue #265
|
|
};
|
|
|
|
configPath.Defrag();
|
|
}
|
|
|
|
Instance::Global::Hooks::Hooks(HINSTANCE const hInstance)
|
|
: handle(::SetWindowsHookEx( WH_CBT, CBTProc, hInstance, ::GetCurrentThreadId() ))
|
|
{
|
|
children.Reserve( CHILDREN_RESERVE );
|
|
}
|
|
|
|
Instance::Global::Hooks::~Hooks()
|
|
{
|
|
if (handle)
|
|
::UnhookWindowsHookEx( handle );
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("t", on)
|
|
#endif
|
|
|
|
void Instance::Global::Hooks::OnCreate(const CREATESTRUCT& createStruct,HWND const hWnd)
|
|
{
|
|
NST_ASSERT( hWnd );
|
|
|
|
if (window == createStruct.hwndParent || !window)
|
|
{
|
|
{
|
|
static const wchar_t menuClassName[] = L"#32768"; // documented on MSDN
|
|
|
|
enum
|
|
{
|
|
MAX_LENGTH = NST_MAX( sizeof(array(appClassName)) - 1,
|
|
NST_MAX( sizeof(array(STATUSCLASSNAME)) - 1, sizeof(array(menuClassName)) - 1 ))
|
|
};
|
|
|
|
String::Stack<MAX_LENGTH,wchar_t> name;
|
|
name.ShrinkTo( ::GetClassName( hWnd, name.Ptr(), MAX_LENGTH+1 ) );
|
|
|
|
if (name.Equal( menuClassName ) || name.Equal( STATUSCLASSNAME ) || name.Equal( L"IME" ))
|
|
return;
|
|
|
|
if (window)
|
|
{
|
|
children.PushBack( hWnd );
|
|
Events::Signal( EVENT_SYSTEM_BUSY );
|
|
}
|
|
else if (name.Equal( appClassName ))
|
|
{
|
|
window = hWnd;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
const Events::WindowCreateParam param =
|
|
{
|
|
hWnd,
|
|
createStruct.cx > 0 ? createStruct.cx : 0,
|
|
createStruct.cy > 0 ? createStruct.cy : 0
|
|
};
|
|
|
|
Events::Signal( EVENT_WINDOW_CREATE, ¶m );
|
|
}
|
|
}
|
|
|
|
void Instance::Global::Hooks::OnDestroy(HWND const hWnd)
|
|
{
|
|
NST_ASSERT( hWnd );
|
|
|
|
Children::Iterator const child = children.Find( hWnd );
|
|
|
|
if (child)
|
|
{
|
|
Events::Signal( EVENT_SYSTEM_BUSY );
|
|
}
|
|
else if (window != hWnd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
{
|
|
const Events::WindowDestroyParam param = { hWnd };
|
|
Events::Signal( EVENT_WINDOW_DESTROY, ¶m );
|
|
}
|
|
|
|
if (child)
|
|
children.Erase( child );
|
|
else
|
|
window = NULL;
|
|
}
|
|
|
|
LRESULT CALLBACK Instance::Global::Hooks::CBTProc(const int nCode,const WPARAM wParam,const LPARAM lParam)
|
|
{
|
|
NST_COMPILE_ASSERT( HCBT_CREATEWND >= 0 && HCBT_DESTROYWND >= 0 );
|
|
|
|
switch (nCode)
|
|
{
|
|
case HCBT_CREATEWND:
|
|
|
|
NST_ASSERT( lParam );
|
|
|
|
if (const CREATESTRUCT* const createStruct = reinterpret_cast<const CBT_CREATEWND*>(lParam)->lpcs)
|
|
{
|
|
NST_VERIFY( wParam );
|
|
global.hooks.OnCreate( *createStruct, reinterpret_cast<HWND>(wParam) );
|
|
}
|
|
|
|
return 0;
|
|
|
|
case HCBT_DESTROYWND:
|
|
|
|
NST_VERIFY( wParam );
|
|
|
|
if (wParam)
|
|
global.hooks.OnDestroy( reinterpret_cast<HWND>(wParam) );
|
|
|
|
return 0;
|
|
}
|
|
|
|
return nCode < 0 ? ::CallNextHookEx( global.hooks.handle, nCode, wParam, lParam ) : 0;
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
Instance::Global::Global()
|
|
:
|
|
paths ( ::GetModuleHandle(NULL) ),
|
|
version ( paths.exePath.Ptr(), Resource::Version::PRODUCT ),
|
|
hooks ( ::GetModuleHandle(NULL) ),
|
|
iconStyle ( ICONSTYLE_NES )
|
|
{
|
|
}
|
|
|
|
Instance::Language& Instance::GetLanguage()
|
|
{
|
|
return global.language;
|
|
}
|
|
|
|
const String::Heap<char>& Instance::GetVersion()
|
|
{
|
|
return global.version;
|
|
}
|
|
|
|
const Path& Instance::GetExePath()
|
|
{
|
|
return global.paths.exePath;
|
|
}
|
|
|
|
const Path Instance::GetExePath(const GenericString file)
|
|
{
|
|
return Path( global.paths.exePath.Directory(), file );
|
|
}
|
|
|
|
const Path Instance::GetConfigPath(const GenericString file)//bg
|
|
{
|
|
return Path( global.paths.configPath, file );
|
|
}
|
|
|
|
const Path Instance::GetFullPath(const GenericString relPath)
|
|
{
|
|
Path curPath( "." );
|
|
curPath.MakeAbsolute( true );
|
|
|
|
const BOOL succeeded = ::SetCurrentDirectory( global.paths.wrkPath.Ptr() );
|
|
|
|
Path wrkPath( relPath );
|
|
wrkPath.MakeAbsolute();
|
|
|
|
if (succeeded)
|
|
::SetCurrentDirectory( curPath.Ptr() );
|
|
|
|
return wrkPath;
|
|
}
|
|
|
|
const Path Instance::GetLongPath(wcstring const shortPath)
|
|
{
|
|
Path longPath;
|
|
|
|
longPath.Reserve( 255 );
|
|
uint length = ::GetLongPathName( shortPath, longPath.Ptr(), longPath.Capacity() + 1 );
|
|
|
|
if (length > longPath.Capacity() + 1)
|
|
{
|
|
longPath.Reserve( length - 1 );
|
|
length = ::GetLongPathName( shortPath, longPath.Ptr(), longPath.Capacity() + 1 );
|
|
}
|
|
|
|
if (!length)
|
|
return shortPath;
|
|
|
|
const wchar_t slashed = GenericString(shortPath).Back();
|
|
|
|
longPath.ShrinkTo( length );
|
|
longPath.MakePretty( slashed == '\\' || slashed == '/' );
|
|
|
|
return longPath;
|
|
}
|
|
|
|
const Path Instance::GetTmpPath(GenericString file)
|
|
{
|
|
Path path;
|
|
path.Reserve( 255 );
|
|
|
|
if (uint length = ::GetTempPath( path.Capacity() + 1, path.Ptr() ))
|
|
{
|
|
if (length > path.Capacity() + 1)
|
|
{
|
|
path.Reserve( length - 1 );
|
|
length = ::GetTempPath( path.Capacity() + 1, path.Ptr() );
|
|
}
|
|
|
|
if (length)
|
|
path = GetLongPath( path.Ptr() );
|
|
}
|
|
|
|
if (path.Length())
|
|
path.MakePretty( true );
|
|
else
|
|
path << ".\\";
|
|
|
|
if (file.Empty())
|
|
file = L"nestopia.tmp";
|
|
|
|
path << file;
|
|
|
|
return path;
|
|
}
|
|
|
|
Instance::Events::Callbacks::~Callbacks()
|
|
{
|
|
NST_VERIFY( Empty() );
|
|
}
|
|
|
|
void Instance::Events::Add(const Callback& callback)
|
|
{
|
|
NST_ASSERT( bool(callback) && !callbacks.Find( callback ) );
|
|
callbacks.PushBack( callback );
|
|
}
|
|
|
|
void Instance::Events::Signal(const Event event,const void* param)
|
|
{
|
|
for (Callbacks::ConstIterator it(callbacks.Begin()), end(callbacks.End()); it != end; ++it)
|
|
(*it)( event, param );
|
|
}
|
|
|
|
void Instance::Events::Remove(const void* const instance)
|
|
{
|
|
for (Callbacks::Iterator it(callbacks.Begin()); it != callbacks.End(); ++it)
|
|
{
|
|
if (it->VoidPtr() == instance)
|
|
{
|
|
callbacks.Erase( it );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Window::Generic Instance::GetMainWindow()
|
|
{
|
|
return global.hooks.window;
|
|
}
|
|
|
|
uint Instance::NumChildWindows()
|
|
{
|
|
return global.hooks.children.Size();
|
|
}
|
|
|
|
Window::Generic Instance::GetChildWindow(uint i)
|
|
{
|
|
return global.hooks.children[i];
|
|
}
|
|
|
|
Window::Generic Instance::GetActiveWindow()
|
|
{
|
|
HWND hWnd = ::GetActiveWindow();
|
|
return hWnd ? hWnd : global.hooks.window;
|
|
}
|
|
|
|
Instance::IconStyle Instance::GetIconStyle()
|
|
{
|
|
return global.iconStyle;
|
|
}
|
|
|
|
void Instance::SetIconStyle(IconStyle style)
|
|
{
|
|
global.iconStyle = style;
|
|
}
|
|
|
|
void Instance::ShowChildWindows(bool show)
|
|
{
|
|
const uint state = (show ? SW_SHOWNA : SW_HIDE);
|
|
|
|
for (Global::Hooks::Children::ConstIterator it(global.hooks.children.Begin()), end(global.hooks.children.End()); it != end; ++it)
|
|
::ShowWindow( *it, state );
|
|
}
|
|
|
|
Instance::Waiter::Waiter()
|
|
: hCursor(::SetCursor(Resource::Cursor::GetWait())) {}
|
|
|
|
Instance::Locker::Locker()
|
|
: hWnd(GetMainWindow()), enabled(::IsWindowEnabled(hWnd))
|
|
{
|
|
::EnableWindow( hWnd, false );
|
|
::LockWindowUpdate( hWnd );
|
|
}
|
|
|
|
bool Instance::Locker::CheckInput(int vKey) const
|
|
{
|
|
if (::GetAsyncKeyState( vKey ) & 0x8000U)
|
|
{
|
|
while (::GetAsyncKeyState( vKey ) & 0x8000U)
|
|
::Sleep( 10 );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Instance::Locker::~Locker()
|
|
{
|
|
::LockWindowUpdate( NULL );
|
|
::EnableWindow( hWnd, enabled );
|
|
for (MSG msg;::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE|PM_QS_INPUT ););
|
|
}
|
|
|
|
Instance::Instance()
|
|
{
|
|
if (!static_cast<const Configuration&>(cfg)["preferences"]["application"]["allow-multiple-instances"].Yes())
|
|
{
|
|
::CreateMutex( NULL, true, L"Nestopia Mutex" );
|
|
|
|
if (::GetLastError() == ERROR_ALREADY_EXISTS)
|
|
{
|
|
if (Window::Generic window = Window::Generic::Find( appClassName ))
|
|
{
|
|
window.Activate();
|
|
|
|
if (const uint length = cfg.GetStartupFile().Length())
|
|
{
|
|
COPYDATASTRUCT cds;
|
|
|
|
cds.dwData = COPYDATA_OPENFILE_ID;
|
|
cds.cbData = (length + 1) * sizeof(wchar_t);
|
|
cds.lpData = const_cast<wchar_t*>(cfg.GetStartupFile().Ptr());
|
|
|
|
window.Send( WM_COPYDATA, 0, &cds );
|
|
}
|
|
}
|
|
|
|
throw Exception();
|
|
}
|
|
}
|
|
|
|
if (global.paths.exePath.Empty())
|
|
throw Exception( L"unicows.dll file is missing!" );
|
|
|
|
global.language.Load( cfg );
|
|
|
|
if (global.hooks.handle == NULL)
|
|
throw Exception( IDS_ERR_FAILED, L"SetWindowsHookEx()" );
|
|
|
|
if (FAILED(::CoInitializeEx( NULL, COINIT_APARTMENTTHREADED )))
|
|
throw Exception( IDS_ERR_FAILED, L"CoInitializeEx()" );
|
|
|
|
Object::Pod<INITCOMMONCONTROLSEX> initCtrlEx;
|
|
|
|
initCtrlEx.dwSize = sizeof(initCtrlEx);
|
|
initCtrlEx.dwICC = ICC_WIN95_CLASSES;
|
|
::InitCommonControlsEx( &initCtrlEx );
|
|
}
|
|
|
|
Instance::~Instance()
|
|
{
|
|
::CoUninitialize();
|
|
}
|
|
|
|
void Instance::Save()
|
|
{
|
|
global.language.Save( cfg );
|
|
}
|
|
}
|
|
}
|