#define PLUGIN_NAME "Nullsoft Embedded Window Api Example" #define PLUGIN_VERSION "1.1" #include #include #include "../winamp/gen.h" #include "../winamp/wa_ipc.h" #include "../nu/autowide.h" #include "resource.h" #include "api.h" #include "embedwnd.h" #include "../winamp/DSP.h" #define IPC_GETINIFILEW 1334 #define IPC_GETINIDIRECTORYW 1335 #define IPC_GETPLUGINDIRECTORYW 1336 /* Nullsoft Embedded Window Api Example ** (15/09/2010) ** ** Changelog ** ** v1.1 [19/09/2010] - Fixed menu state not updating on window close button ** v1.0.1 [15/09/2010] - Fixed opening of the embedded window not going active ** v1.0 [15/09/2010] - Initial release of example project ** ** ** General Information About Language Packs ** ** The purpose of this small plug-in is to show how to create an embedded window ** (skinned window frame) for better integration of a window into Winamp's look. ** Due to the apis this plug-in uses it requires Winamp 5.53 or higher to work. ** ** The example will create an embedded window along with a keyboard accelerator ** and also inserts menu items for accessing the window via the view menu in ** supporting modern skins and in the main right-click menu with other winodws. ** ** This is provided as an example and if you decide you don't like how aspects of ** it have been implemented then you're free to change things though this example ** is known to work correctly with some of the quirks of the different skin types ** so some aspects will need to remain in the same order (in particular the window ** subclassing handling). ** ** Additional, this example plug-in has been based on Language Api Plug-in example ** which is why there are certain aspects of the documentation related to making a ** localised plug-in (though any newly created plug-in should be considering such ** aspects now that full localisation of Winamp has been present since v5.5). For ** more information about the localisation aspects please look at the full version ** of the Language Api Plug-in example at http://nunzioweb.com/daz/gen_lang_example/ ** ** Finally this example plug-in attempts to separate as much of the embedded window ** code away from the general basic aspects of a general purpose plug-in so should ** allow for the embedwnd.cpp and embedwnd.h to be used in other projects easily. ** ** ** ** Additional Notes: ** ** This example plug-in project is set to build the plug-in as statically linked so ** that you won't experience any MSVCR90.DLL dependency loading issues when using it. ** ** This version of the example is just to get an embedded window created and added to ** Winamp's menus so there is nothing done with the skinning of the child window and ** other aspects related to the handling of the child window - that will follow later. */ // if not using static linking to the crt then this would help to reduce the dll size /*#ifndef _DEBUG BOOL WINAPI _DllMainCRTStartup(HINSTANCE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) { DisableThreadLibraryCalls(hInst); return TRUE; } #endif*/ // NOTE: when creating a new plugin this must be changed as it will otherwise cause conflicts! // {388976E6-9306-4fcf-9D9B-765A65F9FEB8} static const GUID GenEmbedWndExampleLangGUID = { 0x486676e6, 0x9306, 0x4fcf, { 0x9d, 0x9b, 0x76, 0x5a, 0x65, 0xf9, 0xfe, 0xb8 } }; // Wasabi based services for localisation support api_service *WASABI_API_SVC = 0; api_language *WASABI_API_LNG = 0; api_application *WASABI_API_APP = 0; // these two must be declared as they're used by the language api's // when the system is comparing/loading the different resources HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; // internal variables for the created embedded windows, subclassing, etc LPWSTR ini_file = 0; HWND embedWnd = 0, childWnd = 0; WNDPROC lpWndProcOld = 0; // this will be used as the menu item identifier for our created embeded window menus UINT_PTR EMBEDWND_ID = -1; // used for the uninstall support so we know when not to save settings // see winampUninstallPlugin(..) [implemented since 5.0] BOOL no_uninstall = 1; // this GUID is used to indentify the embedded window which can allow modern skins or // other aspects of Winamp to 'know' the window and so allow it to be inserted as a // tab or anything else which can be done to the frame (depending on client and skin) // {3FCD6A40-95D2-4b0a-8A96-247EC5C3329B} static const GUID EXAMPLEWNDGUID = { 0x3fcd6a40, 0x95d2, 0x4b0a, { 0x8a, 0x96, 0x24, 0x7e, 0xc5, 0xc3, 0x32, 0x9b } }; static embedWindowState exampleWnd; LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); INT_PTR CALLBACK ChildWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); //void config(void); //void quit(void); //int init(void); // Module header, includes version, description, and address of the module retriever function void config(struct winampDSPModule *this_mod); int init(struct winampDSPModule *this_mod); void quit(struct winampDSPModule *this_mod); int modify_samples(struct winampDSPModule *this_mod, short int *samples, int numsamples, int bps, int nch, int srate); winampDSPModule *getModule(int which); typedef struct { int version; // DSP_HDRVER char *description; // description of library winampDSPModule* (*getModule)(int); // module retrieval function int (*sf)(int); } winampDSPHeaderEx; int sf(int v) { int res; res = v * (unsigned long)1103515245; res += (unsigned long)13293; res &= (unsigned long)0x7FFFFFFF; res ^= v; return res; } winampDSPHeaderEx hdr = { DSP_HDRVER+1, "New Plugin", getModule, sf }; // getmodule routine from the main header. Returns NULL if an invalid module was requested, // otherwise returns either mod1 or mod2 depending on 'which'. // first module winampDSPModule plugin = { "BASIC", NULL, // hwndParent NULL, // hDllInstance config, init, modify_samples, quit }; winampDSPModule *getModule(int which) { switch (which) { case 0: return &plugin; default:return NULL; } } int modify_samples(struct winampDSPModule *this_mod, short int *samples, int numsamples, int bps, int nch, int srate) { /*if (FIRSTTIME) { initAnalysisMain(srate, numsamples, nch); FIRSTTIME = FALSE; } //floatsamples = (float*)malloc(numsamples*sizeof(float)); parentWND = this_mod->hwndParent; WaitForSingleObject(MAINMUTEX, 0); float recip = 1/32767.0f; if(nch == 2) for (int x = 0; x < numsamples; x ++) floatsamples[x] = .5 * (float)(samples[x]+samples[x+1]) * recip; else for (int x = 0; x < numsamples; x ++) floatsamples[x] = (float)samples[x] * recip; NumSamples = numsamples; AnalysisMain(floatsamples); ReleaseMutex(MAINMUTEX);*/ return numsamples; } int opened = 0; void config(struct winampDSPModule *this_mod) { if(!opened){ wchar_t message[256]; opened = 1; StringCchPrintf(message, 256, WASABI_API_LNGSTRINGW(IDS_ABOUT_STRING), TEXT(__DATE__)); MessageBox(0, message, AutoWide(plugin.description),0); opened = 0; } else{ SetActiveWindow(FindWindow(L"#32770", AutoWide(plugin.description))); } } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { // this will handle the message needed to be caught before the original window // proceedure of the subclass can process it. with multiple windows then this // would need to be duplicated for the number of embedded windows your handling HandleEmbeddedWindowWinampWindowMessages(embedWnd, EMBEDWND_ID, &exampleWnd, TRUE, hwnd, message, wParam, lParam); // now make sure to call the original window proceedure otherwise things break! LRESULT ret = CallWindowProc(lpWndProcOld,hwnd,message,wParam,lParam); // this will handle the message needed to be caught after the original window // proceedure of the subclass can process it. with multiple windows then this // would need to be duplicated for the number of embedded windows your handling HandleEmbeddedWindowWinampWindowMessages(embedWnd, EMBEDWND_ID, &exampleWnd, FALSE, hwnd, message, wParam, lParam); return ret; } template 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 = (api_T *)factory->getInterface(); } } int init(struct winampDSPModule *this_mod) { // this example plugin is only designed to work on a 5.5+ client install but does // have some features which are intended for running on earlier client installs // so make sure that we're only properly loaded on a valid client install if(SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_GETVERSION) < 0x5053) { MessageBoxA(plugin.hwndParent,"This plug-in requires Winamp v5.53 and up for it to work.\t\n" "Please upgrade your Winamp client to be able to use this.", plugin.description,MB_OK|MB_ICONINFORMATION); return 1; } else { // this is our first stage and will attempt to load the base of the wasabi services // so we can the get and load other services like the localisation service api WASABI_API_SVC = (api_service*)SendMessage(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 != NULL) { // now that we've got an instance of the base service api, we load the language // api service using it's guid to get it as shown ServiceBuild(WASABI_API_LNG,languageApiGUID); // now we attempt to load the localisation api to load our lng file by passing the // current hinstance of the plugin and it's associated guid // // note: hence the need for GenEmbedWndExampleLangGUID to be unique to our plugin as // that is used to indentify and determine the lng file to be loaded by the loader mechanism WASABI_API_START_LANG(plugin.hDllInstance,GenEmbedWndExampleLangGUID); // however if you wanted to access an existing language file then you could pass in // the known GUID of the file you want after looking in Agave\Language\lang.h // (an example of this will follow in a later example of this example project) // this is our first usage of the localisation api now that it has been initiaiised // by generating a localised string for the plugin's description in the preferences // // we use a static buffer so the localised string will persist after we've called it static char szDescription[256]; StringCchPrintfA(szDescription,256,WASABI_API_LNGSTRING(IDS_NULLSOFT_LANG_EXAMPLE),PLUGIN_VERSION); plugin.description = szDescription; // this unicode version is implemented in 5.58 otherwise you could use nu/AutoWide(..) // on the IPC_GETINIFILE api so it will work with the unicode functions in this example ini_file = (LPWSTR)SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GETINIFILEW); // doing this will mean that Winamp will be correctly subclassed depending on it // being a unicode (5.34+) or a legacy client install so that the taskbar text will // show correctly as SetWindowLongPtrA(..) on a 5.34+ client install will cause ??? // to appear in the taskbar for unicode titled files despite Winamp's unicode support // // however as we only run under 5.5+ in this example then we would only need to call // the SetWindowLongPtrW(..) function for the Winamp subclassing to be valid if (IsWindowUnicode(plugin.hwndParent)) { lpWndProcOld = (WNDPROC)SetWindowLongPtrW(plugin.hwndParent,GWLP_WNDPROC,(LONG)WndProc); } else { lpWndProcOld = (WNDPROC)SetWindowLongPtrA(plugin.hwndParent,GWLP_WNDPROC,(LONG)WndProc); } /* ** this is the start of creating an embedded window which will also add a main menu ** shortcut to allow for menu and keyboard access to the window so everyone is happy */ // we need to get the WASABI_API_APP service so we can regsiter a global shortcut for // accessing the embedded window frame throughout Winamp e.g. Alt + 1 in this example // and also for registering the created window to be included in the Ctrl+Tab handler ServiceBuild(WASABI_API_APP,applicationApiServiceGuid); // now we will attempt to create an embedded window which adds its own main menu entry // and related keyboard accelerator (like how the media library window is integrated) embedWnd = CreateEmbeddedWindow(&exampleWnd, EXAMPLEWNDGUID); // once the window is created we can then specify the window title and menu integration SetWindowText(embedWnd,WASABI_API_LNGSTRINGW(IDS_EMBED_WND_TITLE)); // now we create a child dialog just to show how to parent one into the embedded window // Note: this child is not skinned like Winamp and will just appear like an empty dialog // though there is an intention in a later version of the example to show how to // do the related skinning of the child window contents (can't have it all at once) childWnd = WASABI_API_CREATEDIALOGW(IDD_CHILD, embedWnd, ChildWndProc); if (childWnd && WASABI_API_APP) WASABI_API_APP->app_addModelessDialog(childWnd); // for the purposes of this example we will manually create an accelerator table so // we can use IPC_REGISTER_LOWORD_COMMAND to get a unique id for the menu items we // will be adding into Winamp's menus. using this api will allocate an id which can // vary between Winamp revisions as it moves depending on the resources in Winamp. EMBEDWND_ID = SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_REGISTER_LOWORD_COMMAND); ACCEL accel = {FVIRTKEY|FALT,'1',EMBEDWND_ID}; // Note: WASABI_API_APP->app_addAccelerators(..) requires Winamp 5.53 and higher // otherwise if you want to support older clients then you could use the // IPC_TRANSLATEACCELERATOR callback api which works for v5.0 upto v5.52 HACCEL hAccel = CreateAcceleratorTable(&accel,1); if(hAccel) WASABI_API_APP->app_addAccelerators(childWnd,&hAccel,1,TRANSLATE_MODE_GLOBAL); // then we show the embedded window which will cause the child window to be // sized into the frame without having to do any thing ourselves. also this will // only show the window if Winamp was not minimised on close and the window was // open at the time otherwise it will remain hidden ShowEmbeddedWindow(embedWnd); // finally we add menu items to the main right-click menu and the views menu // with Modern skins which support showing the views menu for accessing windows // // if you store the visible state separately then pass it as the last parameter // otherwise with how this example is setup it will use the visible flag which // was read in with the call to ShowEmbeddedWindow(..) before this is function AddEmbeddedWindowToMenus(TRUE, EMBEDWND_ID, WASABI_API_LNGSTRINGW(IDS_EXAMPLE_MENU_STRING), -1); return GEN_INIT_SUCCESS; } } return 1; } void quit(struct winampDSPModule *this_mod) { if(no_uninstall) { // this will attempt to save any required settings here if not doing an uninstall DestroyEmbeddedWindow(&exampleWnd); } } INT_PTR CALLBACK ChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { // if you need to do other message handling then you can just place this first and // process the messages you need to afterwards. note this is passing the frame and // its id so if you have a few embedded windows you need to do this with each child return HandleEmbeddedWindowChildMessages(embedWnd, EMBEDWND_ID, hwnd, message, wParam, lParam); } #ifdef __cplusplus extern "C" { #endif // this is the only exported symbol. returns our main header. __declspec( dllexport ) winampDSPHeaderEx *winampDSPGetHeader2() { return &hdr; } __declspec(dllexport) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param){ // we prompt the user here if they want us to remove our settings with default as no //(just incase) when the plugin is being uninstalled from within Winamp's preferences if(MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS), plugin.description,MB_YESNO|MB_DEFBUTTON2) == IDYES) { no_uninstall = 0; // if you have settings saved in winamp.ini or a plugins.ini then you could use // something like WritePrivateProfileString(PLUGIN_NAME, 0, 0, ini_file) to // remove the relevant section from the ini file cleanly } // as we're doing too much in subclasses, etc we cannot allow for on-the-fly removal so need to do a normal reboot return GEN_PLUGIN_UNINSTALL_REBOOT; } #ifdef __cplusplus } #endif