#include "precomp__gen_ff.h"
#include <windows.h>
#include <commctrl.h>

#include "main.h"
#include "resource.h"
#include "prefs.h"
#include "wa2frontend.h"
#include "wa2groupdefs.h"
#include "wa2wndembed.h"
#include "wa2cfgitems.h"
#include "wa2coreactions.h"
#include "wa2pledit.h"
#include "embedwndguid.h"
#include "gen_ff_ipc.h"
#include "fsmonitor.h"
#include <api/wnd/wndclass/foreignwnd.h>
#include "../winamp/wa_ipc.h"
#include "../gen_hotkeys/wa_hotkeys.h"
#include <api/script/objects/c_script/c_group.h>
#include <api/script/objects/c_script/c_text.h>

#include <api/apiinit.h>
#include <api/wnd/rootwnd.h>
#include <api/script/objects/guiobject.h>
#include <api/core/coreactions.h>
#include "menuactions.h"
#include <api/wnd/wndclass/oswndhost.h>
#include <api/script/scriptobj.h>
#include <api/service/svcs/svc_wndcreate.h>
#include <bfc/reentryfilter.h>
#include <api/skin/skinparse.h>
#include <api/wndmgr/skinembed.h>
#include <api/skin/widgets/xuiwndholder.h>
#include <api/skin/widgets/text.h>
#include <api/script/scriptmgr.h>
#include <api/wac/compon.h>
#include <api/application/version.h>
#include <bfc/parse/pathparse.h>
#include <tataki/blending/blending.h>
#include "skininfo.h"
#include <api/skin/guitree.h>

#include <bfc/wasabi_std_wnd.h>

#include "wa2core.h"
#include <api/locales/xlatstr.h>

#include <api/wndmgr/gc.h>
#include <api/syscb/callbacks/gccb.h>
#include <api/script/vcpu.h>
#include <tataki/canvas/bltcanvas.h>

#include "../nu/AutoWide.h"
#include <shlwapi.h>
#include <windowsx.h>
//wtf?
#define _WAFE_H_
#define ___HOTAMP3_H___
#include "../gen_hotkeys/hotkey.h"

int embedCreateProc(embedWindowState *p, embedEnumStruct *parms);

DECLARE_MODULE_SVCMGR;

#define VERSION L"1.55"
//#define VIDDEBUG
//#define DEBUG_CAPTURES

#include "../gen_ml/ml_ipc.h"
librarySendToMenuStruct mainSendTo;

#include "../Agave/Language/api_language.h"
// wasabi based services for localisation support
api_language *WASABI_API_LNG = 0;
HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST;
static wchar_t szDescription[256];

//-----------------------------------------------------------------------------------------------
#define GET_X_LPARAM(lp)                        ((int)(short)LOWORD(lp))
#define GET_Y_LPARAM(lp)                        ((int)(short)HIWORD(lp))

#define ID_FILE_SHOWLIBRARY     40379
#define WINAMP_OPTIONS_PREFS    40012
#define WINAMP_HELP_ABOUT       40041
#define WINAMP_LIGHTNING_CLICK  40339
#define WINAMP
#define UPDATE_EGG 0xC0DE+2
#define VIEWPORT 0xC0DE+3
static int DEFERREDCALLBACKMSG;
static int UPDATEDIALOGBOXPARENTMSG;

//-----------------------------------------------------------------------------------------------
void config();
void quit();
int init();
void quit_inst();
void init_inst();
void ToggleLayout(const wchar_t *containerName);
void RestoreClassicWinamp(int was_loaded);
extern "C" winampGeneralPurposePlugin plugin =
{
	GPPHDR_VER_U,
	"nullsoft(gen_ff.dll)",
	init,
	config,
	quit,
};

//-----------------------------------------------------------------------------------------------

int wantContainerInMenu(Container *cont)
{
	wchar_t tmpBuf[96] = {0};
	GUID g = cont->getGUID();
	if (WCSCASEEQLSAFE(cont->getId(), L"main")) return 0;
	if (cont->getNoMenu()) return 0;
	if (g == pleditWndGuid) return 0;
	if (g == videoWndGuid) return 0;
	if (g == avs_guid) return 0;
	if (g == library_guid) return 0;
	if (WCSCASEEQLSAFE(cont->getName(), L":componenttitle")) return 0;
	if (WCSCASEEQLSAFE(cont->getName(), L"Playlist Editor") || WCSCASEEQLSAFE(cont->getName(), L"Playlist") || WCSCASEEQLSAFE(cont->getId(), L"pledit")) return 0;
	if (WCSCASEEQLSAFE(cont->getName(), L"Video Window") || WCSCASEEQLSAFE(cont->getName(), L"Video") || WCSCASEEQLSAFE(cont->getId(), L"Video")) return 0;
	if (WCSCASEEQLSAFE(cont->getName(), WASABI_API_LNG->GetStringFromGUIDW(GenMlLangGUID,plugin.hDllInstance,18)) ||
	    WCSCASEEQLSAFE(cont->getName(), L"Media Library") ||
	    WCSCASEEQLSAFE(cont->getId(), L"Library") ||
	    WCSCASEEQLSAFE(cont->getName(),WASABI_API_LNGSTRINGW_BUF(IDS_MEDIA_LIBRARY,tmpBuf,96))) return 0;
	if (WCSCASEEQLSAFE(cont->getName(), L"AVS") || WCSCASEEQLSAFE(cont->getName(), WASABI_API_LNGSTRINGW(IDS_VISUALIZATIONS)) || WCSCASEEQLSAFE(cont->getName(), L"Vis") || WCSCASEEQLSAFE(cont->getId(), L"Avs") || WCSCASEEQLSAFE(cont->getId(), L"Vis")) return 0;
	return 1;
}


//-----------------------------------------------------------------------------------------------

api_playlists       *AGAVE_API_PLAYLISTS       = 0;
api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
api_albumart        *AGAVE_API_ALBUMART        = 0;
api_downloadManager *WAC_API_DOWNLOADMANAGER = 0;
api_colorthemes     *WASABI_API_COLORTHEMES    = 0;
api_palette         *WASABI_API_PALETTE        = 0;
api_threadpool      *WASABI_API_THREADPOOL     = 0;

StringW m_lastskin_nam, m_lastskin_dir;

inline int lumidiff(int a, int b) {
	int r1 = (a & 0xFF0000) >> 16;
	int r2 = (b & 0xFF0000) >> 16;
	int g1 = (a & 0xFF00) >> 8;
	int g2 = (b & 0xFF00) >> 8;
	int b1 = a & 0xFF;
	int b2 = b & 0xFF;
	return MIN((ABS(r1 - r2), ABS(g1 - g2)), ABS(b1 - b2));
}

static void doplaylistcolors()
{
	int interpolate = 0;
	if (SkinParser::getSkinVersion()*10 < 10) interpolate = 1;

	// update colors
	int buf[6] = {0};
	waSetPlColorsStruct s = {0, };
	extern COLORREF getWindowBackground(COLORREF *);
	COLORREF windowbackground;
	buf[5] = getWindowBackground(&windowbackground); // auto inverted

	int need_cols = 1, need_bms = 1;

	if (!m_lastskin_dir.isempty())
	{
		StringPathCombine bitmapfilename(m_lastskin_dir, L"pledit.bmp");
		HANDLE h = CreateFileW(bitmapfilename, 0, 0, 0, OPEN_EXISTING, 0, 0);
		if (h != INVALID_HANDLE_VALUE)
		{
			CloseHandle(h);
			need_bms = 0;
		}

		bitmapfilename = StringPathCombine(m_lastskin_dir, L"pledit.txt");
		h = CreateFileW(bitmapfilename, 0, 0, 0, OPEN_EXISTING, 0, 0);
		if (h != INVALID_HANDLE_VALUE)
		{
			CloseHandle(h);
			need_cols = 0;
		}
	}

	if (need_cols)
	{
		s.numElems = 6;
		s.elems = buf;

		// list background
		if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.list.background"))
			buf[2] = SkinColor(L"wasabi.list.background") & 0xFFFFFF;
		else
			buf[2] = RGBTOBGR(WASABI_API_SKIN->skin_getBitmapColor(L"wasabi.list.background")) & 0xFFFFFF;  // inverted coz coming from bitmap

		// normal text
		if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.list.text"))
			buf[0] = SkinColor(L"wasabi.list.text") & 0xFFFFFF;
		else
			buf[0] = SkinColor(L"wasabi.edit.text") & 0xFFFFFF;

		if (interpolate)
		{
			int c = buf[0];
			c = lumidiff(c, buf[2]) < 0x1F ? Blenders::BLEND_AVG(buf[0], 0xFF7F7F7F) : c;
			c = lumidiff(c, buf[2]) < 0x1F ? Blenders::BLEND_AVG(buf[0], 0xFF101010) : c;
			buf[0] = c & 0xFFFFFF;
		}

		COLORREF selected;

		// selected items background
		if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.list.text.selected.background"))
		{
			selected = SkinColor(L"wasabi.list.text.selected.background");
			buf[3] = selected;
		}
		else
		{
			// inverted twice, see bellow
			if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.list.item.selected"))
				selected = RGBTOBGR(SkinColor(L"wasabi.list.item.selected"));
			else
				selected = RGBTOBGR(SkinColor(SKINCOLOR_TREE_SELITEMBKG));

			COLORREF col = RGBTOBGR(SkinColor(SKINCOLOR_LIST_COLUMNBKG));
			int a = MAX((col & 0xFF0000) >> 16, MAX((col & 0xFF00) >> 8, col & 0xFF));
			col = a < 0x1F ? Blenders::BLEND_AVG(windowbackground, 0xFF000000) : col;
			int listbkg = RGBTOBGR(buf[2]);
			selected = lumidiff(selected, listbkg) < 0x2F ? Blenders::BLEND_AVG(windowbackground, 0xFF7F7F7F) : selected;
			selected = lumidiff(selected, listbkg) < 0x2F ? Blenders::BLEND_AVG(listbkg, 0xFF7F7F7F) : selected;
			selected = lumidiff(selected, listbkg) < 0x2F ? Blenders::BLEND_AVG(windowbackground, 0xFFF0F0F0) : selected;
			selected = lumidiff(selected, listbkg) < 0x2F ? Blenders::BLEND_AVG(listbkg, 0xFFF0F0F0) : selected;
			selected = lumidiff(selected, listbkg) < 0x2F ? Blenders::BLEND_AVG(col, 0xFFF0F0F0) : selected;
			selected = lumidiff(selected, listbkg) < 0x2F ? Blenders::BLEND_AVG(col, 0xFF101010) : selected;
			selected = lumidiff(selected, RGBTOBGR(buf[0])) < 0x1F ? Blenders::BLEND_AVG(selected, 0xFF101010) : selected;
			selected = lumidiff(selected, RGBTOBGR(buf[0])) < 0x1F ? Blenders::BLEND_AVG(selected, 0xFFF0F0F0) : selected;
			selected &= 0xFFFFFF;
			buf[3] = RGBTOBGR(selected);
		}

		// active text
		if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.list.text.current"))
			buf[1] = SkinColor(L"wasabi.list.text.current");
		else
		{
			COLORREF act = Blenders::BLEND_AVG(selected, buf[0]);
			act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(buf[2], 0xFFFFFFFF) : act;
			act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(buf[2], 0xFF101010) : act;
			act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(selected, buf[0]) : act;
			act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(selected, buf[2]) : act;
			act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(buf[0], buf[2]) : act;
			buf[1] = act & 0xFFFFFF;
		}
	}

	if (need_bms)
	{
		BltCanvas c(280, 186);

		c.fillBits(windowbackground); // already inverted

		void blitButtonToCanvas(int w, int h, int state, const wchar_t *overlayelement, int xpos, int ypos, BltCanvas *c);

		blitButtonToCanvas(8, 18, 0, L"wasabi.scrollbar.vertical.grip", 52, 53, &c);
		blitButtonToCanvas(8, 18, 1, L"wasabi.scrollbar.vertical.grip", 61, 53, &c);

		int xpos, ypos;
		//w=8,h=29
		int cols[2];
		int *fb = (int*)c.getBits() + 280 * 42;

		// inverted colors because going into bitmap

		if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.scrollbar.background.inverted"))
			cols[0] = RGBTOBGR(SkinColor(L"wasabi.scrollbar.background.inverted"));
		else
		{
			if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.list.column.empty"))
				cols[0] = RGBTOBGR(SkinColor(L"wasabi.list.column.empty"));
			else
			{
				COLORREF col = SkinColor(SKINCOLOR_LIST_COLUMNBKG);
				int a = MAX((col & 0xFF0000) >> 16, MAX((col & 0xFF00) >> 8, col & 0xFF));
				col = a < 0x1F ? Blenders::BLEND_AVG(windowbackground, 0xFF000000) : col;
				cols[0] = RGBTOBGR(Blenders::BLEND_AVG(col, 0xFF000000));
			}
		}

		if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.border.sunken"))
			cols[1] = RGBTOBGR(SkinColor(L"wasabi.border.sunken"));
		else
		{
			int a = MAX((windowbackground & 0xFF0000) >> 16, MAX((windowbackground & 0xFF00) >> 8, windowbackground & 0xFF));
			cols[1] = Blenders::BLEND_AVG(windowbackground, a > 0xE0 ? 0xFF000000 : 0xFFFFFFFF);
		}

		for (ypos = 42;ypos < 42 + 29; ypos ++)
		{
			for (xpos = 36; xpos < 36 + 8; xpos ++)
			{
				fb[xpos] = cols[(xpos == 36 || xpos == 36 + 8 - 1)];
			}
			fb += 280;
		}

		extern HBITMAP CreateBitmapDIB(int w, int h, int planes, int bpp, void *data);
		s.bm = CreateBitmapDIB(280, 186, 1, 32, c.getBits());
	}
	if (s.bm || s.elems) SendMessageW(wa2.getMainWindow(), WM_WA_IPC, (WPARAM)&s, IPC_SETPLEDITCOLORS);
}

static prefsDlgRecW ffPrefsItem;

//-----------------------------------------------------------------------------------------------

// a guid for our app : {4BE592C7-6937-426a-A388-ACF0EBC88E93}
static const GUID GEN_FREEFORM =
  { 0x4be592c7, 0x6937, 0x426a, { 0xa3, 0x88, 0xac, 0xf0, 0xeb, 0xc8, 0x8e, 0x93 } };

Wa2Groupdefs *groups;
static WNDPROC wa_oldWndProc;

Wa2CfgItems *cfgitems=0;

HINSTANCE hInstance = NULL;
HWND last_dlg_parent = NULL;
void populateWindowsMenus();
void unpopulateWindowsMenus();
void addWindowOptionsToContextMenu(ifc_window *w);
void removeWindowOptionsFromContextMenu();
void controlOpacity(int v);
void controlScaling(double v);
void customScaling();
void customOpacity();
void autoOpacifyHover();
void autoOpacifyFocus();
ifc_window *g_controlMenuTarget = NULL;
HMENU controlmenu = NULL;
void lockScaling(int lock);
int ffwindowsitempos = -1;
int ffoptionstop = -1;
int ffwoptionstop = -1;
int ffwindowstop = -1;
int ffwindowsitempos2 = -1;
int eqremoved = 0;
void removeEq();
void restoreEq();
int g_timedisplaymode = 0;
ifc_window *lastFocused = NULL;
int before_startup_callback = 0;
void loadExtraColorThemes();
extern _int last_page;
int gothrueqmsg = 0;
void unhookOutputIPC();
int we_have_ml = 0;
void checkMlPresent();
char eggstr[9] = {0};
int eggstat = 0;
int eggfallout = 0;
void initEgg();
void toggleEgg();
Layout *lastlayoutegg = NULL;
int disable_send_visrandom = 0;
void registerGlobalHotkeys();
int processGenericHotkey(const char *hk);
const char *getSkinInfo();
const wchar_t *getSkinInfoW();
void controlAppBar(int side);
void controlAppBarAOT();
void controlAppBarAH();
void updateAppBarMenu(ifc_window *w);
void startFSMonitor();
void stopFSMonitor();
void onGoFullscreen();
void onCancelFullscreen();
FullScreenMonitor *g_fsmonitor = NULL;
class Wa5FSCallback : public FSCallback
{
public:
	virtual void onGoFullscreen()
	{
		::onGoFullscreen();
	}
	virtual void onCancelFullscreen()
	{
		::onCancelFullscreen();
	}
};
Wa5FSCallback *g_fscallback = NULL;
int getAOTTempDisable()
{
	return g_fsmonitor->isFullScreen();
}

LRESULT CallWinampWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	return CallWindowProcW(wa_oldWndProc, hwnd, msg, wParam, lParam);
}

//-----------------------------------------------------------------------------------------------
BOOL WINAPI dll_main_raw_fn(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	hInstance = hinstDLL;
	Wasabi::Std::Initialize();
    return TRUE;
}

extern "C"  
{
    void *_pRawDllMain = &dll_main_raw_fn;
};

__declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	hInstance = hinstDLL;
	return TRUE;
}

//-----------------------------------------------------------------------------------------------
// core actions implement "play", "stop", etc. wndEmbedded embed wa2 windows into GUID-based wnds
//-----------------------------------------------------------------------------------------------
BEGIN_SERVICES(Winamp2_Svcs);
DECLARE_SERVICETSINGLE(svc_action, CoreActions);
DECLARE_SERVICETSINGLE(svc_action, MenuActions);
DECLARE_SERVICETSINGLE(svc_windowCreate, Wa2WndEmbed);
END_SERVICES(Winamp2_Svcs, _Winamp2_Svcs);

//-----------------------------------------------------------------------------------------------
// functions called by the overriden windowproc to create and destroy wndembedders
//-----------------------------------------------------------------------------------------------
ifc_window *plWnd = NULL;
#ifdef MINIBROWSER_SUPPORT
ifc_window *mbWnd = NULL;
#endif
ifc_window *vidWnd = NULL;
TList<HWND> forcedoffwnds;

int going_freeform = 0;
int going_fixedform = 0;

wchar_t *INI_FILE = 0, *INI_DIR = 0;

void ToggleLayout(const wchar_t *containerName)
{
	Container *c = SkinParser::getContainer(containerName);
	if (c)
	{
		int numLayouts = c->getNumLayouts();

		for (int i = 0;i < numLayouts;i++)
		{
			if (c->enumLayout(i) == c->getCurrentLayout())
			{
				int nextLayout = (i + 1) % numLayouts;
				Layout *layout = c->enumLayout(nextLayout);
				c->switchToLayout(layout->getId());
				return ;
			}
		}
	}
}

int updatePl()
{
	// find out if this GUID is already shown.
	WindowHolder *wh = skinEmbedder->getSuitableWindowHolder(pleditWndGuid, NULL, NULL, NULL, 1, -1, 0, 1, -1);
	if (wh != NULL)
	{
		wh->cancelDeferredRemove();
		GuiObject *go = wh->getRootWndPtr()->getGuiObject();
		if (go)
		{
			Layout *l = go->guiobject_getParentLayout();
			if (l)
			{
				Container *c = l->getParentContainer();
				if (c)
				{
					DebugStringW(L"Container is %s\n", c->getId());
					SkinEmbedder::cancelDestroyContainer(c);
				}
			}
		}
		plWnd = wh->getRootWndPtr(); //->getDesktopParent();
		return 1;
	}
	return 0;
}

int createPl()
{
	if (updatePl())
	{
		ShowWindow(wa2.getWnd(IPC_GETWND_PE), SW_SHOWNA);
		return 1;
	}
	ReentryFilter f(&wndMsgFilter, IPC_GETWND_PE);
	if (f.mustLeave()) return 1;
	plWnd = WASABI_API_WNDMGR->skinwnd_createByGuid(pleditWndGuid, L"resizable_status");
	if (plWnd != NULL)
	{
		plWnd->setVisible(1);
	}
	return plWnd != NULL;
}

static ifc_window *updateEmb(GUID thisguid, embedWindowState *ws)
{
	WindowHolder *wh = skinEmbedder->getSuitableWindowHolder(thisguid, NULL, NULL, NULL, 1, -1, 0, 1, -1);
	if (wh != NULL && !(ws->flags & EMBED_FLAGS_LEGACY_WND))
	{
		ifc_window *p = wh->getRootWndPtr(); //->getDesktopParent();

		//    p->getGuiScriptObject()->vcpu_setInterface(embeddedWndStateGuid, ws, INTERFACE_GENERICVOIDPTR);
		ws->extra_data[EMBED_STATE_EXTRA_FFROOTWND] = (intptr_t)p;
		return p;
	}
	return 0;
}

static ifc_window *createEmb(embedWindowState *ws, bool startHidden)
{
	GUID thisguid = EmbedWndGuid(ws).getGuid();
	if (thisguid == INVALID_GUID || (ws->flags & EMBED_FLAGS_LEGACY_WND))
		return 0;

	//thisguid.Data1 = (int)ws->me;
	ifc_window *update = updateEmb(thisguid, ws);
	if (update)
		return update;

	ReentryFilter f(&wndMsgFilter, (intptr_t)ws);
	if (f.mustLeave())
		return 0;

	RECT r;
	if (!GetWindowRect(ws->me, &r))
		SetRectEmpty(&r);

	ifc_window *p = NULL;
	if (ws->flags & EMBED_FLAGS_NORESIZE)
		p = WASABI_API_WNDMGR->skinwnd_createByGuid(thisguid, L"static", 0, NULL, 0, startHidden, NULL);
	else
		p = WASABI_API_WNDMGR->skinwnd_createByGuid(thisguid, L"resizable_nostatus", 0, NULL, 0, startHidden, NULL);
	
	ws->extra_data[EMBED_STATE_EXTRA_FFROOTWND] = (intptr_t) p;
	if (p != NULL)
	{
		//    p->getGuiScriptObject()->vcpu_setInterface(embeddedWndStateGuid, ws, INTERFACE_GENERICVOIDPTR);
		if (NULL != WASABI_API_APP)
			WASABI_API_APP->app_unregisterGlobalWindow(p->getDesktopParent()->gethWnd());
		
		if (!startHidden)
			p->setVisible(1);

		GuiObject *go = p->getGuiObject();
		if (go)
		{			
			Layout *l = go->guiobject_getParentLayout();
			if (l)
			{
				Container *c = l->getParentContainer();
				if (c)
				{
					if (ws->flags & EMBED_FLAGS_NOWINDOWMENU)
						c->setXmlParam(L"nomenu", L"1");
					else
						c->setXmlParam(L"nomenu", L"0");
				}
			}
		}
	}
	if (p == NULL)
	{		
		RECT r;
		GetWindowRect(ws->me, &r);
		//////SetWindowPos( ws->me, NULL, r.left - 20000, r.top - 20000, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOREDRAW | SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_NOCOPYBITS );
		SetWindowPos( ws->me, NULL, r.left, r.top, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOREDRAW | SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_NOCOPYBITS );
		forcedoffwnds.addItem(ws->me);
		//if (0 != (WS_VISIBLE & windowStyle))
		//RedrawWindow(GetDesktopWindow(), &r, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW | RDW_NOERASE);
	}

	RedrawWindow(GetDesktopWindow(), &r, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW | RDW_NOERASE);
	return p;
}

#ifdef MINIBROWSER_SUPPORT
int updateMb()
{
	WindowHolder *wh = skinEmbedder->getSuitableWindowHolder(minibrowserWndGuid, NULL, NULL, NULL, 1, -1, 0, 1, -1);
	if (wh != NULL)
	{
		mbWnd = wh->getRootWndPtr(); //->getDesktopParent();
		ShowWindow(wa2.getWnd(IPC_GETWND_MB), SW_SHOWNA);
		return 1;
	}
	return 0;
}

int createMb()
{
	// see cratePl()
	if (updateMb()) return 1;
	ReentryFilter f(&wndMsgFilter, IPC_GETWND_MB);
	if (f.mustLeave()) return 1;
	mbWnd = WASABI_API_WNDMGR->skinwnd_createByGuid(minibrowserWndGuid, "resizable_status");
	if (mbWnd != NULL) mbWnd->setVisible(1);
	return mbWnd != NULL;
}
#endif

static HWND oldVideoWnd;
static WNDPROC oldVideoWndProc;
static DWORD WINAPI newVideoWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (uMsg == WM_USER + 0x100 && wParam == 1 && lParam)
		return 0;
	
	return CallWindowProc(oldVideoWndProc, hwnd, uMsg, wParam, lParam);
}

int updateVid()
{
	WindowHolder *wh = skinEmbedder->getSuitableWindowHolder(videoWndGuid, NULL, NULL, NULL, 1, -1, 0, 1, -1);
	if (wh != NULL)
	{
#ifdef VIDDEBUG
		DebugString("Video : Host already exists and window already shown in it\n");
#endif
		wh->cancelDeferredRemove();
		GuiObject *go = wh->getRootWndPtr()->getGuiObject();
		if (go)
		{
			Layout *l = go->guiobject_getParentLayout();
			if (l)
			{
				Container *c = l->getParentContainer();
				if (c)
				{
					DebugStringW(L"Container is %s\n", c->getId());
					SkinEmbedder::cancelDestroyContainer(c);
				}
			}
		}
		vidWnd = wh->getRootWndPtr(); //->getDesktopParent();
		return 1;
	}
	return 0;
}

int createVid()
{
	ShowWindow(WASABI_API_WND->main_getRootWnd()->gethWnd(), SW_RESTORE);
	SetForegroundWindow(WASABI_API_WND->main_getRootWnd()->gethWnd());

	if (updateVid())
	{
		ShowWindow(wa2.getWnd(IPC_GETWND_VIDEO), SW_SHOWNA);
		return 1;
	}

	if (vidWnd == NULL)
	{
		ReentryFilter f(&wndMsgFilter, IPC_GETWND_VIDEO);
		if (f.mustLeave()) return 1;
#ifdef VIDDEBUG
		DebugString("Video : Trying to find a host or creating it if needed\n");
#endif
		vidWnd = WASABI_API_WNDMGR->skinwnd_createByGuid(videoWndGuid, L"resizable_status");
	}

	if (vidWnd != NULL)
	{
		vidWnd->setVisible(1);
	}
	return vidWnd != NULL;
}

int destroyPl(HWND hwndDlg, int uMsg, WPARAM wParam, LPARAM lParam)
{
	ReentryFilter f(&wndMsgFilter, IPC_GETWND_PE);
	int r = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
	if (f.mustLeave()) return r;
	if (plWnd != NULL && WASABI_API_WND->rootwndIsValid(plWnd))
	{
		WASABI_API_WNDMGR->skinwnd_destroy(plWnd);
		r = !WASABI_API_WND->rootwndIsValid(plWnd);
	}
	else
		r = !SOM::checkAbortShowHideWindow(pleditWndGuid, 0);
	if (r)
		plWnd = NULL;
	return r;
}

int destroyEmb(HWND hwndDlg, int uMsg, WPARAM wParam, LPARAM lParam, embedWindowState *ws)
{
	ReentryFilter f(&wndMsgFilter, (intptr_t)ws);
	int r = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
	if (f.mustLeave() || (ws->flags & EMBED_FLAGS_LEGACY_WND)) return r;
	if (ws->extra_data[EMBED_STATE_EXTRA_FFROOTWND] == NULL) return r;
	if (ws->extra_data[EMBED_STATE_EXTRA_REPARENTING] == 1) return r;
	ifc_window *w = (ifc_window*)ws->extra_data[EMBED_STATE_EXTRA_FFROOTWND];
#if 1
	if (WASABI_API_WND->rootwndIsValid(w))
	{
		WASABI_API_WNDMGR->skinwnd_destroy(w);
		r = !WASABI_API_WND->rootwndIsValid(w);
	}
	else
	{
		r =	!SOM::checkAbortShowHideWindow(embedWndGuidMgr.getGuid(ws), 0);
	}
	if (r)
		ws->extra_data[EMBED_STATE_EXTRA_FFROOTWND] = NULL;
#else
	if (WASABI_API_WND->rootwndIsValid(w)) WASABI_API_WNDMGR->skinwnd_destroy(w);
	else SOM::checkAbortShowHideWindow(embedWndGuidMgr.getGuid(ws), 0);
	ws->extra_data[EMBED_STATE_EXTRA_FFROOTWND] = NULL;
#endif
	return r;
}

#ifdef MINIBROWSER_SUPPORT
int destroyMb(HWND hwndDlg, int uMsg, WPARAM wParam, LPARAM lParam)
{
	ReentryFilter f(&wndMsgFilter, IPC_GETWND_MB);
	int r = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
	if (f.mustLeave()) return r;

	if (mbWnd != NULL && WASABI_API_WND->rootwndIsValid(mbWnd)) WASABI_API_WNDMGR->skinwnd_destroy(mbWnd);
	else SOM::checkAbortShowHideWindow(minibrowserWndGuid, 0);
	mbWnd = NULL;
	return r;
}
#endif

int destroyVid(HWND hwndDlg, int uMsg, WPARAM wParam, LPARAM lParam)
{
	ReentryFilter f(&wndMsgFilter, IPC_GETWND_VIDEO);
	int r = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);

	if (f.mustLeave()) return r;

	if (vidWnd != NULL && WASABI_API_WND->rootwndIsValid(vidWnd))
	{
		WASABI_API_WNDMGR->skinwnd_destroy(vidWnd);
		r = !WASABI_API_WND->rootwndIsValid(vidWnd);
	}
	else
		r = !SOM::checkAbortShowHideWindow(videoWndGuid, 0);

	if (r)
		vidWnd = NULL;

	return r;
}


int m_loading_at_startup;
int m_are_we_loaded;

#define WINAMP_REFRESHSKIN              40291
#define WINAMP_OPTIONS_EQ               40036
#define WINAMP_OPTIONS_DSIZE            40165
#define WINAMP_OPTIONS_ELAPSED          40037
#define WINAMP_OPTIONS_REMAINING        40038

//-----------------------------------------------------------------------------------------------
class MainLayoutMonitor : public H_Layout
{
  public:
    MainLayoutMonitor(HWND _w, ScriptObject *o) : H_Layout(o) { hwnd= _w; }
    void hook_onResize(int x, int y, int w, int h);
		HWND hwnd;
};

void MainLayoutMonitor::hook_onResize(int x, int y, int w, int h)
{
	PostMessage(wa2.getMainWindow(), WM_WA_IPC, (WPARAM)hwnd, UPDATEDIALOGBOXPARENTMSG); 
}

//-----------------------------------------------------------------------------------------------
// must be called after switching to a new freeform skin
// should also be called when the main container changes its visible layout
//-----------------------------------------------------------------------------------------------
Layout *GetMainLayout()
{
	Container *container = SkinParser::getContainer(L"main");
	if (container != NULL)
	{
		return container->getCurrentLayout();
	}
	return 0;
}

HWND GetMainContainerHWND()
{
	Layout *l = GetMainLayout();
	if (l != NULL)
		return l->gethWnd();

	return 0;
}
static MainLayoutMonitor *mainLayoutMonitor = 0;
void setDialogBoxesParent()
{
	Layout *l = GetMainLayout();
	if (l != NULL)
	{
		HWND hwnd = l->gethWnd();
		delete mainLayoutMonitor;
		mainLayoutMonitor = new MainLayoutMonitor(hwnd, l->getScriptObject());
		wa2.setDialogBoxParent(hwnd);
		PostMessage(wa2.getMainWindow(), WM_WA_IPC, (WPARAM)hwnd, UPDATEDIALOGBOXPARENTMSG); 
		lastFocused = l;
	}
}

static void removeSkinExtension(StringW &skinname)
{
	int x = skinname.len() - 4;
	if (x > 0)
	{
		const wchar_t *p = ((const wchar_t *)skinname) + x;
		if (!_wcsicmp(p, L".zip") || !_wcsicmp(p, L".wal") || !_wcsicmp(p, L".wsz"))
			skinname.trunc(x);
	}
}

extern HWND subWnd, tabwnd;
extern int subWndId;
extern int toggle_from_wa2;
int switching_skin = 0;
void shutdownFFApi();


/*-----------------------------------------------------------------------------------------------
*	Winamp2 message processor
*/

static void RerouteMessage(MSG *pMsg, ifc_window *wnd)
{
	if (NULL == wnd)
		return;

	HWND hReroute = NULL;
	if (pMsg->hwnd == GetMainContainerHWND()) 
	{
		hReroute = WASABI_API_WND->main_getRootWnd()->gethWnd();
		if (NULL != hReroute)
		{
			pMsg->hwnd = hReroute;
		}
		return;
	}
	if (wnd->gethWnd() != pMsg->hwnd)
		return;

	ifc_window *parent = wnd->getRootParent();
	if (NULL == parent)
		return;
		
	Layout *l = static_cast<Layout *>(parent->getInterface(layoutGuid));
	if (NULL == l) 
		return;
			
	Container *c = l->getParentContainer();
	if (NULL == c) 
		return;
	
	GUID g = c->getDefaultContent();
	if (INVALID_GUID == g)
		return;
					
	if (g == playerWndGuid)
		hReroute = wa2.getMainWindow();
	else if (g == pleditWndGuid) 
		hReroute = wa2.getWnd(IPC_GETWND_PE);
	else if (g == videoWndGuid) 
		hReroute = wa2.getWnd(IPC_GETWND_VIDEO);
	else 
	{
		embedWindowState *ews = embedWndGuidMgr.getEmbedWindowState(g);
		if (ews)
			hReroute = ews->me;
	}

	if (NULL != hReroute)
		pMsg->hwnd = hReroute;
}

