1454 lines
41 KiB
C++
1454 lines
41 KiB
C++
#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, ¢erRect))
|
|
{
|
|
HMONITOR monitor;
|
|
MONITORINFO monitorInfo;
|
|
|
|
monitor = MonitorFromWindow(plugin.hwndWinampParent, MONITOR_DEFAULTTONEAREST);
|
|
|
|
monitorInfo.cbSize = sizeof(monitorInfo);
|
|
|
|
if (NULL == monitor ||
|
|
FALSE == GetMonitorInfo(monitor, &monitorInfo) ||
|
|
FALSE == CopyRect(¢erRect, &monitorInfo.rcWork))
|
|
{
|
|
CopyRect(¢erRect, &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;
|
|
}
|
|
}; |