#include "stdafx.h"
#include "Chipcon Remote Control.h"
#include "WinampGen.h"
#include "WinampIpc.h"
static const GUID WINAMP_REMOTE_GUID = { 0xEFA3F566, 0x6E8C, 0x4CC2, { 0x93, 0xFB, 0x31, 0x3B, 0x1B, 0xA3, 0x4D, 0x9E } };
// Global variables
HANDLE hRemoteControlThread;
HANDLE hRemoteDisplayThread;
HANDLE hDeviceMonitorThread;
CudalPipe *pControlPipe;
CudalPipe *pDisplayPipe;
CudalDongle *pDongle;
BOOL quitAsap;
// Function prototypes
int waInit(void);
void waConfig(void);
void waQuit(void);
winampGeneralPurposePlugin plugin = {
GPPHDR_VER,
"Chipcon Remote Control", // Description
waInit, // Initialization function
waConfig, // Configuration function
waQuit // Deinitialization function
};
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
return TRUE;
}
DWORD WINAPI RemoteDisplayThread(LPVOID lpParameter) {
WINAMP_DISPLAY_DATA lastWaDispData;
WINAMP_DISPLAY_DATA waDispData;
int lastTime = -1;
BOOL forceRefresh;
memset(&lastWaDispData, 0x00, sizeof(WINAMP_DISPLAY_DATA));
// Bail out when Winamp tells us to, or die in sympathy for the other thread
while (!quitAsap && pControlPipe) {
// Initialize displayData
memset(&waDispData, 0x00, sizeof(WINAMP_DISPLAY_DATA));
// Get output time
int time = WA_GET_PLAY_TIME();
if (time != -1) {
waDispData.milliseconds = time % 1000;
waDispData.seconds = (time / 1000) % 60;
waDispData.minutes = time / (1000 * 60);
// Check output time for changes
if ((waDispData.milliseconds != lastWaDispData.milliseconds) ||
(waDispData.seconds != lastWaDispData.seconds) ||
(waDispData.minutes != lastWaDispData.minutes)) {
waDispData.refresh |= WDD_REFRESH_TIME_BM;
}
}
// Check the time for significant changes
if (time < lastTime) {
forceRefresh = TRUE;
} else if (time > (lastTime + SIGNIFICANT_TIME_DELTA)) {
forceRefresh = TRUE;
} else {
forceRefresh = FALSE;
}
lastTime = time;
// Get play state
waDispData.state = 0x00;
if (WA_IS_PLAYING()) {
waDispData.state |= WDD_STATE_PLAYING_BM;
} else if (WA_IS_PAUSED()) {
waDispData.state |= WDD_STATE_PAUSED_BM;
}
// Check player state for changes
if ((waDispData.state & (WDD_STATE_PLAYING_BM | WDD_STATE_PAUSED_BM)) !=
(lastWaDispData.state & (WDD_STATE_PLAYING_BM | WDD_STATE_PAUSED_BM))) {
waDispData.refresh |= WDD_REFRESH_PLAY_STATE_BM;
}
// Get shuffle state
if (WA_GET_SHUFFLE()) waDispData.state |= WDD_STATE_SHUFFLE_BM;
// Check shuffle state for changes
if ((waDispData.state & WDD_STATE_SHUFFLE_BM) != (lastWaDispData.state & WDD_STATE_SHUFFLE_BM)) {
waDispData.refresh |= WDD_REFRESH_SHUFFLE_STATE_BM;
}
// Get track title
int titleLength;
if (WA_GET_LIST_LENGTH()) {
char *pTitlePtr = WA_GET_LIST_TITLE(WA_GET_LIST_POS());
memcpy(waDispData.pTitle, pTitlePtr, min(sizeof(waDispData.pTitle), strlen(pTitlePtr)));
titleLength = strlen(pTitlePtr);
} else {
sprintf(waDispData.pTitle, "- No playlist -");
titleLength = strlen(waDispData.pTitle);
}
// Calculate the total length of the display data
waDispData.totalLength = sizeof(waDispData)
- sizeof(waDispData.pTitle)
+ titleLength;
// Check track title for changes
if ((waDispData.totalLength != lastWaDispData.totalLength) ||
(memcmp(waDispData.pTitle, lastWaDispData.pTitle, titleLength) != 0)) {
waDispData.refresh |= WDD_REFRESH_TITLE_BM;
}
// Send it if there are any relevant changes
if ((waDispData.refresh & (WDD_REFRESH_PLAY_STATE_BM | WDD_REFRESH_SHUFFLE_STATE_BM | WDD_REFRESH_TITLE_BM)) ||
(forceRefresh)) {
DWORD length = waDispData.totalLength;
switch (pDisplayPipe->WriteSync(&waDispData, &length, 100)) {
case USBIO_ERR_SUCCESS:
case USBIO_ERR_TIMEOUT:
// It's only a success if the transferred length is correct
if (length == waDispData.totalLength) break;
default:
// Fatal error (possibly disconnection)
goto error;
}
}
memcpy(&lastWaDispData, &waDispData, sizeof(WINAMP_DISPLAY_DATA));
Sleep(DISPLAY_INTERVAL);
}
error:
delete pDisplayPipe;
pDisplayPipe = NULL;
return 0;
}
DWORD WINAPI RemoteControlThread(LPVOID lpParameter) {
WINAMP_CONTROL_DATA controlData;
CudalAsyncInfo asyncInfo;
DWORD length;
// Start polling
if (!pControlPipe->ReadAsync(&asyncInfo, &controlData, sizeof(WINAMP_CONTROL_DATA))) {
goto error;
}
// Bail out when Winamp tells us to, or die in sympathy for the other thread
while (!quitAsap && pDisplayPipe) {
// Did we get anything?
switch (pControlPipe->WaitAsync(&asyncInfo, &length, 100)) {
case USBIO_ERR_SUCCESS:
// Make sure that we got it all...
if (length == 2) {
// When a command is received, perform the corresponding action
switch (controlData.command) {
case DONGLE_CMD_PREVIOUS_TRACK: WA_CMD_PREVIOUS_TRACK(); break;
case DONGLE_CMD_PLAY: WA_CMD_PLAY(); break;
case DONGLE_CMD_PAUSE: WA_CMD_PAUSE(); break;
case DONGLE_CMD_STOP: WA_CMD_STOP(); break;
case DONGLE_CMD_NEXT_TRACK: WA_CMD_NEXT_TRACK(); break;
case DONGLE_CMD_VOLUMEUP: WA_CMD_VOLUMEUP(); break;
case DONGLE_CMD_VOLUMEDOWN: WA_CMD_VOLUMEDOWN(); break;
case DONGLE_CMD_FFWD5S: WA_CMD_FFWD5S(); break;
case DONGLE_CMD_REW5S: WA_CMD_REW5S(); break;
case DONGLE_SET_REPEAT: WA_SET_REPEAT(controlData.value); break;
case DONGLE_SET_SHUFFLE: WA_SET_SHUFFLE(controlData.value); break;
case DONGLE_SET_VOLUME: WA_SET_VOLUME(controlData.value); break;
}
}
// Continue polling
if (!pControlPipe->ReadAsync(&asyncInfo, &controlData, sizeof(WINAMP_CONTROL_DATA))) {
goto error;
}
break;
case USBIO_ERR_TIMEOUT:
// A timeout does not have any effect
break;
default:
// Fatal error (possibly disconnection)
goto error;
}
}
error:
// Terminate the transfer
pControlPipe->AbortAll();
pControlPipe->WaitAsyncTermination(&asyncInfo);
// Delete the control pipe to tell the other threads that we're done
delete pControlPipe;
pControlPipe = NULL;
return 0;
}
DWORD WINAPI DongleMonitorThread(LPVOID lpParameter) {
DWORD dummy;
// This is sort of the main loop. Leave when the quit signal is given
while (!quitAsap) {
// If the pipes don't exist, then try to find a dongle to connect to
if (!pControlPipe && !pDisplayPipe) {
// Close the old thread handles
if (hRemoteControlThread) {
CloseHandle(hRemoteControlThread);
hRemoteControlThread = NULL;
}
if (hRemoteDisplayThread) {
CloseHandle(hRemoteDisplayThread);
hRemoteDisplayThread = NULL;
}
// Clean up after last rounds
if (pDongle) {
delete pDongle;
pDongle = NULL;
}
// Find the correct dongle type
CUDAL_DONGLE_INFO pDongleInfo[MAX_DONGLE_ENUM_COUNT];
int dongleCount = CudalDongle::EnumDongles(pDongleInfo, MAX_DONGLE_ENUM_COUNT, &WINAMP_REMOTE_GUID);
for (int n = 0; n < dongleCount; n++) {
if ((pDongleInfo[n].vendorId == CHIPCON_VID) &&
(pDongleInfo[n].productId == WINAMP_REMOTE_CONTROL_PID)) {
// If the dongle object could be created, then create the control and display pipes
if (pDongle = CudalDongle::CreateDongle(&pDongleInfo[n], 0, &WINAMP_REMOTE_GUID)) {
pDisplayPipe = pDongle->CreatePipe(REMOTE_DISPLAY_ENDPOINT, FALSE);
pControlPipe = pDongle->CreatePipe(REMOTE_CONTROL_ENDPOINT, TRUE);
// When both pipes have been created, the two data transfer threads can be started
if (pDisplayPipe && pControlPipe) {
hRemoteControlThread = CreateThread(NULL, 0, RemoteControlThread, NULL, 0, &dummy);
hRemoteDisplayThread = CreateThread(NULL, 0, RemoteDisplayThread, NULL, 0, &dummy);
n = dongleCount;//stop searching
// If not, then destroy everything
} else {
if (pDisplayPipe) { delete pDisplayPipe; pDisplayPipe = NULL; }
if (pControlPipe) { delete pControlPipe; pControlPipe = NULL; }
if (pDongle) { delete pDongle; pDongle = NULL; }
}
}
}
}
}
// Reduce activity
for (int s = 0; s < 10; s++) {
if (!quitAsap) {
Sleep(MONITOR_INTERVAL / 10);
}
}
}
// Wait for the other threads to quit before destroying the dongle object
if (hRemoteControlThread) {
WaitForSingleObject(hRemoteControlThread, 5000);
CloseHandle(hRemoteControlThread);
}
if (hRemoteDisplayThread) {
WaitForSingleObject(hRemoteDisplayThread, 5000);
CloseHandle(hRemoteDisplayThread);
}
if (pDongle) delete pDongle;
return 0;
}
int waInit(void) {
DWORD dummy;
// Reset relevant globals
hRemoteControlThread = 0;
hRemoteDisplayThread = 0;
pControlPipe = NULL;
pDisplayPipe = NULL;
pDongle = NULL;
quitAsap = FALSE;
// Start the monitoring thread, which discovers a dongle and initializes it
hDeviceMonitorThread = CreateThread(NULL, 0, DongleMonitorThread, NULL, 0, &dummy);
return 0;
}
void waConfig(void) {
MessageBox(plugin.hwndParent,
"This plug-in demonstrates the Chipcon USB Dongle Access Library\n"
"(CUDAL). Download \"winamp_remote.hex\" into a CC2510 on a\n"
"SmartRF04EB, and \"winamp_dongle.hex\" into the CC2511 dongle.\n"
"The plug-in will detect and attach to the first unused remote\n"
"control that it finds. The dongle safely be removed at any time\n"
"and re-connected later on.",
"Chipcon Remote Control",
MB_OK | MB_ICONINFORMATION);
}
void waQuit(void) {
quitAsap = TRUE;
WaitForSingleObject(hDeviceMonitorThread, INFINITE);
CloseHandle(hDeviceMonitorThread);
}
extern "C" {
__declspec(dllexport) winampGeneralPurposePlugin *winampGetGeneralPurposePlugin(void) {
return &plugin;
}
}