class WaMessageProccessor : public ifc_messageprocessor
{
public: 	
	WaMessageProccessor() {} 
	~WaMessageProccessor(void)  {} 

public:
	bool ProcessMessage(MSG *pMsg)
	{	
		if (WM_KEYDOWN == pMsg->message ||
			WM_KEYUP == pMsg->message ||
			WM_SYSKEYDOWN == pMsg->message ||
			WM_SYSKEYUP == pMsg->message)
		{

			ifc_window *wnd;
			HWND h = pMsg->hwnd;
			do
			{
				wnd = WASABI_API_WND->rootWndFromOSHandle(h);
				
			} while (!wnd && NULL!= (h = GetAncestor(h, GA_PARENT)));
						
			if (wnd)
			{
				if (!wnd->isVirtual())
				{
					while (wnd)
					{
						const wchar_t *pid = wnd->getId();
						if (pid && *pid) break;
						wnd = wnd->getParent();
					}
				}
				if (wnd && wnd->getCurVirtualChildFocus())
					wnd = wnd->getCurVirtualChildFocus();
			}
			
			if (wnd) 
				DebugStringW(L"target wnd 0x%08X, id: %s", wnd, wnd->getId());
						
			INT keyMsg = -1;
			switch(pMsg->message)
			{
				case WM_KEYDOWN:
					static int pos;
					if (toupper((int)pMsg->wParam) == eggstr[pos])
					{
						if (!eggstr[++pos])
						{
							toggleEgg();
							pos = 0;
						}
					}
					else pos = 0;
					keyMsg = (0 != WASABI_API_WND->forwardOnKeyDown(wnd, (int)pMsg->wParam, pMsg->lParam));
					break;					
				case WM_KEYUP:		keyMsg = (wnd && 0 != WASABI_API_WND->forwardOnKeyUp(wnd, (int)pMsg->wParam, pMsg->lParam)); break;
				case WM_SYSKEYDOWN:	keyMsg = ( 0 != WASABI_API_WND->forwardOnSysKeyDown(wnd, (int)pMsg->wParam, pMsg->lParam)); break;
				case WM_SYSKEYUP:	keyMsg = (wnd && 0 != WASABI_API_WND->forwardOnSysKeyUp(wnd, (int)pMsg->wParam, pMsg->lParam)); break;

			}
			if (-1 != keyMsg)
			{
				if (keyMsg > 0) 
					return true;

				RerouteMessage(pMsg, wnd);
			}
		}
		return false;
	}

protected:
	RECVS_DISPATCH;
};

#define CBCLASS WaMessageProccessor
START_DISPATCH;
CB(IFC_MESSAGEPROCESSOR_PROCESS_MESSAGE, ProcessMessage)
END_DISPATCH;
#undef CBCLASS

static WaMessageProccessor waMessageProcessor;

#define TabCtrl_InsertItemW(hwnd, iItem, pitem)   \
    (int)SNDMSG((hwnd), TCM_INSERTITEMW, (WPARAM)(int)(iItem), (LPARAM)(const TC_ITEMW *)(pitem))

//-----------------------------------------------------------------------------------------------
void onSkinSwitch()
{
	switching_skin = 1;
	int lastloaded = m_are_we_loaded;

	Wa2WndEmbed::rememberVisibleWindows();

	// get skin name
	wchar_t buf[MAX_PATH] = {0};
	wchar_t *p = (wchar_t *) SendMessageW(plugin.hwndParent, WM_WA_IPC, (WPARAM)buf, IPC_GETSKINW);
	{
		m_lastskin_nam.setValue(p);
		m_lastskin_dir.setValue(buf);

		StringPathCombine t(m_lastskin_dir, L"skin.xml");

		if (!m_lastskin_nam.isempty()
		    && _waccess(t.getValue(), 0))
		{
			t.setValue(m_lastskin_dir);
			StringW n = m_lastskin_nam;
			removeSkinExtension(n);
			t.AppendPath(n);
			t.AppendPath(L"skin.xml");
			if (!_waccess(t.getValue(), 0))
				m_lastskin_dir.AppendPath(n);
		}

		if (!_waccess(t.getValue(), 0))
		{
			if (!m_are_we_loaded) init_inst();
			else
			{
				// load new skin
				StringW skinname = m_lastskin_nam;

				removeSkinExtension(skinname);

				before_startup_callback = 1;
				WASABI_API_SKIN->skin_switchSkin(skinname, m_lastskin_dir);

				if (DEFERREDCALLBACKMSG > 65536)
					PostMessage(wa2.getMainWindow(), WM_WA_IPC, 2, DEFERREDCALLBACKMSG);
				//embedEnumStruct cs = { embedCreateProc, 0 };
				//SendMessageW(plugin.hwndParent, WM_WA_IPC, (WPARAM)&cs, IPC_EMBED_ENUM);

				doplaylistcolors();
				setDialogBoxesParent();
				populateWindowsMenus();
			}
		}
		else
		{
			going_fixedform = 1;
			quit_inst();
		}
	}
	if (subWnd != NULL && IsWindow(subWnd)) SendMessageW(subWnd, WM_INITDIALOG, 0, 0);

	if (IsWindow(tabwnd))
	{
		if (!m_are_we_loaded)
		{
			if (subWndId == 5)
			{
				DestroyWindow(subWnd);
				TabCtrl_SetCurSel(tabwnd,0);
				_dosetsel(GetParent(tabwnd));
			}

			if (TabCtrl_GetItemCount(tabwnd) == 5)
			{
				TabCtrl_DeleteItem(tabwnd,4);
				TabCtrl_DeleteItem(tabwnd,3);
			}
		}
		else
		{
			if (TabCtrl_GetItemCount(tabwnd) == 3)
			{
				TCITEMW item = {0};
				item.mask=TCIF_TEXT;
				item.pszText=WASABI_API_LNGSTRINGW(IDS_COLOR_THEMES);
				TabCtrl_InsertItemW(tabwnd,3,&item);
				item.pszText=WASABI_API_LNGSTRINGW(IDS_CURRENT_SKIN);
				TabCtrl_InsertItemW(tabwnd,4,&item);
			}
		}
	}

	if (m_are_we_loaded || lastloaded) // dont call this if going from classic->classic
		Wa2WndEmbed::restoreVisibleWindows();

	if (m_are_we_loaded && WASABI_API_SYSCB)
		WASABI_API_SYSCB->syscb_issueCallback(SysCallback::GC, GarbageCollectCallback::GARBAGECOLLECT);

	if (WASABI_API_APP)
	{
		if (m_are_we_loaded) WASABI_API_APP->app_addMessageProcessor(&waMessageProcessor);
		else WASABI_API_APP->app_removeMessageProcessor(&waMessageProcessor);
	}

	/* TODO: benski> want to add this, but it's causing a weird crash
	if (going_fixedform)
		shutdownFFApi();
		*/
	switching_skin = 0;
	going_fixedform = 0;
	before_startup_callback = 0;
}

static int embedUpdateColorProc(embedWindowState *p, embedEnumStruct *parms)
{
	SendMessageW(p->me, WM_DISPLAYCHANGE, 0, 0);
	return 0;
}

int embedCreateProc(embedWindowState *ws, embedEnumStruct *parms)
{
	ifc_window *rw = reinterpret_cast<ifc_window*>(ws->extra_data[EMBED_STATE_EXTRA_FFROOTWND]);

	if (!rw && IsWindow(ws->me) && IsWindowVisible(ws->me) && !(ws->flags & EMBED_FLAGS_LEGACY_WND))
	{
		ifc_window *windowParent = createEmb(ws, true);
		if (NULL != windowParent)
		{
			GuiObject *uiObject = windowParent->getGuiObject();
			if (NULL != uiObject)
			{
				if (0 != (EMBED_FLAGS_FFCALLBACK & ws->flags) &&
					NULL != ws->callback)
				{
					ws->callback(ws, FFC_CREATEEMBED, (LPARAM)windowParent);
				}

				Layout *layout = uiObject->guiobject_getParentLayout();
				if (NULL != layout)
				{
					Container *container = layout->getParentContainer();
					if (NULL != container) container->setVisible(1);
					else layout->setVisible(1);
					windowParent->setVisible(1);
				}
			}	
		}

		if (NULL == windowParent)
			ShowWindow(ws->me, SW_HIDE);
	}

	return 0;
}

