aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library/ml_pmp/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Library/ml_pmp/main.cpp')
-rw-r--r--Src/Plugins/Library/ml_pmp/main.cpp1454
1 files changed, 1454 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_pmp/main.cpp b/Src/Plugins/Library/ml_pmp/main.cpp
new file mode 100644
index 00000000..09bd908d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/main.cpp
@@ -0,0 +1,1454 @@
+#define PLUGIN_NAME "Nullsoft Portable Music Player Support"
+#define PLUGIN_VERSION L"2.25"
+
+#include "main.h"
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../winamp/wa_ipc.h"
+#include "nu/ns_wc.h"
+#include "..\..\General\gen_hotkeys/wa_hotkeys.h"
+#include "resource1.h"
+#include "pmp.h"
+#include "DeviceView.h"
+#include "pluginloader.h"
+#include "nu/AutoWide.h"
+#include "api__ml_pmp.h"
+#include "transcoder_imp.h"
+#include <api/service/waservicefactory.h>
+#include "config.h"
+#include "tataki/export.h"
+#include "nu/ServiceWatcher.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "mt19937ar.h"
+#include "./local_menu.h"
+#include "pmpdevice.h"
+#include "IconStore.h"
+#include "../replicant/nx/nxstring.h"
+#include <strsafe.h>
+#include "../nu/MediaLibraryInterface.h"
+#include <vector>
+
+#define MAINTREEID 5002
+#define PROGRESSTIMERID 1
+
+static int init();
+static void quit();
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+LRESULT CALLBACK DeviceMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+extern INT_PTR CALLBACK pmp_devices_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+extern void UpdateDevicesListView(bool softupdate);
+INT_PTR CALLBACK global_config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK config_dlgproc_plugins(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+void UpdateTransfersListView(bool softUpdate, CopyInst * item=NULL);
+
+static void CALLBACK ProgressTimerTickCb(HWND hwnd, UINT uMsg, UINT_PTR eventId, unsigned long elapsed);
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_pmp.dll)",
+ init,
+ quit,
+ PluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+C_ItemList devices;
+C_ItemList loadingDevices;
+extern HNAVITEM cloudQueueTreeItem;
+static HNAVITEM navigationRoot = NULL;
+static ATOM viewAtom = 0;
+int groupBtn = 1, customAllowed = 0, enqueuedef = 0;
+extern HWND hwndMediaView;
+extern DeviceView * currentViewedDevice;
+HMENU m_context_menus = NULL, m_context_menus2 = NULL;
+int prefsPageLoaded = 0, profile = 0;
+prefsDlgRecW prefsPage;
+prefsDlgRecW pluginsPrefsPage;
+C_Config * global_config;
+C_Config * gen_mlconfig;
+UINT genhotkeys_add_ipc;
+HANDLE hMainThread;
+UINT_PTR mainTreeHandle;
+extern HINSTANCE cloud_hinst;
+extern int IPC_GET_CLOUD_HINST, IPC_LIBRARY_PLAYLISTS_REFRESH;
+void deviceConnected(Device * dev);
+void deviceDisconnected(Device * dev);
+void deviceLoading(pmpDeviceLoading * load);
+void deviceNameChanged(Device * dev);
+
+//extern CRITICAL_SECTION listTransfersLock;
+CRITICAL_SECTION csenumdrives;
+
+api_playlists *AGAVE_API_PLAYLISTS = 0;
+api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
+api_mldb *AGAVE_API_MLDB = 0;
+api_memmgr *WASABI_API_MEMMGR = 0;
+api_syscb *WASABI_API_SYSCB = 0;
+api_application *WASABI_API_APP = 0;
+api_podcasts *AGAVE_API_PODCASTS = 0;
+api_albumart *AGAVE_API_ALBUMART = 0;
+api_stats *AGAVE_API_STATS = 0;
+api_threadpool *WASABI_API_THREADPOOL = 0;
+api_devicemanager *AGAVE_API_DEVICEMANAGER = 0;
+api_metadata *AGAVE_API_METADATA = 0;
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0;
+HINSTANCE WASABI_API_ORIG_HINST = 0;
+
+static const GUID pngLoaderGUID =
+ { 0x5e04fb28, 0x53f5, 0x4032, { 0xbd, 0x29, 0x3, 0x2b, 0x87, 0xec, 0x37, 0x25 } };
+
+static svc_imageLoader *wasabiPngLoader = NULL;
+
+HWND mainMessageWindow = 0;
+static bool classRegistered=0;
+HWND CreateDummyWindow()
+{
+ if (!classRegistered)
+ {
+ WNDCLASSW wc = {0, };
+
+ wc.style = 0;
+ wc.lpfnWndProc = DeviceMsgProc;
+ wc.hInstance = plugin.hDllInstance;
+ wc.hIcon = 0;
+ wc.hCursor = NULL;
+ wc.lpszClassName = L"ml_pmp_window";
+
+ if (!RegisterClassW(&wc))
+ return 0;
+
+ classRegistered = true;
+ }
+ HWND dummy = CreateWindowW(L"ml_pmp_window", L"ml_pmp_window", 0, 0, 0, 0, 0, NULL, NULL, plugin.hDllInstance, NULL);
+
+ return dummy;
+}
+
+genHotkeysAddStruct hksync = { 0, HKF_UNICODE_NAME, WM_USER, 0, 0, "ml_pmp_sync", 0 };
+genHotkeysAddStruct hkautofill = { 0, HKF_UNICODE_NAME, WM_USER + 1, 0, 0, "ml_pmp_autofill", 0 };
+genHotkeysAddStruct hkeject = { 0, HKF_UNICODE_NAME, WM_USER + 2, 0, 0, "ml_pmp_eject", 0 };
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+HNAVITEM NavigationItem_Find(HNAVITEM root, const wchar_t *name, BOOL allow_root = 0)
+{
+ NAVCTRLFINDPARAMS find;
+ HNAVITEM item;
+
+ if (NULL == name)
+ return NULL;
+
+ if (NULL == plugin.hwndLibraryParent)
+ return NULL;
+
+ find.pszName = (wchar_t*)name;
+ find.cchLength = -1;
+ find.compFlags = NICF_INVARIANT;
+ find.fFullNameSearch = FALSE;
+
+ item = MLNavCtrl_FindItemByName(plugin.hwndLibraryParent, &find);
+ if (NULL == item)
+ return NULL;
+
+ if (!allow_root)
+ {
+ // if allowed then we can look for root level items which
+ // is really for getting 'cloud' devices to another group
+ if (NULL != root &&
+ root != MLNavItem_GetParent(plugin.hwndLibraryParent, item))
+ {
+ item = NULL;
+ }
+ }
+
+ return item;
+}
+
+HNAVITEM GetNavigationRoot(BOOL forceCreate)
+{
+ if (NULL == navigationRoot &&
+ FALSE != forceCreate)
+ {
+ NAVINSERTSTRUCT nis = {0};
+ wchar_t buffer[512] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PORTABLES, buffer, ARRAYSIZE(buffer));
+
+ nis.hParent = NULL;
+ nis.hInsertAfter = NCI_LAST;
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_ITEMID | NIMF_PARAM;
+ nis.item.id = MAINTREEID;
+ nis.item.pszInvariant = L"Portables";
+ nis.item.style = NIS_HASCHILDREN;
+ nis.item.pszText = buffer;
+ nis.item.lParam = -1;
+
+ navigationRoot = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ if (NULL != navigationRoot)
+ {
+ SetTimer(mainMessageWindow, PROGRESSTIMERID, 250, ProgressTimerTickCb);
+ }
+ }
+
+ return navigationRoot;
+}
+
+
+static HNAVITEM GetNavigationItemFromMessage(int msg, INT_PTR param)
+{
+ return (msg < ML_MSG_NAVIGATION_FIRST) ?
+ MLNavCtrl_FindItemById(plugin.hwndLibraryParent, param) :
+ (HNAVITEM)param;
+}
+
+void Menu_ConvertRatingMenuStar(HMENU menu, UINT menu_id)
+{
+MENUITEMINFOW mi = {sizeof(mi), MIIM_DATA | MIIM_TYPE, MFT_STRING};
+wchar_t rateBuf[32], *rateStr = rateBuf;
+ mi.dwTypeData = rateBuf;
+ mi.cch = 32;
+ if(GetMenuItemInfoW(menu, menu_id, FALSE, &mi))
+ {
+ while(rateStr && *rateStr)
+ {
+ if(*rateStr == L'*') *rateStr = L'\u2605';
+ rateStr=CharNextW(rateStr);
+ }
+ SetMenuItemInfoW(menu, menu_id, FALSE, &mi);
+ }
+}
+
+static ServiceWatcher serviceWatcher;
+
+static int init()
+{
+ // if there are no pmp_*.dll then no reason to load
+ if (!testForDevPlugins())
+ return ML_INIT_FAILURE;
+
+ genrand_int31 = (int (*)())SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_RANDFUNC);
+
+ if (0 == viewAtom)
+ {
+ viewAtom = GlobalAddAtomW(L"WinampPortableMediaView");
+ if (0 == viewAtom)
+ return 2;
+ }
+
+ TranscoderImp::init();
+ InitializeCriticalSection(&csenumdrives);
+
+ Tataki::Init(plugin.service);
+ ServiceBuild( AGAVE_API_PLAYLISTS, api_playlistsGUID );
+ ServiceBuild( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
+ ServiceBuild( WASABI_API_SYSCB, syscbApiServiceGuid );
+ ServiceBuild( WASABI_API_APP, applicationApiServiceGuid );
+ ServiceBuild( AGAVE_API_STATS, AnonymousStatsGUID );
+ ServiceBuild( WASABI_API_THREADPOOL, ThreadPoolGUID );
+ ServiceBuild( AGAVE_API_DEVICEMANAGER, DeviceManagerGUID );
+ ServiceBuild( AGAVE_API_ALBUMART, albumArtGUID );
+ ServiceBuild( AGAVE_API_METADATA, api_metadataGUID );
+
+ // loader so that we can get the localisation service api for use
+ ServiceBuild( WASABI_API_LNG, languageApiGUID );
+ ServiceBuild( WASABI_API_MEMMGR, memMgrApiServiceGuid );
+
+ // no guarantee that AGAVE_API_MLDB will be available yet, so we'll start a watcher for it
+ serviceWatcher.WatchWith( plugin.service );
+ serviceWatcher.WatchFor( &AGAVE_API_MLDB, mldbApiGuid );
+ serviceWatcher.WatchFor( &AGAVE_API_PODCASTS, api_podcastsGUID );
+
+ WASABI_API_SYSCB->syscb_registerCallback( &serviceWatcher );
+
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ mediaLibrary.GetIniDirectory();
+ mediaLibrary.GetIniDirectoryW();
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlPMPLangGUID);
+
+ static wchar_t szDescription[256];
+ StringCbPrintfW(szDescription, ARRAYSIZE(szDescription), WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PMP_SUPPORT), PLUGIN_VERSION);
+ plugin.description = (char*)szDescription;
+
+ hMainThread = GetCurrentThread();
+ //InitializeCriticalSection(&listTransfersLock);
+ global_config = new C_Config( (wchar_t *)mediaLibrary.GetWinampIniW() );
+ profile = global_config->ReadInt( L"profile", 0, L"Winamp" );
+
+ gen_mlconfig = new C_Config( (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW ), L"gen_ml_config" );
+
+ m_context_menus = WASABI_API_LOADMENU( IDR_CONTEXTMENUS );
+ m_context_menus2 = WASABI_API_LOADMENU( IDR_CONTEXTMENUS );
+
+
+ HMENU rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,0),7);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,1),4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,2),7);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,7),5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ //subclass winamp window
+ mainMessageWindow = CreateDummyWindow();
+
+ prefsPage.hInst = WASABI_API_LNG_HINST;
+ prefsPage.dlgID = IDD_CONFIG_GLOBAL;
+ prefsPage.name = _wcsdup( WASABI_API_LNGSTRINGW( IDS_PORTABLES ) );
+ prefsPage.where = 6;
+ prefsPage.proc = global_config_dlgproc;
+
+ pluginsPrefsPage.hInst = WASABI_API_LNG_HINST;
+ pluginsPrefsPage.dlgID = IDD_CONFIG_PLUGINS;
+ pluginsPrefsPage.name = prefsPage.name;
+ pluginsPrefsPage.where = 1;
+ pluginsPrefsPage.proc = config_dlgproc_plugins;
+
+ // only insert the portables page if we've actually loaded a pmp_*.dll
+ int count = 0;
+ if(loadDevPlugins(&count) && count > 0)
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&pluginsPrefsPage,IPC_ADD_PREFS_DLGW);
+ }
+ else if (!count)
+ {
+ // and if there are none, then cleanup and also notify ml_devices.dll to
+ // shut-down as no need for it to keep running if there's not going to be
+ // anything else running (as unlikely we'd have use for it without ml_pmp
+ quit();
+
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(GetModuleHandleW(L"ml_devices.dll"), "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ mlplugin->quit();
+ }
+ }
+
+ return ML_INIT_FAILURE;
+ }
+
+ // we've got pmp_*.dll to load, so lets start the fun...
+ Devices_Init();
+ if (AGAVE_API_DEVICEMANAGER)
+ {
+ SetTimer(mainMessageWindow, PROGRESSTIMERID, 250, ProgressTimerTickCb);
+ }
+ else if (!global_config->ReadInt(L"HideRoot",0))
+ {
+ GetNavigationRoot(TRUE);
+ }
+
+ SetTimer(mainMessageWindow,COMMITTIMERID,5000,NULL);
+
+ IPC_LIBRARY_PLAYLISTS_REFRESH = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"ml_playlist_refresh", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ IPC_GET_CLOUD_HINST = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloud", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+
+ // set up hotkeys...
+ genhotkeys_add_ipc = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&"GenHotkeysAdd",IPC_REGISTER_WINAMP_IPCMESSAGE);
+ hksync.wnd = hkautofill.wnd = hkeject.wnd = mainMessageWindow;
+ hksync.name = (char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_GHK_SYNC_PORTABLE_DEVICE));
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&hksync,genhotkeys_add_ipc);
+ hkautofill.name = (char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_GHK_AUTOFILL_PORTABLE_DEVICE));
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&hkautofill,genhotkeys_add_ipc);
+ hkeject.name = (char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_GHK_EJECT_PORTABLE_DEVICE));
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&hkeject,genhotkeys_add_ipc);
+ return ML_INIT_SUCCESS;
+}
+
+bool quitting=false;
+static void quit()
+{
+ // trigger transfer kill
+ int i = devices.GetSize();
+ if (i > 0)
+ {
+ while(i-- > 0)
+ {
+ DeviceView * device = ((DeviceView*)devices.Get(i));
+ if (device) device->threadKillswitch = 1;
+ }
+ }
+
+ quitting = true;
+ KillTimer(mainMessageWindow, COMMITTIMERID);
+ DeviceMsgProc(NULL, WM_TIMER, COMMITTIMERID, 0);
+
+ i = devices.GetSize();
+ if (i > 0)
+ {
+ while(i-- > 0)
+ {
+ DeviceView * device = ((DeviceView*)devices.Get(i));
+ if (device) device->dev->Close();
+ }
+ }
+ unloadDevPlugins();
+
+ stopServer();
+ HWND f = mainMessageWindow;
+ mainMessageWindow = 0;
+ DestroyWindow(f);
+ delete global_config;
+
+ WASABI_API_SYSCB->syscb_deregisterCallback(&serviceWatcher);
+ serviceWatcher.Clear();
+
+ ServiceRelease( AGAVE_API_PLAYLISTS, api_playlistsGUID );
+ ServiceRelease( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
+ ServiceRelease( WASABI_API_SYSCB, syscbApiServiceGuid );
+ ServiceRelease( WASABI_API_APP, applicationApiServiceGuid );
+ ServiceRelease( WASABI_API_LNG, languageApiGUID );
+ ServiceRelease( WASABI_API_MEMMGR, memMgrApiServiceGuid );
+ ServiceRelease( AGAVE_API_MLDB, mldbApiGuid );
+ ServiceRelease( AGAVE_API_PODCASTS, api_podcastsGUID );
+ ServiceRelease( AGAVE_API_STATS, AnonymousStatsGUID );
+ ServiceRelease( AGAVE_API_DEVICEMANAGER, DeviceManagerGUID );
+ ServiceRelease( AGAVE_API_ALBUMART, albumArtGUID );
+ ServiceRelease( AGAVE_API_METADATA, api_metadataGUID );
+
+ if (NULL != wasabiPngLoader)
+ {
+ ServiceRelease(wasabiPngLoader, pngLoaderGUID);
+ wasabiPngLoader = NULL;
+ }
+
+ DeleteCriticalSection(&csenumdrives);
+ TranscoderImp::quit();
+
+ delete gen_mlconfig;
+ Tataki::Quit();
+ ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID);
+
+ if (0 != viewAtom)
+ {
+ GlobalDeleteAtom(viewAtom);
+ viewAtom = 0;
+ }
+}
+
+typedef struct { ULONG_PTR param; void * proc; int state; CRITICAL_SECTION lock;} spc ;
+
+static VOID CALLBACK spc_caller(ULONG_PTR dwParam) {
+ spc * s = (spc*)dwParam;
+ if(!s) return;
+ EnterCriticalSection(&s->lock);
+ if(s->state == -1) { LeaveCriticalSection(&s->lock); DeleteCriticalSection(&s->lock); free(s); return; }
+ s->state = 2;
+ void (CALLBACK *p)(ULONG_PTR dwParam);
+ p = (void (CALLBACK *)(ULONG_PTR dwParam))s->proc;
+ if(p) p(s->param);
+ s->state=1;
+ LeaveCriticalSection(&s->lock);
+}
+
+static int spc_GetState(spc *s)
+{
+ EnterCriticalSection(&s->lock);
+ int state = s->state;
+ LeaveCriticalSection(&s->lock);
+ return state;
+}
+
+// p must be of type void (CALLBACK *)(ULONG_PTR dwParam). Returns 0 for success.
+int SynchronousProcedureCall(void * p, ULONG_PTR dwParam) {
+ if(!p) return 1;
+ spc * s = (spc*)calloc(1, sizeof(spc));
+ InitializeCriticalSection(&s->lock);
+ s->param = dwParam;
+ s->proc = p;
+ s->state = 0;
+#if 1 // _WIN32_WINNT >= 0x0400
+ if(!QueueUserAPC(spc_caller,hMainThread,(ULONG_PTR)s)) { DeleteCriticalSection(&s->lock); free(s); return 1; } //failed
+#else
+ if(!mainMessageWindow) { DeleteCriticalSection(&s->lock); free(s); return 1; } else PostMessage(mainMessageWindow,WM_USER+3,(WPARAM)spc_caller,(LPARAM)s);
+#endif
+ int i=0;
+ while (spc_GetState(s) != 1)
+ {
+ if (SleepEx(10,TRUE) == 0)
+ i++;
+ if(i >= 100)
+ {
+ EnterCriticalSection(&s->lock);
+ s->state = -1;
+ LeaveCriticalSection(&s->lock);
+ return 1;
+ }
+ }
+ DeleteCriticalSection(&s->lock);
+ free(s);
+ return 0;
+}
+
+static VOID CALLBACK getTranscoder(ULONG_PTR dwParam)
+{
+ C_Config * conf = global_config;
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if(d->dev == *(Device **)dwParam) { conf = d->config; break; }
+ }
+ Transcoder ** t = (Transcoder**)dwParam;
+ *t = new TranscoderImp(plugin.hwndWinampParent,plugin.hDllInstance,conf, *(Device **)dwParam);
+}
+
+static void CALLBACK ProgressTimerTickCb(HWND hwnd, UINT uMsg, UINT_PTR eventId, unsigned long elapsed)
+{
+ wchar_t *buf = (wchar_t*)calloc(sizeof(wchar_t), 256);
+ NAVITEM itemInfo = {0};
+ HNAVITEM rootItem = GetNavigationRoot(FALSE);
+ if (!AGAVE_API_DEVICEMANAGER && NULL == rootItem)
+ {
+ KillTimer(hwnd, eventId);
+ if (buf) free(buf);
+ return;
+ }
+
+ // TODO need to get this working for a merged instance...
+ int num = 0,total = 0, percent = 0;
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView*)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(dev);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(dev);
+ int txProgress = getTransferProgress(dev);
+ int size = (txQueue ? txQueue->GetSize() : 0);
+ int device_num = (100 * size) - txProgress;
+ num += device_num;
+ int device_total = 100 * size + 100 * (finishedTX ? finishedTX->GetSize() : 0);
+ total += device_total;
+ percent = (0 != device_total) ? (((device_total - device_num) * 100) / device_total) : -1;
+ // TODO need to do something to handle it sticking on 100%
+ if (dev->queueTreeItem)
+ {
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.hItem = dev->queueTreeItem;
+ itemInfo.mask = NIMF_PARAM;
+
+ if (MLNavItem_GetInfo(plugin.hwndLibraryParent, &itemInfo) && itemInfo.lParam != percent)
+ {
+ if (-1 == percent)
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERS,buf, 256);
+ else
+ StringCbPrintfW(buf, 256, WASABI_API_LNGSTRINGW(IDS_TRANSFERS_PERCENT), percent);
+
+ itemInfo.mask |= NIMF_TEXT;
+ itemInfo.pszText = buf;
+ itemInfo.lParam = percent;
+ itemInfo.cchTextMax = -1;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+ }
+ }
+
+ dev->UpdateActivityState();
+ }
+
+ if (!rootItem)
+ {
+ if (buf) free(buf);
+ return;
+ }
+
+ percent = (0 != total) ? (((total-num)*100)/total) : -1;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.hItem = rootItem;
+ itemInfo.mask = NIMF_PARAM;
+
+ if (FALSE == MLNavItem_GetInfo(plugin.hwndLibraryParent, &itemInfo))
+ {
+ if (buf) free(buf);
+ return;
+ }
+
+ if (itemInfo.lParam != percent)
+ {
+ if (-1 == percent)
+ WASABI_API_LNGSTRINGW_BUF(IDS_PORTABLES,buf,256);
+ else
+ StringCbPrintfW(buf, sizeof(buf), WASABI_API_LNGSTRINGW(IDS_PORTABLES_PERCENT),percent);
+
+ itemInfo.mask |= NIMF_TEXT;
+ itemInfo.pszText = buf;
+ itemInfo.lParam = percent;
+ itemInfo.cchTextMax = -1;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+ }
+ if (buf) free(buf);
+}
+
+extern LRESULT CALLBACK TranscodeMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void EnumDrives(ENUM_DRIVES_CALLBACK callback)
+{
+ DWORD drives=GetLogicalDrives();
+ wchar_t drivestr[4] = L"X:\\";
+ for(int i=2;i<25;i++) if(drives&(1<<i))
+ {
+ EnterCriticalSection(&csenumdrives);
+ UINT olderrmode=SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+ drivestr[0] = L'A'+i;
+ callback(drivestr[0],GetDriveTypeW(drivestr));
+ SetErrorMode(olderrmode);
+ LeaveCriticalSection(&csenumdrives);
+ }
+}
+
+LRESULT CALLBACK DeviceMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if(uMsg == WM_WA_IPC && hwnd != mainMessageWindow) return TranscodeMsgProc(hwnd,uMsg,wParam,lParam);
+ switch(uMsg)
+ {
+ case WM_USER: // hotkey: sync
+ if(devices.GetSize() > 0)
+ {
+ if (!((DeviceView *)devices.Get(0))->isCloudDevice)
+ ((DeviceView *)devices.Get(0))->Sync();
+ else
+ ((DeviceView *)devices.Get(0))->CloudSync();
+ }
+ break;
+ case WM_USER+1: // hotkey: autofill
+ if(devices.GetSize() > 0)
+ ((DeviceView *)devices.Get(0))->Autofill();
+ break;
+ case WM_USER+2: // hotkey: eject
+ if(devices.GetSize() > 0)
+ ((DeviceView *)devices.Get(0))->Eject();
+ break;
+ case WM_USER+3:
+ {
+ void (CALLBACK *p)(ULONG_PTR dwParam);
+ p = (void (CALLBACK *)(ULONG_PTR dwParam))wParam;
+ p((ULONG_PTR)lParam);
+ }
+ break;
+ case WM_USER+4: // refreshes cloud views (re-checks if we've moved away)
+ if (IsWindow(hwndMediaView) && currentViewedDevice && currentViewedDevice->isCloudDevice)
+ {
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ break;
+ case WM_USER+5: // removes cloud devices from a cloud sources logout...
+ {
+ for (int i = devices.GetSize() - 1; i > -1; --i)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if (dev->isCloudDevice)
+ dev->dev->Close();
+ }
+ break;
+ }
+ case WM_PMP_IPC:
+ {
+ switch(lParam)
+ {
+ case PMP_IPC_DEVICECONNECTED:
+ deviceConnected((Device*)wParam);
+ break;
+ case PMP_IPC_DEVICEDISCONNECTED:
+ deviceDisconnected((Device*)wParam);
+ break;
+ case PMP_IPC_DEVICELOADING:
+ deviceLoading((pmpDeviceLoading *)wParam);
+ break;
+ case PMP_IPC_DEVICENAMECHANGED:
+ deviceNameChanged((Device*)wParam);
+ break;
+ case PMP_IPC_DEVICECLOUDTRANSFER:
+ {
+ int ret = 0;
+ cloudDeviceTransfer * transfer = (cloudDeviceTransfer *)wParam;
+ if (transfer)
+ {
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if (dev->dev->extraActions(DEVICE_IS_CLOUD_TX_DEVICE, (intptr_t)transfer->device_token, 0, 0))
+ {
+ ret = dev->TransferFromML(ML_TYPE_FILENAMESW, (void *)transfer->filenames, 0, 1);
+ }
+ }
+ }
+ return ret;
+ }
+ case PMP_IPC_GETCLOUDTRANSFERS:
+ {
+ typedef std::vector<wchar_t*> CloudFiles;
+ CloudFiles *pending = (CloudFiles *)wParam;
+ if (pending)
+ {
+ cloudTransferQueue.lock();
+ // TODO de-dupe incase going to multiple devices...
+ for(int i = 0; i < cloudTransferQueue.GetSize(); i++)
+ {
+ pending->push_back(((CopyInst *)cloudTransferQueue.Get(i))->sourceFile);
+ }
+ cloudTransferQueue.unlock();
+
+ return pending->size();
+ }
+ return 0;
+ }
+ case PMP_IPC_GET_TRANSCODER:
+ {
+ void * t = (void*)wParam;
+ getTranscoder((ULONG_PTR)&t);
+ if (t == (void*)wParam) return 0;
+ return (LRESULT)t;
+ }
+ case PMP_IPC_RELEASE_TRANSCODER:
+ delete ((TranscoderImp *)wParam);
+ break;
+ case PMP_IPC_ENUM_ACTIVE_DRIVES:
+ {
+ if (wParam == 0)
+ return (LRESULT)EnumDrives;
+ else
+ EnumDrives((ENUM_DRIVES_CALLBACK)wParam);
+ }
+ break;
+ case PMP_IPC_GET_INI_FILE:
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if(dev->dev == (Device*)wParam) return (intptr_t)dev->config->GetIniFile();
+ }
+ break;
+ case PMP_IPC_GET_PREFS_VIEW:
+ {
+ pmpDevicePrefsView *view = (pmpDevicePrefsView *)wParam;
+ if (view)
+ {
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if (!lstrcmpA(dev->GetName(), view->dev_name))
+ {
+ return (LRESULT)OnSelChanged(view->parent, view->parent, dev);
+ }
+ }
+ }
+ return 0;
+ }
+ }
+ }
+ break;
+ case WM_DEVICECHANGE:
+ {
+ int r = wmDeviceChange(wParam,lParam);
+ if (r) return r;
+ }
+ break;
+ case WM_TIMER:
+ switch(wParam)
+ {
+ case COMMITTIMERID:
+ {
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(d);
+ if (txQueue)
+ {
+ if(d->commitNeeded && !txQueue->GetSize())
+ {
+ d->commitNeeded = false;
+ d->dev->commitChanges();
+ }
+ }
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+void UpdateLoadingCaption(wchar_t * caption, void * context)
+{
+ NAVITEM itemInfo = {0};
+
+ itemInfo.hItem = (HNAVITEM)context;
+ if (NULL == itemInfo.hItem)
+ return;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.pszText = caption;
+ itemInfo.cchTextMax = -1;
+ itemInfo.mask = NIMF_TEXT;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+}
+
+void deviceLoading(pmpDeviceLoading * load)
+{
+ if (!AGAVE_API_DEVICEMANAGER)
+ {
+ wchar_t buffer[256] = {0};
+ NAVINSERTSTRUCT nis = {0};
+ MLTREEIMAGE devIcon;
+
+ devIcon.resourceId = IDR_DEVICE_ICON;
+ devIcon.hinst = plugin.hDllInstance;
+ if(load->dev)
+ load->dev->extraActions(DEVICE_SET_ICON,(intptr_t)&devIcon,0,0);
+
+ nis.hParent = GetNavigationRoot(TRUE);
+ nis.hInsertAfter = NCI_LAST;
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.item.pszText = WASABI_API_LNGSTRINGW_BUF(IDS_LOADING, buffer, ARRAYSIZE(buffer));
+ nis.item.pszInvariant = L"device_loading";
+ nis.item.style = NIS_ITALIC;
+ nis.item.iImage = icon_store.GetResourceIcon(devIcon.hinst, (LPCWSTR)MAKEINTRESOURCE(devIcon.resourceId));
+ nis.item.iSelectedImage = nis.item.iImage;
+ load->context = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ load->UpdateCaption = UpdateLoadingCaption;
+ loadingDevices.Add(load);
+ }
+}
+
+void finishDeviceLoading(Device * dev)
+{
+ for(int i=0; i < loadingDevices.GetSize(); i++)
+ {
+ pmpDeviceLoading * l = (pmpDeviceLoading *)loadingDevices.Get(i);
+ if(l->dev == dev) {
+ loadingDevices.Del(i);
+ HNAVITEM treeItem = (HNAVITEM)l->context;
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent,treeItem);
+ return;
+ }
+ }
+}
+
+void deviceConnected(Device * dev)
+{
+ if (!AGAVE_API_DEVICEMANAGER)
+ GetNavigationRoot(TRUE);
+
+ Device * oldDev = dev;
+
+ finishDeviceLoading(oldDev);
+ if(!devices.GetSize()) startServer();
+
+ DeviceView *pmp_device = new DeviceView(dev);
+ devices.Add(pmp_device);
+ UpdateDevicesListView(false);
+}
+
+void deviceDisconnected(Device * dev)
+{
+ finishDeviceLoading(dev);
+ int cloud_count = 0;
+ for(int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if(d->dev == dev)
+ {
+ d->threadKillswitch = 1;
+ d->transferContext.WaitForKill();
+ devices.Del(i);
+ d->Unregister();
+ d->Release();
+ }
+ else
+ {
+ // to keep the 'cloud library' node on the correct expanded state
+ // we need to adjust the cloud sources count due to 'all sources'
+ if (_strnicmp(d->GetName(), "all_sources", 11) && d->isCloudDevice)
+ {
+ cloud_count += 1;
+ }
+ }
+ }
+
+ // if we're only showing a single device, then we can just disable everything else
+ if (cloud_count == 0)
+ {
+ HNAVITEM cloud_transfers = NavigationItem_Find(0, L"cloud_transfers", TRUE);
+ if (cloud_transfers != NULL)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, cloud_transfers);
+ cloudQueueTreeItem = NULL;
+ }
+
+ cloud_transfers = NavigationItem_Find(0, L"cloud_add_sources", TRUE);
+ if (cloud_transfers != NULL)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, cloud_transfers);
+ }
+
+ cloud_transfers = NavigationItem_Find(0, L"cloud_byos", TRUE);
+ if (cloud_transfers != NULL)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, cloud_transfers);
+ }
+
+ HNAVITEM cloud = NavigationItem_Find(0, L"cloud_sources", TRUE);
+ if (cloud != NULL)
+ {
+ NAVITEM item = {0};
+ item.cbSize = sizeof(NAVITEM);
+ item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ item.hItem = cloud;
+ item.iImage = item.iSelectedImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_CLOUD);
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &item);
+ }
+ }
+
+ UpdateDevicesListView(false);
+ if(!devices.GetSize() && !loadingDevices.GetSize() && global_config->ReadInt(L"HideRoot",0))
+ {
+ HNAVITEM rootItem = GetNavigationRoot(FALSE);
+ if (NULL != rootItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, rootItem);
+ }
+ if(!devices.GetSize()) stopServer();
+}
+
+void deviceNameChanged(Device * dev)
+{
+ finishDeviceLoading(dev);
+ for(int i=0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if(d->dev == dev)
+ {
+ wchar_t *buffer = (wchar_t*)calloc(sizeof(wchar_t),128);
+ if (buffer)
+ {
+ dev->getPlaylistName(0, buffer, 128);
+ UpdateLoadingCaption(buffer, d->treeItem);
+ d->SetDisplayName(buffer, 1);
+ free(buffer);
+ }
+ break;
+ }
+ }
+}
+
+svc_imageLoader *GetPngLoaderService()
+{
+ if (NULL == wasabiPngLoader)
+ {
+ if (NULL == WASABI_API_MEMMGR)
+ return NULL;
+
+ ServiceBuild(wasabiPngLoader, pngLoaderGUID);
+ }
+
+ return wasabiPngLoader;
+}
+
+BOOL FormatResProtocol(const wchar_t *resourceName, const wchar_t *resourceType, wchar_t *buffer, size_t bufferMax)
+{
+ unsigned long filenameLength;
+
+ if (NULL == resourceName)
+ return FALSE;
+
+ if (FAILED(StringCchCopyExW(buffer, bufferMax, L"res://", &buffer, &bufferMax, 0)))
+ return FALSE;
+
+ filenameLength = GetModuleFileNameW(plugin.hDllInstance, buffer, bufferMax);
+ if (0 == filenameLength || bufferMax == filenameLength)
+ return FALSE;
+
+ buffer += filenameLength;
+ bufferMax -= filenameLength;
+
+ if (NULL != resourceType)
+ {
+ if (FALSE != IS_INTRESOURCE(resourceType))
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/#%d", (int)(INT_PTR)resourceType)))
+ return FALSE;
+ }
+ else
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/%s", resourceType)))
+ return FALSE;
+ }
+ }
+
+ if (FALSE != IS_INTRESOURCE(resourceName))
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/#%d", (int)(INT_PTR)resourceName)))
+ return FALSE;
+ }
+ else
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/%s", resourceName)))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL
+CenterWindow(HWND window, HWND centerWindow)
+{
+ RECT centerRect, windowRect;
+ long x, y;
+
+ if (NULL == window ||
+ FALSE == GetWindowRect(window, &windowRect))
+ {
+ return FALSE;
+ }
+
+ if (CENTER_OVER_ML_VIEW == centerWindow)
+ {
+ centerWindow = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL == centerWindow || FALSE == IsWindowVisible(centerWindow))
+ centerWindow = CENTER_OVER_ML;
+ }
+
+ if (CENTER_OVER_ML == centerWindow)
+ {
+ centerWindow = plugin.hwndLibraryParent;
+ if (NULL == centerWindow || FALSE == IsWindowVisible(centerWindow))
+ centerWindow = CENTER_OVER_WINAMP;
+ }
+
+ if (CENTER_OVER_WINAMP == centerWindow)
+ {
+ centerWindow = (HWND)SENDWAIPC(plugin.hwndWinampParent, IPC_GETDIALOGBOXPARENT, 0);
+ if (FALSE == IsChild(centerWindow, plugin.hwndLibraryParent))
+ centerWindow = NULL;
+ }
+
+ if (NULL == centerWindow ||
+ FALSE == IsWindowVisible(centerWindow) ||
+ FALSE == GetWindowRect(centerWindow, &centerRect))
+ {
+ HMONITOR monitor;
+ MONITORINFO monitorInfo;
+
+ monitor = MonitorFromWindow(plugin.hwndWinampParent, MONITOR_DEFAULTTONEAREST);
+
+ monitorInfo.cbSize = sizeof(monitorInfo);
+
+ if (NULL == monitor ||
+ FALSE == GetMonitorInfo(monitor, &monitorInfo) ||
+ FALSE == CopyRect(&centerRect, &monitorInfo.rcWork))
+ {
+ CopyRect(&centerRect, &windowRect);
+ }
+ }
+
+ x = centerRect.left + ((centerRect.right - centerRect.left)- (windowRect.right - windowRect.left))/2;
+ y = centerRect.top + ((centerRect.bottom - centerRect.top)- (windowRect.bottom - windowRect.top))/2;
+
+ if (x == windowRect.left && y == windowRect.top)
+ return FALSE;
+
+ return SetWindowPos(window, NULL, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
+}
+ATOM
+GetViewAtom()
+{
+ return viewAtom;
+}
+
+void *
+GetViewData(HWND hwnd)
+{
+ return GetPropW(hwnd, (const wchar_t*)MAKEINTATOM(viewAtom));
+}
+
+BOOL
+SetViewData(HWND hwnd, void *data)
+{
+ return SetPropW(hwnd, (const wchar_t*)MAKEINTATOM(viewAtom), data);
+}
+
+void *
+RemoveViewData(HWND hwnd)
+{
+ return RemovePropW(hwnd, (const wchar_t*)MAKEINTATOM(viewAtom));
+}
+
+static void URL_GetParameter(const wchar_t *param_start, char *dest, size_t dest_len)
+{
+ if (!param_start)
+ {
+ dest[0]=0;
+ return;
+ }
+
+ while (param_start && *param_start++ != '=');
+
+ while (param_start && *param_start && dest_len > 1 && *param_start != L'&')
+ {
+ if (*param_start == '+')
+ {
+ param_start++;
+ *dest++=' ';
+ }
+ else if (*param_start == L'%' && param_start[1] != L'%' && param_start[1])
+ {
+ int a=0;
+ int b=0;
+ for ( b = 0; b < 2; b ++)
+ {
+ int r=param_start[1+b];
+ if (r>='0'&&r<='9') r-='0';
+ else if (r>='a'&&r<='z') r-='a'-10;
+ else if (r>='A'&&r<='Z') r-='A'-10;
+ else break;
+ a*=16;
+ a+=r;
+ }
+ if (b < 2)
+ {
+ *dest++=(char)*param_start++;
+ }
+ else
+ {
+ *dest++=a;
+ param_start += 3;}
+ }
+ else
+ {
+ *dest++=(char)*param_start++;
+ }
+ dest_len--;
+ }
+ *dest = 0;
+}
+
+// this only works for one-character parameters
+static const wchar_t *GetParameterStart(const wchar_t *url, wchar_t param)
+{
+ wchar_t lookup[4] = { L'&', param, L'=', 0 };
+ lookup[1] = param;
+ const wchar_t *val = wcsstr(url, lookup);
+ if (!val)
+ {
+ lookup[0] = L'?';
+ val = wcsstr(url, lookup);
+ }
+ return val;
+}
+extern int serverPort;
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ int i;
+ if (message_type == ML_IPC_HOOKEXTINFOW)
+ {
+ extendedFileInfoStructW *hookMetadata = (extendedFileInfoStructW *)param1;
+ if (hookMetadata->filename && !wcsncmp(hookMetadata->filename, L"http://127.0.0.1:", 17) && _wtoi(hookMetadata->filename + 17) == serverPort)
+ {
+ if (!_wcsicmp(hookMetadata->metadata, L"artist"))
+ {
+ char metadata[1024] = {0};
+ URL_GetParameter(GetParameterStart(hookMetadata->filename, 'a'), metadata, 1024);
+ MultiByteToWideCharSZ(CP_UTF8, 0, metadata, -1, hookMetadata->ret, hookMetadata->retlen);
+ return 1;
+ }
+ else if (!_wcsicmp(hookMetadata->metadata, L"album"))
+ {
+ char metadata[1024] = {0};
+ URL_GetParameter(GetParameterStart(hookMetadata->filename, 'l'), metadata, 1024);
+ MultiByteToWideCharSZ(CP_UTF8, 0, metadata, -1, hookMetadata->ret, hookMetadata->retlen);
+ return 1;
+ }
+ else if (!_wcsicmp(hookMetadata->metadata, L"title"))
+ {
+ char metadata[1024] = {0};
+ URL_GetParameter(GetParameterStart(hookMetadata->filename, 't'), metadata, 1024);
+ MultiByteToWideCharSZ(CP_UTF8, 0, metadata, -1, hookMetadata->ret, hookMetadata->retlen);
+ return 1;
+ }
+ }
+ }
+
+ for(i=0; i < devices.GetSize(); i++)
+ {
+ DeviceView *deviceView;
+ deviceView = (DeviceView *)devices.Get(i);
+ if (NULL != deviceView)
+ {
+ INT_PTR a= deviceView->MessageProc(message_type,param1,param2,param3);
+ if(0 != a)
+ return a;
+ }
+ }
+
+
+ if (message_type >= ML_MSG_TREE_BEGIN &&
+ message_type <= ML_MSG_TREE_END)
+ {
+ HNAVITEM item, rootItem;
+ item = GetNavigationItemFromMessage(message_type, param1);
+ rootItem = GetNavigationRoot(FALSE);
+
+ if(message_type == ML_MSG_TREE_ONCREATEVIEW)
+ {
+ for(i=0; i < loadingDevices.GetSize(); i++)
+ {
+ pmpDeviceLoading * l = (pmpDeviceLoading *)loadingDevices.Get(i);
+ if(NULL != l->context)
+ {
+ if (((HNAVITEM)l->context) == item)
+ {
+ HNAVITEM parentItem;
+ parentItem = MLNavItem_GetParent(plugin.hwndLibraryParent, item);
+ if (NULL == parentItem)
+ parentItem = rootItem;
+
+ PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)parentItem, ML_IPC_NAVITEM_SELECT);
+ return 0;
+ }
+ }
+ }
+ }
+
+ if(NULL != item && item == rootItem)
+ {
+ switch (message_type)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ return (INT_PTR)WASABI_API_CREATEDIALOGW(IDD_VIEW_PMP_DEVICES,(HWND)param2,pmp_devices_dlgproc);
+ case ML_MSG_TREE_ONCLICK:
+ switch(param2)
+ {
+ case ML_ACTION_RCLICK:
+ {
+ HMENU menu = GetSubMenu(m_context_menus,6);
+ int hideRoot = global_config->ReadInt(L"HideRoot",0);
+ CheckMenuItem(menu,ID_MAINTREEROOT_AUTOHIDEROOT,hideRoot?MF_CHECKED:MF_UNCHECKED);
+ POINT p;
+ GetCursorPos(&p);
+ int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,p.x,p.y,plugin.hwndLibraryParent,NULL);
+ switch(r)
+ {
+ case ID_MAINTREEROOT_AUTOHIDEROOT:
+ hideRoot = hideRoot?0:1;
+ global_config->WriteInt(L"HideRoot",hideRoot);
+ if(hideRoot && devices.GetSize() == 0)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, rootItem);
+ }
+ break;
+ case ID_MAINTREEROOT_PREFERENCES:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE, &pluginsPrefsPage);
+ break;
+ case ID_MAINTREEROOT_HELP:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8106455294612-Winamp-Portables-Guide");
+ break;
+ }
+ }
+ break;
+ case ML_ACTION_DBLCLICK:
+ break;
+ case ML_ACTION_ENTER:
+ break;
+ }
+ break;
+ case ML_MSG_TREE_ONDROPTARGET:
+ break;
+ case ML_MSG_TREE_ONDRAG:
+ break;
+ case ML_MSG_TREE_ONDROP:
+ break;
+ case ML_MSG_NAVIGATION_ONDELETE:
+ navigationRoot = NULL;
+ KillTimer(mainMessageWindow, PROGRESSTIMERID);
+ return TRUE;
+ }
+ }
+ }
+ else if (message_type == ML_MSG_NO_CONFIG)
+ {
+ if(prefsPage._id == 0)
+ return TRUE;
+ }
+ else if (message_type == ML_MSG_CONFIG)
+ {
+ if(prefsPage._id == 0) return 0;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, prefsPage._id, IPC_OPENPREFSTOPAGE);
+ }
+ else if (message_type == ML_MSG_NOTOKTOQUIT)
+ {
+ // see if we have any transfers in progress and if so then prompt on what to do...
+ bool transfers = false;
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(d);
+ if(txQueue && txQueue->GetSize() > 0)
+ {
+ transfers = true;
+ break;
+ }
+ }
+
+ if (transfers)
+ {
+ wchar_t titleStr[32] = {0};
+ if (MessageBoxW(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_CANCEL_TRANSFERS_AND_QUIT),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRM_QUIT,titleStr,32), MB_YESNO | MB_ICONQUESTION) == IDNO)
+ return TRUE;
+ }
+ return FALSE;
+ }
+ else if(message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if (param1 == ML_TYPE_ITEMRECORDLIST || param1 == ML_TYPE_ITEMRECORDLISTW ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_FILENAMESW ||
+ param1 == ML_TYPE_PLAYLISTS || param1 == ML_TYPE_PLAYLIST)
+ {
+ if (gen_mlconfig->ReadInt(L"pmp_send_to", DEFAULT_PMP_SEND_TO))
+ {
+ for(int m = 0, mode = 0; m < 2; m++, mode++)
+ {
+ int added = 0;
+ for(i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView *deviceView;
+ deviceView = (DeviceView *)devices.Get(i);
+ if (NULL != deviceView)
+ {
+ if (deviceView->dev->extraActions(DEVICE_SENDTO_UNSUPPORTED, 0, 0, 0) == 0)
+ {
+ wchar_t buffer[128] = {0};
+ deviceView->dev->getPlaylistName(0, buffer, 128);
+ if (buffer[0])
+ {
+ // TODO - this is to block true playlists from appearing on the sendto
+ // for cloud playlists handling - remove this when we can do more
+ // than just uploading the playlist blob without the actual files
+ if (deviceView->isCloudDevice && param3 == ML_TYPE_PLAYLIST) continue;
+
+ if (!deviceView->isCloudDevice == mode)
+ {
+ if (!added)
+ {
+ mediaLibrary.BranchSendTo(param2);
+ added = 1;
+ }
+ mediaLibrary.AddToBranchSendTo(buffer, param2, reinterpret_cast<INT_PTR>(deviceView));
+ }
+ }
+ }
+ }
+ }
+
+ if (added)
+ {
+ mediaLibrary.EndBranchSendTo(WASABI_API_LNGSTRINGW((!m ? IDS_SENDTO_CLOUD : IDS_SENDTO_DEVICES)), param2);
+ }
+ }
+ }
+ }
+ }
+
+ else if (message_type == ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE)
+ {
+ enqueuedef = param1;
+ groupBtn = param2;
+ if (IsWindow(hwndMediaView))
+ PostMessage(hwndMediaView, WM_APP + 104, param1, param2);
+ return 0;
+ }
+
+ return 0;
+}
+
+extern "C" {
+ __declspec( dllexport ) winampMediaLibraryPlugin * winampGetMediaLibraryPlugin()
+ {
+ return &plugin;
+ }
+
+ __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+ // cleanup the ml tree so the portables root isn't left
+
+ HNAVITEM rootItem = GetNavigationRoot(FALSE);
+ if(NULL != rootItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, rootItem);
+
+ // prompt to remove our settings with default as no (just incase)
+ wchar_t title[256] = {0};
+ StringCbPrintfW(title, ARRAYSIZE(title), WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PMP_SUPPORT), PLUGIN_VERSION);
+ if(MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS),
+ title,MB_YESNO|MB_DEFBUTTON2) == IDYES)
+ {
+ global_config->WriteString(0,0);
+ }
+
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&pluginsPrefsPage,IPC_REMOVE_PREFS_DLG);
+
+ // allow an on-the-fly removal (since we've got to be with a compatible client build)
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+}; \ No newline at end of file