nestopia/source/win32/NstApplicationInstance.cpp

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, &param );
}
}
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, &param );
}
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 );
}
}
}