//-----------------------------------------------------------------------------------------------
void syncDoubleSize(int loading = 0)
{
	if (!cfg_uioptions_uselocks.getValueAsInt()) return ;
	int isdsize = wa2.isDoubleSize();
	int i;
	for (i = 0;i < SkinParser::getNumContainers();i++)
	{
		Container *c = SkinParser::enumContainer(i);
		if (c != NULL)
		{
			int j;
			for (j = 0;j < c->getNumLayouts();j++)
			{
				Layout *l = c->enumLayout(j);
				if (l != NULL)
				{
					if (l->isScaleLocked()) continue;
					l->setRenderRatio(isdsize ? 2.0f : 1.0f);
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------------------------
class syncDisplayModeEnumerator : public FindObjectCallbackI
{
public:
	int findobjectcb_matchObject(ifc_window *object)
	{
		ScriptObject *s = NULL;
		GuiObject *g = object->getGuiObject();
		if (g)
		{
			ScriptObject *_s = g->guiobject_getScriptObject();
			if (_s)
			{
				void *v = _s->vcpu_getInterfaceObject(textGuid, &s);
				if (v && !s)
				{
					(static_cast<Text*>(v))->setTimeDisplayMode(g_timedisplaymode);
				}
			}
		}
		return 0;
	}
};

//-----------------------------------------------------------------------------------------------
void syncDisplayMode()
{
	if (switching_skin) return ;
	g_timedisplaymode = wa2.getTimeDisplayMode();
	int i;
	syncDisplayModeEnumerator enumerator;
	for (i = 0;i < SkinParser::getNumContainers();i++)
	{
		Container *c = SkinParser::enumContainer(i);
		if (c != NULL)
		{
			int j;
			for (j = 0;j < c->getNumLayouts();j++)
			{
				Layout *l = c->enumLayout(j);
				if (!WASABI_API_WND->rootwndIsValid(l)) continue;
				l->findWindowByCallback(&enumerator);
			}
		}
	}
}

static POINT viewPort;
VOID CALLBACK ViewPortChanged(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
	KillTimer(hwnd, idEvent);
	if (viewPort.x && viewPort.y)
	{
		SystemObject::onViewPortChanged(viewPort.x, viewPort.y);
	}
}

//-----------------------------------------------------------------------------------------------
// Winamp2 main window subclass
//-----------------------------------------------------------------------------------------------
static LRESULT WINAPI wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static int m_in_skinrefresh;

	if (uMsg == WM_COMMAND && LOWORD(wParam) == WINAMP_REFRESHSKIN)
	{
		HWND prefs = wa2.getPreferencesWindow();
		RECT prefrect = {0};
		if (prefs)
		{
			GetWindowRect(prefs, &prefrect);
			HWND hOwner = (HWND)(LONG_PTR)GetWindowLongPtrW(prefs, GWLP_HWNDPARENT);
			if (NULL != hOwner && hOwner != hwndDlg)
				SetWindowLongPtrW(prefs, GWLP_HWNDPARENT, (LONG_PTR)hwndDlg);
		}
		m_in_skinrefresh++;
		DWORD b = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
		onSkinSwitch();
		if (m_are_we_loaded) doplaylistcolors();
		m_in_skinrefresh--;
		if (prefs && !wa2.getPreferencesWindow())
		{
			SendMessageW(wa2.getMainWindow(), WM_COMMAND, WINAMP_OPTIONS_PREFS, 0);
			SetWindowPos(wa2.getPreferencesWindow(), NULL, prefrect.left, prefrect.top, prefrect.right - prefrect.left, prefrect.bottom - prefrect.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_DEFERERASE);
		}

		return b;
	}
	if (g_Core)
	{
		if (uMsg == WM_WA_IPC && lParam == IPC_CB_MISC) g_Core->gotCallback(wParam);
#define WINAMP_FILE_REPEAT              40022
#define WINAMP_FILE_SHUFFLE             40023
#define WINAMP_FILE_MANUALPLADVANCE     40395
#define UPDATE_DISPLAY_TIMER			38
		if (uMsg == WM_TIMER)
		{
			if (LOWORD(wParam) == UPDATE_EGG)
			{
				for (int i = 0;i < SkinParser::getNumContainers();i++)
				{
					Container *cont = SkinParser::enumContainer(i);
					if (cont->isMainContainer())
					{
						Layout *l = cont->getCurrentLayout();
						if (lastlayoutegg && l != lastlayoutegg)
						{
							if (WASABI_API_WND->rootwndIsValid(lastlayoutegg))
							{
								lastlayoutegg->updateTransparency();
								lastlayoutegg->setTransparencyOverride(-1);
							}
						}
						lastlayoutegg = l;
						int v = (WASABI_API_MEDIACORE->core_getLeftVuMeter(0) + WASABI_API_MEDIACORE->core_getRightVuMeter(0)) / 4;
						eggfallout -= 4;
						if (v > eggfallout) eggfallout = v;
						lastlayoutegg->setTransparencyOverride(255 - eggfallout);
						break;
					}
				}
				return 0;
			}
			if (LOWORD(wParam) == UPDATE_DISPLAY_TIMER + 4)
			{
				syncDisplayMode();
			}
			if (LOWORD(wParam) == 0xC0DE)
			{
				if (!wa2.isVisRunning())
				{
					KillTimer(wa2.getMainWindow(), 0xC0DE);
					KillTimer(wa2.getMainWindow(), 0xC0DE + 1);
					wa2.toggleVis();
				}
			}
			if (LOWORD(wParam) == 0xC0DE + 1)
			{
				KillTimer(wa2.getMainWindow(), 0xC0DE);
				KillTimer(wa2.getMainWindow(), 0xC0DE + 1);
			}
		}
		if (!my_set && (uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) && LOWORD(wParam) == WINAMP_FILE_SHUFFLE)
		{
			DWORD v = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
			int shuf = wa2.getShuffle();
			if (!shuffle.getValueAsInt() != !shuf) shuffle.setValueAsInt(shuf);
			return v;
		}
		if (!my_set && (uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) &&
		    (LOWORD(wParam) == WINAMP_FILE_REPEAT) ||
		    (LOWORD(wParam) == WINAMP_FILE_MANUALPLADVANCE))
		{
			DWORD v = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
			int rep = wa2.getRepeat();
			int manadv = wa2.getManualPlaylistAdvance();
			int _v = (rep && manadv) ? -1 : rep;
			if (repeat.getValueAsInt() != _v) repeat.setValueAsInt(_v);
			return v;
		}
		if ((uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) && LOWORD(wParam) == WINAMP_OPTIONS_DSIZE)
		{
			int v = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
			syncDoubleSize();
			return v;
		}
		if ((uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) && (LOWORD(wParam) == WINAMP_OPTIONS_ELAPSED || LOWORD(wParam) == WINAMP_OPTIONS_REMAINING))
		{
			int v = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
			syncDisplayMode();
			return v;
		}
	}
	if (m_are_we_loaded)
	{
		if (uMsg == WM_COMMAND && LOWORD(wParam) == 40266)
		{
			ToggleLayout(L"pledit");
			return 0;
		}
		else if (uMsg == WM_COMMAND && LOWORD(wParam) == 40064)
		{
			ToggleLayout(L"main");
			return 0;
		}
		else if (uMsg == WM_CLOSE && wParam == 0xDEADBEEF && lParam == 0xDEADF00D)
		{
			//if (/*todo: configurable */ 1)
			{
				uMsg = WM_COMMAND;
				wParam = WINAMP_MAIN_WINDOW;
			}
		}
		else if (uMsg == WM_SETTINGCHANGE)
		{
			// used to track viewport changes so we can update
			// e.g. when removing Win8's side-by-side app mode
			if (wParam == SPI_SETWORKAREA)
			{
				KillTimer(wa2.getMainWindow(), VIEWPORT);
				viewPort.x = -1;
				viewPort.y = -1;
				SetTimer(wa2.getMainWindow(), VIEWPORT, 100, ViewPortChanged);
			}
		}
		else if (uMsg == WM_DISPLAYCHANGE)
		{
			DWORD b = CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
			if (!m_in_skinrefresh) doplaylistcolors();

			// filter out our internal skin updates
			if (wParam && lParam)
			{
				KillTimer(wa2.getMainWindow(), VIEWPORT);
				viewPort.x = LOWORD(lParam);
				viewPort.y = HIWORD(lParam);
				SetTimer(wa2.getMainWindow(), VIEWPORT, 100, ViewPortChanged);
			}

			return b;
		}
		else if (uMsg == WM_INITMENUPOPUP)
		{
			HMENU hmenuPopup = (HMENU) wParam;

			if (hmenuPopup && hmenuPopup == mainSendTo.build_hMenu && mainSendTo.mode == 1)
			{
				int IPC_LIBRARY_SENDTOMENU = SendMessageW(plugin.hwndParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
				if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessageW(plugin.hwndParent, WM_WA_IPC, (WPARAM)&mainSendTo, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
					mainSendTo.mode = 2;
			}

			if(hmenuPopup == wa2.getMenuBarMenu(Winamp2FrontEnd::WA2_MAINMENUBAR_PLAY))
			{
				CheckMenuItem((HMENU)wParam, WINAMP_FILE_SHUFFLE, wa2.getShuffle() ? MF_CHECKED : MF_UNCHECKED);
				CheckMenuItem((HMENU)wParam, WINAMP_FILE_REPEAT, wa2.getRepeat() ? MF_CHECKED : MF_UNCHECKED);
			}

			if (hmenuPopup == wa2.getPopupMenu() ||
			    hmenuPopup == wa2.getMenuBarMenu(Winamp2FrontEnd::WA2_MAINMENUBAR_OPTIONS) ||
			    hmenuPopup == wa2.getMenuBarMenu(Winamp2FrontEnd::WA2_MAINMENUBAR_WINDOWS))
			{
				if (hmenuPopup == wa2.getMenuBarMenu(Winamp2FrontEnd::WA2_MAINMENUBAR_OPTIONS)) {
					EnableMenuItem(hmenuPopup, WINAMP_OPTIONS_DSIZE,
								   MF_BYCOMMAND | (cfg_uioptions_uselocks.getValueAsInt() ? MF_ENABLED : MF_GRAYED));
				}
				if (hmenuPopup == wa2.getPopupMenu()){
					EnableMenuItem(GetSubMenu(hmenuPopup, 11 + wa2.adjustOptionsPopupMenu(0)), WINAMP_OPTIONS_DSIZE,
								   MF_BYCOMMAND | (cfg_uioptions_uselocks.getValueAsInt() ? MF_ENABLED : MF_GRAYED));
				}

				populateWindowsMenus();

				if ((HMENU)wParam == wa2.getPopupMenu())
				{
					for (int i = 0;i < SkinParser::getNumContainers();i++)
					{
						Container *cont = SkinParser::enumContainer(i);
						if (!_wcsicmp(cont->getId(), L"main"))
						{
							CheckMenuItem((HMENU)wParam, WINAMP_MAIN_WINDOW, cont->isVisible() ? MF_CHECKED : MF_UNCHECKED);
							break;
						}
					}
				}
			}
		}
		else if (uMsg == WM_SETFOCUS)
		{
			if (m_are_we_loaded && !switching_skin)
			{
				Container *c = SkinParser::getContainer(L"main");
				if (c)
				{
					Layout *l = c->enumLayout(0);
					if (l) l->setFocus();
				}
				return 1;
			}
		}
		else if (uMsg == WM_WA_IPC)
		{
			if (lParam == DEFERREDCALLBACKMSG && DEFERREDCALLBACKMSG > 65536)
			{
				if (wParam == 1)
				{
					setDialogBoxesParent();
					return 1;
				}
				else if (wParam == 2)
				{
					// embed the windows that are currently visible because at the time the window were originally shown,
					// we hadn't been loaded yet
					// but then again, we might if it's only a switch skin and not a app load, so let's check anyway

					populateWindowsMenus();

					doplaylistcolors();

					if (plWnd == NULL && wa2.isWindowVisible(IPC_GETWND_PE))
						if (!createPl()) wa2.setWindowVisible(IPC_GETWND_PE, 0);

#ifdef MINIBROWSER_SUPPORT
					if (mbWnd == NULL && wa2.isWindowVisible(IPC_GETWND_MB))
						if (!createMb()) wa2.setWindowVisible(IPC_GETWND_MB, 0);
#endif
					if (vidWnd == NULL && wa2.isWindowVisible(IPC_GETWND_VIDEO))
						if (!createVid()) wa2.setWindowVisible(IPC_GETWND_VIDEO, 0);

					embedEnumStruct cs = { embedCreateProc, 0 };
					SendMessageW(hwndDlg, WM_WA_IPC, (WPARAM)&cs, IPC_EMBED_ENUM);

					for (int i = 0;i < SkinParser::getNumContainers();i++)
					{
						Container *cont = SkinParser::enumContainer(i);
						if (!_wcsicmp(cont->getId(), L"main"))
						{
							for (int j = 0;j < cont->getNumLayouts();j++)
							{
								Layout *l = cont->enumLayout(j);
								if (l && l->isVisible())
								{
									SetFocus(l->gethWnd());
									break;
								}
							}
						}
					}

					Crossfader::onOutputChanged();

					extern int make_sure_library_is_here_at_startup;
					if (make_sure_library_is_here_at_startup)
						SendMessageW(wa2.getMainWindow(), WM_COMMAND, ID_FILE_SHOWLIBRARY, 0);
					make_sure_library_is_here_at_startup = 0;

					before_startup_callback = 0;

					registerGlobalHotkeys();

					// force a title update since it's not always correct
					// if gen_ml hasn't loaded fully when we're loading
					if (wa2.isPlaying())
						g_Core->gotCallback(IPC_CB_MISC_TITLE, 2);
					return 1;
				}
			}
			else if (lParam == UPDATEDIALOGBOXPARENTMSG && UPDATEDIALOGBOXPARENTMSG > 65536)
			{
				wa2.updateDialogBoxParent((HWND)wParam);
			}
			else switch (lParam)
				{
				case IPC_GETSKININFO:
					return (LRESULT)getSkinInfo();
				case IPC_GETSKININFOW:
					return (LRESULT)getSkinInfoW();
				case IPC_SHOW_NOTIFICATION:
					return SystemObject::onShowNotification();
				case IPC_CB_VISRANDOM:
				{
					int v = wParam;
					extern _bool visrandom;
					if (!!v == !!visrandom.getValueAsInt()) break;
					disable_send_visrandom = 1;
					visrandom.setValueAsInt(v);
					disable_send_visrandom = 0;
					break;
				}
				case IPC_CB_OUTPUTCHANGED:
					Crossfader::onOutputChanged();
					break;

				case IPC_CB_ONTOGGLEAOT:
				{
					if ((WPARAM)cfg_options_alwaysontop.getValueAsInt() != wParam)
						cfg_options_alwaysontop.setValueAsInt(wParam);
					break;
				}

				case IPC_SETIDEALVIDEOSIZE:
				{
					wa2.setIdealVideoSize(HIWORD(wParam), LOWORD(wParam));
					break;
				}
				// this is where we detect that wa2 wants to open one of its windows (thru popup menu, button, whatever)
				// when this happens, we create a freeform wndembedder if one doesn't already exist. that embedder will
				// reparent and resize the wa2 window on its own. when we return, winamp then shows the HWND inside our frame
				// as it would show the HWND as a popup normally.
				case IPC_CB_ONSHOWWND:
					switch (wParam)
					{
					case IPC_CB_WND_PE:
						if (!createPl()) return 0;
						break;
#ifdef MINIBROWSER_SUPPORT
					case IPC_CB_WND_MB:
						if (!createMb()) return 0;
						break;
#endif
					case IPC_CB_WND_VIDEO:
#ifdef VIDDEBUG
						DebugString("Video : Got IPC_ONSHOW\n");
#endif
						if (!createVid())
						{
#ifdef VIDDEBUG
							DebugString("Video : SHOW was cancelled by script\n");
#endif
							return 0;
						}
						break;
					default:
						DebugStringW(L"embedWnd : Got IPC_ONSHOW\n");
						if (IsWindow((HWND)wParam))
						{
							HWND hTarget = (HWND)wParam;
							embedWindowState *ws = (embedWindowState*)GetWindowLongPtrW(hTarget, GWLP_USERDATA);
							if (ws && ws->me == hTarget)
							{
								GUID thisguid = EmbedWndGuid(ws).getGuid();
								if (INVALID_GUID == thisguid || NULL == updateEmb(thisguid, ws))
								{
									ifc_window *windowParent = createEmb(ws, true);
									if (NULL != windowParent)
									{										
										GuiObject *uiObject = windowParent->getGuiObject();
										if (NULL != uiObject)
										{			
											if (0 != (EMBED_FLAGS_FFCALLBACK & ws->flags) &&
												NULL != ws->callback)
											{
												ws->callback(ws, FFC_CREATEEMBED, (LPARAM)windowParent);
											}

											Layout *layout = uiObject->guiobject_getParentLayout();
											if (NULL != layout)
											{
												Container *container = layout->getParentContainer();
												if (NULL != container) container->setVisible(1);
												else layout->setVisible(1);
												windowParent->setVisible(1);
											}
										}

									}
									return (NULL != windowParent);
								}
							}
						}
						break;
					}
					break;
					// here we do the reverse, we detect that wa2 wants to close one of its windows, so we destroy our window
					// embedder (it will reparent the wa2 window back to its former parent and resize it back to where it was
					// on its own). when we return, winamp then hides the window.

					// NOTE! because of this, there might be a split second where the window is seen on the screen as a popup
					// after you closed the window (this won't happen for static containers [ie: pledit/video in mmd3] since
					// they are hidden rather than destroyed). this can be fixed in the future
				case IPC_CB_ONHIDEWND:
					switch (wParam)
					{
					case IPC_CB_WND_PE:
						destroyPl(hwndDlg, uMsg, wParam, lParam);
						break;
#ifdef MINIBROWSER_SUPPORT
					case IPC_CB_WND_MB:
						return destroyMb(hwndDlg, uMsg, wParam, lParam);
#endif
					case IPC_CB_WND_VIDEO:
#ifdef VIDDEBUG
						DebugString("Video : Got IPC_ONHIDE\n");
#endif
						return destroyVid(hwndDlg, uMsg, wParam, lParam);
					default:
						DebugStringW(L"embedWnd : Got IPC_ONHIDE\n");
						if (IsWindow((HWND)wParam))
						{
							HWND h = (HWND)wParam;
							embedWindowState *ws = (embedWindowState *)GetWindowLongPtrW(h, GWLP_USERDATA);
							if (ws) return destroyEmb(hwndDlg, uMsg, wParam, lParam, ws);
						}
						break;
					}
					break;
				case IPC_FF_ISMAINWND:
				{
					for (int i = 0;i < SkinParser::getNumContainers();i++)
					{
						Container *cont = SkinParser::enumContainer(i);
						if (!_wcsicmp(cont->getId(), L"main"))
						{
							for (int j = 0;j < cont->getNumLayouts();j++)
							{
								if (cont->enumLayout(j)->gethWnd() == (HWND)wParam) return 1;
							}
							break;
						}
					}
					return 0;
				}
				case IPC_FF_GETCONTENTWND:
					return (LRESULT)ff_ipc_getContentWnd((HWND)wParam);
				case IPC_FF_GETSKINCOLOR:
					ff_ipc_getSkinColor((ff_skincolor*)wParam);
					return 1;
				case IPC_FF_GENSKINBITMAP:
					ff_ipc_genSkinBitmap((ff_skinbitmap*)wParam);
					return 1;
				case IPC_FF_NOTIFYHOTKEY:
					if (processGenericHotkey((const char *)wParam)) return 0; // prevent gen_hotkey from processing the hotkey
					return 1; // let gen_hotkey process the hotkey
				case IPC_GET_GENSKINBITMAP:
					if (m_are_we_loaded)
					{
						if (wParam == 0)
						{
							if (m_lastskin_dir[0])
							{
								HBITMAP bm;
								wchar_t bitmapfilename[MAX_PATH] = {0};
								PathCombineW(bitmapfilename, m_lastskin_dir, L"genex.bmp");
								bm = (HBITMAP)LoadImageW(NULL, bitmapfilename, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);
								if (bm) return (LRESULT)bm;
							}
							return (LRESULT)ff_genwa2skinbitmap();
						}
						else if (wParam == 4)
						{
							// TODO need to make sure we're covering everything needed for all of this!!
							int buf[6] = {0};
							// active text
							if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.list.text.current"))
								buf[1] = SkinColor(L"wasabi.list.text.current");
							/*else
							{
								COLORREF act = Blenders::BLEND_AVG(selected, buf[0]);
								act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(buf[2], 0xFFFFFFFF) : act;
								act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(buf[2], 0xFF101010) : act;
								act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(selected, buf[0]) : act;
								act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(selected, buf[2]) : act;
								act = (lumidiff(act, buf[0]) < 0x3F || lumidiff(act, buf[2]) < 0x2F) ? Blenders::BLEND_AVG(buf[0], buf[2]) : act;
								buf[1] = act & 0xFFFFFF;
							}*/
							return buf[1];
						}
					}
					break;
				case IPC_FF_ONCOLORTHEMECHANGED:
				{
					embedEnumStruct cs = { embedUpdateColorProc, 0 };
					SendMessageW(hwndDlg, WM_WA_IPC, (WPARAM)&cs, IPC_EMBED_ENUM);
					if (wParam != 0xf00d) doplaylistcolors();
				}
				break;
				case IPC_PLAYLIST_MODIFIED:
				{
					Wa2PlaylistEditor::_onPlaylistModified();
				}
				break;
			}
		}
		else if (uMsg == WM_COMMAND && (LOWORD(wParam) == 40144 || LOWORD(wParam) == 40148))
		{
			// seek left/right, we need to disable them if we are in a menu, because this is a
			// plugin (ie: milkdrop) sending us the command manually as a forward rather than
			// sending us the WM_KEY* directly
			if (WASABI_API_WND->isKeyboardLocked()) return 0;
		}
		else if ((uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) && LOWORD(wParam) >= 43000 && LOWORD(wParam) < ffwindowstop)
		{
			int id = LOWORD(wParam) - 43000;

			int n = SkinParser::getNumContainers();
			if (id < n)
			{
				Container *cont = SkinParser::enumContainer(id);
				if (cont) cont->toggle();
				return 1;
			}
			id -= n;

			n = WASABI_API_WNDMGR->autopopup_getNumGuids();
			if (id < n)
			{
				GUID guid = WASABI_API_WNDMGR->autopopup_enumGuid(id);
				if (guid != INVALID_GUID)
					WASABI_API_WNDMGR->skinwnd_toggleByGuid(guid);
				return 1;
			}
			id -= n;

			n = WASABI_API_WNDMGR->autopopup_getNumGroups();
			if (id < n)
			{
				const wchar_t *gid = WASABI_API_WNDMGR->autopopup_enumGroup(id);
				if (gid && *gid)
					WASABI_API_WNDMGR->skinwnd_toggleByGroupId(gid);
				return 1;
			}

			/*      for (int c=0;c<SkinParser::getNumContainers();c++) {
			        Container *cont = SkinParser::enumContainer(c);
			        if (cont && wantContainerInMenu(cont)) {
				        if (cont->getName() == NULL) continue;
			          if (id == 0) {
			            if (cont)
			              cont->toggle();
			            return 1;
			          }
			          id--;
			        }
			      }
			      int n = WASABI_API_WNDMGR->autopopup_getNumGuids();
			      for (c=0;c<n;c++) {
			        GUID guid = WASABI_API_WNDMGR->autopopup_enumGuid(c);
			        const char *groupdesc = WASABI_API_WNDMGR->autopopup_enumGuidDescription(c);
			        if (guid != INVALID_GUID  && groupdesc && *groupdesc) {
			          if (id == 0) {
			            WASABI_API_WNDMGR->skinwnd_toggleByGuid(guid);
			            return 1;
			          }
			          id--;
			        }
			      }
			      n = WASABI_API_WNDMGR->autopopup_getNumGroups();
			      for (c=0;c<n;c++) {
			        const char *gid = WASABI_API_WNDMGR->autopopup_enumGroup(c);
			        const char *groupdesc = WASABI_API_WNDMGR->autopopup_enumGroupDescription(c);
			        if (id && groupdesc && *gid && *groupdesc) {
			          if (id == 0) {
			            WASABI_API_WNDMGR->skinwnd_toggleByGroupId(gid);
			            return 1;
			          }
			          id--;
			        }
			      }*/
			return 0;
		}
		else if ((uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) && LOWORD(wParam) >= 44000 && LOWORD(wParam) < ffoptionstop)
		{
			int id = LOWORD(wParam);
			MenuActions::toggleOption(id - 44000);
		}
		else if ((uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) && LOWORD(wParam) >= 44500 && LOWORD(wParam) < 45000)
		{
			int id = LOWORD(wParam) - 44500;
			WASABI_API_SKIN->colortheme_setColorSet(WASABI_API_SKIN->colortheme_enumColorSet(id));
		}
		else if ((uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) && LOWORD(wParam) >= 42000 && LOWORD(wParam) < ffwoptionstop)
		{
			int id = LOWORD(wParam);
			MenuActions::toggleWindowOption(id - 42000);
		}
		else if ((uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) && LOWORD(wParam) == WINAMP_MAIN_WINDOW)
		{
			for (int i = 0;i < SkinParser::getNumContainers();i++)
			{
				Container *cont = SkinParser::enumContainer(i);
				if (!_wcsicmp(cont->getId(), L"main"))
				{
					cont->toggle();
					return 1;
				}
			}
		}
		else if ((uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND) && LOWORD(wParam) == WINAMP_OPTIONS_EQ)
		{
			if (!gothrueqmsg)
				return 0;
		}
		else if ((uMsg == WM_COMMAND || uMsg == WM_SYSCOMMAND))
		{
			switch (LOWORD(wParam))
			{
				case ID_CONTROLMENU_OPACITY_10: controlOpacity(10); return 0;
				case ID_CONTROLMENU_OPACITY_20: controlOpacity(20); return 0;
				case ID_CONTROLMENU_OPACITY_30: controlOpacity(30); return 0;
				case ID_CONTROLMENU_OPACITY_40: controlOpacity(40); return 0;
				case ID_CONTROLMENU_OPACITY_50: controlOpacity(50); return 0;
				case ID_CONTROLMENU_OPACITY_60: controlOpacity(60); return 0;
				case ID_CONTROLMENU_OPACITY_70: controlOpacity(70); return 0;
				case ID_CONTROLMENU_OPACITY_80: controlOpacity(80); return 0;
				case ID_CONTROLMENU_OPACITY_90: controlOpacity(90); return 0;
				case ID_CONTROLMENU_OPACITY_100: controlOpacity(100); return 0;
				case ID_CONTROLMENU_SCALING_50: controlScaling(0.5); return 0;
				case ID_CONTROLMENU_SCALING_75: controlScaling(0.75); return 0;
				case ID_CONTROLMENU_SCALING_100: controlScaling(1.0); return 0;
				case ID_CONTROLMENU_SCALING_125: controlScaling(1.25); return 0;
				case ID_CONTROLMENU_SCALING_150: controlScaling(1.5); return 0;
				case ID_CONTROLMENU_SCALING_200: controlScaling(2.0); return 0;
				case ID_CONTROLMENU_SCALING_250: controlScaling(2.5); return 0;
				case ID_CONTROLMENU_SCALING_300: controlScaling(3.0); return 0;
				case ID_CONTROLMENU_SCALING_LOCKED: lockScaling(1); return 0;
				case ID_CONTROLMENU_SCALING_FOLLOWDOUBLESIZE: lockScaling(0); return 0;
				case ID_CONTROLMENU_SCALING_CUSTOM: customScaling(); return 0;
				case ID_CONTROLMENU_OPACITY_CUSTOM: customOpacity(); return 0;
				case ID_CONTROLMENU_OPACITY_AUTO100_HOVER: autoOpacifyHover(); return 0;
				case ID_CONTROLMENU_OPACITY_AUTO100_FOCUS: autoOpacifyFocus(); return 0;
				case SC_MOVE:
				{
					Layout *l = SkinParser::getMainLayout();
					if (l != NULL)
					{
						Container *c = l->getParentContainer();
						if (c)
						{
							Layout *ll = c->getCurrentLayout();
							if (ll) l = ll;
							if (l)
							{
								if (!l->isVisible()) l->setVisible(1);
								return SendMessageW(l->gethWnd(), WM_SYSCOMMAND, wParam, lParam);
							}
						}
					}
					break;
				}
				case ID_CONTROLMENU_TOOLBAR_DISABLED: controlAppBar(APPBAR_NOTDOCKED); break;
				case ID_CONTROLMENU_TOOLBAR_TOP: controlAppBar(APPBAR_TOP); break;
				case ID_CONTROLMENU_TOOLBAR_LEFT: controlAppBar(APPBAR_LEFT); break;
				case ID_CONTROLMENU_TOOLBAR_RIGHT: controlAppBar(APPBAR_RIGHT); break;
				case ID_CONTROLMENU_TOOLBAR_BOTTOM: controlAppBar(APPBAR_BOTTOM); break;
				case ID_CONTROLMENU_TOOLBAR_ALWAYSONTOP: controlAppBarAOT(); break;
				case ID_CONTROLMENU_TOOLBAR_AUTOHIDE: controlAppBarAH(); break;
				case ID_CONTROLMENU_TOOLBAR_AUTODOCKONDRAG: cfg_options_appbarondrag = !cfg_options_appbarondrag; break;
			}
		}
	}

	return CallWinampWndProc(hwndDlg, uMsg, wParam, lParam);
}

void onLayoutChanged()
{
	if (DEFERREDCALLBACKMSG > 65536)
		PostMessage(wa2.getMainWindow(), WM_WA_IPC, 1, DEFERREDCALLBACKMSG);
}

#ifdef DEBUG_CAPTURES
int tid = 0;

VOID TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD time)
{
	DebugString("Capture belongs to %x (foregroundwnd = %x)\n", GetCapture(), GetForegroundWindow());
}
#endif

template <class api_T>
static void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
{
	if (WASABI_API_SVC)
	{
		waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(factoryGUID_t);
		if (factory)
			api_t = reinterpret_cast<api_T *>( factory->getInterface() );
	}
}

template <class api_T>
static void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
{
	if (WASABI_API_SVC && api_t)
	{
		waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(factoryGUID_t);
		if (factory)
			factory->releaseInterface(api_t);
	}
	api_t = NULL;
}

//-----------------------------------------------------------------------------------------------
// initializes freeform library
//-----------------------------------------------------------------------------------------------
int     m_loaded_at_all = 0;
StringW g_resourcepath;

void initFFApi()
{
	if ( !m_loaded_at_all )
	{
		m_loaded_at_all = 1;
		api_service *svc = (api_service *)SendMessageW( wa2.getMainWindow(), WM_WA_IPC, 0, IPC_GET_API_SERVICE );

		ServiceBuild( AGAVE_API_PLAYLISTS,       api_playlistsGUID );
		ServiceBuild( AGAVE_API_ALBUMART,        albumArtGUID );
		ServiceBuild( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
		ServiceBuild( WAC_API_DOWNLOADMANAGER, DownloadManagerGUID );
		ServiceBuild( WASABI_API_COLORTHEMES,    ColorThemesAPIGUID );
		ServiceBuild( WASABI_API_PALETTE,        PaletteManagerGUID );
		ServiceBuild( WASABI_API_THREADPOOL,     ThreadPoolGUID );

		ApiInit::init( hInstance, wa2.getMainWindow(), svc );
		cfgitems = new Wa2CfgItems();

#ifdef DEBUG_CAPTURES
		tid = SetTimer( NULL, 0x159, 50, (TIMERPROC)TimerProc );
#endif

		wchar_t filename[ WA_MAX_PATH ] = { 0 };
		GetModuleFileNameW( hInstance, filename, WA_MAX_PATH );
		PathParserW pp( filename );
		StringW path;
		for ( int i = 0; i < pp.getNumStrings() - 1; i++ )
		{
			path.AppendPath( pp.enumString( i ) );
		}
		path.AppendPath( L"freeform" );
		path.AppendFolder( L"wacs" );

		// we can load a somewhat restricted version of the wac format

		ComponentManager::loadAll( path );
		ComponentManager::postLoad();

		ApiInit::widgets->loadResources();

		startFSMonitor();
	}
	cfg_options_alwaysontop.setValueAsInt( wa2.isOnTop() );
}

//-----------------------------------------------------------------------------------------------
// shutdown ff lib
//-----------------------------------------------------------------------------------------------
void shutdownFFApi()
{
	if (m_loaded_at_all)
	{
		ServiceRelease( AGAVE_API_PLAYLISTS,       api_playlistsGUID);
		ServiceRelease( AGAVE_API_ALBUMART,        albumArtGUID);
		ServiceRelease( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
		ServiceRelease( WAC_API_DOWNLOADMANAGER, DownloadManagerGUID);
		ServiceRelease( WASABI_API_COLORTHEMES,    ColorThemesAPIGUID);
		ServiceRelease( WASABI_API_PALETTE,        PaletteManagerGUID);
		ServiceRelease( WASABI_API_THREADPOOL,     ThreadPoolGUID);

		stopFSMonitor();

		// shutdown the library
#ifdef DEBUG_CAPTURES
		KillTimer(NULL, tid);
#endif

		delete cfgitems; cfgitems = NULL;
		ApiInit::shutdown();
ComponentManager::unloadAll();

		m_loaded_at_all = 0;
	}
}

static void MakeControlMenu()
{
	if (controlmenu)
		return ;

	controlmenu = WASABI_API_LOADMENU(IDR_CONTROLMENU);

	if (!Wasabi::Std::Wnd::isDesktopAlphaAvailable())
		EnableMenuItem(controlmenu, 0, MF_BYPOSITION | MF_GRAYED);
}

//-----------------------------------------------------------------------------------------------
// go freeform
//-----------------------------------------------------------------------------------------------
void init_inst()
{
	if ( m_are_we_loaded )
		return;

	// set the classic main window to be transparent
	// so we can move it around the screen to allow
	// certain Windows 8 modes as well as improving
	// where it is placed for some multi-mon setups
	Wasabi::Std::Wnd::setLayeredWnd( wa2.getMainWindow(), 1 );
	Wasabi::Std::Wnd::setLayeredAlpha( wa2.getMainWindow(), 0 );

	initEgg();

	wa2.setDrawBorders( 0 );
	wa2.disableSkinnedCursors( 1 );

	int wasminimized = IsIconic( wa2.getMainWindow() );
	if ( wasminimized )
		ShowWindow( wa2.getMainWindow(), SW_RESTORE );

	MakeControlMenu();

	going_freeform = 1;

	Wa2WndEmbed::rememberVisibleWindows();

	if ( wa2.isWindowVisible( IPC_GETWND_PE ) )
	{
		if ( !m_loading_at_startup ) WritePrivateProfileStringW( L"gen_ff", L"classicpe", L"1", INI_FILE );
	}
	else
		if ( !m_loading_at_startup ) WritePrivateProfileStringW( L"gen_ff", L"classicpe", L"0", INI_FILE );

	if ( wa2.isWindowVisible( IPC_GETWND_EQ ) )
	{
		gothrueqmsg = 1;
		SendMessageW( plugin.hwndParent, WM_COMMAND, WINAMP_OPTIONS_EQ, 0 );
		gothrueqmsg = 0;
		if ( !m_loading_at_startup ) WritePrivateProfileStringW( L"gen_ff", L"classiceq", L"1", INI_FILE );
	}
	else
		if ( !m_loading_at_startup ) WritePrivateProfileStringW( L"gen_ff", L"classiceq", L"0", INI_FILE );

	removeEq();

	if ( SendMessageW( plugin.hwndParent, WM_WA_IPC, 0, IPC_ISMAINWNDVISIBLE ) )
	{
		SendMessageW( plugin.hwndParent, WM_COMMAND, WINAMP_MAIN_WINDOW, 0 );
		if ( !m_loading_at_startup ) WritePrivateProfileStringW( L"gen_ff", L"classicmw", L"1", INI_FILE );
	}
	else
		if ( !m_loading_at_startup ) WritePrivateProfileStringW( L"gen_ff", L"classicmw", L"0", INI_FILE );

	m_are_we_loaded = 1;

	initFFApi();

	// redirect drag and drop to winamp2 by default
	WASABI_API_WND->setDefaultDropTarget( (void *)wa2.getDropTarget() );

	// this installs a bunch of predefined groups to map wa3 to wa2 functionnality
	groups = new Wa2Groupdefs();

	// now load a skin ! the path to the skin is extracted and temporarilly becomes the official skins directory

	StringW skinname = m_lastskin_nam;

	removeSkinExtension( skinname );

	before_startup_callback = 1;
	WASABI_API_SKIN->skin_switchSkin( skinname, m_lastskin_dir );

	/*  // if we wanted to have drag&drop support for just the main window, we'd do it this way :-)
	  Container *maincontainer = SkinParser::getContainer("main");
	  if (maincontainer != NULL) {
		for (int i=0;i<maincontainer->getNumLayouts();i++) {
		  Layout *layout = maincontainer->enumLayout(i);
		  if (layout != NULL) {
			layout->setDropTarget((void *)wa2.getDropTarget());
		  }
		}
	  }*/

	  // send event for dialog parent
	onLayoutChanged();

	if ( wa2.export_sa_setreq )
		wa2.export_sa_setreq( 1 );
	
	shuffle.setValueAsInt( wa2.getShuffle() );
	
	int rep    = wa2.getRepeat();
	int manadv = wa2.getManualPlaylistAdvance();
	
	disable_set_wa2_repeat = 1;
	repeat.setValueAsInt( ( rep && manadv ) ? -1 : rep );
	disable_set_wa2_repeat = 0;

	if ( DEFERREDCALLBACKMSG > 65536 )
		PostMessage( wa2.getMainWindow(), WM_WA_IPC, 2, DEFERREDCALLBACKMSG );

	// so if some embedwindows are already visible, they update their look
	PostMessage( wa2.getMainWindow(), WM_WA_IPC, 0xf00d, IPC_FF_ONCOLORTHEMECHANGED );

	// monitor color theme
	colorThemeMonitor = new ColorThemeMonitor();

	syncDoubleSize( 1 );
	syncDisplayMode();

	going_freeform = 0;

	if ( wa2.getWnd( IPC_GETWND_VIDEO ) )
	{
		oldVideoWnd = wa2.getWnd( IPC_GETWND_VIDEO );
		if ( !oldVideoWndProc )
			oldVideoWndProc = (WNDPROC)SetWindowLongPtrW( oldVideoWnd, GWLP_WNDPROC, (LONG_PTR)newVideoWndProc );
	}

	if ( wasminimized )
		ShowWindow( wa2.getMainWindow(), SW_MINIMIZE );

	if ( eggstat )
		SetTimer( wa2.getMainWindow(), UPDATE_EGG, 25, NULL );
}

void quit_inst()
{
	if (!m_are_we_loaded) return ;

	KillTimer(wa2.getMainWindow(), UPDATE_EGG);

	wa2.setDrawBorders(1);
	wa2.disableSkinnedCursors(0);

	KillTimer(wa2.getMainWindow(), 0xC0DE);
	KillTimer(wa2.getMainWindow(), 0xC0DE + 1);

	EnableMenuItem(wa2.getMenuBarMenu(Winamp2FrontEnd::WA2_MAINMENUBAR_OPTIONS), WINAMP_OPTIONS_DSIZE, MF_ENABLED);
	EnableMenuItem(GetSubMenu(wa2.getPopupMenu(), 11 + wa2.adjustOptionsPopupMenu(0)), WINAMP_OPTIONS_DSIZE, MF_ENABLED);

	if (oldVideoWnd && oldVideoWndProc)
		SetWindowLongPtrW(oldVideoWnd, GWLP_WNDPROC, (LONG_PTR)oldVideoWndProc);
	oldVideoWndProc = 0;
	oldVideoWnd = 0;

	removeWindowOptionsFromContextMenu();

	delete mainLayoutMonitor;
	mainLayoutMonitor=0;

	wa2.setDialogBoxParent(NULL);
	unpopulateWindowsMenus();
	restoreEq();

	// unload the skin -- skinspath is restored to default
	WASABI_API_SKIN->skin_unloadSkin();

	// delete predefined groups service
	delete groups; groups = NULL;

	// delete options
	delete ffoptions; ffoptions = NULL;

	// stop monitoring color theme
	delete colorThemeMonitor; colorThemeMonitor = NULL;

	if (wa2.export_sa_setreq) wa2.export_sa_setreq(0);

	m_are_we_loaded = 0;

	int classicmw = GetPrivateProfileIntW(L"gen_ff", L"classicmw", 1, INI_FILE);
	if (classicmw && !SendMessageW(plugin.hwndParent, WM_WA_IPC, 0, IPC_ISMAINWNDVISIBLE))
		SendMessageW(plugin.hwndParent, WM_COMMAND, WINAMP_MAIN_WINDOW, 0);

	CheckMenuItem(wa2.getPopupMenu(), WINAMP_MAIN_WINDOW, classicmw ? MF_CHECKED : MF_UNCHECKED);

	if (GetPrivateProfileIntW(L"gen_ff", L"classiceq", 1, INI_FILE) && !wa2.isWindowVisible(IPC_GETWND_EQ))
	{
		gothrueqmsg = 1;
		SendMessageW(plugin.hwndParent, WM_COMMAND, WINAMP_OPTIONS_EQ, 0);
		gothrueqmsg = 0;
	}

	if (GetPrivateProfileIntW(L"gen_ff", L"classicpe", 1, INI_FILE) && !wa2.isWindowVisible(IPC_GETWND_PE))
	{
		SendMessageW(plugin.hwndParent, WM_COMMAND, WINAMP_OPTIONS_PLEDIT, 0);
	}

	// restore the classic main window to be solid
	Wasabi::Std::Wnd::setLayeredWnd(wa2.getMainWindow(), 0);
}

//-----------------------------------------------------------------------------------------------
// init (from Winamp2)
//-----------------------------------------------------------------------------------------------
int init()
{
	// loader so that we can get the localisation service api for use
	WASABI_API_SVC = (api_service*) SendMessageW(plugin.hwndParent, WM_WA_IPC, 0, IPC_GET_API_SERVICE);
	if (WASABI_API_SVC == (api_service*)1) WASABI_API_SVC = NULL;
	if (!WASABI_API_SVC || WASABI_API_SVC == (api_service *)1)
		return GEN_INIT_FAILURE;

	static wchar_t modskin[128];
	INI_FILE = (wchar_t*) SendMessageW(plugin.hwndParent, WM_WA_IPC, 0, IPC_GETINIFILEW);
	INI_DIR = (wchar_t*) SendMessageW(plugin.hwndParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORYW);

	waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(languageApiGUID);
	if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface());

	// need to have this initialised before we try to do anything with localisation features
	WASABI_API_START_LANG(plugin.hDllInstance,GenFFLangGUID);

	swprintf(szDescription, ARRAYSIZE(szDescription),
			 WASABI_API_LNGSTRINGW(IDS_NULLSOFT_MODERN_SKINS), VERSION);
	plugin.description = (char*)szDescription;

	wa2.init(plugin.hwndParent);

	DEFERREDCALLBACKMSG = SendMessageW(plugin.hwndParent, WM_WA_IPC, (WPARAM)"gen_ff_deferred", IPC_REGISTER_WINAMP_IPCMESSAGE);
	UPDATEDIALOGBOXPARENTMSG = SendMessageW(plugin.hwndParent, WM_WA_IPC, (WPARAM)"gen_ff_update", IPC_REGISTER_WINAMP_IPCMESSAGE);

	// subclass the Winamp2 main window to receive our callbacks
	wa_oldWndProc = (WNDPROC) SetWindowLongPtrW(wa2.getMainWindow(), GWLP_WNDPROC, (LONG_PTR)wa_newWndProc);
	
	ffPrefsItem.dlgID = IDD_PREFS;
	ffPrefsItem.name  = WASABI_API_LNGSTRINGW_BUF(IDS_MODERN_SKINS,modskin,128);
	ffPrefsItem.proc  = (void*)ffPrefsProc;
	ffPrefsItem.hInst = WASABI_API_LNG_HINST;
	ffPrefsItem.where = 2; //skins subtreeitem
	SendMessageW(plugin.hwndParent, WM_WA_IPC, (WPARAM)&ffPrefsItem, IPC_ADD_PREFS_DLGW);

	checkMlPresent();

	m_loading_at_startup = 1;
	onSkinSwitch();
	m_loading_at_startup = 0;
	return 0;
}

void RestoreClassicWinamp(int was_loaded)
{
	// JF> my proposed fix to the problems :)
	// investigating doing this from winamp.exe on startup, gotta figure it out
	if (INI_FILE && was_loaded) // restore winamp.ini to about what it shoulda been
	{
		int classicpews   = GetPrivateProfileIntW(L"gen_ff", L"classicplws",       0, INI_FILE);
		int classicwidth  = GetPrivateProfileIntW(L"gen_ff", L"classicplwidth",  275, INI_FILE);
		int classicheight = GetPrivateProfileIntW(L"gen_ff", L"classicplheight", 145, INI_FILE);
		int classicmw     = GetPrivateProfileIntW(L"gen_ff", L"classicmw",         1, INI_FILE);
		int classiceq     = GetPrivateProfileIntW(L"gen_ff", L"classiceq",         1, INI_FILE);
		wchar_t buf[64] = {0};

		wsprintfW(buf, L"%d", classicheight);
		if (classicpews)
		{
			WritePrivateProfileStringW(L"winamp", L"pe_height", L"14", INI_FILE);
			WritePrivateProfileStringW(L"winamp", L"pe_height_ws", buf, INI_FILE);
		}
		else
		{
			WritePrivateProfileStringW(L"winamp", L"pe_height", buf, INI_FILE);
			WritePrivateProfileStringW(L"winamp", L"pe_height_ws", L"", INI_FILE);
		}
		wsprintfW(buf, L"%d", classicwidth);
		WritePrivateProfileStringW(L"winamp", L"pe_width", buf, INI_FILE);

		WritePrivateProfileStringW(L"winamp", L"eq_open", classiceq ? L"1" : L"0", INI_FILE);
		WritePrivateProfileStringW(L"winamp", L"mw_open", classicmw ? L"1" : L"0", INI_FILE);
	}
}

//-----------------------------------------------------------------------------------------------
// quit (from Winamp 2)
//-----------------------------------------------------------------------------------------------
void quit()
{
	int was_loaded = m_are_we_loaded;
	quit_inst();

	RestoreClassicWinamp(was_loaded);
	// restore wa2's windowproc
	//SetWindowLong(wa2.getMainWindow(), GWL_WNDPROC, (LONG)wa_oldWndProc);
	shutdownFFApi();
}

//-----------------------------------------------------------------------------------------------
// About box wndproc
//-----------------------------------------------------------------------------------------------
ifc_window *about_group = NULL;
StringW oldrenderer;
BOOL CALLBACK aboutProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_INITDIALOG:
	{
		oldrenderer = cfg_options_fontrenderer.getValue();
		if (!WCSCASEEQLSAFE(cfg_options_fontrenderer.getValue(), L"FreeType"))
			cfg_options_fontrenderer.setValue(L"FreeType");
		about_group = WASABI_API_SKIN->group_create(L"wasabi.gen_ff.about");
		about_group->setVirtual(0);
		HWND w = GetDlgItem(hwndDlg, IDC_STATIC_GROUP);
		about_group->setStartHidden(1);
		about_group->init(WASABI_API_WND->main_getRootWnd(), 1);
		SetWindowLong(about_group->gethWnd(), GWL_STYLE, GetWindowLong(about_group->gethWnd(), GWL_STYLE) | WS_CHILD);
		SetParent(about_group->gethWnd(), w);
		SetWindowLong(w, GWL_STYLE, GetWindowLong(w, GWL_STYLE) | WS_CLIPCHILDREN);
		RECT r;
		GetClientRect(w, &r);
		about_group->resize(r.left + 1, r.top + 1, r.right - r.left - 2, r.bottom - r.top - 2);
		about_group->setVisible(1);
		C_Group g(about_group->getGuiObject()->guiobject_getScriptObject());
		ScriptObject *ver = g.findObject(L"version");
		if (ver)
		{
			C_Text t(ver);
			t.setText(StringPrintfW(L". � 2003-2023 Winamp SA %s", VERSION));
		}
		ShowWindow(about_group->gethWnd(), SW_NORMAL);
		return 1;
	}
	case WM_DESTROY:
		WASABI_API_SKIN->group_destroy(about_group);
		about_group = NULL;
		break;
	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
	case IDOK: case IDCANCEL:
			if (!WCSCASEEQLSAFE(cfg_options_fontrenderer.getValue(), oldrenderer))
				cfg_options_fontrenderer.setValue(oldrenderer);
			EndDialog(hwndDlg, 0);
			return 0;
		}
		return 0;
	}
	return 0;
}

//-----------------------------------------------------------------------------------------------
// configure plugin (from Winamp 2)
//-----------------------------------------------------------------------------------------------
void config()
{
	if (m_loaded_at_all)
	{
		StringW skin = WASABI_API_SKIN->getSkinName();
		wchar_t _skName[64] = {0};
		WASABI_API_LNGSTRINGW_BUF(IDS_NO_SKIN_LOADED_,_skName,64);

		if ((!skin.iscaseequal(_skName)) || guiTree->getNumObject() > 0)
		{
			WASABI_API_DIALOGBOXW(IDD_ABOUT, wa2.getPreferencesWindow(), aboutProc);
			return ;
		}
	}

	MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMS),0};
	msgbx.lpszText = WASABI_API_LNGSTRINGW(IDS_MODERN_SKIN_SUPPORT_CLASSIC);
	msgbx.lpszCaption = szDescription;
	msgbx.lpszIcon = MAKEINTRESOURCEW(102);
	msgbx.hInstance = GetModuleHandle(0);
	msgbx.dwStyle = MB_USERICON;
	MessageBoxIndirectW(&msgbx);
}

//-----------------------------------------------------------------------------------------------
// expose the genpurp plugin interface to dll
//-----------------------------------------------------------------------------------------------
extern "C" __declspec(dllexport) winampGeneralPurposePlugin * winampGetGeneralPurposePlugin() { return &plugin; }

//-----------------------------------------------------------------------------------------------
// a window was right clicked where there was no custom context menu available, spawn Wa2's menu
//-----------------------------------------------------------------------------------------------
void appContextMenu(ifc_window *w)
{
	if (!WASABI_API_WND->rootwndIsValid(w)) return ;
	WASABI_API_WND->appdeactivation_setbypass(1);
	SetCursor(LoadCursor(NULL, IDC_ARROW));
	w->setFocus();
	DWORD p = GetMessagePos();
	int x = GET_X_LPARAM(p);
	int y = GET_Y_LPARAM(p);
	addWindowOptionsToContextMenu(w);
	wa2.triggerPopupMenu(x, y);
	WASABI_API_WND->appdeactivation_setbypass(0);
}

//-----------------------------------------------------------------------------------------------
void updateControlMenu(ifc_window *w)
{
	int curalpha = 255;
	double curratio = 1.;
	int opacitysafe = 0;
	int scalelocked = 0;
	int auto100_hover = 0;
	int auto100_focus = 0;

	if (g_controlMenuTarget == NULL) return ;

	if (w)
	{
		ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
		if (rootparent && rootparent->getInterface(layoutGuid))
		{
			Layout *l = static_cast<Layout*>(rootparent);
			opacitysafe = l->isTransparencySafe();
			scalelocked = l->isScaleLocked();
			curratio = w->getRenderRatio();
			if (!cfg_uioptions_linkallalpha.getValueAsInt())
			{
				auto100_hover = l->getAutoOpacify() == 1;
				auto100_focus = l->getAutoOpacify() == 2;
				curalpha = l->getAlpha();
			}
			else
			{
				auto100_hover = l->getAlphaMgr()->getAutoOpacify() == 1;
				auto100_focus = l->getAlphaMgr()->getAutoOpacify() == 2;
				curalpha = l->getAlphaMgr()->getGlobalAlpha();
			}
		}
	}

	HMENU ctrlmenu = GetSubMenu(controlmenu, 0);
	EnableMenuItem(ctrlmenu, 0, MF_BYPOSITION | (opacitysafe ? MF_ENABLED : MF_GRAYED));
	HMENU scalemenu = GetSubMenu(ctrlmenu, 1);
	HMENU alphamenu = GetSubMenu(ctrlmenu, 0);
	int uselocks = cfg_uioptions_uselocks.getValueAsInt();
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_LOCKED, MF_BYCOMMAND | ((scalelocked || !uselocks) ? MF_CHECKED : MF_UNCHECKED));
	EnableMenuItem(scalemenu, ID_CONTROLMENU_SCALING_LOCKED, MF_BYCOMMAND | (uselocks ? MF_ENABLED : MF_GRAYED));
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_FOLLOWDOUBLESIZE, MF_BYCOMMAND | (scalelocked || !uselocks ? MF_UNCHECKED : MF_CHECKED));
	EnableMenuItem(scalemenu, ID_CONTROLMENU_SCALING_FOLLOWDOUBLESIZE, MF_BYCOMMAND | (uselocks ? MF_ENABLED : MF_GRAYED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_AUTO100_HOVER, MF_BYCOMMAND | (auto100_hover ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_AUTO100_FOCUS, MF_BYCOMMAND | (auto100_focus ? MF_CHECKED : MF_UNCHECKED));

	int v = (int)((curalpha / 255.0f * 100.0f) + 0.5f);
	int u = (int)((curratio * 100.0f) + 0.5f);

	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_10, MF_BYCOMMAND | (v == 10 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_20, MF_BYCOMMAND | (v == 20 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_30, MF_BYCOMMAND | (v == 30 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_40, MF_BYCOMMAND | (v == 40 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_50, MF_BYCOMMAND | (v == 50 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_60, MF_BYCOMMAND | (v == 60 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_70, MF_BYCOMMAND | (v == 70 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_80, MF_BYCOMMAND | (v == 80 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_90, MF_BYCOMMAND | (v == 90 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_100, MF_BYCOMMAND | (v == 100 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_50, MF_BYCOMMAND | (u == 50 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_75, MF_BYCOMMAND | (u == 75 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_100, MF_BYCOMMAND | (u == 100 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_125, MF_BYCOMMAND | (u == 125 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_150, MF_BYCOMMAND | (u == 150 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_200, MF_BYCOMMAND | (u == 200 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_250, MF_BYCOMMAND | (u == 250 ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_300, MF_BYCOMMAND | (u == 300 ? MF_CHECKED : MF_UNCHECKED));

	if (u != 50 && u != 75 && u != 100 && u != 125 && u != 150 && u != 200 && u != 250 && u != 300)
	{
		ModifyMenuW(scalemenu, ID_CONTROLMENU_SCALING_CUSTOM, MF_BYCOMMAND | MF_STRING, ID_CONTROLMENU_SCALING_CUSTOM, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_CUSTOM_X_PERCENT), u));
		CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_CUSTOM, MF_BYCOMMAND | MF_CHECKED);
	}
	else
	{
		ModifyMenuW(scalemenu, ID_CONTROLMENU_SCALING_CUSTOM, MF_BYCOMMAND | MF_STRING, ID_CONTROLMENU_SCALING_CUSTOM, WASABI_API_LNGSTRINGW(IDS_CUSTOM));
		CheckMenuItem(scalemenu, ID_CONTROLMENU_SCALING_CUSTOM, MF_BYCOMMAND | MF_UNCHECKED);
	}
	if (((float)v / 10.0) - (v / 10) != 0.0)
	{
		ModifyMenuW(alphamenu, ID_CONTROLMENU_OPACITY_CUSTOM, MF_BYCOMMAND | MF_STRING, ID_CONTROLMENU_OPACITY_CUSTOM, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_CUSTOM_X_PERCENT), v));
		CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_CUSTOM, MF_BYCOMMAND | MF_CHECKED);
	}
	else
	{
		ModifyMenuW(alphamenu, ID_CONTROLMENU_OPACITY_CUSTOM, MF_BYCOMMAND | MF_STRING, ID_CONTROLMENU_OPACITY_CUSTOM, WASABI_API_LNGSTRINGW(IDS_CUSTOM));
		CheckMenuItem(alphamenu, ID_CONTROLMENU_OPACITY_CUSTOM, MF_BYCOMMAND | MF_UNCHECKED);
	}
}

//-----------------------------------------------------------------------------------------------
// a window has triggered the control menu (control scale & alpha)
//-----------------------------------------------------------------------------------------------
void appControlMenu(ifc_window *w)
{
	WASABI_API_WND->appdeactivation_setbypass(1);
	int x, y;
	if (w)
	{
		Wasabi::Std::getMousePos(&x, &y);
		g_controlMenuTarget = w;
		updateControlMenu(w);
		updateAppBarMenu(w);
		HMENU ctrlmenu = GetSubMenu(controlmenu, 0);
		DoTrackPopup(ctrlmenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, x, y, wa2.getMainWindow());
	}
	WASABI_API_WND->appdeactivation_setbypass(0);
}

//-----------------------------------------------------------------------------------------------
// a close application button was clicked
//-----------------------------------------------------------------------------------------------
void appQuit()
{
	if (!wa2.isExitEnabled()) return ;
	wa2.quit(); // this is in fact *posting* a quit message
}


//-----------------------------------------------------------------------------------------------
// a fatal skin error occured, we should revert to the wa2 skin (deferred)
//-----------------------------------------------------------------------------------------------
#define DCB_UNLOADSKIN 64

class deferredUnloadSkin : public TimerClientDI
{
public:
	deferredUnloadSkin()
	{
		timerclient_postDeferredCallback(DCB_UNLOADSKIN, 0);
	}

	virtual int timerclient_onDeferredCallback(intptr_t param1, intptr_t param2)
	{
		if (param1 == DCB_UNLOADSKIN)
		{
			quit_inst();
			delete this;
			return 1;
		}
		else return TimerClientDI::timerclient_onDeferredCallback(param1, param2);
	}
};

//-----------------------------------------------------------------------------------------------
// this is the actual event for skin fata errors, it is called when a try/except block failed
// upon calling a skin operation which should not be able to fail (ie: calling a script function pointer)
//-----------------------------------------------------------------------------------------------
void onFatalSkinError()
{
	new deferredUnloadSkin();
}


//-----------------------------------------------------------------------------------------------
// map a GUID to the toggling of a window for which there is no permanent wndCreationService,
// ie: library, prefs, avs
//-----------------------------------------------------------------------------------------------

// called by a skinwnd_toggle call when the guid isn't found in the wndcreation services
int onCreateExternalWindowGuid(GUID g)
{
	if (g == library_guid)
	{
		SendMessageW(wa2.getMainWindow(), WM_COMMAND, ID_FILE_SHOWLIBRARY, 0);
		return 1;
	}
	else if (g == preferences_guid)
	{
		SendMessageW(wa2.getMainWindow(), WM_COMMAND, WINAMP_OPTIONS_PREFS, 0);
		return 1;
	}
	else if (g == about_guid)
	{
		SendMessageW(wa2.getMainWindow(), WM_COMMAND, WINAMP_HELP_ABOUT, 0);
		return 1;
	}
	else if (g == lightning_bolt_guid)
	{
		SendMessageW(wa2.getMainWindow(), WM_COMMAND, WINAMP_LIGHTNING_CLICK, 0);
		return 1;
	}
	else if (g == colorthemes_guid)
	{
		last_page.setData(L"3");
		SendMessageW(plugin.hwndParent, WM_WA_IPC, (WPARAM)&ffPrefsItem, IPC_OPENPREFSTOPAGE);
		return 1;
	}
	else if (g == avs_guid)
	{
		if (wa2.isVisRunning())
		{
			SetTimer(wa2.getMainWindow(), 0xC0DE, 50, NULL);
			SetTimer(wa2.getMainWindow(), 0xC0DE + 1, 5000, NULL);
		}
		else
			wa2.toggleVis();
		return 1;
	}
	return 0;
}

//-----------------------------------------------------------------------------------------------
void onToggleDesktopAlpha(int v)
{
	if (!toggle_from_wa2 && subWndId == 0 && IsWindow(subWnd)) SendMessageW(subWnd, WM_INITDIALOG, 0, 0);
}


//-----------------------------------------------------------------------------------------------
// changes the name of a container so that static containers with the wrong name don't load the config
// values off the wrong name. this should match the WindowText of the embededwindow. note: this is only
// necessary for static containers holding an internal embedwnd (ie: library, avs, but not third party wnds
// as they can't be refered to by GUID anyway [if they can't be refered to by GUID, you can't make a static
// container for them])
//-----------------------------------------------------------------------------------------------

const wchar_t *onTweakContainerNameW(const wchar_t *name)
{
	static wchar_t tweaked[96];
	ZERO(tweaked);
	if (!_wcsicmp(name, WASABI_API_LNG->GetStringFromGUIDW(GenMlLangGUID, plugin.hDllInstance, 18, tweaked, sizeof(tweaked)/sizeof(wchar_t))) ||
	    !_wcsicmp(name, L"Media Library") ||
	    !_wcsicmp(name, L"Winamp Library") ||
	    !_wcsicmp(name, L"Library"))
	{
		return WASABI_API_LNGSTRINGW_BUF(IDS_MEDIA_LIBRARY,tweaked, sizeof(tweaked)/sizeof(wchar_t));
	}
	if (!_wcsicmp(name, L"Avs")) return WASABI_API_LNGSTRINGW_BUF(IDS_VISUALIZATIONS,tweaked, sizeof(tweaked)/sizeof(wchar_t));
	return name;
}

const wchar_t *GetMenuItemString(HMENU menu, int id, int bypos)
{
	static StringW rtn;
	rtn.trunc(0);
	MENUITEMINFOW info = {sizeof(info), MIIM_DATA | MIIM_TYPE | MIIM_STATE | MIIM_ID, MFT_STRING, };
	GetMenuItemInfoW(menu, id, bypos, &info);
	if (info.cch > 0)
	{
		info.dwTypeData = WMALLOC(++info.cch + 1);
		GetMenuItemInfoW(menu, id, bypos, &info);
		info.dwTypeData[info.cch] = 0;
		rtn = info.dwTypeData;
		FREE(info.dwTypeData);
	}
	return rtn;
}

StringW eqmenustring;

//-----------------------------------------------------------------------------------------------
void removeEq()
{
	if (eqremoved) return ;
	eqremoved = 1;
	eqmenustring = GetMenuItemString(wa2.getPopupMenu(), WINAMP_OPTIONS_EQ, FALSE);
	RemoveMenu(wa2.getPopupMenu(), WINAMP_OPTIONS_EQ, MF_BYCOMMAND);
	wa2.adjustOptionsPopupMenu(-1);
}

//-----------------------------------------------------------------------------------------------
void restoreEq()
{
	if (!eqremoved) return ;
	MENUITEMINFOW i = {sizeof(i), MIIM_ID | MIIM_STATE | MIIM_TYPE, MFT_STRING, wa2.isWindowVisible(IPC_GETWND_EQ) ? MFS_CHECKED : 0, WINAMP_OPTIONS_EQ};
	i.dwTypeData = eqmenustring.getNonConstVal();
	InsertMenuItemW(wa2.getPopupMenu(), 8, TRUE, &i);
	wa2.adjustOptionsPopupMenu(1);
	eqremoved = 0;
}

//-----------------------------------------------------------------------------------------------
void unpopulateWindowsMenus()
{
	if (ffwindowsitempos == -1) return ;

	HMENU menuBarMenu = wa2.getMenuBarMenu(Winamp2FrontEnd::WA2_MAINMENUBAR_WINDOWS);
	for(int i = GetMenuItemCount(menuBarMenu)-1; i >= 0; i--)
	{
		MENUITEMINFOW info = {sizeof(info), MIIM_DATA, 0, };
		if(GetMenuItemInfoW(menuBarMenu,i,TRUE,&info))
		{
			if(info.dwItemData == 0xD01){
				RemoveMenu(menuBarMenu,i,MF_BYPOSITION);
				wa2.adjustFFWindowsMenu(-1);
			}
		}
	}
	
	HMENU menuPopupMenu = wa2.getPopupMenu();
	for(int i = GetMenuItemCount(menuPopupMenu)-1; i >= 0; i--)
	{
		MENUITEMINFOW info = {sizeof(info), MIIM_DATA, 0, };
		if(GetMenuItemInfoW(menuPopupMenu,i,TRUE,&info))
		{
			if(info.dwItemData == 0xD01){
				RemoveMenu(menuPopupMenu,i,MF_BYPOSITION);
				wa2.adjustOptionsPopupMenu(-1);
			}
		}
	}

	ffwindowsitempos = -1;
	ffwindowsitempos2 = -1;

	MenuActions::removeSkinWindowOptions();
	MenuActions::removeSkinOptions();
}

//-----------------------------------------------------------------------------------------------
void populateWindowsMenus()
{
	if (ffwindowsitempos != -1) unpopulateWindowsMenus();
	MenuActions::installSkinOptions();
	MenuActions::installSkinWindowOptions();

	ffwindowsitempos = wa2.adjustFFWindowsMenu(0) + NUMSTATICWINDOWS;
	ffwindowsitempos2 = wa2.adjustOptionsPopupMenu(0) + 6 + NUMSTATICWINDOWS + 1;

	MENUITEMINFOW i = {sizeof(i), };
	i.fMask = MIIM_TYPE | MIIM_DATA | MIIM_ID | MIIM_STATE;
	i.fType = MFT_STRING;
	i.wID = 43000;
	i.dwItemData = 0xD01;	// use this as a check so we're only removing the correct items!!
	int pos = ffwindowsitempos;
	int pos2 = ffwindowsitempos2;
	PtrListQuickSorted<StringW, StringWComparator> items;

	HMENU hMenu = wa2.getMenuBarMenu(Winamp2FrontEnd::WA2_MAINMENUBAR_WINDOWS);
	HMENU hMenu2 = wa2.getPopupMenu();
	for (int c = 0;c < SkinParser::getNumContainers();c++)
	{
		Container *cont = SkinParser::enumContainer(c);
		if (cont && wantContainerInMenu(cont))
		{
			i.dwTypeData = const_cast<wchar_t *>(cont->getName());
			if (i.dwTypeData != NULL)
			{
				if (!items.findItem(i.dwTypeData))
				{
					items.addItem(new StringW(i.dwTypeData));
					i.cch = wcslen(i.dwTypeData);
					i.fState = cont->isVisible() ? MFS_CHECKED : 0;
					InsertMenuItemW(hMenu, pos++, TRUE, &i);
					wa2.adjustFFWindowsMenu(1);
					InsertMenuItemW(hMenu2, pos2++, TRUE, &i);
					wa2.adjustOptionsPopupMenu(1);
				}
			}
		}
		i.wID++;
	}

	int n = WASABI_API_WNDMGR->autopopup_getNumGuids();
	for (int c = 0;c < n;c++)
	{
		GUID guid = WASABI_API_WNDMGR->autopopup_enumGuid(c);
		const wchar_t *groupdesc = WASABI_API_WNDMGR->autopopup_enumGuidDescription(c);
		if (guid != INVALID_GUID && groupdesc && *groupdesc)
		{
			i.dwTypeData = const_cast<wchar_t *>(groupdesc);
			if (!items.findItem(i.dwTypeData))
			{
				items.addItem(new StringW(i.dwTypeData));
				i.cch = wcslen(i.dwTypeData);
				i.fState = WASABI_API_WNDMGR->skinwnd_getNumByGuid(guid) ? MFS_CHECKED : 0;
				InsertMenuItemW(hMenu, pos++, TRUE, &i);
				wa2.adjustFFWindowsMenu(1);
				InsertMenuItemW(hMenu2, pos2++, TRUE, &i);
				wa2.adjustOptionsPopupMenu(1);
			}
		}
		i.wID++;
	}

	n = WASABI_API_WNDMGR->autopopup_getNumGroups();
	for (int c = 0;c < n;c++)
	{
		const wchar_t *id = WASABI_API_WNDMGR->autopopup_enumGroup(c);
		const wchar_t *groupdesc = WASABI_API_WNDMGR->autopopup_enumGroupDescription(c);
		if (id && groupdesc && *id && *groupdesc)
		{
			i.dwTypeData = const_cast<wchar_t *>(groupdesc);
			// allow localisation of the color editor menu item
			i.dwTypeData = const_cast<wchar_t *>(MenuActions::localizeSkinWindowName(i.dwTypeData));
			if (!items.findItem(i.dwTypeData))
			{
				items.addItem(new StringW(i.dwTypeData));
				i.cch = wcslen(i.dwTypeData);
				i.fState = WASABI_API_WNDMGR->skinwnd_getNumByGroupId(id) ? MFS_CHECKED : 0;
				InsertMenuItemW(hMenu, pos++, TRUE, &i);
				wa2.adjustFFWindowsMenu(1);
				InsertMenuItemW(hMenu2, pos2++, TRUE, &i);
				wa2.adjustOptionsPopupMenu(1);
			}
		}
		i.wID++;
	}

	items.deleteAll();
	ffwindowstop = i.wID;
}

//-----------------------------------------------------------------------------------------------
void switchSkin(const wchar_t *skinname)
{
	wa2.switchSkin(skinname);
}

//-----------------------------------------------------------------------------------------------
void addWindowOptionsToContextMenu(ifc_window *w)
{
	if (g_controlMenuTarget != NULL)
		removeWindowOptionsFromContextMenu();
	if (w == NULL && WASABI_API_WND->rootwndIsValid(lastFocused))
		w = lastFocused;
	g_controlMenuTarget = w;

	if (g_controlMenuTarget == NULL) return ;

	/*int opacitysafe = 1;
	int scalelocked = 0;

	ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
	if (rootparent && rootparent->getInterface(layoutGuid))
	{
		Layout *l = static_cast<Layout*>(rootparent);
		opacitysafe = l->isTransparencySafe();
		scalelocked = l->isScaleLocked();
	}*/

	updateControlMenu(w);
	updateAppBarMenu(w);

	HMENU menu = wa2.getPopupMenu();
	HMENU ctrlmenu = GetSubMenu(controlmenu, 0);

	// JF> Francis, made this use command IDs (inserts it right before the help ID in the main menu).
	// does this look OK? if you want, revert back to the old one. I just thought this seemed a tiny
	// bit cleaner... :)
#define ID_HELP_HELPTOPICS              40347
	InsertMenuW(menu, ID_HELP_HELPTOPICS, MF_BYCOMMAND | MF_POPUP, (UINT_PTR)ctrlmenu, WASABI_API_LNGSTRINGW(IDS_WINDOW_SETTINGS));
	InsertMenu(menu, ID_HELP_HELPTOPICS, MF_BYCOMMAND | MF_SEPARATOR, 0, NULL);

}

//-----------------------------------------------------------------------------------------------
void removeWindowOptionsFromContextMenu()
{
	if (g_controlMenuTarget == NULL) return ;
	g_controlMenuTarget = NULL;
	HMENU menu = wa2.getPopupMenu();
	HMENU ctrlmenu = GetSubMenu(controlmenu, 0);
	if (ctrlmenu)
	{
		int l = GetMenuItemCount(menu);
		while (l-- > 0 && GetSubMenu(menu, l) != ctrlmenu);
		if (l >= 0)
		{
			RemoveMenu(menu, l, MF_BYPOSITION);
			RemoveMenu(menu, l, MF_BYPOSITION); // remove sep
		}
	}
}

//-----------------------------------------------------------------------------------------------
void controlOpacity(int v)
{
	if (!g_controlMenuTarget) return ;
	if (!WASABI_API_WND->rootwndIsValid(g_controlMenuTarget)) return ;
	v = (int)(((float)v / 100.0f) * 255.0f + 0.5f);
	ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
	if (rootparent && rootparent->getInterface(layoutGuid))
	{
		Layout *l = static_cast<Layout*>(rootparent);
		if (!cfg_uioptions_linkallalpha.getValueAsInt())
		{
			l->setAlpha(v);
		}
		else
		{
			cfg_uioptions_linkedalpha.setValueAsInt(v);
		}
	}
}

//-----------------------------------------------------------------------------------------------
void controlScaling(double v)
{
	if (!g_controlMenuTarget) return ;
	if (!WASABI_API_WND->rootwndIsValid(g_controlMenuTarget)) return ;
	ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
	if (rootparent) rootparent->setRenderRatio(v);
}

//-----------------------------------------------------------------------------------------------
void lockScaling(int lock)
{
	ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
	if (!WASABI_API_WND->rootwndIsValid(g_controlMenuTarget)) return ;
	if (rootparent && rootparent->getInterface(layoutGuid))
	{
		Layout *l = static_cast<Layout*>(rootparent);
		l->lockScale(lock);
	}
}

//-----------------------------------------------------------------------------------------------
void controlAppBar(int side)
{
	ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
	if (rootparent)
	{
		AppBar *ab = (AppBar *)rootparent->getInterface(appBarGuid);
		if (ab)
		{
			ab->appbar_dock(side);
		}
	}
}

//-----------------------------------------------------------------------------------------------
void controlAppBarAOT()
{
	ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
	if (rootparent)
	{
		Layout *l = (Layout *)rootparent->getInterface(layoutGuid);
		if (l)
		{
			int curaot = l->getAppBarAlwaysOnTop();
			l->setAppBarAlwaysOnTop(!curaot);
		}
	}
}

//-----------------------------------------------------------------------------------------------
void controlAppBarAH()
{
	ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
	if (rootparent)
	{
		Layout *l = (Layout *)rootparent->getInterface(layoutGuid);
		if (l)
		{
			int curah = l->getAppBarAutoHide();
			l->setAppBarAutoHide(!curah);
		}
	}
}

//-----------------------------------------------------------------------------------------------
void updateAppBarMenu(ifc_window *w)
{
	if (g_controlMenuTarget == NULL) return ;
	ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
	if (rootparent)
	{
		Layout *l = (Layout *)rootparent->getInterface(layoutGuid);
		if (l)
		{
			HMENU ctrlmenu = GetSubMenu(controlmenu, 0);
			HMENU appbarmenu = GetSubMenu(ctrlmenu, 2);
			if (l->appbar_getEnabledSides() == 0)
			{
				EnableMenuItem(ctrlmenu, 2, MF_BYPOSITION | MF_GRAYED);
				return ;
			}
			else
				EnableMenuItem(ctrlmenu, 2, MF_BYPOSITION | MF_ENABLED);
			int docked = l->appbar_isDocked();
			int side = l->appbar_getSide();
			CheckMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_ALWAYSONTOP, MF_BYCOMMAND | (l->appbar_wantAlwaysOnTop() ? MF_CHECKED : MF_UNCHECKED));
			CheckMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_AUTOHIDE, MF_BYCOMMAND | (l->appbar_wantAutoHide() ? MF_CHECKED : MF_UNCHECKED));
			CheckMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_LEFT, MF_BYCOMMAND | ((docked && side == APPBAR_LEFT) ? MF_CHECKED : MF_UNCHECKED));
			CheckMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_TOP, MF_BYCOMMAND | ((docked && side == APPBAR_TOP) ? MF_CHECKED : MF_UNCHECKED));
			CheckMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_RIGHT, MF_BYCOMMAND | ((docked && side == APPBAR_RIGHT) ? MF_CHECKED : MF_UNCHECKED));
			CheckMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_BOTTOM, MF_BYCOMMAND | ((docked && side == APPBAR_BOTTOM) ? MF_CHECKED : MF_UNCHECKED));
			CheckMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_DISABLED, MF_BYCOMMAND | (!docked ? MF_CHECKED : MF_UNCHECKED));
			EnableMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_AUTOHIDE, MF_BYCOMMAND | ((!docked || l->appbar_isSideAutoHideSafe(side)) ? MF_ENABLED : MF_GRAYED));
			EnableMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_LEFT, MF_BYCOMMAND | (l->appbar_isSideEnabled(APPBAR_LEFT) ? MF_ENABLED : MF_GRAYED));
			EnableMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_TOP, MF_BYCOMMAND | (l->appbar_isSideEnabled(APPBAR_TOP) ? MF_ENABLED : MF_GRAYED));
			EnableMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_RIGHT, MF_BYCOMMAND | (l->appbar_isSideEnabled(APPBAR_RIGHT) ? MF_ENABLED : MF_GRAYED));
			EnableMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_BOTTOM, MF_BYCOMMAND | (l->appbar_isSideEnabled(APPBAR_BOTTOM) ? MF_ENABLED : MF_GRAYED));
			CheckMenuItem(appbarmenu, ID_CONTROLMENU_TOOLBAR_AUTODOCKONDRAG, MF_BYCOMMAND | (cfg_options_appbarondrag ? MF_CHECKED : MF_UNCHECKED));
		}
	}
}

//-----------------------------------------------------------------------------------------------
double onTweakRenderRatio(double v)
{
	if (!cfg_uioptions_uselocks.getValueAsInt())
		return v;
	return wa2.isDoubleSize() ? 2.0 : 1.0;
}

//-----------------------------------------------------------------------------------------------
void onCustomAltF4()
{
	SendMessageW(wa2.getMainWindow(), WM_CLOSE, 0, 0);
}

int isSkinStillLoading()
{
	return before_startup_callback;
}

//-----------------------------------------------------------------------------------------------
void loadExtraColorThemes()
{
	wchar_t filename[WA_MAX_PATH] = {0};
	GetModuleFileNameW(hInstance, filename, WA_MAX_PATH);
	PathParserW pp(filename);
	StringW path;
	for (int i = 0;i < pp.getNumStrings() - 1;i++)
	{
		path.AppendPath(pp.enumString(i));
	}
	StringW file = path;
	file.AppendPath(L"ColorThemes");
	file.AppendPath(WASABI_API_SKIN->getSkinName());
	file.AppendPath(L"*.xml");
	WASABI_API_SKIN->loadSkinFile(file);
}

double oldscale = 1.;

//-----------------------------------------------------------------------------------------------
static BOOL CALLBACK customScaleProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_INITDIALOG:
	{
		//CUT: double curratio=1.;
		ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
		if (!rootparent || !WASABI_API_WND->rootwndIsValid(rootparent))
		{
			g_controlMenuTarget = NULL; return TRUE;
		}

		oldscale = rootparent->getRenderRatio();

		int u = (int)((oldscale * 100.0f) + 0.5f);

		SendMessageW(GetDlgItem(hwndDlg, IDC_SLIDER_CUSTOMSCALE), TBM_SETRANGEMAX, 0, 300);
		SendMessageW(GetDlgItem(hwndDlg, IDC_SLIDER_CUSTOMSCALE), TBM_SETRANGEMIN, 0, 10);
		SendMessageW(GetDlgItem(hwndDlg, IDC_SLIDER_CUSTOMSCALE), TBM_SETPOS, 1, u);
		SetDlgItemTextW(hwndDlg, IDC_STATIC_SCALE, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_SCALE_X_PERCENT), u));
		return TRUE;
	}
	case WM_HSCROLL:
	{
		int t = (int)SendMessageW((HWND) lParam, TBM_GETPOS, 0, 0);
		if ((HWND) lParam == GetDlgItem(hwndDlg, IDC_SLIDER_CUSTOMSCALE))
		{
			SetDlgItemTextW(hwndDlg, IDC_STATIC_SCALE, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_SCALE_X_PERCENT), t));
			controlScaling((double)t / 100.0);
			if (g_controlMenuTarget)
			{
				ifc_window *w = g_controlMenuTarget->getDesktopParent();
				UpdateWindow(w->gethWnd());
			}
		}
		break;
	}
	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
			EndDialog(hwndDlg, IDOK);
			return 0;
		case IDCANCEL:
			EndDialog(hwndDlg, IDCANCEL);
			return 0;
		}
		break;
	}
	return FALSE;
}

double oldalpha = 255.;

//-----------------------------------------------------------------------------------------------
static BOOL CALLBACK customAlphaProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_INITDIALOG:
	{
		ifc_window *rootparent = g_controlMenuTarget->getDesktopParent();
		if (!rootparent || !WASABI_API_WND->rootwndIsValid(rootparent))
		{
			g_controlMenuTarget = NULL; return TRUE;
		}

		int v = 100;
		if (!cfg_uioptions_linkallalpha.getValueAsInt())
		{
			Layout *l = static_cast<Layout *>(rootparent);
			if (l != NULL)
				oldalpha = static_cast<double>(l->getAlpha());
		}
		else
		{
			oldalpha = static_cast<double>(cfg_uioptions_linkedalpha.getValueAsInt());
		}
		v = (int)((oldalpha / 255.0f * 100.0f) + 0.5f);
		SendMessageW(GetDlgItem(hwndDlg, IDC_SLIDER_CUSTOMALPHA), TBM_SETRANGEMAX, 0, 100);
		SendMessageW(GetDlgItem(hwndDlg, IDC_SLIDER_CUSTOMALPHA), TBM_SETRANGEMIN, 0, 10);
		SendMessageW(GetDlgItem(hwndDlg, IDC_SLIDER_CUSTOMALPHA), TBM_SETPOS, 1, v);
		SetDlgItemTextW(hwndDlg, IDC_STATIC_ALPHA, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_OPACITY_X_PERCENT), v));
		return TRUE;
	}
	case WM_HSCROLL:
	{
		int t = (int)SendMessageW((HWND) lParam, TBM_GETPOS, 0, 0);
		if ((HWND) lParam == GetDlgItem(hwndDlg, IDC_SLIDER_CUSTOMALPHA))
		{
			SetDlgItemTextW(hwndDlg, IDC_STATIC_ALPHA, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_OPACITY_X_PERCENT), t));
			controlOpacity(t);
		}
		break;
	}
	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
			EndDialog(hwndDlg, IDOK);
			return 0;
		case IDCANCEL:
			EndDialog(hwndDlg, IDCANCEL);
			return 0;
		}
		break;
	}
	return FALSE;
}

void customScaling()
{
	if (!g_controlMenuTarget) return ;
	if (!WASABI_API_WND->rootwndIsValid(g_controlMenuTarget)) return ;
	ifc_window *w = g_controlMenuTarget->getDesktopParent();
	if (!w || !WASABI_API_WND->rootwndIsValid(w)) return ;
	int r = WASABI_API_DIALOGBOXW(IDD_CUSTOMSCALE, w->gethWnd(), customScaleProc);
	if (r != IDOK)
		controlScaling(oldscale);
}

void customOpacity()
{
	if (!g_controlMenuTarget) return ;
	if (!WASABI_API_WND->rootwndIsValid(g_controlMenuTarget)) return ;
	ifc_window *w = g_controlMenuTarget->getDesktopParent();
	if (!w || !WASABI_API_WND->rootwndIsValid(w)) return ;
	int r = WASABI_API_DIALOGBOXW(IDD_CUSTOMALPHA, w->gethWnd(), customAlphaProc);
	if (r != IDOK)
		controlOpacity((int)((oldalpha / 255.0f * 100.0f) + 0.5f));
}

void autoOpacifyHover()
{
	if (!g_controlMenuTarget) return ;
	if (!WASABI_API_WND->rootwndIsValid(g_controlMenuTarget)) return ;
	ifc_window *w = g_controlMenuTarget->getDesktopParent();
	Layout *l = static_cast<Layout*>(w->getInterface(layoutGuid));
	if (l)
	{
		if (!cfg_uioptions_linkallalpha.getValueAsInt())
		{
			int a = l->getAutoOpacify();
			if (a == 2) a = 1;
			else a = !a;
			l->setAutoOpacify(a);
		}
		else
		{
			int a = cfg_uioptions_autoopacitylinked.getValueAsInt();
			if (a == 2) a = 1;
			else a = !a;
			cfg_uioptions_autoopacitylinked.setValueAsInt(a);
		}
	}
}

void autoOpacifyFocus()
{
	if (!g_controlMenuTarget) return ;
	if (!WASABI_API_WND->rootwndIsValid(g_controlMenuTarget)) return ;
	ifc_window *w = g_controlMenuTarget->getDesktopParent();
	Layout *l = static_cast<Layout*>(w->getInterface(layoutGuid));
	if (l)
	{
		if (!cfg_uioptions_linkallalpha.getValueAsInt())
		{
			int a = l->getAutoOpacify();
			if (a == 1) a = 2;
			else if (a == 0) a = 2;
			else if (a == 2) a = 0;
			l->setAutoOpacify(a);
		}
		else
		{
			int a = cfg_uioptions_autoopacitylinked.getValueAsInt();
			if (a == 1) a = 2;
			else if (a == 0) a = 2;
			else if (a == 2) a = 0;
			cfg_uioptions_autoopacitylinked.setValueAsInt(a);
		}
	}
}

StringW langpackfilename;

const wchar_t *localesCustomGetFile()
{
	const wchar_t *langDir = WASABI_API_LNG->GetLanguageFolder();
	if (!langDir || !*langDir)
		return NULL;

	langpackfilename = StringPathCombine(langDir, L"freeform");
	return langpackfilename;
#if 0 // old code
	wchar_t buf[256] = L"";
	GetPrivateProfileStringW(L"Winamp", L"langpack", L"", buf, 256, AutoWide(INI_FILE));  // TODO: maybe we should change all ini file stuff to W versions
	if (*buf == 0)
		return NULL;
	wchar_t *p = wcschr(buf, '.');
	if (p)
		*p = 0;

	wchar_t filename[WA_MAX_PATH] = {0};
	GetModuleFileNameW(hInstance, filename, WA_MAX_PATH);
	PathParserW pp(filename);
	langpackfilename = L"";

	for (int i = 0;i < pp.getNumStrings() - 1;i++)
	{
		langpackfilename.AppendPath(pp.enumString(i));
	}
	langpackfilename.AppendPath(L"freeform");
	langpackfilename.AppendPath(L"langpacks");
	langpackfilename.AppendPath(StringPrintfW(L"%s.xml", buf));

	return langpackfilename;
#endif
}

const wchar_t *getCustomVar(const wchar_t *var)
{
	static StringW ret;
	if (WCSCASEEQLSAFE(var, L"@HAVE_LIBRARY@"))
	{
		ret = StringPrintfW(L"%d", we_have_ml);
		return ret;
	}
	return NULL;
}

void checkMlPresent()
{
	wchar_t filename[WA_MAX_PATH] = {0};
	GetModuleFileNameW(hInstance, filename, WA_MAX_PATH);
	PathParserW pp(filename);
	StringW path;
	for (int i = 0;i < pp.getNumStrings() - 1;i++)
	{
		path.AppendPath(pp.enumString(i));
	}
	path.AppendPath(L"gen_ml.dll");
	we_have_ml = !WACCESS(path, 0);
}

void initEgg()
{
	eggstr[0] = ~'N';
	eggstr[1] = ~'U';
	eggstr[2] = ~'L';
	eggstr[3] = ~'L';
	eggstr[4] = ~'S';
	eggstr[5] = ~'O';
	eggstr[6] = ~'F';
	eggstr[7] = ~'T';
	eggstr[8] = 0;

	{
		int x;
		for (x = 0; x < 8; x ++) eggstr[x] ^= 255;
	}
}

void toggleEgg()
{
	eggstat = !eggstat;
	if (!eggstat)
	{
		KillTimer(wa2.getMainWindow(), UPDATE_EGG);
		if (lastlayoutegg && WASABI_API_WND->rootwndIsValid(lastlayoutegg)) lastlayoutegg->setTransparencyOverride(-1);
		lastlayoutegg = NULL;
	}
	else SetTimer(wa2.getMainWindow(), UPDATE_EGG, 25, NULL);
}

void getCustomMetaData(const wchar_t *field, wchar_t *buf, int len)
{
	StringW curfile = WASABI_API_MEDIACORE->core_getCurrent(0);
	if (curfile.isempty())
	{
		buf[0] = 0;
		return ;
	}
	if (!_wcsnicmp(curfile, L"file://", 7))
		curfile = StringW(curfile.getValue() + 7);
	buf[0] = 0;

	if (WCSCASEEQLSAFE(field, L"filename"))
	{
		WCSCPYN(buf, curfile, len);
	}
	else
	{
		wa2.getMetaData(curfile, field, buf, len);
	}
}

void registerGlobalHotkeys()
{
	static int registered = 0;
	if (!registered)
	{
		static wchar_t ghkStr[96];
		wa2.registerGlobalHotkey((char*)WASABI_API_LNGSTRINGW_BUF(IDS_GHK_SHOW_NOTIFICATION,ghkStr,96), WM_WA_IPC, 0, IPC_SHOW_NOTIFICATION, HKF_UNICODE_NAME, "genff shn");
		registered = 1;
	}
}

const wchar_t *getSongInfoText()
{
	return Core::getSongInfoText();
}

const wchar_t *getSongInfoTextTranslated()
{
	return Core::getSongInfoTextTranslated();
}

OSWINDOWHANDLE getKeyboardForwardWnd(GUID g)
{
	if (g != INVALID_GUID)
	{
		if (g == playerWndGuid)
			return wa2.getMainWindow();
		else if (g == pleditWndGuid)
			return wa2.getWnd(IPC_GETWND_PE);
		else if (g == videoWndGuid)
			return wa2.getWnd(IPC_GETWND_VIDEO);
		else
		{
			embedWindowState *ews = embedWndGuidMgr.getEmbedWindowState(g);
			if (ews && wa2.isValidEmbedWndState(ews)) return ews->me;
		}
	}
	return WASABI_API_WND->main_getRootWnd()->gethWnd();
}

void onAppBarDockChanged(ifc_window *w)
{
	setDialogBoxesParent();
}

void onMainLayoutMove(HWND w)
{
	// for Winamp to appear on the correct taskbar on Windows 8
	// its necessary to set the classic main window to appear on
	// that monitor and to be on-screen (but hidden) otherwise
	// Windows will ignore it and it then makes us look buggy.
	RECT r;
	Wasabi::Std::getViewport(&r, w, 1);
	SetWindowPos(wa2.getMainWindow(), NULL, r.left, r.bottom - 1, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW);
}

void updateAll()
{
	updatePl();
#ifdef MINIBROWSER_SUPPORT
	updateMb();
#endif
	updateVid();
	int n = embedWndGuidMgr.getNumWindowStates();
	for (int i = 0;i < n;i++)
	{
		embedWindowState *ws = NULL;
		GUID g = embedWndGuidMgr.enumWindowState(i, &ws);
		if (g != INVALID_GUID)
		{
			updateEmb(g, ws);
		}
	}
}

void onReParent(HWND wnd)
{
	updateAll();
}

void onReInit(HWND wnd)
{
	updateAll();
}

void startFSMonitor()
{
	g_fsmonitor = new FullScreenMonitor();
	g_fscallback = new Wa5FSCallback();
	g_fsmonitor->registerCallback(g_fscallback);
}

void stopFSMonitor()
{
	delete g_fsmonitor;
	g_fsmonitor = NULL;
	delete g_fscallback;
	g_fscallback = NULL;
}

void updateParentlessOnTop()
{
	int i;
	for (i = 0;i < SkinParser::getNumContainers();i++)
	{
		Container *c = SkinParser::enumContainer(i);
		if (c != NULL)
		{
			int j;
			for (j = 0;j < c->getNumLayouts();j++)
			{
				Layout *l = c->enumLayout(j);
				if (l != NULL)
				{
					// skip windows owned by winamp
					// skip appbars, they take care of themselves
					if (l->getNoParent() && !l->appbar_isDocked())
					{
						l->updateOnTop();
					}
				}
			}
		}
	}
}

void onGoFullscreen()
{
	// hidden windows will not receive APPBAR_CALLBACK, so forward it to winamp's main
	SendMessageW(wa2.getMainWindow(), APPBAR_CALLBACK, ABN_FULLSCREENAPP, 1);
	// update ontop flag for windows that are not parented to winamp
	updateParentlessOnTop();
}

void onCancelFullscreen()
{
	// hidden windows will not receive APPBAR_CALLBACK, so forward it to winamp's main
	SendMessageW(wa2.getMainWindow(), APPBAR_CALLBACK, ABN_FULLSCREENAPP, 0);
	// update ontop flag for windows that are not owned by winamp
	updateParentlessOnTop();
}

int processGenericHotkey(const char *hk)
{
	if (!m_are_we_loaded) return 0;

	SystemObject::onKeyDown(AutoWide(StringPrintf("HOTKEY: %s", hk)));
	if (VCPU::getComplete())
	{
		DebugStringW(L"HOTKEY: %s trapped by script\n", hk);
		return 1;
	}
	return 0;
}

int canExitWinamp()
{
	return wa2.isExitEnabled();
}

int fsMonitorIsFS()
{
	return g_fsmonitor->isFullScreen();
}

void modalPush()
{
	wa2.pushExitDisabled();
}

void modalPop()
{
	wa2.popExitDisabled();
}